@@ -729,90 +729,140 @@ static cb::mcbp::Status subdoc_operate_wholedoc(
729729 }
730730}
731731
732+ static cb::mcbp::Status subdoc_operate_attributes_and_body (
733+ SubdocCmdContext& context,
734+ SubdocCmdContext::OperationSpec& spec,
735+ MemoryBackedBuffer* xattr,
736+ MemoryBackedBuffer& body) {
737+ if (spec.traits .mcbpCommand !=
738+ cb::mcbp::ClientOpcode::SubdocReplaceBodyWithXattr) {
739+ return cb::mcbp::Status::NotSupported;
740+ }
741+
742+ if (xattr == nullptr ) {
743+ throw std::invalid_argument (
744+ " subdoc_operate_attributes_and_body: can't be called with "
745+ " xattr being nullptr" );
746+ }
747+
748+ // Currently we only support SubdocReplaceBodyWithXattr through this
749+ // API, and we can implement that by using a SUBDOC-GET to get the location
750+ // of the data; and then reset the body with the content of the operation.
751+ auto st = subdoc_operate_one_path (context, spec, xattr->view );
752+ if (st == cb::mcbp::Status::Success) {
753+ body.reset (spec.result .matchloc ().to_string ());
754+ spec.result .clear ();
755+ }
756+ return st;
757+ }
758+
732759/* *
733760 * Run through all of the subdoc operations for the current phase on
734- * a single 'document' (either the user document, or a XATTR).
761+ * the documents content (either the xattr section or the document body,
762+ * depending on the execution phase)
735763 *
736764 * @param context The context object for this operation
737- * @param doc the document to operate on
765+ * @param xattr the documents attribute section (MUST be set in the xattr
766+ * phase, and should be set to nullptr in the body phase)
767+ * @param body the documents body section
738768 * @param doc_datatype The datatype of the document. Updated if a
739769 * wholedoc op changes the datatype.
740- * @param temp_buffer where to store the data for our temporary buffer
741- * allocations if we need to change the doc.
742770 * @param modified set to true upon return if any modifications happened
743771 * to the input document.
744772 * @return true if we should continue processing this request,
745- * false if we've sent the error packet and should temrinate
773+ * false if we've sent the error packet and should terminate
746774 * execution for this request
747775 *
748776 * @throws std::bad_alloc if allocation fails
749777 */
750778static bool operate_single_doc (SubdocCmdContext& context,
751- MemoryBackedBuffer& doc,
779+ MemoryBackedBuffer* xattr,
780+ MemoryBackedBuffer& body,
752781 protocol_binary_datatype_t & doc_datatype,
753782 bool & modified) {
754783 modified = false ;
755784 auto & operations = context.getOperations ();
756785
786+ if (context.getCurrentPhase () == SubdocCmdContext::Phase::XATTR) {
787+ Expects (xattr);
788+ } else {
789+ // The xattr is only provided in the xattr phase as supplying them
790+ // would require us to rebuild a JSON document for the key (and there
791+ // is no operations in the BODY phase which require the header)
792+ Expects (!xattr);
793+ }
794+
795+ auto & current = context.getCurrentPhase () == SubdocCmdContext::Phase::XATTR
796+ ? *xattr
797+ : body;
798+
757799 // 2. Perform each of the operations on document.
758800 for (auto & op : operations) {
759801 switch (op.traits .scope ) {
760802 case CommandScope::SubJSON:
761803 if (mcbp::datatype::is_json (doc_datatype)) {
762804 // Got JSON, perform the operation.
763- op.status = subdoc_operate_one_path (context, op, doc .view );
805+ op.status = subdoc_operate_one_path (context, op, current .view );
764806 } else {
765807 // No good; need to have JSON.
766808 op.status = cb::mcbp::Status::SubdocDocNotJson;
767809 }
768810 break ;
769811
770812 case CommandScope::WholeDoc:
771- op.status = subdoc_operate_wholedoc (context, op, doc.view );
813+ op.status = subdoc_operate_wholedoc (context, op, current.view );
814+ break ;
815+
816+ case CommandScope::AttributesAndBody:
817+ op.status = subdoc_operate_attributes_and_body (
818+ context, op, xattr, body);
819+ break ;
772820 }
773821
774822 if (op.status == cb::mcbp::Status::Success) {
775823 if (context.traits .is_mutator ) {
776824 modified = true ;
777825
778- // Determine how much space we now need.
779- size_t new_doc_len = 0 ;
780- for (auto & loc : op.result .newdoc ()) {
781- new_doc_len += loc.length ;
782- }
826+ if (op.traits .scope != CommandScope::AttributesAndBody) {
827+ // Determine how much space we now need.
828+ size_t new_doc_len = 0 ;
829+ for (auto & loc : op.result .newdoc ()) {
830+ new_doc_len += loc.length ;
831+ }
783832
784- std::string next;
785- next.reserve (new_doc_len);
786- // TODO-PERF: We need to create a contiguous input
787- // region for the next subjson call, from the set of
788- // iovecs in the result. We can't simply write into
789- // the dynamic_buffer, as that may be the underlying
790- // storage for iovecs from the result. Ideally we'd
791- // either permit subjson to take an iovec as input, or
792- // permit subjson to take all the multipaths at once.
793- // For now we make a contiguous region in a temporary
794- // char[], and point in_doc at that.
795- for (auto & loc : op.result .newdoc ()) {
796- next.insert (next.end (), loc.at , loc.at + loc.length );
797- }
833+ std::string next;
834+ next.reserve (new_doc_len);
835+ // TODO-PERF: We need to create a contiguous input
836+ // region for the next subjson call, from the set of
837+ // iovecs in the result. We can't simply write into
838+ // the dynamic_buffer, as that may be the underlying
839+ // storage for iovecs from the result. Ideally we'd
840+ // either permit subjson to take an iovec as input, or
841+ // permit subjson to take all the multipaths at once.
842+ // For now we make a contiguous region in a temporary
843+ // char[], and point in_doc at that.
844+ for (auto & loc : op.result .newdoc ()) {
845+ next.insert (next.end (), loc.at , loc.at + loc.length );
846+ }
798847
799- // Copying complete - safe to delete the old temp_doc
800- // (even if it was the source of some of the newdoc
801- // iovecs).
802- doc.reset (std::move (next));
803-
804- if (op.traits .scope == CommandScope::WholeDoc) {
805- // the entire document has been replaced as part of a
806- // wholedoc op update the datatype to match
807- JSON_checker::Validator validator;
808- bool isValidJson = validator.validate (doc.view );
809-
810- // don't alter context.in_datatype directly here in case we
811- // are in xattrs phase
812- if (isValidJson) {
813- doc_datatype |= PROTOCOL_BINARY_DATATYPE_JSON;
814- } else {
815- doc_datatype &= ~PROTOCOL_BINARY_DATATYPE_JSON;
848+ // Copying complete - safe to delete the old temp_doc
849+ // (even if it was the source of some of the newdoc
850+ // iovecs).
851+ current.reset (std::move (next));
852+
853+ if (op.traits .scope == CommandScope::WholeDoc) {
854+ // the entire document has been replaced as part of a
855+ // wholedoc op update the datatype to match
856+ JSON_checker::Validator validator;
857+ bool isValidJson = validator.validate (current.view );
858+
859+ // don't alter context.in_datatype directly here in case
860+ // we are in xattrs phase
861+ if (isValidJson) {
862+ doc_datatype |= PROTOCOL_BINARY_DATATYPE_JSON;
863+ } else {
864+ doc_datatype &= ~PROTOCOL_BINARY_DATATYPE_JSON;
865+ }
816866 }
817867 }
818868 } else { // lookup
@@ -1064,17 +1114,19 @@ static bool do_xattr_phase(SubdocCmdContext& context) {
10641114 value_buf = { context.xattr_buffer .get (), total};
10651115 }
10661116
1067- MemoryBackedBuffer document{{value_buf.data (), value_buf.size ()}};
1117+ MemoryBackedBuffer body{{context.in_doc .view .data () + bodyoffset,
1118+ context.in_doc .view .size () - bodyoffset}};
1119+ MemoryBackedBuffer xattr{{value_buf.data (), value_buf.size ()}};
10681120
1069- for (const auto & m : {cb::xattr::macros::CAS,
1070- cb::xattr::macros::SEQNO,
1071- cb::xattr::macros::VALUE_CRC32C}) {
1072- context.generate_macro_padding (document .view , m);
1121+ for (const auto m : {cb::xattr::macros::CAS,
1122+ cb::xattr::macros::SEQNO,
1123+ cb::xattr::macros::VALUE_CRC32C}) {
1124+ context.generate_macro_padding (xattr .view , m);
10731125 }
10741126
10751127 bool modified;
10761128 auto datatype = PROTOCOL_BINARY_DATATYPE_JSON;
1077- if (!operate_single_doc (context, document , datatype, modified)) {
1129+ if (!operate_single_doc (context, &xattr, body , datatype, modified)) {
10781130 // Something failed..
10791131 return false ;
10801132 }
@@ -1098,17 +1150,32 @@ static bool do_xattr_phase(SubdocCmdContext& context) {
10981150 // document.. create a copy we can use for replace.
10991151 cb::xattr::Blob copy (xattr_blob);
11001152
1101- if (document .view .size () > key.size ()) {
1102- const char * start = strchr (document .view .data (), ' :' ) + 1 ;
1103- const char * end = document .view .data () + document .view .size () - 1 ;
1153+ if (xattr .view .size () > key.size ()) {
1154+ const char * start = strchr (xattr .view .data (), ' :' ) + 1 ;
1155+ const char * end = xattr .view .data () + xattr .view .size () - 1 ;
11041156
11051157 copy.set (key, {start, size_t (end - start)});
11061158 } else {
11071159 copy.remove (key);
11081160 }
11091161 const auto new_xattr = copy.finalize ();
1110- replace_xattrs (new_xattr, context, bodyoffset, bodysize);
11111162
1163+ if (body.isModified ()) {
1164+ auto total = new_xattr.size () + body.view .size ();
1165+ std::string next;
1166+ next.reserve (total);
1167+ next.insert (next.end (), new_xattr.begin (), new_xattr.end ());
1168+ next.insert (next.end (), body.view .begin (), body.view .end ());
1169+ context.in_doc .reset (std::move (next));
1170+ if (new_xattr.empty ()) {
1171+ context.in_datatype &= ~PROTOCOL_BINARY_DATATYPE_XATTR;
1172+ context.no_sys_xattrs = true ;
1173+ } else {
1174+ context.in_datatype |= PROTOCOL_BINARY_DATATYPE_XATTR;
1175+ }
1176+ } else {
1177+ replace_xattrs (new_xattr, context, bodyoffset, bodysize);
1178+ }
11121179 return true ;
11131180}
11141181
@@ -1126,17 +1193,18 @@ static bool do_body_phase(SubdocCmdContext& context) {
11261193 }
11271194
11281195 size_t xattrsize = 0 ;
1129- MemoryBackedBuffer document {context.in_doc };
1196+ MemoryBackedBuffer body {context.in_doc };
11301197
11311198 if (mcbp::datatype::is_xattr (context.in_datatype )) {
11321199 // We shouldn't have any documents like that!
1133- xattrsize = cb::xattr::get_body_offset (document .view );
1134- document .view .remove_prefix (xattrsize);
1200+ xattrsize = cb::xattr::get_body_offset (body .view );
1201+ body .view .remove_prefix (xattrsize);
11351202 }
11361203
11371204 bool modified;
11381205
1139- if (!operate_single_doc (context, document, context.in_datatype , modified)) {
1206+ if (!operate_single_doc (
1207+ context, nullptr , body, context.in_datatype , modified)) {
11401208 return false ;
11411209 }
11421210
@@ -1149,18 +1217,18 @@ static bool do_body_phase(SubdocCmdContext& context) {
11491217 // reallocate and move things around but just reuse the temporary
11501218 // buffer we've already created.
11511219 if (xattrsize == 0 ) {
1152- context.in_doc .swap (document );
1220+ context.in_doc .swap (body );
11531221 return true ;
11541222 }
11551223
1156- // Time to rebuild the full document.
1157- auto total = xattrsize + document .view .size ();
1224+ // Just stitch our new modified value behind the existing xattrs
1225+ auto total = xattrsize + body .view .size ();
11581226 std::string next;
11591227 next.reserve (total);
11601228 next.insert (next.end (),
11611229 context.in_doc .view .data (),
11621230 context.in_doc .view .data () + xattrsize);
1163- next.insert (next.end (), document .view .begin (), document .view .end ());
1231+ next.insert (next.end (), body .view .begin (), body .view .end ());
11641232 context.in_doc .reset (std::move (next));
11651233
11661234 return true ;
@@ -1744,6 +1812,12 @@ void subdoc_get_count_executor(Cookie& cookie) {
17441812 get_traits<cb::mcbp::ClientOpcode::SubdocGetCount>());
17451813}
17461814
1815+ void subdoc_replace_body_with_xattr_executor (Cookie& cookie) {
1816+ subdoc_executor (
1817+ cookie,
1818+ get_traits<cb::mcbp::ClientOpcode::SubdocReplaceBodyWithXattr>());
1819+ }
1820+
17471821void subdoc_multi_lookup_executor (Cookie& cookie) {
17481822 subdoc_executor (cookie,
17491823 get_traits<cb::mcbp::ClientOpcode::SubdocMultiLookup>());
0 commit comments