Skip to content

Port FieldTransform to C++ #1033

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 19 commits into from
Apr 10, 2018
Merged

Port FieldTransform to C++ #1033

merged 19 commits into from
Apr 10, 2018

Conversation

zxu123
Copy link
Contributor

@zxu123 zxu123 commented Apr 5, 2018

Discussion

This is part of the C++ migration.

Testing

unit and integration test pass.

API Changes

n/a

@zxu123
Copy link
Contributor Author

zxu123 commented Apr 5, 2018

The true commits start from 8eecd35. (The earlier ones are from base branch)

@@ -278,15 +281,14 @@ NSComparator FSTTestDocComparator(const absl::string_view fieldPath) {
FSTTransformMutation *FSTTestTransformMutation(NSString *path,
NSArray<NSString *> *serverTimestampFields) {
FSTDocumentKey *key = [FSTDocumentKey keyWithPath:testutil::Resource(util::MakeStringView(path))];
NSMutableArray<FSTFieldTransform *> *fieldTransforms = [NSMutableArray array];
std::vector<FieldTransform> fieldTransforms{};
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: {} is unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -278,15 +281,14 @@ NSComparator FSTTestDocComparator(const absl::string_view fieldPath) {
FSTTransformMutation *FSTTestTransformMutation(NSString *path,
NSArray<NSString *> *serverTimestampFields) {
FSTDocumentKey *key = [FSTDocumentKey keyWithPath:testutil::Resource(util::MakeStringView(path))];
NSMutableArray<FSTFieldTransform *> *fieldTransforms = [NSMutableArray array];
std::vector<FieldTransform> fieldTransforms{};
fieldTransforms.reserve(serverTimestampFields.count);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: unless the number is expected to be pretty large, this is probably overkill.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

}

#if defined(__OBJC__)
bool operator==(const FieldTransform& other) const {
Copy link
Contributor

Choose a reason for hiding this comment

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

Equality operator seems like a reasonable operation to have, maybe move outside the #if section?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

private:
FieldPath path_;
// Shared by copies of the same FieldTransform.
std::shared_ptr<TransformOperation> transform_;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can it be <const TransformOperation>? Also, can you please provide more context for this optimization?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Change to const. Really does not matter for the only ServerTimestampTransform. I think we can safely assume a TransformOperation will not mutate itself when proceeding fields.

We need to access a TransformOperation here. Since it is poly, we need either a pointer or a reference. Conceptually, FieldTransform should own it instead of each TransformOperation type owns it (for ServingTimestampTransform might be OK since it builds a singleton). Now if you wonder why unique_ptr does not work here, that's because a FieldTransform object can be copied.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can safely assume a TransformOperation will not mutate itself when proceeding fields.

Thanks for making the change. I think it's useful to enforce such assumptions using the type system. Shared ownership of immutable data is a much simpler model than shared ownership of mutable data.

Now if you wonder why unique_ptr does not work here, that's because a FieldTransform object can be copied.

It's possible to do something like:

FieldTransform(const FieldTransform& rhs)
: transform_{rhs.transform_.Clone()}
{}

But it's more hassle -- as long as shared_ptr points to const, I'm fine with it.

return path_;
}

const TransformOperation* transform() const {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: the function name sounds like an action (i.e., "do apply transformation"). transformation? transform_operation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

(NSArray<GCFSDocumentTransform_FieldTransform *> *)protos {
NSMutableArray<FSTFieldTransform *> *fieldTransforms = [NSMutableArray array];
std::vector<FieldTransform> fieldTransforms{};
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comments on vector initialization.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return result;
}

- (NSString *)description {
return [NSString stringWithFormat:@"<FSTTransformMutation key=%s transforms=%@ precondition=%@>",
self.key.ToString().c_str(), self.fieldTransforms,
std::string fieldTransforms = "";
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: = "" is unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// _fieldMask is shared across all active context objects to accumulate the result. For example,
// the result of calling any of contextForField, contextForFieldPath and contextForArrayIndex
// shares the ownership of _fieldMask.
// _fieldMask and _fieldTransforms are shared across all active context objects to accumulate the
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess alternatively, a single context object could be wrapped in a shared_ptr. Can you please provide some context on how it was implemented in Objective-C?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In ObjC, NSMutableArray* is used. It is not a single FSTParseContext object is shared by many users. It is multiple FSTParseContext objects will update the same (shared) fieldMask and fieldTransforms. From my limited reading on the codes, it is mainly due to some recursive parse of the serialized stream input, where each sub-structure has their own FSTParseContext object but all put the parsed result into the shared fieldMask and fieldTransforms. I am confident there is alternative design where there is separate class for those shared part, say SharedParsedContext. But I'd not do the refactor in this porting PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the explanation.

private:
FieldPath path_;
// Shared by copies of the same FieldTransform.
std::shared_ptr<TransformOperation> transform_;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can safely assume a TransformOperation will not mutate itself when proceeding fields.

Thanks for making the change. I think it's useful to enforce such assumptions using the type system. Shared ownership of immutable data is a much simpler model than shared ownership of mutable data.

Now if you wonder why unique_ptr does not work here, that's because a FieldTransform object can be copied.

It's possible to do something like:

FieldTransform(const FieldTransform& rhs)
: transform_{rhs.transform_.Clone()}
{}

But it's more hassle -- as long as shared_ptr points to const, I'm fine with it.

// _fieldMask is shared across all active context objects to accumulate the result. For example,
// the result of calling any of contextForField, contextForFieldPath and contextForArrayIndex
// shares the ownership of _fieldMask.
// _fieldMask and _fieldTransforms are shared across all active context objects to accumulate the
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the explanation.

@zxu123 zxu123 requested a review from wilhuff April 10, 2018 16:37
Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

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

Pretty much LGTM

FSTFieldTransform *fieldTransform = self.fieldTransforms[i];
const TransformOperation *transform = fieldTransform.transform;
FieldPath fieldPath = fieldTransform.path;
for (NSUInteger i = 0; i < self.fieldTransforms.size(); i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The loop variable here should now be size_t.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

public:
FieldTransform(FieldPath path,
std::unique_ptr<TransformOperation> transformation)
: path_(std::move(path)), transformation_(std::move(transformation)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Prefer brace initialization for consistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

class FieldTransform {
public:
FieldTransform(FieldPath path,
std::unique_ptr<TransformOperation> transformation)
Copy link
Contributor

Choose a reason for hiding this comment

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

Move constructors should be marked noexcept, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return path_;
}

const TransformOperation* transformation() const {
Copy link
Contributor

Choose a reason for hiding this comment

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

I realize that inside FieldTransform we need the TransformOperation to be stored in a unique_ptr so that we can store different subtypes. However, it seems like this implementation detail does not need to leak outside FieldTransform and this method could return a const TransformOperation& instead of a const pointer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's true. Fixed.

Copy link
Contributor Author

@zxu123 zxu123 left a comment

Choose a reason for hiding this comment

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

Updated.

FSTFieldTransform *fieldTransform = self.fieldTransforms[i];
const TransformOperation *transform = fieldTransform.transform;
FieldPath fieldPath = fieldTransform.path;
for (NSUInteger i = 0; i < self.fieldTransforms.size(); i++) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

public:
FieldTransform(FieldPath path,
std::unique_ptr<TransformOperation> transformation)
: path_(std::move(path)), transformation_(std::move(transformation)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

class FieldTransform {
public:
FieldTransform(FieldPath path,
std::unique_ptr<TransformOperation> transformation)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return path_;
}

const TransformOperation* transformation() const {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's true. Fixed.

Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

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

LGTM

@zxu123 zxu123 merged commit 2ac778f into master Apr 10, 2018
@zxu123 zxu123 deleted the cpp/port_field_transform branch April 10, 2018 20:36
minafarid pushed a commit to minafarid/firebase-ios-sdk that referenced this pull request Jun 6, 2018
* port FieldMask to C++

* address changes

* address changes

* fix test

* address change

* Port transform operations (FSTTransformOperation, FSTServerTimestampTransform) to C++

* address changes

* address changes

* address changes

* implement `FieldTransform` in C++

* port `FieldTransform`

* make `fieldTransforms` shared inside `context`

* address changes

* fix lint

* address changes
@firebase firebase locked and limited conversation to collaborators Nov 5, 2019
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.

4 participants