Skip to content

Commit 9e5bd1d

Browse files
Mizuchifacebook-github-bot
authored andcommitted
In DiffVisitor, if field exists in source, always ensure first
Summary: Field could be `deprecated_terse_writes`. If we don't ensure, it might not exist when calling diff. Reviewed By: thedavekwon Differential Revision: D68867578 fbshipit-source-id: 6ba5547d2fbbbefa0eb7391e7a0d6b2fa141bcb8
1 parent 80c74de commit 9e5bd1d

File tree

2 files changed

+109
-2
lines changed

2 files changed

+109
-2
lines changed

thrift/lib/cpp2/patch/test/DynamicPatchTest.cpp

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@
1515
*/
1616

1717
#include <gtest/gtest.h>
18+
#include <thrift/lib/cpp2/Flags.h>
1819
#include <thrift/lib/cpp2/patch/DynamicPatch.h>
1920
#include <thrift/lib/cpp2/patch/detail/PatchBadge.h>
20-
#include <thrift/lib/thrift/gen-cpp2/any_patch_types.h>
21-
2221
#include <thrift/lib/cpp2/patch/test/gen-cpp2/gen_patch_DynamicPatchTest_types.h>
22+
#include <thrift/lib/cpp2/patch/test/gen-cpp2/gen_patch_OldTerseWrite_types.h>
23+
#include <thrift/lib/cpp2/protocol/Serializer.h>
24+
#include <thrift/lib/thrift/gen-cpp2/any_patch_types.h>
2325

