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` --- .../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 +++++++++ 7 files changed, 470 insertions(+) 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 (limited to 'Firestore/core/src/firebase/firestore') 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_ -- cgit v1.2.3