Skip to content

Commit 65a586f

Browse files
trondndaverigby
authored andcommitted
MB-41550: Add new command SubdocReplaceBodyWithXattr
This command allows the caller to replace the value of a document with the content of an xattr. Change-Id: Ifc72ba83f80275cc100a5f6dd746b5dacea7d8b9 Reviewed-on: http://review.couchbase.org/c/kv_engine/+/136775 Tested-by: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent f733cdd commit 65a586f

17 files changed

+296
-80
lines changed

daemon/mcbp_executors.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,8 @@ void initialize_mbcp_lookup_map() {
818818
subdoc_multi_mutation_executor);
819819
setup_handler(cb::mcbp::ClientOpcode::SubdocGetCount,
820820
subdoc_get_count_executor);
821+
setup_handler(cb::mcbp::ClientOpcode::SubdocReplaceBodyWithXattr,
822+
subdoc_replace_body_with_xattr_executor);
821823

822824
setup_handler(cb::mcbp::ClientOpcode::AdjustTimeofday,
823825
adjust_timeofday_executor);

daemon/mcbp_privileges.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,9 @@ McbpPrivilegeChains::McbpPrivilegeChains() {
314314
setup(cb::mcbp::ClientOpcode::SubdocMultiMutation,
315315
require<Privilege::Upsert>);
316316

317+
setup(cb::mcbp::ClientOpcode::SubdocReplaceBodyWithXattr,
318+
require<Privilege::Upsert>);
319+
317320
/* Scrub the data */
318321
setup(cb::mcbp::ClientOpcode::Scrub, require<Privilege::NodeManagement>);
319322
/* Refresh the ISASL data */

daemon/mcbp_validators.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2323,6 +2323,8 @@ McbpValidator::McbpValidator() {
23232323
setup(cb::mcbp::ClientOpcode::SubdocMultiMutation,
23242324
subdoc_multi_mutation_validator);
23252325
setup(cb::mcbp::ClientOpcode::SubdocGetCount, subdoc_get_count_validator);
2326+
setup(cb::mcbp::ClientOpcode::SubdocReplaceBodyWithXattr,
2327+
subdoc_replace_body_with_xattr_validator);
23262328

23272329
setup(cb::mcbp::ClientOpcode::Setq, set_replace_validator);
23282330
setup(cb::mcbp::ClientOpcode::Set, set_replace_validator);

daemon/subdocument.cc

Lines changed: 136 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
750778
static 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+
17471821
void subdoc_multi_lookup_executor(Cookie& cookie) {
17481822
subdoc_executor(cookie,
17491823
get_traits<cb::mcbp::ClientOpcode::SubdocMultiLookup>());

daemon/subdocument.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ void subdoc_array_insert_executor(Cookie& cookie);
3636
void subdoc_array_add_unique_executor(Cookie& cookie);
3737
void subdoc_counter_executor(Cookie& cookie);
3838
void subdoc_get_count_executor(Cookie& cookie);
39+
void subdoc_replace_body_with_xattr_executor(Cookie& cookie);
3940
void subdoc_multi_lookup_executor(Cookie& cookie);
4041
void subdoc_multi_mutation_executor(Cookie& cookie);

daemon/subdocument_context.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct MemoryBackedBuffer {
4444
void reset(std::string&& next) {
4545
backing.swap(next);
4646
view = {backing.data(), backing.size()};
47+
modified = true;
4748
}
4849

4950
void swap(MemoryBackedBuffer& next) {
@@ -53,8 +54,14 @@ struct MemoryBackedBuffer {
5354

5455
std::string_view view;
5556

57+
/// Check if the buffer was modified since it was created
58+
bool isModified() const {
59+
return modified;
60+
}
61+
5662
protected:
5763
std::string backing;
64+
bool modified = false;
5865
};
5966

6067
enum class MutationSemantics : uint8_t { Add, Replace, Set };

0 commit comments

Comments
 (0)