aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/core/src/firebase
diff options
context:
space:
mode:
authorGravatar rsgowman <rgowman@google.com>2018-06-21 13:59:56 -0400
committerGravatar GitHub <noreply@github.com>2018-06-21 13:59:56 -0400
commitb271a6e25144be8cf872d028bb82336b5da2074c (patch)
treefba5841dcf2ad065a32c7fc992c81a090e22675b /Firestore/core/src/firebase
parent11abb976702341e0b67c1030a3f23f57c8534517 (diff)
Initial creation of the local serializer. (#1415)
Added a single, basic test as a motivator.
Diffstat (limited to 'Firestore/core/src/firebase')
-rw-r--r--Firestore/core/src/firebase/firestore/local/CMakeLists.txt6
-rw-r--r--Firestore/core/src/firebase/firestore/local/local_serializer.cc185
-rw-r--r--Firestore/core/src/firebase/firestore/local/local_serializer.h109
-rw-r--r--Firestore/core/src/firebase/firestore/remote/serializer.cc55
-rw-r--r--Firestore/core/src/firebase/firestore/remote/serializer.h17
5 files changed, 353 insertions, 19 deletions
diff --git a/Firestore/core/src/firebase/firestore/local/CMakeLists.txt b/Firestore/core/src/firebase/firestore/local/CMakeLists.txt
index 4d39c3d..3e009bb 100644
--- a/Firestore/core/src/firebase/firestore/local/CMakeLists.txt
+++ b/Firestore/core/src/firebase/firestore/local/CMakeLists.txt
@@ -19,9 +19,15 @@ cc_library(
leveldb_key.cc
leveldb_transaction.h
leveldb_transaction.cc
+ local_serializer.h
+ local_serializer.cc
DEPENDS
LevelDB::LevelDB
absl_strings
firebase_firestore_model
+ firebase_firestore_nanopb
+ firebase_firestore_protos_nanopb
+ firebase_firestore_remote
firebase_firestore_util
+ nanopb
)
diff --git a/Firestore/core/src/firebase/firestore/local/local_serializer.cc b/Firestore/core/src/firebase/firestore/local/local_serializer.cc
new file mode 100644
index 0000000..fea99fb
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/local/local_serializer.cc
@@ -0,0 +1,185 @@
+/*
+ * 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/local/local_serializer.h"
+
+#include <cstdlib>
+#include <utility>
+
+#include "Firestore/Protos/nanopb/firestore/local/maybe_document.nanopb.h"
+#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.nanopb.h"
+#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+#include "Firestore/core/src/firebase/firestore/model/no_document.h"
+#include "Firestore/core/src/firebase/firestore/nanopb/tag.h"
+#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
+
+namespace firebase {
+namespace firestore {
+namespace local {
+
+using firebase::firestore::model::ObjectValue;
+using firebase::firestore::nanopb::Reader;
+using firebase::firestore::nanopb::Tag;
+using firebase::firestore::nanopb::Writer;
+using firebase::firestore::util::Status;
+
+Status LocalSerializer::EncodeMaybeDocument(
+ const model::MaybeDocument& document,
+ std::vector<uint8_t>* out_bytes) const {
+ Writer writer = Writer::Wrap(out_bytes);
+ EncodeMaybeDocument(&writer, document);
+ return writer.status();
+}
+
+void LocalSerializer::EncodeMaybeDocument(
+ Writer* writer, const model::MaybeDocument& maybe_doc) const {
+ switch (maybe_doc.type()) {
+ case model::MaybeDocument::Type::Document:
+ writer->WriteTag(
+ {PB_WT_STRING, firestore_client_MaybeDocument_document_tag});
+ writer->WriteNestedMessage([&](Writer* writer) {
+ EncodeDocument(writer, static_cast<const model::Document&>(maybe_doc));
+ });
+ return;
+
+ case model::MaybeDocument::Type::NoDocument:
+ // TODO(rsgowman)
+ abort();
+
+ case model::MaybeDocument::Type::Unknown:
+ // TODO(rsgowman)
+ abort();
+ }
+
+ UNREACHABLE();
+}
+
+std::unique_ptr<model::MaybeDocument> LocalSerializer::DecodeMaybeDocument(
+ Reader* reader) const {
+ if (!reader->status().ok()) return nullptr;
+
+ // Initialize MaybeDocument fields to their default values. (Due to the
+ // 'oneof' in MaybeDocument, only one of 'no_document' or 'document' should
+ // ever be set.)
+ std::unique_ptr<model::NoDocument> no_document;
+ std::unique_ptr<model::Document> document;
+
+ while (reader->bytes_left()) {
+ Tag tag = reader->ReadTag();
+ if (!reader->status().ok()) return nullptr;
+
+ // Ensure the tag matches the wire type
+ switch (tag.field_number) {
+ case firestore_client_MaybeDocument_document_tag:
+ case firestore_client_MaybeDocument_no_document_tag:
+ if (tag.wire_type != PB_WT_STRING) {
+ reader->set_status(
+ Status(FirestoreErrorCode::DataLoss,
+ "Input proto bytes cannot be parsed (mismatch between "
+ "the wiretype and the field number (tag))"));
+ return nullptr;
+ }
+ break;
+
+ default:
+ // Unknown tag. According to the proto spec, we need to ignore these. No
+ // action required here, though we'll need to skip the relevant bytes
+ // below.
+ break;
+ }
+
+ switch (tag.field_number) {
+ case firestore_client_MaybeDocument_document_tag:
+ // 'no_document' and 'document' are part of a oneof. The proto docs
+ // claim that if both are set on the wire, the last one wins.
+ no_document = nullptr;
+
+ // TODO(rsgowman): If multiple '_document' values are found, we should
+ // merge them (rather than using the last one.)
+ document = reader->ReadNestedMessage<std::unique_ptr<model::Document>>(
+ [&](Reader* reader) -> std::unique_ptr<model::Document> {
+ return rpc_serializer_.DecodeDocument(reader);
+ });
+ break;
+
+ case firestore_client_MaybeDocument_no_document_tag:
+ // 'no_document' and 'document' are part of a oneof. The proto docs
+ // claim that if both are set on the wire, the last one wins.
+ document = nullptr;
+
+ // TODO(rsgowman): Parse the no_document field.
+ abort();
+
+ default:
+ // Unknown tag. According to the proto spec, we need to ignore these.
+ reader->SkipField(tag);
+ }
+ }
+
+ if (no_document) {
+ return no_document;
+ } else if (document) {
+ return document;
+ } else {
+ reader->set_status(Status(FirestoreErrorCode::DataLoss,
+ "Invalid MaybeDocument message: Neither "
+ "'no_document' nor 'document' fields set."));
+ return nullptr;
+ }
+}
+
+void LocalSerializer::EncodeDocument(Writer* writer,
+ const model::Document& doc) const {
+ // Encode Document.name
+ writer->WriteTag({PB_WT_STRING, google_firestore_v1beta1_Document_name_tag});
+ writer->WriteString(rpc_serializer_.EncodeKey(doc.key()));
+
+ // Encode Document.fields (unless it's empty)
+ const ObjectValue& object_value = doc.data().object_value();
+ if (!object_value.internal_value.empty()) {
+ rpc_serializer_.EncodeObjectMap(
+ writer, object_value.internal_value,
+ google_firestore_v1beta1_Document_fields_tag,
+ google_firestore_v1beta1_Document_FieldsEntry_key_tag,
+ google_firestore_v1beta1_Document_FieldsEntry_value_tag);
+ }
+
+ // Encode Document.update_time
+ writer->WriteTag(
+ {PB_WT_STRING, google_firestore_v1beta1_Document_update_time_tag});
+ writer->WriteNestedMessage([&](Writer* writer) {
+ rpc_serializer_.EncodeVersion(writer, doc.version());
+ });
+
+ // Ignore Document.create_time. (We don't use this in our on-disk protos.)
+}
+
+util::StatusOr<std::unique_ptr<model::MaybeDocument>>
+LocalSerializer::DecodeMaybeDocument(const uint8_t* bytes,
+ size_t length) const {
+ Reader reader = Reader::Wrap(bytes, length);
+ std::unique_ptr<model::MaybeDocument> maybe_doc =
+ DecodeMaybeDocument(&reader);
+ if (reader.status().ok()) {
+ return std::move(maybe_doc);
+ } else {
+ return reader.status();
+ }
+}
+
+} // namespace local
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/local/local_serializer.h b/Firestore/core/src/firebase/firestore/local/local_serializer.h
new file mode 100644
index 0000000..c1b4761
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/local/local_serializer.h
@@ -0,0 +1,109 @@
+/*
+ * 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_LOCAL_LOCAL_SERIALIZER_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LOCAL_SERIALIZER_H_
+
+#include <memory>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/model/maybe_document.h"
+#include "Firestore/core/src/firebase/firestore/nanopb/reader.h"
+#include "Firestore/core/src/firebase/firestore/nanopb/writer.h"
+#include "Firestore/core/src/firebase/firestore/remote/serializer.h"
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+
+namespace firebase {
+namespace firestore {
+namespace local {
+
+/**
+ * @brief Serializer for values stored in the LocalStore.
+ *
+ * Note that local::LocalSerializer currently delegates to the
+ * remote::Serializer (for the Firestore v1beta1 RPC protocol) to save
+ * implementation time and code duplication. We'll need to revisit this when the
+ * RPC protocol we use diverges from local storage.
+ */
+class LocalSerializer {
+ public:
+ explicit LocalSerializer(const remote::Serializer& rpc_serializer)
+ : rpc_serializer_(rpc_serializer) {
+ }
+
+ /**
+ * @brief Encodes a MaybeDocument model to the equivalent bytes for local
+ * storage.
+ *
+ * @param document the model to convert.
+ * @param[out] out_bytes A buffer to place the output. The bytes will be
+ * appended to this vector.
+ * @return A Status, which if not ok(), indicates what went wrong. Note that
+ * errors during encoding generally indicate a serious/fatal error.
+ */
+ // TODO(rsgowman): If we never support any output except to a vector, it may
+ // make sense to have LocalSerializer own the vector and provide an accessor
+ // rather than asking the user to create it first.
+ util::Status EncodeMaybeDocument(const model::MaybeDocument& maybe_doc,
+ std::vector<uint8_t>* out_bytes) const;
+
+ /**
+ * @brief Decodes bytes representing a MaybeDocument proto to the equivalent
+ * model.
+ *
+ * @param bytes The bytes to convert. It's assumed that exactly all of the
+ * bytes will be used by this conversion.
+ * @return The model equivalent of the bytes or a Status indicating what went
+ * wrong.
+ */
+ util::StatusOr<std::unique_ptr<model::MaybeDocument>> DecodeMaybeDocument(
+ const uint8_t* bytes, size_t length) const;
+
+ /**
+ * @brief Decodes bytes representing a MaybeDocument proto to the equivalent
+ * model.
+ *
+ * @param bytes The bytes to convert. It's assumed that exactly all of the
+ * bytes will be used by this conversion.
+ * @return The model equivalent of the bytes or a Status indicating what went
+ * wrong.
+ */
+ util::StatusOr<std::unique_ptr<model::MaybeDocument>> DecodeMaybeDocument(
+ const std::vector<uint8_t>& bytes) const {
+ return DecodeMaybeDocument(bytes.data(), bytes.size());
+ }
+
+ private:
+ void EncodeMaybeDocument(nanopb::Writer* writer,
+ const model::MaybeDocument& maybe_doc) const;
+ std::unique_ptr<model::MaybeDocument> DecodeMaybeDocument(
+ nanopb::Reader* reader) const;
+
+ /**
+ * Encodes a Document for local storage. This differs from the v1beta1 RPC
+ * serializer for Documents in that it preserves the updateTime, which is
+ * considered an output only value by the server.
+ */
+ void EncodeDocument(nanopb::Writer* writer, const model::Document& doc) const;
+
+ const remote::Serializer& rpc_serializer_;
+};
+
+} // namespace local
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LOCAL_SERIALIZER_H_
diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.cc b/Firestore/core/src/firebase/firestore/remote/serializer.cc
index 19a068b..fc8b312 100644
--- a/Firestore/core/src/firebase/firestore/remote/serializer.cc
+++ b/Firestore/core/src/firebase/firestore/remote/serializer.cc
@@ -63,11 +63,11 @@ using firebase::firestore::util::StatusOr;
namespace {
void EncodeMapValue(Writer* writer, const ObjectValue& object_value);
-void EncodeObjectMap(Writer* writer,
- const ObjectValue::Map& object_value_map,
- uint32_t map_tag,
- uint32_t key_tag,
- uint32_t value_tag);
+void EncodeObjectMapImpl(Writer* writer,
+ const ObjectValue::Map& object_value_map,
+ uint32_t map_tag,
+ uint32_t key_tag,
+ uint32_t value_tag);
ObjectValue::Map DecodeMapValue(Reader* reader);
@@ -336,11 +336,11 @@ ObjectValue::Map::value_type DecodeDocumentFieldsEntry(Reader* reader) {
google_firestore_v1beta1_Document_FieldsEntry_value_tag);
}
-void EncodeObjectMap(Writer* writer,
- const ObjectValue::Map& object_value_map,
- uint32_t map_tag,
- uint32_t key_tag,
- uint32_t value_tag) {
+void EncodeObjectMapImpl(Writer* writer,
+ const ObjectValue::Map& object_value_map,
+ uint32_t map_tag,
+ uint32_t key_tag,
+ uint32_t value_tag) {
// Write each FieldsEntry (i.e. key-value pair.)
for (const auto& kv : object_value_map) {
writer->WriteTag({PB_WT_STRING, map_tag});
@@ -351,10 +351,10 @@ void EncodeObjectMap(Writer* writer,
}
void EncodeMapValue(Writer* writer, const ObjectValue& object_value) {
- EncodeObjectMap(writer, object_value.internal_value,
- google_firestore_v1beta1_MapValue_fields_tag,
- google_firestore_v1beta1_MapValue_FieldsEntry_key_tag,
- google_firestore_v1beta1_MapValue_FieldsEntry_value_tag);
+ EncodeObjectMapImpl(writer, object_value.internal_value,
+ google_firestore_v1beta1_MapValue_fields_tag,
+ google_firestore_v1beta1_MapValue_FieldsEntry_key_tag,
+ google_firestore_v1beta1_MapValue_FieldsEntry_value_tag);
}
ObjectValue::Map DecodeMapValue(Reader* reader) {
@@ -499,10 +499,11 @@ void Serializer::EncodeDocument(Writer* writer,
// Encode Document.fields (unless it's empty)
if (!object_value.internal_value.empty()) {
- EncodeObjectMap(writer, object_value.internal_value,
- google_firestore_v1beta1_Document_fields_tag,
- google_firestore_v1beta1_Document_FieldsEntry_key_tag,
- google_firestore_v1beta1_Document_FieldsEntry_value_tag);
+ EncodeObjectMapImpl(
+ writer, object_value.internal_value,
+ google_firestore_v1beta1_Document_fields_tag,
+ google_firestore_v1beta1_Document_FieldsEntry_key_tag,
+ google_firestore_v1beta1_Document_FieldsEntry_value_tag);
}
// Skip Document.create_time and Document.update_time, since they're
@@ -667,6 +668,24 @@ std::unique_ptr<Document> Serializer::DecodeDocument(Reader* reader) const {
/*has_local_modifications=*/false);
}
+void Serializer::EncodeObjectMap(
+ nanopb::Writer* writer,
+ const model::ObjectValue::Map& object_value_map,
+ uint32_t map_tag,
+ uint32_t key_tag,
+ uint32_t value_tag) {
+ // TODO(rsgowman): Move the implementation of EncodeObjectMapImpl here and
+ // eliminate that function. The only reason I'm (temporarily) keeping it, is
+ // that performing that refactoring now will cause a cascade of things that
+ // need to move into the local serializer class (as private functions).
+ EncodeObjectMapImpl(writer, object_value_map, map_tag, key_tag, value_tag);
+}
+
+void Serializer::EncodeVersion(nanopb::Writer* writer,
+ const model::SnapshotVersion& version) {
+ EncodeTimestamp(writer, version.timestamp());
+}
+
} // namespace remote
} // namespace firestore
} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.h b/Firestore/core/src/firebase/firestore/remote/serializer.h
index 379069c..3b72693 100644
--- a/Firestore/core/src/firebase/firestore/remote/serializer.h
+++ b/Firestore/core/src/firebase/firestore/remote/serializer.h
@@ -38,6 +38,11 @@
namespace firebase {
namespace firestore {
+
+namespace local {
+class LocalSerializer;
+}
+
namespace remote {
/**
@@ -158,13 +163,23 @@ class Serializer {
return DecodeMaybeDocument(bytes.data(), bytes.size());
}
+ std::unique_ptr<model::Document> DecodeDocument(nanopb::Reader* reader) const;
+
+ static void EncodeObjectMap(nanopb::Writer* writer,
+ const model::ObjectValue::Map& object_value_map,
+ uint32_t map_tag,
+ uint32_t key_tag,
+ uint32_t value_tag);
+
+ static void EncodeVersion(nanopb::Writer* writer,
+ const model::SnapshotVersion& version);
+
private:
void EncodeDocument(nanopb::Writer* writer,
const model::DocumentKey& key,
const model::ObjectValue& object_value) const;
std::unique_ptr<model::MaybeDocument> DecodeBatchGetDocumentsResponse(
nanopb::Reader* reader) const;
- std::unique_ptr<model::Document> DecodeDocument(nanopb::Reader* reader) const;
const firebase::firestore::model::DatabaseId& database_id_;
};