Skip to content

Add ObjectValue #7712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 18, 2021
Merged

Conversation

schmidt-sebastian
Copy link
Contributor

This PR adds a mutable, Protobuf-backed ObjectValue similar to https://github.com/firebase/firebase-android-sdk/blob/master/firebase-firestore/src/main/java/com/google/firebase/firestore/model/ObjectValue.java

Since nanopb Protos can be directly manipulated, I did not port the Overlay approach from Android. Rather, the protos are modified directly. To ensure O(log n) Map lookups, Maps are kept sorted as well (all maps start out sorted when we receive them from the backend, but on Web and Android they are not necessarily sorted after we apply local modifications). The guaranteed sorted order also allows me to simplify some of the code in value_utils as well.

@google-oss-bot
Copy link

1 Warning
⚠️ Did you forget to add a changelog entry? (Add #no-changelog to the PR description to silence this warning.)

Generated by 🚫 Danger

@google-oss-bot
Copy link

google-oss-bot commented Mar 13, 2021

Coverage Report

Affected SDKs

  • FirebaseFirestore-iOS-FirebaseFirestore.framework

    SDK overall coverage changed from 88.65% (5f6feba) to 88.44% (618fe52) by -0.21%.

    Filename Base (5f6feba) Head (618fe52) Diff
    leveldb_key.cc 97.60% 98.08% +0.48%
    mutable_document.cc ? 0.00% ?
    object_value.cc ? 98.02% ?
    ordered_code.cc 93.24% 92.68% -0.56%
    server_timestamp_util.cc ? 84.85% ?
    task.cc 95.21% 97.60% +2.40%
    value_util.cc ? 88.50% ?

Test Logs

using testutil::Map;
using testutil::Value;

class ObjectValueTest : public ::testing::Test {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note - can be compared to end result of firebase/firebase-android-sdk#2494

@schmidt-sebastian
Copy link
Contributor Author

I think this leaks memory - we need to free the underlying Protobuf structures when MutableObjectValue is destructed. I will work on a fix.

@schmidt-sebastian
Copy link
Contributor Author

I think this leaks memory - we need to free the underlying Protobuf structures when MutableObjectValue is destructed. I will work on a fix.

Done

Copy link
Contributor

@var-const var-const left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a partial review.

// it to verify that all values own their own memory.
nanopb::Message<google_firestore_v1_Value> clone1{DeepClone(value)};
nanopb::Message<google_firestore_v1_Value> clone2{DeepClone(*clone1)};
EXPECT_TRUE(value == *clone1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: when these tests fail, what do the failure messages look like? Is it clear which invocation of VerifyDeepClone failed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it requires debugging. I don't know how to solve this though (other than by passing in the index of the callsite). I wish there was a proper stracktrace.

I added a better error message though, which should help a bit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check again after replacing EXPECT_TRUE with EXPECT_EQ? One possible approach is to add SCOPED_TRACE before every invocation of this helper, but please check with EXPECT_EQ first to see if it's necessary. Another approach, should EXPECT_EQ not work out, is to log the stringified form of both values being compared upon failure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a log for both message (similar to what I do above). I am not sure why EXPECT_EQ does not compile.

Copy link
Contributor Author

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next round. Thanks for the feedback.

// it to verify that all values own their own memory.
nanopb::Message<google_firestore_v1_Value> clone1{DeepClone(value)};
nanopb::Message<google_firestore_v1_Value> clone2{DeepClone(*clone1)};
EXPECT_TRUE(value == *clone1);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it requires debugging. I don't know how to solve this though (other than by passing in the index of the callsite). I wish there was a proper stracktrace.

I added a better error message though, which should help a bit.

for (pb_size_t source_index = 0, target_index = 0;
target_index < target_count;) {
if (source_index < source_count) {
std::string key = MakeString(source_fields[source_index].key);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Using a string_view here causes an ASAN failure as the key may get freed using FreeNanopbMessage in the loop.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, but key doesn't seem to be used after FreeNanopbMessage? AFAICS, the memory can only be freed after a comparison and it causes the loop to continue.

Copy link
Contributor

@var-const var-const left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Close to LGTM. Please see a couple of comments I bumped from the last time.


[&] {
nanopb::Message<google_firestore_v1_Value> clone2{DeepClone(value)};
EXPECT_TRUE(value == *clone2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use EXPECT_EQ. Also, a failure message should contain the line where it happened, so the before/after message is probably not necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't able to get this to work:

/Users/mrschmidt/GitHub/firebase/firebase-ios-sdk/cmake-build-debug/external/src/googletest/googletest/include/gtest/gtest.h:1527:11: error: invalid operands to binary expression ('const firebase::firestore::_google_firestore_v1_Value' and 'const firebase::firestore::_google_firestore_v1_Value')
  if (lhs == rhs) {
      ~~~ ^  ~~~
/Users/mrschmidt/GitHub/firebase/firebase-ios-sdk/cmake-build-debug/external/src/googletest/googletest/include/gtest/gtest.h:1554:12: note: in instantiation of function template specialization 'testing::internal::CmpHelperEQ<firebase::firestore::_google_firestore_v1_Value, firebase::firestore::_google_firestore_v1_Value>' requested here
    return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs);
           ^
/Users/mrschmidt/GitHub/firebase/firebase-ios-sdk/Firestore/core/test/unit/model/value_util_test.cc:141:7: note: in instantiation of function template specialization 'testing::internal::EqHelper::Compare<firebase::firestore::_google_firestore_v1_Value, firebase::firestore::_google_firestore_v1_Value, nullptr>' requested here
      EXPECT_EQ(value, *clone2)
      ^
/Users/mrschmidt/GitHub/firebase/firebase-ios-sdk/cmake-build-debug/external/src/googletest/googletest/include/gtest/gtest.h:2028:54: note: expanded from macro 'EXPECT_EQ'
  EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)
                                                     ^
/Users/mrschmidt/GitHub/firebase/firebase-ios-sdk/Firestore/core/include/firebase/firestore/geo_point.h:113:13: note: candidate function not viable: no known conversion from 'const firebase::firestore::_google_firestore_v1_Value' to 'const firebase::firestore::GeoPoint' for 1st argument
inline bool operator==(const GeoPoint& lhs, const GeoPoint& rhs) {
            ^
/Users/mrschmidt/GitHub/firebase/firebase-ios-sdk/cmake-build-debug/external/src/googletest/googletest/include/gtest/gtest.h:1518:13: note: candidate function not viable: no known conversion from 'const firebase::firestore::_google_firestore_v1_Value' to 'testing::internal::faketype' for 1st argument
inline bool operator==(faketype, faketype) { return true; }

I tried to use EXPECT_EQ in my earlier attempts at writing these tests and for some reason the == operator is not recognized.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, what happens is that due to the way EXPECT_EQ is implemented, the operator== has to be in the same namespace as at least one of the classes being compared. In this case, google_firestore_v1_Value is in the firebase::firestore namespace, but the comparison operator is in firebase::firestore::model. It's probably a good idea to move operator== one namespace level up.

// it to verify that all values own their own memory.
nanopb::Message<google_firestore_v1_Value> clone1{DeepClone(value)};
nanopb::Message<google_firestore_v1_Value> clone2{DeepClone(*clone1)};
EXPECT_TRUE(value == *clone1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check again after replacing EXPECT_TRUE with EXPECT_EQ? One possible approach is to add SCOPED_TRACE before every invocation of this helper, but please check with EXPECT_EQ first to see if it's necessary. Another approach, should EXPECT_EQ not work out, is to log the stringified form of both values being compared upon failure.

for (pb_size_t source_index = 0, target_index = 0;
target_index < target_count;) {
if (source_index < source_count) {
std::string key = MakeString(source_fields[source_index].key);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, but key doesn't seem to be used after FreeNanopbMessage? AFAICS, the memory can only be freed after a comparison and it causes the loop to continue.

@var-const var-const removed their assignment Mar 17, 2021
Copy link
Contributor

@var-const var-const left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing all the feedback! LGTM (with one small comment about includes).

@@ -23,6 +23,7 @@
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
#include "Firestore/Protos/nanopb/google/firestore/v1/firestore.nanopb.h"
#include "Firestore/Protos/nanopb/google/type/latlng.nanopb.h"
#include "Firestore/core/src/nanopb/message.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This include should no longer be necessary.

@schmidt-sebastian schmidt-sebastian merged commit a12b78e into mrschmidt/mutabledocuments Mar 18, 2021
@schmidt-sebastian schmidt-sebastian deleted the mrschmidt/sortedvalue branch March 18, 2021 21:43
@firebase firebase locked and limited conversation to collaborators Apr 18, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants