diff --git a/Firestore/Example/Tests/API/FSTAPIHelpers.mm b/Firestore/Example/Tests/API/FSTAPIHelpers.mm index 16e205e75e5..344ac40b671 100644 --- a/Firestore/Example/Tests/API/FSTAPIHelpers.mm +++ b/Firestore/Example/Tests/API/FSTAPIHelpers.mm @@ -91,7 +91,7 @@ doc = Doc(path, version, parsed, hasMutations ? DocumentState::kLocalMutations : DocumentState::kSynced); } - return [[FIRDocumentSnapshot alloc] initWithFirestore:FSTTestFirestore().wrapped + return [[FIRDocumentSnapshot alloc] initWithFirestore:FSTTestFirestore() documentKey:testutil::Key(path) document:doc fromCache:fromCache diff --git a/Firestore/Source/API/FIRDocumentSnapshot+Internal.h b/Firestore/Source/API/FIRDocumentSnapshot+Internal.h index 8b24fce6a15..103f5cc471f 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot+Internal.h +++ b/Firestore/Source/API/FIRDocumentSnapshot+Internal.h @@ -21,6 +21,8 @@ #include "Firestore/core/src/api/api_fwd.h" #include "Firestore/core/src/model/model_fwd.h" +@class FIRFirestore; + namespace api = firebase::firestore::api; namespace model = firebase::firestore::model; @@ -30,12 +32,12 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithSnapshot:(api::DocumentSnapshot &&)snapshot NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithFirestore:(std::shared_ptr)firestore +- (instancetype)initWithFirestore:(FIRFirestore *)firestore documentKey:(model::DocumentKey)documentKey document:(const absl::optional &)document metadata:(api::SnapshotMetadata)metadata; -- (instancetype)initWithFirestore:(std::shared_ptr)firestore +- (instancetype)initWithFirestore:(FIRFirestore *)firestore documentKey:(model::DocumentKey)documentKey document:(const absl::optional &)document fromCache:(bool)fromCache diff --git a/Firestore/Source/API/FIRDocumentSnapshot.mm b/Firestore/Source/API/FIRDocumentSnapshot.mm index 022b09452df..4c220ef25db 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot.mm +++ b/Firestore/Source/API/FIRDocumentSnapshot.mm @@ -27,8 +27,10 @@ #import "Firestore/Source/API/FIRGeoPoint+Internal.h" #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" #import "Firestore/Source/API/FIRTimestamp+Internal.h" +#import "Firestore/Source/API/FSTUserDataWriter.h" #import "Firestore/Source/API/converters.h" +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/api/document_reference.h" #include "Firestore/core/src/api/document_snapshot.h" #include "Firestore/core/src/api/firestore.h" @@ -39,6 +41,7 @@ #include "Firestore/core/src/model/field_value.h" #include "Firestore/core/src/model/field_value_options.h" #include "Firestore/core/src/nanopb/nanopb_util.h" +#include "Firestore/core/src/remote/serializer.h" #include "Firestore/core/src/util/exception.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/log.h" @@ -60,9 +63,11 @@ using firebase::firestore::model::FieldValueOptions; using firebase::firestore::model::ObjectValue; using firebase::firestore::model::ServerTimestampBehavior; +using firebase::firestore::remote::Serializer; using firebase::firestore::nanopb::MakeNSData; using firebase::firestore::util::MakeString; using firebase::firestore::util::ThrowInvalidArgument; +using firebase::firestore::google_firestore_v1_Value; NS_ASSUME_NONNULL_BEGIN @@ -90,32 +95,35 @@ ServerTimestampBehavior InternalServerTimestampBehavior(FIRServerTimestampBehavi @implementation FIRDocumentSnapshot { DocumentSnapshot _snapshot; + std::unique_ptr _serializer; FIRSnapshotMetadata *_cachedMetadata; } - (instancetype)initWithSnapshot:(DocumentSnapshot &&)snapshot { if (self = [super init]) { _snapshot = std::move(snapshot); + _serializer.reset(new Serializer(_snapshot.firestore()->database_id())); } return self; } -- (instancetype)initWithFirestore:(std::shared_ptr)firestore +- (instancetype)initWithFirestore:(FIRFirestore *)firestore documentKey:(DocumentKey)documentKey document:(const absl::optional &)document metadata:(SnapshotMetadata)metadata { DocumentSnapshot wrapped; if (document.has_value()) { wrapped = - DocumentSnapshot::FromDocument(std::move(firestore), document.value(), std::move(metadata)); + DocumentSnapshot::FromDocument(firestore.wrapped, document.value(), std::move(metadata)); } else { - wrapped = DocumentSnapshot::FromNoDocument(std::move(firestore), std::move(documentKey), + wrapped = DocumentSnapshot::FromNoDocument(firestore.wrapped, std::move(documentKey), std::move(metadata)); } + _serializer.reset(new Serializer(firestore.databaseID)); return [self initWithSnapshot:std::move(wrapped)]; } -- (instancetype)initWithFirestore:(std::shared_ptr)firestore +- (instancetype)initWithFirestore:(FIRFirestore *)firestore documentKey:(DocumentKey)documentKey document:(const absl::optional &)document fromCache:(bool)fromCache @@ -176,7 +184,7 @@ - (FIRSnapshotMetadata *)metadata { absl::optional data = _snapshot.GetData(); if (!data) return nil; - return [self convertedObject:data->GetInternalValue() options:options]; + return [self convertedValue:*data options:options]; } - (nullable id)valueForField:(id)field { @@ -208,94 +216,27 @@ - (FieldValueOptions)optionsForServerTimestampBehavior: return FieldValueOptions(InternalServerTimestampBehavior(serverTimestampBehavior)); } -// TODO(mutabledocuments): Replace with UserDataWriter +// TODO(mutabledocuments): Replace this method and call UserDataWriter directly - (id)convertedValue:(FieldValue)value options:(const FieldValueOptions &)options { - switch (value.type()) { - case FieldValue::Type::Null: - return [NSNull null]; - case FieldValue::Type::Boolean: - return value.boolean_value() ? @YES : @NO; - case FieldValue::Type::Integer: - return @(value.integer_value()); - case FieldValue::Type::Double: - return @(value.double_value()); - case FieldValue::Type::Timestamp: - return [self convertedTimestamp:value]; - case FieldValue::Type::ServerTimestamp: - return [self convertedServerTimestamp:value options:options]; - case FieldValue::Type::String: - return util::MakeNSString(value.string_value()); - case FieldValue::Type::Blob: - return MakeNSData(value.blob_value()); - case FieldValue::Type::Reference: - return [self convertedReference:value]; - case FieldValue::Type::GeoPoint: - return MakeFIRGeoPoint(value.geo_point_value()); - case FieldValue::Type::Array: - return [self convertedArray:value.array_value() options:options]; - case FieldValue::Type::Object: - return [self convertedObject:value.object_value() options:options]; - } - - UNREACHABLE(); -} - -- (id)convertedTimestamp:(const FieldValue &)value { - return MakeFIRTimestamp(value.timestamp_value()); -} - -- (id)convertedServerTimestamp:(const FieldValue &)value - options:(const FieldValueOptions &)options { - const auto &sts = value.server_timestamp_value(); + FIRServerTimestampBehavior behavior; switch (options.server_timestamp_behavior()) { case ServerTimestampBehavior::kNone: - return [NSNull null]; - case ServerTimestampBehavior::kEstimate: { - FieldValue local_write_time = FieldValue::FromTimestamp(sts.local_write_time()); - return [self convertedTimestamp:local_write_time]; - } + behavior = FIRServerTimestampBehaviorNone; + break; + case ServerTimestampBehavior::kEstimate: + behavior = FIRServerTimestampBehaviorEstimate; + break; case ServerTimestampBehavior::kPrevious: - return sts.previous_value() ? [self convertedValue:*sts.previous_value() options:options] - : [NSNull null]; - } - - UNREACHABLE(); -} - -- (id)convertedReference:(const FieldValue &)value { - const auto &ref = value.reference_value(); - const DatabaseId &refDatabase = ref.database_id(); - const DatabaseId &database = _snapshot.firestore()->database_id(); - if (refDatabase != database) { - LOG_WARN("Document %s contains a document reference within a different database (%s/%s) which " - "is not supported. It will be treated as a reference within the current database " - "(%s/%s) instead.", - _snapshot.CreateReference().Path(), refDatabase.project_id(), - refDatabase.database_id(), database.project_id(), database.database_id()); - } - const DocumentKey &key = ref.key(); - return [[FIRDocumentReference alloc] initWithKey:key firestore:_snapshot.firestore()]; -} - -- (NSArray *)convertedArray:(const FieldValue::Array &)arrayContents - options:(const FieldValueOptions &)options { - NSMutableArray *result = [NSMutableArray arrayWithCapacity:arrayContents.size()]; - for (const FieldValue &value : arrayContents) { - [result addObject:[self convertedValue:value options:options]]; + behavior = FIRServerTimestampBehaviorPrevious; + break; + default: + HARD_FAIL("Unexpected server timestamp option: %s", options.server_timestamp_behavior()); } - return result; -} -- (NSDictionary *)convertedObject:(const FieldValue::Map &)objectValue - options:(const FieldValueOptions &)options { - NSMutableDictionary *result = [NSMutableDictionary dictionary]; - for (const auto &kv : objectValue) { - const std::string &key = kv.first; - const FieldValue &value = kv.second; - - result[util::MakeNSString(key)] = [self convertedValue:value options:options]; - } - return result; + FSTUserDataWriter *dataWriter = [[FSTUserDataWriter alloc] initWithFirestore:_snapshot.firestore() + serverTimestampBehavior:behavior]; + google_firestore_v1_Value protoValue = _serializer->EncodeFieldValue(value); + return [dataWriter convertedValue:protoValue]; } @end diff --git a/Firestore/Source/API/FIRTransaction.mm b/Firestore/Source/API/FIRTransaction.mm index d7f85549479..3ff27903af4 100644 --- a/Firestore/Source/API/FIRTransaction.mm +++ b/Firestore/Source/API/FIRTransaction.mm @@ -134,16 +134,15 @@ - (void)getDocument:(FIRDocumentReference *)document HARD_ASSERT(documents.size() == 1, "Mismatch in docs returned from document lookup."); const MaybeDocument &internalDoc = documents.front(); if (internalDoc.is_no_document()) { - FIRDocumentSnapshot *doc = - [[FIRDocumentSnapshot alloc] initWithFirestore:self.firestore.wrapped - documentKey:document.key - document:absl::nullopt - fromCache:false - hasPendingWrites:false]; + FIRDocumentSnapshot *doc = [[FIRDocumentSnapshot alloc] initWithFirestore:self.firestore + documentKey:document.key + document:absl::nullopt + fromCache:false + hasPendingWrites:false]; completion(doc, nil); } else if (internalDoc.is_document()) { FIRDocumentSnapshot *doc = - [[FIRDocumentSnapshot alloc] initWithFirestore:self.firestore.wrapped + [[FIRDocumentSnapshot alloc] initWithFirestore:self.firestore documentKey:internalDoc.key() document:Document(internalDoc) fromCache:false diff --git a/Firestore/Source/API/FSTUserDataReader.mm b/Firestore/Source/API/FSTUserDataReader.mm index c0726742ac9..356a08560b3 100644 --- a/Firestore/Source/API/FSTUserDataReader.mm +++ b/Firestore/Source/API/FSTUserDataReader.mm @@ -340,7 +340,7 @@ - (google_firestore_v1_Value)parseDictionary:(NSDictionary *)dic // Compute the final size of the fields array, which contains an entry for // all fields that are not FieldValue sentinels __block pb_size_t count = 0; - [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *) { + [dict enumerateKeysAndObjectsUsingBlock:^(NSString *, id value, BOOL *) { if (![value isKindOfClass:[FIRFieldValue class]]) { ++count; } diff --git a/Firestore/Source/API/FSTUserDataWriter.h b/Firestore/Source/API/FSTUserDataWriter.h new file mode 100644 index 00000000000..2f0f3d67633 --- /dev/null +++ b/Firestore/Source/API/FSTUserDataWriter.h @@ -0,0 +1,40 @@ +/* + * Copyright 2021 Google LLC + * + * 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. + */ + +#import + +#include + +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" +#include "Firestore/core/src/api/api_fwd.h" + +namespace api = firebase::firestore::api; + +/** + * Converts Firestore's internal types to the API types that we expose to the + * user. + */ +@interface FSTUserDataWriter : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithFirestore:(std::shared_ptr)firestore + serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior; + +- (id)convertedValue:(const firebase::firestore::google_firestore_v1_Value&)value; + +@end diff --git a/Firestore/Source/API/FSTUserDataWriter.mm b/Firestore/Source/API/FSTUserDataWriter.mm new file mode 100644 index 00000000000..72c85998ff7 --- /dev/null +++ b/Firestore/Source/API/FSTUserDataWriter.mm @@ -0,0 +1,166 @@ +// Copyright 2021 Google LLC +// +// 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/Source/API/FSTUserDataWriter.h" + +#import +#import +#import + +#include +#include + +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/Source/API/FIRDocumentReference+Internal.h" +#include "Firestore/Source/API/converters.h" +#include "Firestore/core/include/firebase/firestore/geo_point.h" +#include "Firestore/core/include/firebase/firestore/timestamp.h" +#include "Firestore/core/src/api/firestore.h" +#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/model/value_util.h" +#include "Firestore/core/src/util/log.h" +#include "Firestore/core/src/util/string_apple.h" + +@class FIRTimestamp; + +namespace api = firebase::firestore::api; +namespace util = firebase::firestore::util; +namespace model = firebase::firestore::model; +namespace nanopb = firebase::firestore::nanopb; + +using api::MakeFIRDocumentReference; +using api::MakeFIRGeoPoint; +using api::MakeFIRTimestamp; +using firebase::firestore::GeoPoint; +using firebase::firestore::google_firestore_v1_ArrayValue; +using firebase::firestore::google_firestore_v1_MapValue; +using firebase::firestore::google_firestore_v1_Value; +using firebase::firestore::google_protobuf_Timestamp; +using model::DatabaseId; +using model::DocumentKey; +using model::GetLocalWriteTime; +using model::GetPreviousValue; +using model::GetTypeOrder; +using model::TypeOrder; +using nanopb::MakeByteString; +using nanopb::MakeBytesArray; +using nanopb::MakeNSData; +using nanopb::MakeString; +using nanopb::MakeStringView; + +NS_ASSUME_NONNULL_BEGIN + +@implementation FSTUserDataWriter { + std::shared_ptr _firestore; + FIRServerTimestampBehavior _serverTimestampBehavior; +} + +- (instancetype)initWithFirestore:(std::shared_ptr)firestore + serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior { + self = [super init]; + if (self) { + _firestore = std::move(firestore); + _serverTimestampBehavior = serverTimestampBehavior; + } + return self; +} + +- (id)convertedValue:(const google_firestore_v1_Value &)value { + switch (GetTypeOrder(value)) { + case TypeOrder::kMap: + return [self convertedObject:value.map_value]; + case TypeOrder::kArray: + return [self convertedArray:value.array_value]; + case TypeOrder::kReference: + return [self convertedReference:value]; + case TypeOrder::kTimestamp: + return [self convertedTimestamp:value.timestamp_value]; + case TypeOrder::kServerTimestamp: + return [self convertedServerTimestamp:value]; + case TypeOrder::kNull: + return [NSNull null]; + case TypeOrder::kBoolean: + return value.boolean_value ? @YES : @NO; + case TypeOrder::kNumber: + return value.which_value_type == google_firestore_v1_Value_integer_value_tag + ? @(value.integer_value) + : @(value.double_value); + case TypeOrder::kString: + return util::MakeNSString(MakeStringView(value.string_value)); + case TypeOrder::kBlob: + return MakeNSData(value.bytes_value); + case TypeOrder::kGeoPoint: + return MakeFIRGeoPoint( + GeoPoint(value.geo_point_value.latitude, value.geo_point_value.longitude)); + } + + UNREACHABLE(); +} + +- (NSDictionary *)convertedObject:(const google_firestore_v1_MapValue &)mapValue { + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + for (pb_size_t i = 0; i < mapValue.fields_count; ++i) { + absl::string_view key = MakeStringView(mapValue.fields[i].key); + const google_firestore_v1_Value &value = mapValue.fields[i].value; + result[util::MakeNSString(key)] = [self convertedValue:value]; + } + return result; +} + +- (NSArray *)convertedArray:(const google_firestore_v1_ArrayValue &)arrayValue { + NSMutableArray *result = [NSMutableArray arrayWithCapacity:arrayValue.values_count]; + for (pb_size_t i = 0; i < arrayValue.values_count; ++i) { + [result addObject:[self convertedValue:arrayValue.values[i]]]; + } + return result; +} + +- (id)convertedServerTimestamp:(const google_firestore_v1_Value &)serverTimestampValue { + switch (_serverTimestampBehavior) { + case FIRServerTimestampBehavior::FIRServerTimestampBehaviorNone: + return [NSNull null]; + case FIRServerTimestampBehavior::FIRServerTimestampBehaviorEstimate: + return [self convertedTimestamp:GetLocalWriteTime(serverTimestampValue)]; + case FIRServerTimestampBehavior::FIRServerTimestampBehaviorPrevious: { + auto previous_value = GetPreviousValue(serverTimestampValue); + return previous_value ? [self convertedValue:*previous_value] : [NSNull null]; + } + } + + UNREACHABLE(); +} + +- (FIRTimestamp *)convertedTimestamp:(const google_protobuf_Timestamp &)value { + return MakeFIRTimestamp(firebase::Timestamp{value.seconds, value.nanos}); +} + +- (FIRDocumentReference *)convertedReference:(const google_firestore_v1_Value &)value { + std::string ref = MakeString(value.reference_value); + DatabaseId databaseID = DatabaseId::FromName(ref); + DocumentKey key = DocumentKey::FromName(ref); + if (databaseID != _firestore->database_id()) { + LOG_WARN("Document reference is for a different database (%s/%s) which " + "is not supported. It will be treated as a reference within the current database " + "(%s/%s) instead.", + databaseID.project_id(), databaseID.database_id(), databaseID.project_id(), + databaseID.database_id()); + } + return MakeFIRDocumentReference(key, _firestore); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/converters.h b/Firestore/Source/API/converters.h index 199df59cdc0..f5d0e4545b8 100644 --- a/Firestore/Source/API/converters.h +++ b/Firestore/Source/API/converters.h @@ -23,8 +23,11 @@ #import +#include + @class FIRGeoPoint; @class FIRTimestamp; +@class FIRDocumentReference; NS_ASSUME_NONNULL_BEGIN @@ -36,8 +39,14 @@ namespace firestore { class GeoPoint; +namespace model { +class DocumentKey; +} + namespace api { +class Firestore; + /** Converts a user-supplied FIRGeoPoint to the equivalent C++ GeoPoint. */ GeoPoint MakeGeoPoint(FIRGeoPoint* geo_point); @@ -50,6 +59,9 @@ Timestamp MakeTimestamp(NSDate* date); FIRTimestamp* MakeFIRTimestamp(const Timestamp& timestamp); +FIRDocumentReference* MakeFIRDocumentReference(const model::DocumentKey& document_key, + std::shared_ptr firestore); + } // namespace api } // namespace firestore } // namespace firebase diff --git a/Firestore/Source/API/converters.mm b/Firestore/Source/API/converters.mm index bc96fb153a8..251d3e578bf 100644 --- a/Firestore/Source/API/converters.mm +++ b/Firestore/Source/API/converters.mm @@ -16,11 +16,16 @@ #include "Firestore/Source/API/converters.h" +#include + #import "FIRGeoPoint.h" #import "FIRTimestamp.h" +#include "Firestore/Source/API/FIRDocumentReference+Internal.h" #include "Firestore/core/include/firebase/firestore/geo_point.h" #include "Firestore/core/include/firebase/firestore/timestamp.h" +#include "Firestore/core/src/api/firestore.h" +#include "Firestore/core/src/model/document_key.h" NS_ASSUME_NONNULL_BEGIN @@ -51,6 +56,11 @@ Timestamp MakeTimestamp(NSDate* date) { nanoseconds:timestamp.nanoseconds()]; } +FIRDocumentReference* MakeFIRDocumentReference(const model::DocumentKey& key, + std::shared_ptr firestore) { + return [[FIRDocumentReference alloc] initWithKey:key firestore:std::move(firestore)]; +} + } // namespace api } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/model/database_id.cc b/Firestore/core/src/model/database_id.cc index afee7c14805..63022d52e3d 100644 --- a/Firestore/core/src/model/database_id.cc +++ b/Firestore/core/src/model/database_id.cc @@ -18,8 +18,10 @@ #include +#include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/hashing.h" +#include "absl/strings/string_view.h" namespace firebase { namespace firestore { @@ -34,6 +36,14 @@ DatabaseId::DatabaseId(std::string project_id, std::string database_id) { rep_ = std::make_shared(std::move(project_id), std::move(database_id)); } +DatabaseId DatabaseId::FromName(const std::string& name) { + auto resource_name = ResourcePath::FromString(name); + HARD_ASSERT(resource_name.size() > 3 && resource_name[0] == "projects" && + resource_name[2] == "databases", + "Tried to parse an invalid resource name: %s", name); + return DatabaseId{resource_name[1], resource_name[3]}; +} + util::ComparisonResult DatabaseId::CompareTo( const firebase::firestore::model::DatabaseId& rhs) const { util::ComparisonResult cmp = util::Compare(project_id(), rhs.project_id()); diff --git a/Firestore/core/src/model/database_id.h b/Firestore/core/src/model/database_id.h index 5cb2fc72bba..276511757b4 100644 --- a/Firestore/core/src/model/database_id.h +++ b/Firestore/core/src/model/database_id.h @@ -23,6 +23,7 @@ #include #include "Firestore/core/src/util/comparison.h" +#include "absl/strings/string_view.h" namespace firebase { namespace firestore { @@ -46,6 +47,9 @@ class DatabaseId : public util::Comparable { explicit DatabaseId(std::string project_id, std::string database_id = kDefault); + /** Returns a DatabaseId from a fully qualified resource name. */ + static DatabaseId FromName(const std::string& name); + const std::string& project_id() const { return rep_->project_id; } diff --git a/Firestore/core/src/model/document_key.cc b/Firestore/core/src/model/document_key.cc index f5fd8c73d82..a8da934347f 100644 --- a/Firestore/core/src/model/document_key.cc +++ b/Firestore/core/src/model/document_key.cc @@ -50,13 +50,22 @@ DocumentKey::DocumentKey(ResourcePath&& path) } DocumentKey DocumentKey::FromPathString(const std::string& path) { - return DocumentKey{ResourcePath::FromStringView(path)}; + return DocumentKey{ResourcePath::FromString(path)}; } DocumentKey DocumentKey::FromSegments(std::initializer_list list) { return DocumentKey{ResourcePath{list}}; } +DocumentKey DocumentKey::FromName(const std::string& name) { + auto resource_name = ResourcePath::FromString(name); + HARD_ASSERT(resource_name.size() > 4 && resource_name[0] == "projects" && + resource_name[2] == "databases" && + resource_name[4] == "documents", + "Tried to parse an invalid key: %s", name); + return DocumentKey{resource_name.PopFirst(5)}; +} + const DocumentKey& DocumentKey::Empty() { static const DocumentKey* empty = new DocumentKey(); return *empty; diff --git a/Firestore/core/src/model/document_key.h b/Firestore/core/src/model/document_key.h index 9becf3841fe..2b17cb7251a 100644 --- a/Firestore/core/src/model/document_key.h +++ b/Firestore/core/src/model/document_key.h @@ -23,6 +23,8 @@ #include #include +#include "absl/strings/string_view.h" + namespace firebase { namespace firestore { @@ -57,6 +59,9 @@ class DocumentKey { /** Creates and returns a new document key with the given segments. */ static DocumentKey FromSegments(std::initializer_list list); + /** Returns a DocumentKey from a fully qualified resource name. */ + static DocumentKey FromName(const std::string& name); + /** Returns a shared instance of an empty document key. */ static const DocumentKey& Empty(); diff --git a/Firestore/core/src/model/field_value_options.h b/Firestore/core/src/model/field_value_options.h index dbe5f8c3ca2..a3a14677914 100644 --- a/Firestore/core/src/model/field_value_options.h +++ b/Firestore/core/src/model/field_value_options.h @@ -21,6 +21,8 @@ namespace firebase { namespace firestore { namespace model { +// TODO(mutabledocuments): Remove this class + /** Defines the return value for pending server timestamps. */ enum class ServerTimestampBehavior { kNone, kEstimate, kPrevious }; diff --git a/Firestore/core/src/model/server_timestamp_util.cc b/Firestore/core/src/model/server_timestamp_util.cc index 683953a4602..13d80d0df56 100644 --- a/Firestore/core/src/model/server_timestamp_util.cc +++ b/Firestore/core/src/model/server_timestamp_util.cc @@ -25,8 +25,41 @@ namespace model { const char kTypeKey[] = "__type__"; const char kLocalWriteTimeKey[] = "__local_write_time__"; +const char kPreviousValueKey[] = "__previous_value__"; const char kServerTimestampSentinel[] = "server_timestamp"; +google_firestore_v1_Value EncodeServerTimestamp( + google_protobuf_Timestamp local_write_time, + absl::optional previous_value) { + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_map_value_tag; + + pb_size_t count = previous_value ? 3 : 2; + + auto& map_value = result.map_value; + map_value.fields_count = count; + map_value.fields = + nanopb::MakeArray(count); + + auto* field = map_value.fields; + field->key = nanopb::MakeBytesArray(kTypeKey); + field->value.which_value_type = google_firestore_v1_Value_string_value_tag; + field->value.string_value = nanopb::MakeBytesArray(kServerTimestampSentinel); + + ++field; + field->key = nanopb::MakeBytesArray(kLocalWriteTimeKey); + field->value.which_value_type = google_firestore_v1_Value_timestamp_value_tag; + field->value.timestamp_value = local_write_time; + + if (previous_value) { + ++field; + field->key = nanopb::MakeBytesArray(kPreviousValueKey); + field->value = *previous_value; + } + + return result; +} + bool IsServerTimestamp(const google_firestore_v1_Value& value) { if (value.which_value_type != google_firestore_v1_Value_map_value_tag) { return false; @@ -50,19 +83,38 @@ bool IsServerTimestamp(const google_firestore_v1_Value& value) { return false; } -const google_firestore_v1_Value& GetLocalWriteTime( +google_protobuf_Timestamp GetLocalWriteTime( const firebase::firestore::google_firestore_v1_Value& value) { for (size_t i = 0; i < value.map_value.fields_count; ++i) { const auto& field = value.map_value.fields[i]; absl::string_view key = nanopb::MakeStringView(field.key); - if (key == kLocalWriteTimeKey) { - return field.value; + if (key == kLocalWriteTimeKey && + field.value.which_value_type == + google_firestore_v1_Value_timestamp_value_tag) { + return field.value.timestamp_value; } } HARD_FAIL("LocalWriteTime not found"); } +absl::optional GetPreviousValue( + const google_firestore_v1_Value& value) { + for (size_t i = 0; i < value.map_value.fields_count; ++i) { + const auto& field = value.map_value.fields[i]; + absl::string_view key = nanopb::MakeStringView(field.key); + if (key == kPreviousValueKey) { + if (IsServerTimestamp(field.value)) { + return GetPreviousValue(field.value); + } else { + return field.value; + } + } + } + + return absl::nullopt; +} + } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/model/server_timestamp_util.h b/Firestore/core/src/model/server_timestamp_util.h index b5c79f55c3d..ba1fb08f5f7 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 "absl/types/optional.h" namespace firebase { namespace firestore { @@ -26,6 +27,12 @@ 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. + */ +google_firestore_v1_Value EncodeServerTimestamp( + google_protobuf_Timestamp local_write_time, + absl::optional previous_value); /** * Returns whether the provided value is a field map that contains the * sentinel values of a ServerTimestamp. @@ -35,7 +42,16 @@ bool IsServerTimestamp(const google_firestore_v1_Value& value); /** * Returns the local time at which the timestamp was written to the document. */ -const google_firestore_v1_Value& GetLocalWriteTime( +google_protobuf_Timestamp GetLocalWriteTime( + const google_firestore_v1_Value& value); + +/** + * Returns the value of the field before this ServerTimestamp was set. + * + * Preserving the previous values allows the user to display the last resolved + * value until the backend responds with the timestamp. + */ +absl::optional GetPreviousValue( const google_firestore_v1_Value& value); } // namespace model diff --git a/Firestore/core/src/model/value_util.cc b/Firestore/core/src/model/value_util.cc index b89fde5cfd5..694f6245b41 100644 --- a/Firestore/core/src/model/value_util.cc +++ b/Firestore/core/src/model/value_util.cc @@ -97,14 +97,13 @@ ComparisonResult CompareNumbers(const google_firestore_v1_Value& left, } } -ComparisonResult CompareTimestamps(const google_firestore_v1_Value& left, - const google_firestore_v1_Value& right) { - ComparisonResult cmp = util::Compare(left.timestamp_value.seconds, - right.timestamp_value.seconds); +ComparisonResult CompareTimestamps(const google_protobuf_Timestamp& left, + const google_protobuf_Timestamp& right) { + ComparisonResult cmp = util::Compare(left.seconds, right.seconds); if (cmp != ComparisonResult::Same) { return cmp; } - return util::Compare(left.timestamp_value.nanos, right.timestamp_value.nanos); + return util::Compare(left.nanos, right.nanos); } ComparisonResult CompareStrings(const google_firestore_v1_Value& left, @@ -219,7 +218,7 @@ ComparisonResult Compare(const google_firestore_v1_Value& left, return CompareNumbers(left, right); case TypeOrder::kTimestamp: - return CompareTimestamps(left, right); + return CompareTimestamps(left.timestamp_value, right.timestamp_value); case TypeOrder::kServerTimestamp: return CompareTimestamps(GetLocalWriteTime(left), @@ -327,8 +326,12 @@ bool Equals(const google_firestore_v1_Value& lhs, return lhs.timestamp_value.seconds == rhs.timestamp_value.seconds && lhs.timestamp_value.nanos == rhs.timestamp_value.nanos; - case TypeOrder::kServerTimestamp: - return GetLocalWriteTime(lhs) == GetLocalWriteTime(rhs); + case TypeOrder::kServerTimestamp: { + const auto& left_ts = GetLocalWriteTime(lhs); + const auto& right_ts = GetLocalWriteTime(rhs); + return left_ts.seconds == right_ts.seconds && + left_ts.nanos == right_ts.nanos; + } case TypeOrder::kString: return nanopb::MakeStringView(lhs.string_value) == diff --git a/Firestore/core/src/nanopb/nanopb_util.h b/Firestore/core/src/nanopb/nanopb_util.h index 580b17576d3..df2593c9625 100644 --- a/Firestore/core/src/nanopb/nanopb_util.h +++ b/Firestore/core/src/nanopb/nanopb_util.h @@ -116,6 +116,10 @@ inline NSData* _Nonnull MakeNSData(const ByteString& str) { return [[NSData alloc] initWithBytes:str.data() length:str.size()]; } +inline NSData* _Nonnull MakeNSData(const pb_bytes_array_t* _Nullable data) { + return [[NSData alloc] initWithBytes:data->bytes length:data->size]; +} + inline NSData* _Nullable MakeNullableNSData(const ByteString& str) { if (str.empty()) return nil; return MakeNSData(str); diff --git a/Firestore/core/src/remote/serializer.cc b/Firestore/core/src/remote/serializer.cc index 642969bb15e..7f7c6bfbef7 100644 --- a/Firestore/core/src/remote/serializer.cc +++ b/Firestore/core/src/remote/serializer.cc @@ -42,6 +42,7 @@ #include "Firestore/core/src/model/no_document.h" #include "Firestore/core/src/model/patch_mutation.h" #include "Firestore/core/src/model/resource_path.h" +#include "Firestore/core/src/model/server_timestamp_util.h" #include "Firestore/core/src/model/set_mutation.h" #include "Firestore/core/src/model/verify_mutation.h" #include "Firestore/core/src/nanopb/byte_string.h" @@ -78,6 +79,7 @@ using model::DeleteMutation; using model::Document; using model::DocumentKey; using model::DocumentState; +using model::EncodeServerTimestamp; using model::FieldMask; using model::FieldPath; using model::FieldTransform; @@ -219,8 +221,7 @@ google_firestore_v1_Value Serializer::EncodeFieldValue( } case FieldValue::Type::ServerTimestamp: - HARD_FAIL("Unhandled type %s on %s", field_value.type(), - field_value.ToString()); + return EncodeServerTimestampValue(field_value.server_timestamp_value()); } UNREACHABLE(); } @@ -261,6 +262,17 @@ google_firestore_v1_Value Serializer::EncodeTimestampValue( return result; } +google_firestore_v1_Value Serializer::EncodeServerTimestampValue( + const FieldValue::ServerTimestamp& value) const { + auto previous_value = + value.previous_value() + ? absl::optional{EncodeFieldValue( + *value.previous_value())} + : absl::nullopt; + return EncodeServerTimestamp(EncodeTimestamp(value.local_write_time()), + previous_value); +} + google_firestore_v1_Value Serializer::EncodeStringValue( const std::string& value) const { google_firestore_v1_Value result{}; @@ -479,7 +491,7 @@ pb_bytes_array_t* Serializer::EncodeResourceName( ResourcePath Serializer::DecodeResourceName(ReadContext* context, absl::string_view encoded) const { - ResourcePath resource = ResourcePath::FromStringView(encoded); + auto resource = ResourcePath::FromStringView(encoded); if (!IsValidResourceName(resource)) { context->Fail(StringFormat("Tried to deserialize an invalid key %s", resource.CanonicalString())); diff --git a/Firestore/core/src/remote/serializer.h b/Firestore/core/src/remote/serializer.h index d7a4c188688..b28b1b7c3c1 100644 --- a/Firestore/core/src/remote/serializer.h +++ b/Firestore/core/src/remote/serializer.h @@ -274,6 +274,8 @@ class Serializer { google_firestore_v1_Value EncodeInteger(int64_t value) const; google_firestore_v1_Value EncodeDouble(double value) const; google_firestore_v1_Value EncodeTimestampValue(Timestamp value) const; + google_firestore_v1_Value EncodeServerTimestampValue( + const model::FieldValue::ServerTimestamp& value) const; google_firestore_v1_Value EncodeStringValue(const std::string& value) const; google_firestore_v1_Value EncodeBlob(const nanopb::ByteString& value) const; google_firestore_v1_Value EncodeReference(