couchbase_core/memdx/
subdoc.rs

1/*
2 *
3 *  * Copyright (c) 2025 Couchbase, Inc.
4 *  *
5 *  * Licensed under the Apache License, Version 2.0 (the "License");
6 *  * you may not use this file except in compliance with the License.
7 *  * You may obtain a copy of the License at
8 *  *
9 *  *    http://www.apache.org/licenses/LICENSE-2.0
10 *  *
11 *  * Unless required by applicable law or agreed to in writing, software
12 *  * distributed under the License is distributed on an "AS IS" BASIS,
13 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  * See the License for the specific language governing permissions and
15 *  * limitations under the License.
16 *
17 */
18
19use crate::memdx::error;
20use crate::memdx::opcode::OpCode;
21use bitflags::bitflags;
22
23pub trait SubdocOp {
24    fn is_xattr_op(&self) -> bool;
25}
26
27pub fn reorder_subdoc_ops<T: SubdocOp>(ops: &[T]) -> (Vec<&T>, Vec<usize>) {
28    let mut ordered_ops = Vec::with_capacity(ops.len());
29    let mut op_indexes = Vec::with_capacity(ops.len());
30
31    for (i, op) in ops.iter().enumerate() {
32        if op.is_xattr_op() {
33            ordered_ops.push(op);
34            op_indexes.push(i);
35        }
36    }
37
38    for (i, op) in ops.iter().enumerate() {
39        if !op.is_xattr_op() {
40            ordered_ops.push(op);
41            op_indexes.push(i);
42        }
43    }
44
45    (ordered_ops, op_indexes)
46}
47
48// Request-info needed for response parsing
49#[derive(Debug, Clone, Copy, Default)]
50pub struct SubdocRequestInfo {
51    pub(crate) flags: SubdocDocFlag,
52    pub(crate) op_count: u8,
53}
54
55#[derive(Debug)]
56pub struct SubDocResult {
57    pub err: Option<error::Error>,
58    pub value: Option<Vec<u8>>,
59}
60
61#[derive(Clone, Debug, Copy, Eq, PartialEq)]
62pub struct LookupInOp<'a> {
63    pub(crate) op: LookupInOpType,
64    pub(crate) flags: SubdocOpFlag,
65    pub(crate) path: &'a [u8],
66}
67
68impl<'a> LookupInOp<'a> {
69    pub fn new(op: LookupInOpType, path: &'a [u8]) -> Self {
70        Self {
71            op,
72            flags: SubdocOpFlag::empty(),
73            path,
74        }
75    }
76
77    pub fn flags(mut self, flags: SubdocOpFlag) -> Self {
78        self.flags = flags;
79        self
80    }
81}
82
83impl SubdocOp for LookupInOp<'_> {
84    fn is_xattr_op(&self) -> bool {
85        self.flags.contains(SubdocOpFlag::XATTR_PATH)
86    }
87}
88
89#[derive(Clone, Debug, Eq, PartialEq)]
90pub struct MutateInOp<'a> {
91    pub(crate) op: MutateInOpType,
92    pub(crate) flags: SubdocOpFlag,
93    pub(crate) path: &'a [u8],
94    pub(crate) value: &'a [u8],
95}
96
97impl<'a> MutateInOp<'a> {
98    pub fn new(op: MutateInOpType, path: &'a [u8], value: &'a [u8]) -> Self {
99        Self {
100            op,
101            flags: SubdocOpFlag::empty(),
102            path,
103            value,
104        }
105    }
106
107    pub fn flags(mut self, flags: SubdocOpFlag) -> Self {
108        self.flags = flags;
109        self
110    }
111}
112
113impl SubdocOp for MutateInOp<'_> {
114    fn is_xattr_op(&self) -> bool {
115        self.flags.contains(SubdocOpFlag::XATTR_PATH)
116    }
117}
118
119bitflags! {
120    #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
121    pub struct SubdocOpFlag: u8 {
122        // SubdocOpFlagMkDirP indicates that the path should be created if it does not already exist.
123        const MKDIR_P = 0x01;
124
125        // 0x02 is unused, formally SubdocFlagMkDoc
126
127        // SubdocOpFlagXattrPath indicates that the path refers to an Xattr rather than the document body.
128        const XATTR_PATH = 0x04;
129
130        // 0x08 is unused, formally SubdocFlagAccessDeleted
131
132        // SubdocOpFlagExpandMacros indicates that the value portion of any sub-document mutations
133        // should be expanded if they contain macros such as \${Mutation.CAS}.
134        const EXPAND_MACROS = 0x10;
135    }
136
137    #[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Ord, PartialOrd, Hash)]
138    pub struct SubdocDocFlag: u8 {
139        // SubdocDocFlagMkDoc indicates that the document should be created if it does not already exist.
140        const MkDoc = 0x01;
141        // SubdocDocFlagAddDoc indices that this operation should be an add rather than set.
142        const AddDoc = 0x02;
143
144        // SubdocDocFlagAccessDeleted indicates that you wish to receive soft-deleted documents.
145        // Internal: This should never be used and is not supported.
146        const AccessDeleted = 0x04;
147
148        // SubdocDocFlagCreateAsDeleted indicates that the document should be created as deleted.
149        // That is, to create a tombstone only.
150        // Internal: This should never be used and is not supported.
151        const CreateAsDeleted = 0x08;
152
153        // SubdocDocFlagReviveDocument indicates that the document should be revived from a tombstone.
154        // Internal: This should never be used and is not supported.
155        const ReviveDocument = 0x10;
156    }
157}
158
159// LookupInOpType specifies the type of lookup in operation.
160#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
161pub enum LookupInOpType {
162    // LookupInOpTypeGet indicates the operation is a sub-document `Get` operation.
163    Get,
164    // LookupInOpTypeExists indicates the operation is a sub-document `Exists` operation.
165    Exists,
166    // LookupInOpTypeGetCount indicates the operation is a sub-document `GetCount` operation.
167    GetCount,
168    // LookupInOpTypeGetDoc represents a full document retrieval, for use with extended attribute ops.
169    GetDoc,
170}
171
172impl From<LookupInOpType> for OpCode {
173    fn from(op_type: LookupInOpType) -> Self {
174        match op_type {
175            LookupInOpType::Get => OpCode::SubDocGet,
176            LookupInOpType::Exists => OpCode::SubDocExists,
177            LookupInOpType::GetCount => OpCode::SubDocGetCount,
178            LookupInOpType::GetDoc => OpCode::Get,
179        }
180    }
181}
182
183// MutateInOpType specifies the type of mutate in operation.
184#[derive(Debug, Copy, Clone, PartialEq, Eq)]
185pub enum MutateInOpType {
186    // MutateInOpTypeDictAdd indicates the operation is a sub-document `Add` operation.
187    DictAdd,
188    // MutateInOpTypeDictSet indicates the operation is a sub-document `Set` operation.
189    DictSet,
190    // MutateInOpTypeDelete indicates the operation is a sub-document `Remove` operation.
191    Delete,
192    // MutateInOpTypeReplace indicates the operation is a sub-document `Replace` operation.
193    Replace,
194    // MutateInOpTypeArrayPushLast indicates the operation is a sub-document `ArrayPushLast` operation.
195    ArrayPushLast,
196    // MutateInOpTypeArrayPushFirst indicates the operation is a sub-document `ArrayPushFirst` operation.
197    ArrayPushFirst,
198    // MutateInOpTypeArrayInsert indicates the operation is a sub-document `ArrayInsert` operation.
199    ArrayInsert,
200    // MutateInOpTypeArrayAddUnique indicates the operation is a sub-document `ArrayAddUnique` operation.
201    ArrayAddUnique,
202    // MutateInOpTypeCounter indicates the operation is a sub-document `Counter` operation.
203    Counter,
204    // MutateInOpTypeSetDoc represents a full document set, for use with extended attribute ops.
205    SetDoc,
206    // MutateInOpTypeAddDoc represents a full document add, for use with extended attribute ops.
207    AddDoc,
208    // MutateInOpTypeDeleteDoc represents a full document delete, for use with extended attribute ops.
209    DeleteDoc,
210    // MutateInOpTypeReplaceBodyWithXattr represents a replace body with xattr op.
211    // Uncommitted: This API may change in the future.
212    ReplaceBodyWithXattr,
213}
214
215impl From<MutateInOpType> for OpCode {
216    fn from(op_type: MutateInOpType) -> Self {
217        match op_type {
218            MutateInOpType::DictAdd => OpCode::SubDocDictAdd,
219            MutateInOpType::DictSet => OpCode::SubDocDictSet,
220            MutateInOpType::Delete => OpCode::SubDocDelete,
221            MutateInOpType::Replace => OpCode::SubDocReplace,
222            MutateInOpType::ArrayPushLast => OpCode::SubDocArrayPushLast,
223            MutateInOpType::ArrayPushFirst => OpCode::SubDocArrayPushFirst,
224            MutateInOpType::ArrayInsert => OpCode::SubDocArrayInsert,
225            MutateInOpType::ArrayAddUnique => OpCode::SubDocArrayAddUnique,
226            MutateInOpType::Counter => OpCode::SubDocCounter,
227            MutateInOpType::SetDoc => OpCode::Set,
228            MutateInOpType::AddDoc => OpCode::Add,
229            MutateInOpType::DeleteDoc => OpCode::Delete,
230            MutateInOpType::ReplaceBodyWithXattr => OpCode::SubDocReplaceBodyWithXattr,
231        }
232    }
233}
234
235pub const SUBDOC_XATTR_PATH_HLC: &[u8] = b"$vbucket.HLC";
236
237pub const SUBDOC_MACRO_NEW_CRC32C: &[u8] = b"\"${Mutation.value_crc32c}\"";
238pub const SUBDOC_MACRO_NEW_CAS: &[u8] = b"\"${Mutation.CAS}\"";
239pub const SUBDOC_MACRO_OLD_REVID: &[u8] = b"\"${$document.revid}\"";
240pub const SUBDOC_MACRO_OLD_EXPTIME: &[u8] = b"\"${$document.exptime}\"";
241pub const SUBDOC_MACRO_OLD_CAS: &[u8] = b"\"${$document.CAS}\"";