From bfa0e40795ba676fd02d616794cce1c9b2d6a95f Mon Sep 17 00:00:00 2001 From: zxu Date: Thu, 25 Jan 2018 09:14:19 -0500 Subject: Implement the rest of FieldValue types for C++ (#687) * implement FieldValue for null and boolean. * Implement number and string FieldValue. * Implement object FieldValue. * implement timestamp FieldValue. * Implement number and string FieldValue. * implement public type `Blob` and `GeoPoint` * implement Blob FieldValue * Implement GeoPoint FieldValue * refactoring `Blob` --- .../Example/Firestore.xcodeproj/project.pbxproj | 8 + Firestore/core/CMakeLists.txt | 2 + .../core/include/firebase/firestore/geo_point.h | 89 ++++++ .../core/src/firebase/firestore/CMakeLists.txt | 22 ++ Firestore/core/src/firebase/firestore/geo_point.cc | 50 ++++ .../src/firebase/firestore/model/CMakeLists.txt | 2 + .../src/firebase/firestore/model/field_value.cc | 209 ++++++++++++++ .../src/firebase/firestore/model/field_value.h | 39 +++ .../core/src/firebase/firestore/model/timestamp.cc | 54 ++++ .../core/src/firebase/firestore/model/timestamp.h | 94 +++++++ .../core/test/firebase/firestore/CMakeLists.txt | 21 ++ .../core/test/firebase/firestore/geo_point_test.cc | 41 +++ .../test/firebase/firestore/model/CMakeLists.txt | 1 + .../firebase/firestore/model/field_value_test.cc | 300 ++++++++++++++++++++- .../firebase/firestore/model/timestamp_test.cc | 49 ++++ 15 files changed, 980 insertions(+), 1 deletion(-) create mode 100644 Firestore/core/include/firebase/firestore/geo_point.h create mode 100644 Firestore/core/src/firebase/firestore/CMakeLists.txt create mode 100644 Firestore/core/src/firebase/firestore/geo_point.cc create mode 100644 Firestore/core/src/firebase/firestore/model/timestamp.cc create mode 100644 Firestore/core/src/firebase/firestore/model/timestamp.h create mode 100644 Firestore/core/test/firebase/firestore/CMakeLists.txt create mode 100644 Firestore/core/test/firebase/firestore/geo_point_test.cc create mode 100644 Firestore/core/test/firebase/firestore/model/timestamp_test.cc diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 7e922b6..5db2b56 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; AB382F7C1FE02A1F007CA955 /* FIRDocumentReferenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB382F7B1FE02A1F007CA955 /* FIRDocumentReferenceTests.m */; }; AB382F7E1FE03059007CA955 /* FIRFieldPathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB382F7D1FE03059007CA955 /* FIRFieldPathTests.m */; }; + AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB7BAB332012B519001E0872 /* geo_point_test.cc */; }; AB9945261FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB9945251FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m */; }; AB9945281FE2DE0C00DFC1E6 /* FIRSnapshotMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB9945271FE2DE0C00DFC1E6 /* FIRSnapshotMetadataTests.m */; }; AB99452A1FE2F9EB00DFC1E6 /* FIRDocumentSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB9945291FE2F9EB00DFC1E6 /* FIRDocumentSnapshotTests.m */; }; @@ -75,6 +76,7 @@ AB99452E1FE30AC800DFC1E6 /* FIRFieldValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB99452D1FE30AC800DFC1E6 /* FIRFieldValueTests.m */; }; ABAEEF4F1FD5F8B100C966CB /* FIRQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ABAEEF4E1FD5F8B100C966CB /* FIRQueryTests.m */; }; ABF341051FE860CA00C48322 /* FSTAPIHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ABF341021FE8593500C48322 /* FSTAPIHelpers.m */; }; + ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */; }; C4E749275AD0FBDF9F4716A8 /* Pods_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */; }; D5B2532E4676014F57A7EAB9 /* FSTStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25C0D4AADFCA3ADB883E4 /* FSTStreamTests.m */; }; @@ -250,6 +252,7 @@ AB356EF6200EA5EB0089B766 /* field_value_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = field_value_test.cc; sourceTree = ""; }; AB382F7B1FE02A1F007CA955 /* FIRDocumentReferenceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRDocumentReferenceTests.m; sourceTree = ""; }; AB382F7D1FE03059007CA955 /* FIRFieldPathTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRFieldPathTests.m; sourceTree = ""; }; + AB7BAB332012B519001E0872 /* geo_point_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = geo_point_test.cc; path = ../../core/test/firebase/firestore/geo_point_test.cc; sourceTree = ""; }; AB9945251FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRCollectionReferenceTests.m; sourceTree = ""; }; AB9945271FE2DE0C00DFC1E6 /* FIRSnapshotMetadataTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRSnapshotMetadataTests.m; sourceTree = ""; }; AB9945291FE2F9EB00DFC1E6 /* FIRDocumentSnapshotTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRDocumentSnapshotTests.m; sourceTree = ""; }; @@ -258,6 +261,7 @@ ABAEEF4E1FD5F8B100C966CB /* FIRQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRQueryTests.m; sourceTree = ""; }; ABF341011FE858B500C48322 /* FSTAPIHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTAPIHelpers.h; sourceTree = ""; }; ABF341021FE8593500C48322 /* FSTAPIHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTAPIHelpers.m; sourceTree = ""; }; + ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = ""; }; B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CE00BABB5A3AAB44A4C209E2 /* Pods-Firestore_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.debug.xcconfig"; sourceTree = ""; }; D3CC3DC5338DCAF43A211155 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; @@ -415,6 +419,7 @@ 54764FAD1FAA0C650085E60A /* Port */, 54740A561FC913EB00713A1A /* util */, 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */, + AB7BAB332012B519001E0872 /* geo_point_test.cc */, ); name = GoogleTests; sourceTree = ""; @@ -548,6 +553,7 @@ isa = PBXGroup; children = ( AB356EF6200EA5EB0089B766 /* field_value_test.cc */, + ABF6506B201131F8005F2C74 /* timestamp_test.cc */, ); name = model; path = ../../core/test/firebase/firestore/model; @@ -1251,6 +1257,7 @@ 54764FAB1FAA0C320085E60A /* string_util_test.cc in Sources */, 54E9282C1F339CAD00C1953E /* XCTestCase+Await.m in Sources */, AB99452E1FE30AC800DFC1E6 /* FIRFieldValueTests.m in Sources */, + AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */, DE51B1DF1F0D490D0013853F /* FSTMemoryMutationQueueTests.m in Sources */, DE51B1F31F0D491B0013853F /* FSTDatastoreTests.m in Sources */, DE51B1D01F0D48CD0013853F /* FSTQueryTests.m in Sources */, @@ -1266,6 +1273,7 @@ AB9945261FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m in Sources */, DE51B1E31F0D490D0013853F /* FSTPersistenceTestHelpers.m in Sources */, AB382F7C1FE02A1F007CA955 /* FIRDocumentReferenceTests.m in Sources */, + ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */, DE51B1CF1F0D48CD0013853F /* FSTQueryListenerTests.m in Sources */, DE51B1DA1F0D490D0013853F /* FSTLevelDBLocalStoreTests.m in Sources */, DE51B1FA1F0D492C0013853F /* FSTLevelDBSpecTests.m in Sources */, diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt index da08dfc..f023446 100644 --- a/Firestore/core/CMakeLists.txt +++ b/Firestore/core/CMakeLists.txt @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +add_subdirectory(src/firebase/firestore) add_subdirectory(src/firebase/firestore/model) add_subdirectory(src/firebase/firestore/remote) add_subdirectory(src/firebase/firestore/util) +add_subdirectory(test/firebase/firestore) add_subdirectory(test/firebase/firestore/model) add_subdirectory(test/firebase/firestore/remote) add_subdirectory(test/firebase/firestore/util) diff --git a/Firestore/core/include/firebase/firestore/geo_point.h b/Firestore/core/include/firebase/firestore/geo_point.h new file mode 100644 index 0000000..cc366be --- /dev/null +++ b/Firestore/core/include/firebase/firestore/geo_point.h @@ -0,0 +1,89 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_GEO_POINT_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_GEO_POINT_H_ + +namespace firebase { +namespace firestore { + +/** + * An immutable object representing a geographical point in Firestore. The point + * is represented as a latitude/longitude pair. + * + * Latitude values are in the range of [-90, 90]. + * Longitude values are in the range of [-180, 180]. + */ +class GeoPoint { + public: + /** + * Creates a `GeoPoint` with both latitude and longitude being 0. + */ + GeoPoint(); + + /** + * Creates a `GeoPoint` from the provided latitude and longitude degrees. + * + * @param latitude The latitude as number between -90 and 90. + * @param longitude The longitude as number between -180 and 180. + */ + GeoPoint(double latitude, double longitude); + + GeoPoint(const GeoPoint& other) = default; + GeoPoint(GeoPoint&& other) = default; + GeoPoint& operator=(const GeoPoint& other) = default; + GeoPoint& operator=(GeoPoint&& other) = default; + + double latitude() const { + return latitude_; + } + + double longitude() const { + return longitude_; + } + + private: + double latitude_; + double longitude_; +}; + +/** Compares against another GeoPoint. */ +bool operator<(const GeoPoint& lhs, const GeoPoint& rhs); + +inline bool operator>(const GeoPoint& lhs, const GeoPoint& rhs) { + return rhs < lhs; +} + +inline bool operator>=(const GeoPoint& lhs, const GeoPoint& rhs) { + return !(lhs < rhs); +} + +inline bool operator<=(const GeoPoint& lhs, const GeoPoint& rhs) { + return !(lhs > rhs); +} + +inline bool operator!=(const GeoPoint& lhs, const GeoPoint& rhs) { + return lhs < rhs || lhs > rhs; +} + +inline bool operator==(const GeoPoint& lhs, const GeoPoint& rhs) { + return !(lhs != rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_GEO_POINT_H_ diff --git a/Firestore/core/src/firebase/firestore/CMakeLists.txt b/Firestore/core/src/firebase/firestore/CMakeLists.txt new file mode 100644 index 0000000..3f5522c --- /dev/null +++ b/Firestore/core/src/firebase/firestore/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Public types to be used both internally and externally. +cc_library( + firebase_firestore_types + SOURCES + geo_point.cc + DEPENDS + firebase_firestore_util +) diff --git a/Firestore/core/src/firebase/firestore/geo_point.cc b/Firestore/core/src/firebase/firestore/geo_point.cc new file mode 100644 index 0000000..fb01023 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/geo_point.cc @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/include/firebase/firestore/geo_point.h" + +#include + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +namespace firebase { +namespace firestore { + +GeoPoint::GeoPoint() : GeoPoint(0, 0) { +} + +GeoPoint::GeoPoint(double latitude, double longitude) + : latitude_(latitude), longitude_(longitude) { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + !isnan(latitude) && -90 <= latitude && latitude <= 90, + -90 <= latitude && latitude <= 90, + "Latitude must be in the range of [-90, 90]"); + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + !isnan(longitude) && -180 <= longitude && longitude <= 180, + -180 <= longitude && longitude <= 180, + "Latitude must be in the range of [-180, 180]"); +} + +bool operator<(const GeoPoint& lhs, const GeoPoint& rhs) { + if (lhs.latitude() == rhs.latitude()) { + return lhs.longitude() < rhs.longitude(); + } else { + return lhs.latitude() < rhs.latitude(); + } +} + +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt index ae80de3..3af56ec 100644 --- a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt @@ -16,6 +16,8 @@ cc_library( firebase_firestore_model SOURCES field_value.cc + timestamp.cc DEPENDS firebase_firestore_util + firebase_firestore_types ) diff --git a/Firestore/core/src/firebase/firestore/model/field_value.cc b/Firestore/core/src/firebase/firestore/model/field_value.cc index 5583afc..b063543 100644 --- a/Firestore/core/src/firebase/firestore/model/field_value.cc +++ b/Firestore/core/src/firebase/firestore/model/field_value.cc @@ -16,6 +16,8 @@ #include "Firestore/core/src/firebase/firestore/model/field_value.h" +#include + #include #include #include @@ -31,6 +33,7 @@ namespace firestore { namespace model { using Type = FieldValue::Type; +using firebase::firestore::util::ComparisonResult; namespace { /** @@ -75,12 +78,42 @@ FieldValue& FieldValue::operator=(const FieldValue& value) { case Type::Boolean: boolean_value_ = value.boolean_value_; break; + case Type::Long: + integer_value_ = value.integer_value_; + break; + case Type::Double: + double_value_ = value.double_value_; + break; + case Type::Timestamp: + timestamp_value_ = value.timestamp_value_; + break; + case Type::ServerTimestamp: + server_timestamp_value_ = value.server_timestamp_value_; + break; + case Type::String: + string_value_ = value.string_value_; + break; + case Type::Blob: { + // copy-and-swap + std::vector tmp = value.blob_value_; + std::swap(blob_value_, tmp); + break; + } + case Type::GeoPoint: + geo_point_value_ = value.geo_point_value_; + break; case Type::Array: { // copy-and-swap std::vector tmp = value.array_value_; std::swap(array_value_, tmp); break; } + case Type::Object: { + // copy-and-swap + std::map tmp = value.object_value_; + std::swap(object_value_, tmp); + break; + } default: FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( false, lhs.type(), "Unsupported type %d", value.type()); @@ -90,10 +123,22 @@ FieldValue& FieldValue::operator=(const FieldValue& value) { FieldValue& FieldValue::operator=(FieldValue&& value) { switch (value.tag_) { + case Type::String: + SwitchTo(Type::String); + string_value_.swap(value.string_value_); + return *this; + case Type::Blob: + SwitchTo(Type::Blob); + std::swap(blob_value_, value.blob_value_); + return *this; case Type::Array: SwitchTo(Type::Array); std::swap(array_value_, value.array_value_); return *this; + case Type::Object: + SwitchTo(Type::Object); + std::swap(object_value_, value.object_value_); + return *this; default: // We just copy over POD union types. return *this = value; @@ -119,6 +164,82 @@ const FieldValue& FieldValue::BooleanValue(bool value) { return value ? TrueValue() : FalseValue(); } +const FieldValue& FieldValue::NanValue() { + static const FieldValue kNanInstance = FieldValue::DoubleValue(NAN); + return kNanInstance; +} + +FieldValue FieldValue::IntegerValue(int64_t value) { + FieldValue result; + result.SwitchTo(Type::Long); + result.integer_value_ = value; + return result; +} + +FieldValue FieldValue::DoubleValue(double value) { + FieldValue result; + result.SwitchTo(Type::Double); + result.double_value_ = value; + return result; +} + +FieldValue FieldValue::TimestampValue(const Timestamp& value) { + FieldValue result; + result.SwitchTo(Type::Timestamp); + result.timestamp_value_ = value; + return result; +} + +FieldValue FieldValue::ServerTimestampValue(const Timestamp& local_write_time, + const Timestamp& previous_value) { + FieldValue result; + result.SwitchTo(Type::ServerTimestamp); + result.server_timestamp_value_.local_write_time = local_write_time; + result.server_timestamp_value_.previous_value = previous_value; + result.server_timestamp_value_.has_previous_value_ = true; + return result; +} + +FieldValue FieldValue::ServerTimestampValue(const Timestamp& local_write_time) { + FieldValue result; + result.SwitchTo(Type::ServerTimestamp); + result.server_timestamp_value_.local_write_time = local_write_time; + result.server_timestamp_value_.has_previous_value_ = false; + return result; +} + +FieldValue FieldValue::StringValue(const char* value) { + std::string copy(value); + return StringValue(std::move(copy)); +} + +FieldValue FieldValue::StringValue(const std::string& value) { + std::string copy(value); + return StringValue(std::move(copy)); +} + +FieldValue FieldValue::StringValue(std::string&& value) { + FieldValue result; + result.SwitchTo(Type::String); + result.string_value_.swap(value); + return result; +} + +FieldValue FieldValue::BlobValue(const uint8_t* source, size_t size) { + FieldValue result; + result.SwitchTo(Type::Blob); + std::vector copy(source, source + size); + std::swap(result.blob_value_, copy); + return result; +} + +FieldValue FieldValue::GeoPointValue(const GeoPoint& value) { + FieldValue result; + result.SwitchTo(Type::GeoPoint); + result.geo_point_value_ = value; + return result; +} + FieldValue FieldValue::ArrayValue(const std::vector& value) { std::vector copy(value); return ArrayValue(std::move(copy)); @@ -131,6 +252,20 @@ FieldValue FieldValue::ArrayValue(std::vector&& value) { return result; } +FieldValue FieldValue::ObjectValue( + const std::map& value) { + std::map copy(value); + return ObjectValue(std::move(copy)); +} + +FieldValue FieldValue::ObjectValue( + std::map&& value) { + FieldValue result; + result.SwitchTo(Type::Object); + std::swap(result.object_value_, value); + return result; +} + bool operator<(const FieldValue& lhs, const FieldValue& rhs) { if (!Comparable(lhs.type(), rhs.type())) { return lhs.type() < rhs.type(); @@ -141,8 +276,45 @@ bool operator<(const FieldValue& lhs, const FieldValue& rhs) { return false; case Type::Boolean: return Comparator()(lhs.boolean_value_, rhs.boolean_value_); + case Type::Long: + if (rhs.type() == Type::Long) { + return Comparator()(lhs.integer_value_, rhs.integer_value_); + } else { + return util::CompareMixedNumber(rhs.double_value_, + lhs.integer_value_) == + ComparisonResult::Descending; + } + case Type::Double: + if (rhs.type() == Type::Double) { + return Comparator()(lhs.double_value_, rhs.double_value_); + } else { + return util::CompareMixedNumber(lhs.double_value_, + rhs.integer_value_) == + ComparisonResult::Ascending; + } + case Type::Timestamp: + if (rhs.type() == Type::Timestamp) { + return lhs.timestamp_value_ < rhs.timestamp_value_; + } else { + return true; + } + case Type::ServerTimestamp: + if (rhs.type() == Type::ServerTimestamp) { + return lhs.server_timestamp_value_.local_write_time < + rhs.server_timestamp_value_.local_write_time; + } else { + return false; + } + case Type::String: + return lhs.string_value_.compare(rhs.string_value_) < 0; + case Type::Blob: + return lhs.blob_value_ < rhs.blob_value_; + case Type::GeoPoint: + return lhs.geo_point_value_ < rhs.geo_point_value_; case Type::Array: return lhs.array_value_ < rhs.array_value_; + case Type::Object: + return lhs.object_value_ < rhs.object_value_; default: FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( false, lhs.type(), "Unsupported type %d", lhs.type()); @@ -159,17 +331,54 @@ void FieldValue::SwitchTo(const Type type) { // Not same type. Destruct old type first and then initialize new type. // Must call destructor explicitly for any non-POD type. switch (tag_) { + case Type::Timestamp: + timestamp_value_.~Timestamp(); + break; + case Type::ServerTimestamp: + server_timestamp_value_.~ServerTimestamp(); + break; + case Type::String: + string_value_.~basic_string(); + break; + case Type::Blob: + blob_value_.~vector(); + break; + case Type::GeoPoint: + geo_point_value_.~GeoPoint(); + break; case Type::Array: array_value_.~vector(); break; + case Type::Object: + object_value_.~map(); + break; default: {} // The other types where there is nothing to worry about. } tag_ = type; // Must call constructor explicitly for any non-POD type to initialize. switch (tag_) { + case Type::Timestamp: + new (×tamp_value_) Timestamp(0, 0); + break; + case Type::ServerTimestamp: + new (&server_timestamp_value_) ServerTimestamp(); + break; + case Type::String: + new (&string_value_) std::string(); + break; + case Type::Blob: + // Do not even bother to allocate a new array of size 0. + new (&blob_value_) std::vector(); + break; + case Type::GeoPoint: + new (&geo_point_value_) GeoPoint(); + break; case Type::Array: new (&array_value_) std::vector(); break; + case Type::Object: + new (&object_value_) std::map(); + break; default: {} // The other types where there is nothing to worry about. } } diff --git a/Firestore/core/src/firebase/firestore/model/field_value.h b/Firestore/core/src/firebase/firestore/model/field_value.h index 781e34f..bb6594f 100644 --- a/Firestore/core/src/firebase/firestore/model/field_value.h +++ b/Firestore/core/src/firebase/firestore/model/field_value.h @@ -17,13 +17,27 @@ #ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_VALUE_H_ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_VALUE_H_ +#include + +#include #include +#include #include +#include "Firestore/core/include/firebase/firestore/geo_point.h" +#include "Firestore/core/src/firebase/firestore/model/timestamp.h" + namespace firebase { namespace firestore { namespace model { +struct ServerTimestamp { + Timestamp local_write_time; + Timestamp previous_value; + // TODO(zxu123): adopt absl::optional once abseil is ported. + bool has_previous_value_; +}; + /** * tagged-union class representing an immutable data value as stored in * Firestore. FieldValue represents all the different kinds of values @@ -78,8 +92,25 @@ class FieldValue { static const FieldValue& TrueValue(); static const FieldValue& FalseValue(); static const FieldValue& BooleanValue(bool value); + static const FieldValue& NanValue(); + static FieldValue IntegerValue(int64_t value); + static FieldValue DoubleValue(double value); + static FieldValue TimestampValue(const Timestamp& value); + static FieldValue ServerTimestampValue(const Timestamp& local_write_time, + const Timestamp& previous_value); + static FieldValue ServerTimestampValue(const Timestamp& local_write_time); + static FieldValue StringValue(const char* value); + static FieldValue StringValue(const std::string& value); + static FieldValue StringValue(std::string&& value); + static FieldValue BlobValue(const uint8_t* source, size_t size); + // static FieldValue ReferenceValue(); + static FieldValue GeoPointValue(const GeoPoint& value); static FieldValue ArrayValue(const std::vector& value); static FieldValue ArrayValue(std::vector&& value); + static FieldValue ObjectValue( + const std::map& value); + static FieldValue ObjectValue( + std::map&& value); friend bool operator<(const FieldValue& lhs, const FieldValue& rhs); @@ -96,7 +127,15 @@ class FieldValue { union { // There is no null type as tag_ alone is enough for Null FieldValue. bool boolean_value_; + int64_t integer_value_; + double double_value_; + Timestamp timestamp_value_; + ServerTimestamp server_timestamp_value_; + std::string string_value_; + std::vector blob_value_; + GeoPoint geo_point_value_; std::vector array_value_; + std::map object_value_; }; }; diff --git a/Firestore/core/src/firebase/firestore/model/timestamp.cc b/Firestore/core/src/firebase/firestore/model/timestamp.cc new file mode 100644 index 0000000..c2bb008 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/timestamp.cc @@ -0,0 +1,54 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/timestamp.h" + +#include + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +namespace firebase { +namespace firestore { +namespace model { + +Timestamp::Timestamp(long seconds, int nanos) + : seconds_(seconds), nanos_(nanos) { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + nanos >= 0, nanos >= 0, "timestamp nanoseconds out of range: %d", nanos); + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + nanos < 1e9, nanos < 1e9, "timestamp nanoseconds out of range: %d", + nanos); + // Midnight at the beginning of 1/1/1 is the earliest timestamp Firestore + // supports. + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + seconds >= -62135596800L, seconds >= -62135596800L, + "timestamp seconds out of range: %lld", seconds); + // This will break in the year 10,000. + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + seconds < 253402300800L, seconds < 253402300800L, + "timestamp seconds out of range: %lld", seconds); +} + +Timestamp::Timestamp() : seconds_(0), nanos_(0) { +} + +Timestamp Timestamp::Now() { + return Timestamp(time(nullptr), 0); +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/timestamp.h b/Firestore/core/src/firebase/firestore/model/timestamp.h new file mode 100644 index 0000000..3ffee48 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/timestamp.h @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TIMESTAMP_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TIMESTAMP_H_ + +#include + +namespace firebase { +namespace firestore { +namespace model { + +/** + * A Timestamp represents an absolute time from the backend at up to nanosecond + * precision. A Timestamp is always UTC. + */ +class Timestamp { + public: + /** + * Creates a new timestamp with seconds and nanos set to 0. + * + * PORTING NOTE: This does NOT set to current timestamp by default. To get the + * current timestamp, call Timestamp::Now(). + */ + Timestamp(); + + /** + * Creates a new timestamp. + * + * @param seconds the number of seconds since epoch. + * @param nanos the number of nanoseconds after the seconds. + */ + Timestamp(long seconds, int nanos); + + /** Returns a timestamp with the current date / time. */ + static Timestamp Now(); + + long seconds() const { + return seconds_; + } + + int nanos() const { + return nanos_; + } + + private: + long seconds_; + int nanos_; +}; + +/** Compares against another Timestamp. */ +inline bool operator<(const Timestamp& lhs, const Timestamp& rhs) { + return lhs.seconds() < rhs.seconds() || + (lhs.seconds() == rhs.seconds() && lhs.nanos() < rhs.nanos()); +} + +inline bool operator>(const Timestamp& lhs, const Timestamp& rhs) { + return rhs < lhs; +} + +inline bool operator>=(const Timestamp& lhs, const Timestamp& rhs) { + return !(lhs < rhs); +} + +inline bool operator<=(const Timestamp& lhs, const Timestamp& rhs) { + return !(lhs > rhs); +} + +inline bool operator!=(const Timestamp& lhs, const Timestamp& rhs) { + return lhs < rhs || lhs > rhs; +} + +inline bool operator==(const Timestamp& lhs, const Timestamp& rhs) { + return !(lhs != rhs); +} + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_VALUE_H_ diff --git a/Firestore/core/test/firebase/firestore/CMakeLists.txt b/Firestore/core/test/firebase/firestore/CMakeLists.txt new file mode 100644 index 0000000..ed5760f --- /dev/null +++ b/Firestore/core/test/firebase/firestore/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_test( + firebase_firestore_types_test + SOURCES + geo_point_test.cc + DEPENDS + firebase_firestore_types +) diff --git a/Firestore/core/test/firebase/firestore/geo_point_test.cc b/Firestore/core/test/firebase/firestore/geo_point_test.cc new file mode 100644 index 0000000..bd8d76f --- /dev/null +++ b/Firestore/core/test/firebase/firestore/geo_point_test.cc @@ -0,0 +1,41 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/include/firebase/firestore/geo_point.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { + +TEST(GeoPoint, Getter) { + const GeoPoint zero; + EXPECT_EQ(0, zero.latitude()); + EXPECT_EQ(0, zero.longitude()); + + const GeoPoint point{12, 34}; + EXPECT_EQ(12, point.latitude()); + EXPECT_EQ(34, point.longitude()); +} + +TEST(GeoPoint, Comparison) { + EXPECT_EQ(GeoPoint(12, 34), GeoPoint(12, 34)); + EXPECT_LT(GeoPoint(12, 34), GeoPoint(34, 12)); + EXPECT_LT(GeoPoint(12, 34), GeoPoint(12, 56)); +} + +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt index 31fe040..9153a57 100644 --- a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt @@ -16,6 +16,7 @@ cc_test( firebase_firestore_model_test SOURCES field_value_test.cc + timestamp_test.cc DEPENDS firebase_firestore_model ) diff --git a/Firestore/core/test/firebase/firestore/model/field_value_test.cc b/Firestore/core/test/firebase/firestore/model/field_value_test.cc index 1194e63..a006d46 100644 --- a/Firestore/core/test/firebase/firestore/model/field_value_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_value_test.cc @@ -26,6 +26,14 @@ namespace model { using Type = FieldValue::Type; +namespace { + +const uint8_t* Bytes(const char* value) { + return reinterpret_cast(value); +} + +} // namespace + TEST(FieldValue, NullType) { const FieldValue value = FieldValue::NullValue(); EXPECT_EQ(Type::Null, value.type()); @@ -42,6 +50,107 @@ TEST(FieldValue, BooleanType) { EXPECT_TRUE(false_value < true_value); } +TEST(FieldValue, NumberType) { + const FieldValue nan_value = FieldValue::NanValue(); + const FieldValue integer_value = FieldValue::IntegerValue(10L); + const FieldValue double_value = FieldValue::DoubleValue(10.1); + EXPECT_EQ(Type::Double, nan_value.type()); + EXPECT_EQ(Type::Long, integer_value.type()); + EXPECT_EQ(Type::Double, double_value.type()); + EXPECT_TRUE(nan_value < integer_value); + EXPECT_TRUE(nan_value < double_value); + EXPECT_FALSE(nan_value < nan_value); + EXPECT_FALSE(integer_value < nan_value); + EXPECT_FALSE(integer_value < nan_value); + EXPECT_TRUE(integer_value < double_value); // 10 < 10.1 + EXPECT_FALSE(double_value < integer_value); + EXPECT_FALSE(integer_value < integer_value); + EXPECT_FALSE(double_value < double_value); + + // Number comparison craziness + // Integers + EXPECT_TRUE(FieldValue::IntegerValue(1L) < FieldValue::IntegerValue(2L)); + EXPECT_FALSE(FieldValue::IntegerValue(1L) < FieldValue::IntegerValue(1L)); + EXPECT_FALSE(FieldValue::IntegerValue(2L) < FieldValue::IntegerValue(1L)); + // Doubles + EXPECT_TRUE(FieldValue::DoubleValue(1.0) < FieldValue::DoubleValue(2.0)); + EXPECT_FALSE(FieldValue::DoubleValue(1.0) < FieldValue::DoubleValue(1.0)); + EXPECT_FALSE(FieldValue::DoubleValue(2.0) < FieldValue::DoubleValue(1.0)); + EXPECT_TRUE(FieldValue::NanValue() < FieldValue::DoubleValue(1.0)); + EXPECT_FALSE(FieldValue::NanValue() < FieldValue::NanValue()); + EXPECT_FALSE(FieldValue::DoubleValue(1.0) < FieldValue::NanValue()); + // Mixed + EXPECT_TRUE(FieldValue::DoubleValue(-1e20) < + FieldValue::IntegerValue(LLONG_MIN)); + EXPECT_FALSE(FieldValue::DoubleValue(1e20) < + FieldValue::IntegerValue(LLONG_MAX)); + EXPECT_TRUE(FieldValue::DoubleValue(1.234) < FieldValue::IntegerValue(2L)); + EXPECT_FALSE(FieldValue::DoubleValue(2.345) < FieldValue::IntegerValue(1L)); + EXPECT_FALSE(FieldValue::DoubleValue(1.0) < FieldValue::IntegerValue(1L)); + EXPECT_FALSE(FieldValue::DoubleValue(1.234) < FieldValue::IntegerValue(1L)); + EXPECT_FALSE(FieldValue::IntegerValue(LLONG_MIN) < + FieldValue::DoubleValue(-1e20)); + EXPECT_TRUE(FieldValue::IntegerValue(LLONG_MAX) < + FieldValue::DoubleValue(1e20)); + EXPECT_FALSE(FieldValue::IntegerValue(1) < FieldValue::DoubleValue(1.0)); + EXPECT_TRUE(FieldValue::IntegerValue(1) < FieldValue::DoubleValue(1.234)); +} + +TEST(FieldValue, TimestampType) { + const FieldValue o = FieldValue::TimestampValue(Timestamp()); + const FieldValue a = FieldValue::TimestampValue({100, 0}); + const FieldValue b = FieldValue::TimestampValue({200, 0}); + EXPECT_EQ(Type::Timestamp, a.type()); + EXPECT_TRUE(o < a); + EXPECT_TRUE(a < b); + EXPECT_FALSE(a < a); + const FieldValue c = FieldValue::ServerTimestampValue({100, 0}); + const FieldValue d = FieldValue::ServerTimestampValue({200, 0}, {300, 0}); + EXPECT_EQ(Type::ServerTimestamp, c.type()); + EXPECT_EQ(Type::ServerTimestamp, d.type()); + EXPECT_TRUE(c < d); + EXPECT_FALSE(c < c); + // Mixed + EXPECT_TRUE(o < c); + EXPECT_TRUE(a < c); + EXPECT_TRUE(b < c); + EXPECT_TRUE(b < d); + EXPECT_FALSE(c < o); + EXPECT_FALSE(c < a); + EXPECT_FALSE(c < b); + EXPECT_FALSE(d < b); +} + +TEST(FieldValue, StringType) { + const FieldValue a = FieldValue::StringValue("abc"); + std::string xyz("xyz"); + const FieldValue b = FieldValue::StringValue(xyz); + const FieldValue c = FieldValue::StringValue(std::move(xyz)); + EXPECT_EQ(Type::String, a.type()); + EXPECT_EQ(Type::String, b.type()); + EXPECT_EQ(Type::String, c.type()); + EXPECT_TRUE(a < b); + EXPECT_FALSE(a < a); +} + +TEST(FieldValue, BlobType) { + const FieldValue a = FieldValue::BlobValue(Bytes("abc"), 4); + const FieldValue b = FieldValue::BlobValue(Bytes("def"), 4); + EXPECT_EQ(Type::Blob, a.type()); + EXPECT_EQ(Type::Blob, b.type()); + EXPECT_TRUE(a < b); + EXPECT_FALSE(a < a); +} + +TEST(FieldValue, GeoPointType) { + const FieldValue a = FieldValue::GeoPointValue({1, 2}); + const FieldValue b = FieldValue::GeoPointValue({3, 4}); + EXPECT_EQ(Type::GeoPoint, a.type()); + EXPECT_EQ(Type::GeoPoint, b.type()); + EXPECT_TRUE(a < b); + EXPECT_FALSE(a < a); +} + TEST(FieldValue, ArrayType) { const FieldValue empty = FieldValue::ArrayValue(std::vector{}); @@ -64,6 +173,29 @@ TEST(FieldValue, ArrayType) { EXPECT_FALSE(large < small); } +TEST(FieldValue, ObjectType) { + const FieldValue empty = + FieldValue::ObjectValue(std::map{}); + std::map object{ + {"null", FieldValue::NullValue()}, + {"true", FieldValue::TrueValue()}, + {"false", FieldValue::FalseValue()}}; + // copy the map + const FieldValue small = FieldValue::ObjectValue(object); + std::map another_object{ + {"null", FieldValue::NullValue()}, {"true", FieldValue::FalseValue()}}; + // move the array + const FieldValue large = FieldValue::ObjectValue(std::move(another_object)); + EXPECT_EQ(Type::Object, empty.type()); + EXPECT_EQ(Type::Object, small.type()); + EXPECT_EQ(Type::Object, large.type()); + EXPECT_TRUE(empty < small); + EXPECT_FALSE(small < empty); + EXPECT_FALSE(small < small); + EXPECT_TRUE(small < large); + EXPECT_FALSE(large < small); +} + TEST(FieldValue, Copy) { FieldValue clone = FieldValue::TrueValue(); const FieldValue null_value = FieldValue::NullValue(); @@ -82,6 +214,80 @@ TEST(FieldValue, Copy) { clone = null_value; EXPECT_EQ(FieldValue::NullValue(), clone); + const FieldValue nan_value = FieldValue::NanValue(); + clone = nan_value; + EXPECT_EQ(FieldValue::NanValue(), clone); + EXPECT_EQ(FieldValue::NanValue(), nan_value); + clone = clone; + EXPECT_EQ(FieldValue::NanValue(), clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue integer_value = FieldValue::IntegerValue(1L); + clone = integer_value; + EXPECT_EQ(FieldValue::IntegerValue(1L), clone); + EXPECT_EQ(FieldValue::IntegerValue(1L), integer_value); + clone = clone; + EXPECT_EQ(FieldValue::IntegerValue(1L), clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue double_value = FieldValue::DoubleValue(1.0); + clone = double_value; + EXPECT_EQ(FieldValue::DoubleValue(1.0), clone); + EXPECT_EQ(FieldValue::DoubleValue(1.0), double_value); + clone = clone; + EXPECT_EQ(FieldValue::DoubleValue(1.0), clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue timestamp_value = FieldValue::TimestampValue({100, 200}); + clone = timestamp_value; + EXPECT_EQ(FieldValue::TimestampValue({100, 200}), clone); + EXPECT_EQ(FieldValue::TimestampValue({100, 200}), timestamp_value); + clone = clone; + EXPECT_EQ(FieldValue::TimestampValue({100, 200}), clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue server_timestamp_value = + FieldValue::ServerTimestampValue({1, 2}, {3, 4}); + clone = server_timestamp_value; + EXPECT_EQ(FieldValue::ServerTimestampValue({1, 2}, {3, 4}), clone); + EXPECT_EQ(FieldValue::ServerTimestampValue({1, 2}, {3, 4}), + server_timestamp_value); + clone = clone; + EXPECT_EQ(FieldValue::ServerTimestampValue({1, 2}, {3, 4}), clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue string_value = FieldValue::StringValue("abc"); + clone = string_value; + EXPECT_EQ(FieldValue::StringValue("abc"), clone); + EXPECT_EQ(FieldValue::StringValue("abc"), string_value); + clone = clone; + EXPECT_EQ(FieldValue::StringValue("abc"), clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue blob_value = FieldValue::BlobValue(Bytes("abc"), 4); + clone = blob_value; + EXPECT_EQ(FieldValue::BlobValue(Bytes("abc"), 4), clone); + EXPECT_EQ(FieldValue::BlobValue(Bytes("abc"), 4), blob_value); + clone = clone; + EXPECT_EQ(FieldValue::BlobValue(Bytes("abc"), 4), clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue geo_point_value = FieldValue::GeoPointValue({1, 2}); + clone = geo_point_value; + EXPECT_EQ(FieldValue::GeoPointValue({1, 2}), clone); + EXPECT_EQ(FieldValue::GeoPointValue({1, 2}), geo_point_value); + clone = clone; + EXPECT_EQ(FieldValue::GeoPointValue({1, 2}), clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + const FieldValue array_value = FieldValue::ArrayValue(std::vector{ FieldValue::TrueValue(), FieldValue::FalseValue()}); @@ -98,6 +304,30 @@ TEST(FieldValue, Copy) { clone); clone = null_value; EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue object_value = + FieldValue::ObjectValue(std::map{ + {"true", FieldValue::TrueValue()}, + {"false", FieldValue::FalseValue()}}); + clone = object_value; + EXPECT_EQ( + FieldValue::ObjectValue(std::map{ + {"true", FieldValue::TrueValue()}, + {"false", FieldValue::FalseValue()}}), + clone); + EXPECT_EQ( + FieldValue::ObjectValue(std::map{ + {"true", FieldValue::TrueValue()}, + {"false", FieldValue::FalseValue()}}), + object_value); + clone = clone; + EXPECT_EQ( + FieldValue::ObjectValue(std::map{ + {"true", FieldValue::TrueValue()}, + {"false", FieldValue::FalseValue()}}), + clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); } TEST(FieldValue, Move) { @@ -113,6 +343,48 @@ TEST(FieldValue, Move) { clone = FieldValue::NullValue(); EXPECT_EQ(FieldValue::NullValue(), clone); + FieldValue nan_value = FieldValue::NanValue(); + clone = std::move(nan_value); + EXPECT_EQ(FieldValue::NanValue(), clone); + clone = FieldValue::NullValue(); + EXPECT_EQ(FieldValue::NullValue(), clone); + + FieldValue integer_value = FieldValue::IntegerValue(1L); + clone = std::move(integer_value); + EXPECT_EQ(FieldValue::IntegerValue(1L), clone); + clone = FieldValue::NullValue(); + EXPECT_EQ(FieldValue::NullValue(), clone); + + FieldValue double_value = FieldValue::DoubleValue(1.0); + clone = std::move(double_value); + EXPECT_EQ(FieldValue::DoubleValue(1.0), clone); + clone = FieldValue::NullValue(); + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue timestamp_value = FieldValue::TimestampValue({100, 200}); + clone = std::move(timestamp_value); + EXPECT_EQ(FieldValue::TimestampValue({100, 200}), clone); + clone = FieldValue::NullValue(); + EXPECT_EQ(FieldValue::NullValue(), clone); + + FieldValue string_value = FieldValue::StringValue("abc"); + clone = std::move(string_value); + EXPECT_EQ(FieldValue::StringValue("abc"), clone); + clone = FieldValue::NullValue(); + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue blob_value = FieldValue::BlobValue(Bytes("abc"), 4); + clone = std::move(blob_value); + EXPECT_EQ(FieldValue::BlobValue(Bytes("abc"), 4), clone); + clone = FieldValue::NullValue(); + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue geo_point_value = FieldValue::GeoPointValue({1, 2}); + clone = std::move(geo_point_value); + EXPECT_EQ(FieldValue::GeoPointValue({1, 2}), clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + FieldValue array_value = FieldValue::ArrayValue(std::vector{ FieldValue::TrueValue(), FieldValue::FalseValue()}); clone = std::move(array_value); @@ -121,15 +393,41 @@ TEST(FieldValue, Move) { clone); clone = FieldValue::NullValue(); EXPECT_EQ(FieldValue::NullValue(), clone); + + FieldValue object_value = + FieldValue::ObjectValue(std::map{ + {"true", FieldValue::TrueValue()}, + {"false", FieldValue::FalseValue()}}); + clone = std::move(object_value); + EXPECT_EQ( + FieldValue::ObjectValue(std::map{ + {"true", FieldValue::TrueValue()}, + {"false", FieldValue::FalseValue()}}), + clone); + clone = FieldValue::NullValue(); + EXPECT_EQ(FieldValue::NullValue(), clone); } TEST(FieldValue, CompareMixedType) { const FieldValue null_value = FieldValue::NullValue(); const FieldValue true_value = FieldValue::TrueValue(); + const FieldValue number_value = FieldValue::NanValue(); + const FieldValue timestamp_value = FieldValue::TimestampValue({100, 200}); + const FieldValue string_value = FieldValue::StringValue("abc"); + const FieldValue blob_value = FieldValue::BlobValue(Bytes("abc"), 4); + const FieldValue geo_point_value = FieldValue::GeoPointValue({1, 2}); const FieldValue array_value = FieldValue::ArrayValue(std::vector()); + const FieldValue object_value = + FieldValue::ObjectValue(std::map()); EXPECT_TRUE(null_value < true_value); - EXPECT_TRUE(true_value < array_value); + EXPECT_TRUE(true_value < number_value); + EXPECT_TRUE(number_value < timestamp_value); + EXPECT_TRUE(timestamp_value < string_value); + EXPECT_TRUE(string_value < blob_value); + EXPECT_TRUE(blob_value < geo_point_value); + EXPECT_TRUE(geo_point_value < array_value); + EXPECT_TRUE(array_value < object_value); } TEST(FieldValue, CompareWithOperator) { diff --git a/Firestore/core/test/firebase/firestore/model/timestamp_test.cc b/Firestore/core/test/firebase/firestore/model/timestamp_test.cc new file mode 100644 index 0000000..55ee378 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/model/timestamp_test.cc @@ -0,0 +1,49 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/timestamp.h" + +#include + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace model { + +TEST(Timestamp, Getter) { + const Timestamp timestamp_zero; + EXPECT_EQ(0, timestamp_zero.seconds()); + EXPECT_EQ(0, timestamp_zero.nanos()); + + const Timestamp timestamp(100, 200); + EXPECT_EQ(100, timestamp.seconds()); + EXPECT_EQ(200, timestamp.nanos()); + + const Timestamp timestamp_now = Timestamp::Now(); + EXPECT_LT(0, timestamp_now.seconds()); + EXPECT_LE(0, timestamp_now.nanos()); +} + +TEST(Timestamp, Comparison) { + EXPECT_TRUE(Timestamp() < Timestamp(1, 2)); + EXPECT_TRUE(Timestamp(1, 2) < Timestamp(2, 1)); + EXPECT_TRUE(Timestamp(2, 1) < Timestamp(2, 2)); +} + +} // namespace model +} // namespace firestore +} // namespace firebase -- cgit v1.2.3