164828eb2SDave Rigby /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
264828eb2SDave Rigby /*
364828eb2SDave Rigby  *     Copyright 2015 Couchbase, Inc
464828eb2SDave Rigby  *
564828eb2SDave Rigby  *   Licensed under the Apache License, Version 2.0 (the "License");
664828eb2SDave Rigby  *   you may not use this file except in compliance with the License.
764828eb2SDave Rigby  *   You may obtain a copy of the License at
864828eb2SDave Rigby  *
964828eb2SDave Rigby  *       http://www.apache.org/licenses/LICENSE-2.0
1064828eb2SDave Rigby  *
1164828eb2SDave Rigby  *   Unless required by applicable law or agreed to in writing, software
1264828eb2SDave Rigby  *   distributed under the License is distributed on an "AS IS" BASIS,
1364828eb2SDave Rigby  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1464828eb2SDave Rigby  *   See the License for the specific language governing permissions and
1564828eb2SDave Rigby  *   limitations under the License.
1664828eb2SDave Rigby  */
1764828eb2SDave Rigby 
1864828eb2SDave Rigby #pragma once
1964828eb2SDave Rigby 
20aff004bbSDave Rigby #include "memcached.h"
2164828eb2SDave Rigby 
229738c807STrond Norbye #include "connection.h"
239738c807STrond Norbye #include "cookie.h"
249fd51ab2SDave Rigby #include "subdocument_traits.h"
258b9f99b0STrond Norbye #include "xattr/utils.h"
269fd51ab2SDave Rigby 
2764828eb2SDave Rigby #include <cstddef>
2816d39defSTrond Norbye #include <iomanip>
291008a7c8SDave Rigby #include <memory>
307f9fe569STrond Norbye #include <platform/compress.h>
31c7fdf35fSJim Walker #include <platform/sized_buffer.h>
3264828eb2SDave Rigby 
33d24dbc18Solivermd #include <unordered_map>
34d24dbc18Solivermd 
35a3421b95Solivermd enum class MutationSemantics : uint8_t { Add, Replace, Set };
36a3421b95Solivermd 
37435411baSolivermd // Used to describe which xattr keys the xtoc vattr should return
38435411baSolivermd enum class XtocSemantics : uint8_t { None, User, System, All };
39435411baSolivermd 
4064828eb2SDave Rigby /** Subdoc command context. An instance of this exists for the lifetime of
4164828eb2SDave Rigby  *  each subdocument command, and it used to hold information which needs to
4264828eb2SDave Rigby  *  persist across calls to subdoc_executor; for example when one or more
4364828eb2SDave Rigby  *  engine functions return EWOULDBLOCK and hence the executor needs to be
4464828eb2SDave Rigby  *  retried.
4564828eb2SDave Rigby  */
4616d39defSTrond Norbye class SubdocCmdContext : public CommandContext {
4716d39defSTrond Norbye public:
4816d39defSTrond Norbye     /**
4916d39defSTrond Norbye      * All subdoc access happens in two phases... First we'll run through
5016d39defSTrond Norbye      * all of the operations on the extended attributes, then we'll run
5116d39defSTrond Norbye      * over all of the ones in the body.
5216d39defSTrond Norbye      */
5316d39defSTrond Norbye     enum class Phase : uint8_t {
5416d39defSTrond Norbye         XATTR,
5516d39defSTrond Norbye         Body
5616d39defSTrond Norbye     };
5764828eb2SDave Rigby 
58aff004bbSDave Rigby     class OperationSpec;
59aff004bbSDave Rigby     typedef std::vector<OperationSpec> Operations;
60aff004bbSDave Rigby 
SubdocCmdContext(Cookie & cookie_,const SubdocCmdTraits traits_)612284dcc8STrond Norbye     SubdocCmdContext(Cookie& cookie_, const SubdocCmdTraits traits_)
622284dcc8STrond Norbye         : cookie(cookie_),
632284dcc8STrond Norbye           connection(cookie_.getConnection()),
642284dcc8STrond Norbye           traits(traits_) {
65282c54b7STrond Norbye     }
6664828eb2SDave Rigby 
67db6aa48eSTrond Norbye     ENGINE_ERROR_CODE pre_link_document(item_info& info) override;
68db6aa48eSTrond Norbye 
69db6aa48eSTrond Norbye     /**
70db6aa48eSTrond Norbye      * Get the padded value we want to use for values with macro expansion.
71db6aa48eSTrond Norbye      * Note that the macro name must be evaluated elsewhere as this method
72db6aa48eSTrond Norbye      * expect the input value to be one of the legal macros.
73db6aa48eSTrond Norbye      *
74db6aa48eSTrond Norbye      * @param macro the name of the macro to return the padded value for
75db6aa48eSTrond Norbye      * @return the buffer we want to pass on to subdoc instead of the macro
76db6aa48eSTrond Norbye      *         name
77db6aa48eSTrond Norbye      */
78d24dbc18Solivermd     cb::const_char_buffer get_padded_macro(cb::const_char_buffer macro);
79db6aa48eSTrond Norbye 
80db6aa48eSTrond Norbye     /**
81d24dbc18Solivermd      * Generate macro padding we may use to substitute a macro with. E.g. We
82d24dbc18Solivermd      * replace "${Mutation.CAS}" or "${Mutation.seqno}" with the generated
83d24dbc18Solivermd      * padding. It needs to be wide enough so that we can do an in-place
84d24dbc18Solivermd      * replacement with the actual CAS or seqno in the pre_link_document
85d24dbc18Solivermd      * callback.
86db6aa48eSTrond Norbye      *
87db6aa48eSTrond Norbye      * We can't really use a hardcoded value (as we would limit the user
88db6aa48eSTrond Norbye      * for what they could inject in their documents, and we don't want to
89db6aa48eSTrond Norbye      * suddenly replace user data with a cas value ;).
90db6aa48eSTrond Norbye      *
91db6aa48eSTrond Norbye      * This method tries to generate a string, and then scans through the
92db6aa48eSTrond Norbye      * supplied payload to ensure that it isn't present there before
93db6aa48eSTrond Norbye      * scanning through all of the values in the xattr modidications to
94db6aa48eSTrond Norbye      * ensure that it isn't part of any of them either.
95db6aa48eSTrond Norbye      *
96db6aa48eSTrond Norbye      * You might think: oh, why don't you just store the pointers to where
97db6aa48eSTrond Norbye      * in the blob we injected the macro? The problem with that is that
98db6aa48eSTrond Norbye      * there isn't any restrictions on the order you may specify the
99db6aa48eSTrond Norbye      * mutations in a multiop, so that you could move the stuff around;
100db6aa48eSTrond Norbye      * replace it; delete it. That means that you would have to go
101db6aa48eSTrond Norbye      * through and relocate all of these offsets after each mutation.
102db6aa48eSTrond Norbye      * Not impossible, but I don't think it would simplify the logic
103db6aa48eSTrond Norbye      * that much ;-)
104db6aa48eSTrond Norbye      *
105db6aa48eSTrond Norbye      * @param payload the JSON value for the xattr to perform macro
106db6aa48eSTrond Norbye      *                substitution in
107*90e646e4SPaolo Cocchi      * @param macro the macro for which we want to generate the padding
108*90e646e4SPaolo Cocchi      *
109*90e646e4SPaolo Cocchi      * @throws std::logic_error if the macro expansion size is invalid
110db6aa48eSTrond Norbye      */
111d24dbc18Solivermd     void generate_macro_padding(cb::const_char_buffer payload,
112*90e646e4SPaolo Cocchi                                 cb::xattr::macros::macro macro);
113db6aa48eSTrond Norbye 
getOperations(const Phase phase)11416d39defSTrond Norbye     Operations& getOperations(const Phase phase) {
11516d39defSTrond Norbye         switch (phase) {
11616d39defSTrond Norbye         case Phase::Body:
117db6aa48eSTrond Norbye             return operations[0];
11816d39defSTrond Norbye         case Phase::XATTR:
119db6aa48eSTrond Norbye             return operations[1];
12016d39defSTrond Norbye         }
12116d39defSTrond Norbye         throw std::invalid_argument("SubdocCmdContext::getOperations() invalid phase");
12216d39defSTrond Norbye     }
12316d39defSTrond Norbye 
getOperations()12416d39defSTrond Norbye     Operations& getOperations() {
12516d39defSTrond Norbye         return getOperations(currentPhase);
12616d39defSTrond Norbye     }
12716d39defSTrond Norbye 
getCurrentPhase()12816d39defSTrond Norbye     Phase getCurrentPhase() {
12916d39defSTrond Norbye         return currentPhase;
13016d39defSTrond Norbye     }
13116d39defSTrond Norbye 
setCurrentPhase(Phase phase)13216d39defSTrond Norbye     void setCurrentPhase(Phase phase) {
13316d39defSTrond Norbye         currentPhase = phase;
13416d39defSTrond Norbye     }
13516d39defSTrond Norbye 
136edfcab0dSDave Rigby     // Returns the total size of all Operation values (bytes).
137edfcab0dSDave Rigby     uint64_t getOperationValueBytesTotal() const;
138edfcab0dSDave Rigby 
1392284dcc8STrond Norbye     // Cookie this command is associated with.
1402284dcc8STrond Norbye     Cookie& cookie;
1412284dcc8STrond Norbye 
142bf74734dSTrond Norbye     Connection& connection;
14364828eb2SDave Rigby 
1449fd51ab2SDave Rigby     // The traits for this command.
1459fd51ab2SDave Rigby     SubdocCmdTraits traits;
1469fd51ab2SDave Rigby 
147c8e591e0SDave Rigby     // The expanded input JSON document. This may either refer to:
148c8e591e0SDave Rigby     // a). The raw engine item iovec
1497f9fe569STrond Norbye     // b). The 'inflated_doc_buffer' if the input document had to be
1507f9fe569STrond Norbye     //     inflated.
151c8e591e0SDave Rigby     // c). {intermediate_result} member of this object.
152aa327187SDave Rigby     // Either way, it should /not/ be cb_free()d.
1531787635dSDave Rigby     // Note this is *always* in a decompressed form (and hence can safely be
1541787635dSDave Rigby     // read / manipulated directly) - see get_document_for_searching().
155c8e591e0SDave Rigby     // TODO: Remove (b), and just use intermediate result.
156a5933ed9STrond Norbye     cb::const_char_buffer in_doc{};
15764828eb2SDave Rigby 
1587f9fe569STrond Norbye     // Temporary buffer to hold the inflated content in case of the
1597f9fe569STrond Norbye     // document in the engine being compressed
1607f9fe569STrond Norbye     cb::compression::Buffer inflated_doc_buffer;
1617f9fe569STrond Norbye 
162c8e591e0SDave Rigby     // Temporary buffer used to hold the intermediate result document for
163c8e591e0SDave Rigby     // multi-path mutations. {in_doc} is then updated to point to this to use
164c8e591e0SDave Rigby     // as input for the next multi-path mutation.
1651008a7c8SDave Rigby     std::unique_ptr<char[]> temp_doc;
166c8e591e0SDave Rigby 
167b1739921STrond Norbye     // Temporary buffer used to hold the xattrs in use, as a get request
168b1739921STrond Norbye     // may hold pointers into the repacked xattr buckets
169d6affa8dSJim Walker     std::unique_ptr<char[]> xattr_buffer;
170b1739921STrond Norbye 
17164828eb2SDave Rigby     // CAS value of the input document. Required to ensure we only store a
17264828eb2SDave Rigby     // new document which was derived from the same original input document.
173a5933ed9STrond Norbye     uint64_t in_cas = 0;
17464828eb2SDave Rigby 
17575c428a7SDave Rigby     // Flags of the input document. Required so we can set the same flags to
17675c428a7SDave Rigby     // to the new document, so flags are unchanged by subdoc.
177a5933ed9STrond Norbye     uint32_t in_flags = 0;
17875c428a7SDave Rigby 
1792a2509acSTrond Norbye     // The datatype for the document currently held in `in_doc`. This
1802a2509acSTrond Norbye     // is used to set the new documents datatype.
1811787635dSDave Rigby     // Note: If the original input was Snappy compressed; it will be
1821787635dSDave Rigby     // decompressed during fetch (by get_document_for_searching()) - as such
1831787635dSDave Rigby     // this field will never have the Snappy bit set.
184a5933ed9STrond Norbye     protocol_binary_datatype_t in_datatype = PROTOCOL_BINARY_RAW_BYTES;
1852a2509acSTrond Norbye 
186db6aa48eSTrond Norbye     // The state of the document currently held in `in_doc`. This is used
187db6aa48eSTrond Norbye     // to to set the new documents state.
188a5933ed9STrond Norbye     DocumentState in_document_state = DocumentState::Alive;
189db6aa48eSTrond Norbye 
190a157ba75SDave Rigby     // True if this operation has been successfully executed (via subjson)
191a157ba75SDave Rigby     // and we have valid result.
192a5933ed9STrond Norbye     bool executed = false;
193a157ba75SDave Rigby 
19449a24506SMark Nunberg     // [Mutations only] The type of the root element, if flags & FLAG_MKDOC
195a5933ed9STrond Norbye     jsonsl_type_t jroot_type = JSONSL_T_ROOT;
196a5933ed9STrond Norbye 
19749a24506SMark Nunberg     // [Mutations only] True if the doc does not exist and an insert (rather
19849a24506SMark Nunberg     // than replace) is required.
199a5933ed9STrond Norbye     bool needs_new_doc = false;
20049a24506SMark Nunberg 
201aff004bbSDave Rigby     // Overall status of the entire command.
202aff004bbSDave Rigby     // For single-path commands this is simply the same as the first (and only)
203aff004bbSDave Rigby     // opetation, for multi-path it's an aggregate status.
204a5933ed9STrond Norbye     protocol_binary_response_status overall_status =
205a5933ed9STrond Norbye             PROTOCOL_BINARY_RESPONSE_SUCCESS;
20664828eb2SDave Rigby 
207d655a3aaSDave Rigby     // [Mutations only] Mutation sequence number and vBucket UUID. Only set
208d655a3aaSDave Rigby     // if the calling connection has the MUTATION_SEQNO feature enabled; to be
209d655a3aaSDave Rigby     // included in the response back to the client.
210a5933ed9STrond Norbye     uint64_t vbucket_uuid = 0;
211a5933ed9STrond Norbye     uint64_t sequence_no = 0;
212d655a3aaSDave Rigby 
213edfcab0dSDave Rigby     // [Mutations only] Size in bytes of the new item to store into engine.
214edfcab0dSDave Rigby     // Held in the context so upon success we can update statistics.
215a5933ed9STrond Norbye     size_t out_doc_len = 0;
216edfcab0dSDave Rigby 
2172582d53dSTrond Norbye     // [Mutations only] New item to store into engine.
2182582d53dSTrond Norbye     cb::unique_item_ptr out_doc;
219aff004bbSDave Rigby 
220edfcab0dSDave Rigby     // Size in bytes of the response value to send back to the client.
221a5933ed9STrond Norbye     size_t response_val_len = 0;
222edfcab0dSDave Rigby 
223db6aa48eSTrond Norbye     // Set to true if one (or more) of the xattr operation wants to do
224db6aa48eSTrond Norbye     // macro expansion.
225a5933ed9STrond Norbye     bool do_macro_expansion = false;
226db6aa48eSTrond Norbye 
227db6aa48eSTrond Norbye     // Set to true if we want to operate on deleted documents
228a5933ed9STrond Norbye     bool do_allow_deleted_docs = false;
229db6aa48eSTrond Norbye 
230ecd8b191Solivermd     // Set to true if we want to delete the document after modifying it
231a5933ed9STrond Norbye     bool do_delete_doc = false;
232ecd8b191Solivermd 
233ecd8b191Solivermd     // true if there are no system xattrs after the operation. In
234ecd8b191Solivermd     // reality this means we do a bucket_remove rather than a bucket_update
235a5933ed9STrond Norbye     bool no_sys_xattrs = false;
236a5933ed9STrond Norbye 
237aff004bbSDave Rigby     /* Specification of a single path operation. Encapsulates both the request
238aff004bbSDave Rigby      * parameters, and (later) the result of the operation.
239aff004bbSDave Rigby      */
240aff004bbSDave Rigby     class OperationSpec {
241aff004bbSDave Rigby     public:
242aff004bbSDave Rigby         // Constructor for lookup operations (no value).
243aff004bbSDave Rigby         OperationSpec(SubdocCmdTraits traits_,
244db6aa48eSTrond Norbye                       protocol_binary_subdoc_flag flags_,
245c7fdf35fSJim Walker                       cb::const_char_buffer path_);
246aff004bbSDave Rigby 
247aff004bbSDave Rigby         // Constructor for operations requiring a value.
248aff004bbSDave Rigby         OperationSpec(SubdocCmdTraits traits_,
249db6aa48eSTrond Norbye                       protocol_binary_subdoc_flag flags_,
250c7fdf35fSJim Walker                       cb::const_char_buffer path_,
251c7fdf35fSJim Walker                       cb::const_char_buffer value_);
252aff004bbSDave Rigby 
253aff004bbSDave Rigby         // Move constructor.
254aff004bbSDave Rigby         OperationSpec(OperationSpec&& other);
255aff004bbSDave Rigby 
256aff004bbSDave Rigby         // The traits of this individual Operation.
257aff004bbSDave Rigby         SubdocCmdTraits traits;
258aff004bbSDave Rigby 
259db6aa48eSTrond Norbye         // The flags set for this individual Operation
260db6aa48eSTrond Norbye         protocol_binary_subdoc_flag flags;
261db6aa48eSTrond Norbye 
262aff004bbSDave Rigby         // Path to operate on. Owned by the original request packet.
263c7fdf35fSJim Walker         cb::const_char_buffer path;
264aff004bbSDave Rigby 
265aff004bbSDave Rigby         // [For mutations only] Value to apply to document. Owned by the
266aff004bbSDave Rigby         // original request packet.
267c7fdf35fSJim Walker         cb::const_char_buffer value;
268aff004bbSDave Rigby 
269aff004bbSDave Rigby         // Status code of the operation.
270aff004bbSDave Rigby         protocol_binary_response_status status;
271aff004bbSDave Rigby 
272aff004bbSDave Rigby         // Result of this operation, to be returned back to the client (for
273aff004bbSDave Rigby         // operations which return a result).
274aff004bbSDave Rigby         Subdoc::Result result;
27564828eb2SDave Rigby     };
27616d39defSTrond Norbye 
277db6aa48eSTrond Norbye     /**
278db6aa48eSTrond Norbye      * Get the xattr key being accessed in this context. Only one
279db6aa48eSTrond Norbye      * xattr key is allowed in each multi op
280db6aa48eSTrond Norbye      *
281db6aa48eSTrond Norbye      * @return the key
282db6aa48eSTrond Norbye      */
get_xattr_key()283d6affa8dSJim Walker     cb::const_char_buffer get_xattr_key() {
284db6aa48eSTrond Norbye         return xattr_key;
285db6aa48eSTrond Norbye     }
286db6aa48eSTrond Norbye 
287db6aa48eSTrond Norbye     /**
288db6aa48eSTrond Norbye      * Set the xattr key being accessed in this context. Only one
289db6aa48eSTrond Norbye      * xattr key is allowed in each multi op
290db6aa48eSTrond Norbye      *
291db6aa48eSTrond Norbye      * @param key the key to be accessed
292db6aa48eSTrond Norbye      */
set_xattr_key(const cb::const_char_buffer & key)293d6affa8dSJim Walker     void set_xattr_key(const cb::const_char_buffer& key) {
294db6aa48eSTrond Norbye         xattr_key = key;
295db6aa48eSTrond Norbye     }
29616d39defSTrond Norbye 
297a5933ed9STrond Norbye     MutationSemantics mutationSemantics = MutationSemantics::Replace;
298a3421b95Solivermd 
299a3421b95Solivermd     void setMutationSemantics(mcbp::subdoc::doc_flag docFlags);
300a3421b95Solivermd 
30149a21153STrond Norbye     /**
30249a21153STrond Norbye      * Get the document containing all of the virtual attributes for
30349a21153STrond Norbye      * the document. The storage is created the first time the method is called,
30449a21153STrond Norbye      * and reused for the rest of the lifetime of the context.
30549a21153STrond Norbye      */
30649a21153STrond Norbye     cb::const_char_buffer get_document_vattr();
30749a21153STrond Norbye 
308435411baSolivermd     /*
309435411baSolivermd      * Get the xtoc document which contains a list of xattr keys that exist for
310435411baSolivermd      * the document.
311435411baSolivermd      */
312435411baSolivermd     cb::const_char_buffer get_xtoc_vattr();
313435411baSolivermd 
31449a21153STrond Norbye     // This is the item info for the item we've fetched from the
31549a21153STrond Norbye     // database
getInputItemInfo()316282c54b7STrond Norbye     item_info& getInputItemInfo() {
317282c54b7STrond Norbye         return input_item_info;
318282c54b7STrond Norbye     }
319282c54b7STrond Norbye 
32027949f83STrond Norbye     /**
32127949f83STrond Norbye      * Initialize all of the internal input variables with a
32227949f83STrond Norbye      * flat, uncompressed JSON document ready for performing a subjson
32327949f83STrond Norbye      * operation on it.
32427949f83STrond Norbye      *
32527949f83STrond Norbye      * @param client_cas The CAS provided by the client (which should be
32627949f83STrond Norbye      *                   used for updates to the document
32727949f83STrond Norbye      *
32827949f83STrond Norbye      * @return PROTOCOL_BINARY_RESPONSE_SUCCESS for success, otherwise an
32927949f83STrond Norbye      *         error code which should be returned to the client immediately
33027949f83STrond Norbye      *         (and stop executing of the command)
33127949f83STrond Norbye      */
33227949f83STrond Norbye     protocol_binary_response_status get_document_for_searching(
33327949f83STrond Norbye             uint64_t client_cas);
33427949f83STrond Norbye 
3357389303aSolivermd     /**
3367389303aSolivermd      * The result of subdoc_fetch.
3377389303aSolivermd      */
3387389303aSolivermd     cb::unique_item_ptr fetchedItem;
3397389303aSolivermd 
340a5933ed9STrond Norbye     XtocSemantics xtocSemantics = XtocSemantics::None;
341435411baSolivermd 
34216d39defSTrond Norbye private:
343282c54b7STrond Norbye     // The item info representing the input document
344a5933ed9STrond Norbye     item_info input_item_info = {};
345282c54b7STrond Norbye 
346db6aa48eSTrond Norbye     // The array containing all of the operations requested by the user.
347db6aa48eSTrond Norbye     // Each element in the array contains the operations which should be
348db6aa48eSTrond Norbye     // run in each phase. Use `getOperations()` to get the correct entry
349db6aa48eSTrond Norbye     // in this array as that method contains the logic of where each
350db6aa48eSTrond Norbye     // phase lives.
351db6aa48eSTrond Norbye     std::array<Operations, 2> operations;
35216d39defSTrond Norbye 
35316d39defSTrond Norbye     // The phase we're currently operating in
354a5933ed9STrond Norbye     Phase currentPhase = Phase::XATTR;
355db6aa48eSTrond Norbye 
356d24dbc18Solivermd     template <typename T>
357d24dbc18Solivermd     std::string macroToString(T macroValue);
358d24dbc18Solivermd 
3590647c0afSTim Bradgate     /**
3600647c0afSTim Bradgate      * Check whether or not the SubdocCmdContext contains a given macro
3610647c0afSTim Bradgate      * @param macro The macro we are checking for
3620647c0afSTim Bradgate      * @return True if the macro exists, False otherwise
3630647c0afSTim Bradgate      */
3640647c0afSTim Bradgate     bool containsMacro(const cb::const_char_buffer& macro);
3650647c0afSTim Bradgate 
366d24dbc18Solivermd     void substituteMacro(cb::const_char_buffer macroName,
367d24dbc18Solivermd                          const std::string& macroValue,
368d6affa8dSJim Walker                          cb::char_buffer& value);
369d24dbc18Solivermd 
370bdff49bcSPaolo Cocchi     /**
371e7a21c91SPaolo Cocchi      * Returns the value CRC32C of the document processed by the current
372bdff49bcSPaolo Cocchi      * subdoc context
373bdff49bcSPaolo Cocchi      */
374e7a21c91SPaolo Cocchi     uint32_t computeValueCRC32C();
375bdff49bcSPaolo Cocchi 
376db6aa48eSTrond Norbye     // The xattr key being accessed in this command
377d6affa8dSJim Walker     cb::const_char_buffer xattr_key;
378db6aa48eSTrond Norbye 
379d24dbc18Solivermd     using MacroPair = std::pair<cb::const_char_buffer, std::string>;
380d24dbc18Solivermd     std::vector<MacroPair> paddedMacros;
38149a21153STrond Norbye 
38249a21153STrond Norbye     std::string document_vattr;
383435411baSolivermd     std::string xtoc_vattr;
384aff004bbSDave Rigby }; // class SubdocCmdContext
385