2426
namespace apache::thrift::protocol {
2527
using detail::badge;
28+
THRIFT_FLAG_DECLARE_bool(
29+
thrift_patch_diff_visitor_ensure_on_potential_terse_write_field);
2630

2731
class DemoDiffVisitor : public DiffVisitorBase {
2832
public:
@@ -1006,4 +1010,56 @@ TEST(DynamicPatchTest, MergeMovedStructPatch) {
10061010
testMergeMovedPatch<DynamicStructPatch>(obj);
10071011
}
10081012

1013+
TEST(DemoDiffVisitor, TerseWriteFieldMismatch1) {
1014+
THRIFT_FLAG_SET_MOCK(
1015+
thrift_patch_diff_visitor_ensure_on_potential_terse_write_field, true);
1016+
using test::Foo;
1017+
Foo src, dst;
1018+
dst.bar() = "123";
1019+
1020+
// Field exists when generating the patch but not when applying the diff
1021+
auto srcObj = protocol::asValueStruct<type::struct_t<Foo>>(src).as_object();
1022+
auto dstObj = protocol::asValueStruct<type::struct_t<Foo>>(dst).as_object();
1023+
1024+
DemoDiffVisitor visitor;
1025+
auto patch = visitor.diff(srcObj, dstObj);
1026+
1027+
auto srcBuf = CompactSerializer::serialize<folly::IOBufQueue>(src).move();
1028+
auto dstBuf = CompactSerializer::serialize<folly::IOBufQueue>(dst).move();
1029+
1030+
auto srcVal = protocol::parseValue<CompactProtocolReader>(
1031+
*srcBuf, type::BaseType::Struct);
1032+
auto dstVal = protocol::parseValue<CompactProtocolReader>(
1033+
*dstBuf, type::BaseType::Struct);
1034+
1035+
protocol::applyPatch(patch.toObject(), srcVal);
1036+
1037+
EXPECT_EQ(srcVal, dstVal);
1038+
}
1039+
1040+
TEST(DemoDiffVisitor, TerseWriteFieldMismatch2) {
1041+
THRIFT_FLAG_SET_MOCK(
1042+
thrift_patch_diff_visitor_ensure_on_potential_terse_write_field, true);
1043+
using test::Foo;
1044+
Foo src, dst;
1045+
dst.bar() = "123";
1046+
1047+
// Field exists when applying the patch but not when generating the diff
1048+
auto srcBuf = CompactSerializer::serialize<folly::IOBufQueue>(src).move();
1049+
auto dstBuf = CompactSerializer::serialize<folly::IOBufQueue>(dst).move();
1050+
1051+
auto srcObj = protocol::parseObject<CompactProtocolReader>(*srcBuf);
1052+
auto dstObj = protocol::parseObject<CompactProtocolReader>(*dstBuf);
1053+
1054+
DemoDiffVisitor visitor;
1055+
auto patch = visitor.diff(srcObj, dstObj);
1056+
1057+
auto srcVal = protocol::asValueStruct<type::struct_t<Foo>>(src);
1058+
auto dstVal = protocol::asValueStruct<type::struct_t<Foo>>(dst);
1059+
1060+
protocol::applyPatch(patch.toObject(), srcVal);
1061+
1062+
EXPECT_EQ(srcVal, dstVal);
1063+
}
1064+
10091065
} // namespace apache::thrift::protocol

thrift/lib/thrift/detail/DynamicPatch.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
#include <folly/Overload.h>
18+
#include <thrift/lib/cpp2/Flags.h>
1819
#include <thrift/lib/cpp2/op/Clear.h>
1920
#include <thrift/lib/cpp2/op/Patch.h>
2021
#include <thrift/lib/thrift/detail/DynamicPatch.h>
@@ -23,6 +24,9 @@
2324
#include <thrift/lib/cpp2/patch/detail/PatchBadge.h>
2425

2526
namespace apache::thrift::protocol {
27+
THRIFT_FLAG_DEFINE_bool(
28+
thrift_patch_diff_visitor_ensure_on_potential_terse_write_field, false);
29+
2630
using detail::badge;
2731
using detail::ValueList;
2832
using detail::ValueMap;
@@ -841,6 +845,38 @@ DynamicMapPatch DiffVisitorBase::diffMap(
841845
return patch;
842846
}
843847

848+
// Check whether value should not be serialized due to deprecated_terse_writes,
849+
// but serialized in languages other than C++.
850+
static bool maybeEmptyDeprecatedTerseField(const Value& value) {
851+
switch (value.getType()) {
852+
case Value::Type::boolValue:
853+
case Value::Type::byteValue:
854+
case Value::Type::i16Value:
855+
case Value::Type::i32Value:
856+
case Value::Type::i64Value:
857+
case Value::Type::floatValue:
858+
case Value::Type::doubleValue:
859+
// Numeric fields maybe empty terse field regardless the value, since it
860+
// honors custom default
861+
return true;
862+
case Value::Type::stringValue:
863+
case Value::Type::binaryValue:
864+
case Value::Type::listValue:
865+
case Value::Type::setValue:
866+
case Value::Type::mapValue:
867+
// string/binary and containers fields don't honor custom default.
868+
// It can only be empty if it is intrinsic default
869+
return protocol::isIntrinsicDefault(value);
870+
case Value::Type::objectValue:
871+
// struct/union/exception can never be empty terse field
872+
return false;
873+
case Value::Type::__EMPTY__:
874+
folly::throw_exception<std::runtime_error>("value is empty.");
875+
default:
876+
notImpl();
877+
}
878+
}
879+
844880
void DiffVisitorBase::diffElement(
845881
const ValueMap& src,
846882
const ValueMap& dst,
@@ -889,6 +925,16 @@ DynamicPatch DiffVisitorBase::diffStructured(
889925
bool shouldUseAssignPatch =
890926
src.empty() || dst.empty() || src.begin()->first != dst.begin()->first;
891927

928+
if (THRIFT_FLAG(
929+
thrift_patch_diff_visitor_ensure_on_potential_terse_write_field)) {
930+
// If field is src looks like deprecated terse field, we need to use
931+
// EnsureStruct, which is not supported by UnionPatch, thus we need to use
932+
// AssignPatch.
933+
shouldUseAssignPatch = shouldUseAssignPatch ||
934+
(src.begin()->second != dst.begin()->second &&
935+
maybeEmptyDeprecatedTerseField(src.begin()->second));
936+
}
937+
892938
if (shouldUseAssignPatch) {
893939
DynamicUnknownPatch patch;
894940
patch.doNotConvertStringToBinary(badge);
@@ -1027,6 +1073,11 @@ void DiffVisitorBase::diffField(
10271073
auto guard = folly::makeGuard([&] { pop(); });
10281074
auto subPatch = diff(badge, src.at(id), dst.at(id));
10291075
if (!subPatch.empty(badge)) {
1076+
if (THRIFT_FLAG(
1077+
thrift_patch_diff_visitor_ensure_on_potential_terse_write_field) &&
1078+
maybeEmptyDeprecatedTerseField(src.at(id))) {
1079+
patch.ensure(badge, id, emptyValue(src.at(id).getType()));
1080+
}
10301081
patch.patchIfSet(badge, id).merge(badge, DynamicPatch{std::move(subPatch)});
10311082
}
10321083
}

0 commit comments

Comments
 (0)