/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ #pragma once #include #include #include #include #include #include #include #include #include namespace apache { namespace thrift { namespace op { namespace detail { struct FieldIdListToSetAdapter { using FieldIdSet = std::unordered_set; using FieldIdList = std::vector; static FieldIdSet fromThrift(const FieldIdList& v) { FieldIdSet ret; ret.reserve(v.size()); for (auto i : v) { ret.emplace(static_cast(i)); } return ret; } static FieldIdList toThrift(const FieldIdSet& v) { FieldIdList ret; ret.reserve(v.size()); for (auto i : v) { ret.emplace_back(folly::to_underlying(i)); } return ret; } }; /// Patch for a Thrift field. /// /// Requires Patch have fields with ids 1:1 with the fields they patch. template class FieldPatch : public BasePatch> { using Base = BasePatch; public: using Base::apply; using Base::Base; using Base::operator=; using Base::get; template static FieldPatch createFrom(T&& val) { FieldPatch patch; patch.assignFrom(std::forward(val)); return patch; } /// Returns the pointer to the Thrift patch struct. Patch* operator->() noexcept { return &data_; } const Patch* operator->() const noexcept { return &data_; } /// Returns the reference to the Thrift patch struct. Patch& operator*() noexcept { return data_; } const Patch& operator*() const noexcept { return data_; } template void customVisit(Visitor&& v) const { for_each_field_id( [&](auto id) { v.template patchIfSet(*get(id)); }); } private: using Base::data_; }; /// Create a base patch that supports Ensure operator. /// /// The `Patch` template parameter must be a Thrift struct with the following /// fields: /// * `optional T assign` /// * `terse bool clear` /// * `terse P patchPrior` /// * `terse T ensure` /// * `terse P patch` /// Where `P` is the field patch type for the struct type `T`. template class BaseEnsurePatch : public BaseClearPatch { using Base = BaseClearPatch; using T = typename Base::value_type; template using FieldType = type::native_type>; // Needed to access patchIfSet(...) for merge(...) method template friend class FieldPatch; struct Applier { T& v; void assign(const T& t) { v = t; } void clear() { ::apache::thrift::clear(v); } template void patchIfSet(const FieldPatch& patch) { patch.apply(op::get(v)); } template void ensure() { // For non-optional field, ensure is no-op } template void ensure(const Field& def) { if (isAbsent(op::get(v))) { op::ensure(v) = def; } } template void remove() { using field_tag = op::get_field_tag; op::clear_field(op::get(v), v); } }; template void removeImpl() { ensurePatchable(); Base::toThrift().remove()->insert(op::get_field_id_v); } template std::enable_if_t> remove() { // Usually for non-optional field, it should not be removable. // This can only happen if the field was optional when creating the Patch, // but later we changed the field to non-optional. // In this case we should set it to intrinsic default when removing it. removeImpl(); } public: using Base::Base; using Base::operator=; using Base::assign; /// Corresponding FieldPatch of this struct patch. using patch_type = get_native_type; BaseEnsurePatch(const BaseEnsurePatch&) = default; BaseEnsurePatch(BaseEnsurePatch&&) noexcept = default; BaseEnsurePatch& operator=(const BaseEnsurePatch&) = default; BaseEnsurePatch& operator=(BaseEnsurePatch&&) noexcept = default; /// Returns if the patch ensures the given field is set (explicitly or /// implicitly). template constexpr bool ensures() const { return !isAbsent(getEnsure(data_)); } bool empty() const { bool b = true; op::for_each_ordinal([&](auto id) { using Id = decltype(id); if constexpr (!apache::thrift::detail::is_shared_ptr_v< op::get_field_ref>) { b = b && !this->modifies(); } }); return b; } template std::enable_if_t< type::is_optional_or_union_field_v && !is_thrift_union_v> remove() { removeImpl(); } /// Returns if the patch modifies the given field. template bool modifies() const { return hasAssign() || data_.clear() == true || (getEnsure(data_) && type::is_optional_or_union_field_v) || !getRawPatch(data_.patchPrior()).empty() || !getRawPatch(data_.patch()).empty(); } template std::enable_if_t modifies() const { // If hasAssign() == true, the whole struct (all fields) will be replaced. if (hasAssign() || data_.clear() == true || (getEnsure(data_) && type::is_optional_or_union_field_v)) { return true; } return getRawPatch(data_.patchPrior()).template modifies() || getRawPatch(data_.patch()).template modifies(); } /// Ensures the given field is set. template void ensure() { // Ensuring non-optional field to intrinsic default is allowed since we // might want to ensure field in case the field doesn't exist in dynamic // value. (e.g., Terse field with default value. Without ensuring it first, // we will not be able to patch such field at all). maybeEnsure(); } /// Same as `ensure()` method, except uses the provided default value. template > std::enable_if_t> ensure( U&& defaultVal) { if (maybeEnsure()) { if (patchPrior().toThrift().clear().value() && !is_thrift_union_v) { // If the field is cleared, we need to assign the value in PatchAfter. // Why? In dynamic patch, the PatchOp::Clear in PatchPrior will set the // field to intrinsic default rather than removing it (unlike static // patch). If we assign the value in Patch after, then the behavior // would be the same between static/dynamic patch. patchPrior().reset(); patchAfter() = std::forward(defaultVal); } else { getEnsure(data_) = std::forward(defaultVal); } } } /// Ensures the given field is initalized, and return the associated patch /// object. template decltype(auto) patch() { return (maybeEnsure(), patchAfter()); } /// Returns the proper patch object for the given field. template decltype(auto) patchIfSet() { if (!type::is_optional_or_union_field_v) { return patch(); } ensurePatchable(); if constexpr (!is_thrift_union_v) { if (Base::derived().template isRemoved()) { // If field is already cleared, Patch should be ignored. getRawPatch(data_.patch()).toThrift().clear() = true; return getRawPatch(data_.patchPrior()); } } return ensures() ? getRawPatch(data_.patch()) : getRawPatch(data_.patchPrior()); } /// @copybrief AssignPatch::customVisit /// /// Users should provide a visitor with the following methods /// /// struct Visitor { /// void assign(const MyClass&); /// void clear(); /// template void patchIfSet(const FieldPatch&); /// // For optional fields in structs and fields in unions /// template void ensure(const op::get_native_type&); /// // For non-optional fields in structs /// template void ensure(); /// } /// /// For example, let's assume you have the following thrift struct: /// /// struct MyClass { /// 1: string foo; /// 2: bool bar; /// } /// /// and then you created the following patch: /// /// MyClassPatch patch; /// patch.patch().invert(); /// patch.patch().invert(); /// patch.patch().append("_"); /// /// `patch.customVisit(v)` will invoke the following methods /// /// v.ensure(); /// v.ensure(); /// v.patchIfSet(StringPatch::createAppend("_")); /// v.patchIfSet(BoolPatch{}); // no-op since inverted twice template void customVisit(Visitor&& v) const { if (false) { // Test whether the required methods exist in Visitor v.assign(T{}); v.clear(); for_each_field_id([&](auto id) { using Id = decltype(id); if constexpr (!apache::thrift::detail::is_shared_ptr_v< op::get_field_ref>) { using FieldPatchType = folly::remove_cvref_t())>; v.template patchIfSet(FieldPatchType{}); v.template ensure(); if constexpr (type::is_optional_or_union_field_v) { v.template ensure(FieldType{}); } } }); } if (Base::template customVisitAssignAndClear(std::forward(v))) { return; } data_.patchPrior()->customVisit(std::forward(v)); // TODO: Optimize ensure for UnionPatch for_each_field_id([&](auto id) { using Id = decltype(id); if constexpr (!apache::thrift::detail::is_shared_ptr_v< op::get_field_ref>) { if (auto p = op::get(*data_.ensure())) { if constexpr (type::is_optional_or_union_field_v) { std::forward(v).template ensure(*p); } else { std::forward(v).template ensure(); } } } }); data_.patch()->customVisit(std::forward(v)); if constexpr (!is_thrift_union_v) { for (auto fieldId : *data_.remove()) { op::invoke_by_field_id( fieldId, [&](auto id) { using Id = decltype(id); if constexpr (!apache::thrift::detail::is_shared_ptr_v< op::get_field_ref>) { std::forward(v).template remove(); } }, [] { // Ignore if the specified field is not part of the struct // considering schema evolution. }); } } } void apply(T& val) const { return customVisit(Applier{val}); } protected: using Base::apply; using Base::data_; using Base::hasAssign; ~BaseEnsurePatch() = default; // Clears the field with the given id. template void clear() { if (hasAssign()) { op::clear(*data_.assign()); return; } patchPrior().clear(); op::clear(*data_.ensure()); patchAfter().reset(); } using Base::clear; template static decltype(auto) getEnsure(U&& data) { return op::get(*std::forward(data).ensure()); } template decltype(auto) patchPrior() { return (ensurePatchable(), getRawPatch(data_.patchPrior())); } template decltype(auto) patchAfter() { return (ensurePatchable(), getRawPatch(data_.patch())); } void ensurePatchable() { if (data_.assign().has_value()) { for_each_field_id([&](auto id) { using Id = decltype(id); if constexpr (!apache::thrift::detail::is_shared_ptr_v< op::get_field_ref>) { auto&& field = op::get<>(id, *data_.assign()); auto&& prior = getRawPatch(data_.patchPrior()); auto&& ensure = op::get<>(id, *data_.ensure()); auto&& after = getRawPatch(data_.patch()); if (isAbsent(field)) { prior.toThrift().clear() = true; } else { ensure = {}; after.assign(std::move(*field)); } } }); // Unset assign. data_.assign().reset(); } } template bool maybeEnsure() { if (*patchAfter().toThrift().clear()) { // Since we cleared the field in PatchAfter, we should remove any existing // ensured value. op::clear(*data_.ensure()); } if constexpr (!is_thrift_union_v) { auto removeRef = Base::toThrift().remove(); auto iter = removeRef->find(op::get_field_id_v); if (iter != removeRef->end()) { // If the current patch removed the field and now we want to ensure it, // we should clear it in patch prior and then ensure it. removeRef->erase(iter); patchPrior().reset(); patchAfter().reset(); patchPrior().toThrift().clear() = true; getEnsure(data_).emplace(); return true; } } if (ensures()) { return false; } // Merge anything (oddly) in patchAfter into patchPrior. if (!patchAfter().empty()) { patchPrior().merge(std::move(patchAfter())); patchAfter().reset(); } getEnsure(data_).ensure(); return true; } private: template decltype(auto) getRawPatch(U&& patch) const { // Field Ids must always be used to access patch(Prior). return *patch->get(get_field_id{}); } // Needed for merge(...). We can consider making this a public API. template void patchIfSet(const FieldPatch& p) { patchIfSet().merge(p); } }; /// Patch for a Thrift struct. /// /// The `Patch` template parameter must be a Thrift struct with the following /// fields: /// * `optional T assign` /// * `terse bool clear` /// * `terse P patchPrior` /// * `terse T ensure` /// * `terse P patch` /// Where `P` is the field patch type for the struct type `T`. template class StructPatch : public BaseEnsurePatch> { using Base = BaseEnsurePatch; using T = typename Base::value_type; template using F = type::native_type>; friend class BaseEnsurePatch>; public: using Base::apply; using Base::assign; using Base::Base; using Base::operator=; using patch_type = get_native_type; void clear() { Base::clear(); // Custom defaults must also be cleared. op::clear<>(*data_.ensure()); } template void clear() { Base::template clear(); } /// Assigns to the given field, ensuring first if needed. template > void assign(U&& val) { if (data_.assign().has_value()) { op::get(*data_.assign()) = std::forward(val); } else { Base::template patch().assign(std::forward(val)); } } template uint32_t encode(Protocol& prot) const { // PatchOp::Remove using PatchOpRemoveId = field_id<7>; uint32_t s = 0; s += prot.writeStructBegin(op::get_class_name_v.data()); const auto remove = removedFields(); op::for_each_field_id([&](auto id) { using Id = decltype(id); using Tag = op::get_type_tag; constexpr bool isRemoveField = std::is_same::value; auto&& field = op::get(data_); if (!isRemoveField && !should_write(field)) { return; } if (isRemoveField && remove.empty()) { return; } s += prot.writeFieldBegin( &*op::get_name_v.begin(), typeTagToTType, folly::to_underlying(Id::value)); if constexpr (isRemoveField) { s += op::encode(prot, remove); } else { s += op::encode(prot, *field); } s += prot.writeFieldEnd(); }); s += prot.writeFieldStop(); s += prot.writeStructEnd(); return s; } private: using Base::data_; // Whether the field is removed template bool isRemoved() const { const auto& prior = data_.patchPrior()->toThrift(); const auto& ensure = *data_.ensure(); const auto& after = data_.patch()->toThrift(); const auto& remove = *data_.remove(); if constexpr (!type::is_optional_or_union_field_v) { // non-optional fields can not be removed return false; } if (*get(after)->toThrift().clear()) { // Cleared field in patch after return true; } if (*get(prior)->toThrift().clear() && !get(ensure).has_value()) { // Cleared field in patch prior and not ensured return true; } return remove.count(op::get_field_id_v); } // Combine fields from PatchOp::Clear and PatchOp::Remove operations std::unordered_set removedFields() const { auto removed = *data_.remove(); op::for_each_field_id([&](auto id) { using Id = decltype(id); if constexpr (!apache::thrift::detail::is_shared_ptr_v< op::get_field_ref>) { if (this->isRemoved()) { removed.insert(id.value); } } }); return removed; } }; /// Patch for a Thrift union. /// /// The `Patch` template parameter must be a Thrift struct with the following /// fields: /// * `optional T assign` /// * `terse bool clear` /// * `terse P patchPrior` /// * `terse T ensure` /// * `terse P patch` /// Where `P` is the field patch type for the union type `T`. template class UnionPatch : public BaseEnsurePatch> { using Base = BaseEnsurePatch; using T = typename Base::value_type; template using F = type::native_type>; public: using Base::Base; using Base::operator=; using Base::apply; using Base::assign; using Base::clear; /// Assigns to the given field, ensuring first if needed. template > void assign(U&& val) { op::get(Base::resetAnd().assign().ensure()) = std::forward(val); } }; } // namespace detail } // namespace op } // namespace thrift } // namespace apache