Skip to content

Commit 1f7a675

Browse files
committed
PYCBC-1525: Add LookupIn and MutateIn Macros
Motivation ========== Add macros to the subdoc API as they are part of the RFC. Modification ============ Add LookupIn and MutateIn Macros and tests to verify functionaly. Results ======= All tests pass. Change-Id: Ie7e4506f120f88661d169cabd8174d0c30608bdc Reviewed-on: https://review.couchbase.org/c/couchbase-python-client/+/205715 Reviewed-by: Dimitris Christodoulou <dimitris.christodoulou@couchbase.com> Tested-by: Build Bot <build@couchbase.com>
1 parent d558396 commit 1f7a675

File tree

3 files changed

+319
-68
lines changed

3 files changed

+319
-68
lines changed

couchbase/subdocument.py

Lines changed: 125 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16+
from __future__ import annotations
17+
1618
from enum import IntEnum
1719
from typing import (TYPE_CHECKING,
1820
Any,
1921
Dict,
2022
Iterable,
2123
List,
22-
Optional)
24+
Optional,
25+
Union)
2326

2427
from couchbase.exceptions import (CouchbaseException,
2528
DeltaInvalidException,
@@ -138,6 +141,62 @@ class StoreSemantics(IntEnum):
138141
INSERT = 2
139142

140143

144+
class LookupInMacro():
145+
146+
@staticmethod
147+
def document() -> str:
148+
return '$document'
149+
150+
@staticmethod
151+
def expiry_time() -> str:
152+
return '$document.exptime'
153+
154+
@staticmethod
155+
def cas() -> str:
156+
return '$document.CAS'
157+
158+
@staticmethod
159+
def seq_no() -> str:
160+
return '$document.seqno'
161+
162+
@staticmethod
163+
def last_modified() -> str:
164+
return '$document.last_modified'
165+
166+
@staticmethod
167+
def is_deleted() -> str:
168+
return '$document.deleted'
169+
170+
@staticmethod
171+
def value_size_bytes() -> str:
172+
return '$document.value_bytes'
173+
174+
@staticmethod
175+
def rev_id() -> str:
176+
return '$document.revid'
177+
178+
179+
class MutationMacro():
180+
def __init__(self, value: str):
181+
self._value = value
182+
183+
@property
184+
def value(self) -> str:
185+
return self._value
186+
187+
@classmethod
188+
def cas(cls) -> MutationMacro:
189+
return cls("${Mutation.CAS}")
190+
191+
@classmethod
192+
def seq_no(cls) -> MutationMacro:
193+
return cls("${Mutation.seqno}")
194+
195+
@classmethod
196+
def value_crc32c(cls) -> MutationMacro:
197+
return cls("${Mutation.value_crc32c}")
198+
199+
141200
class Spec(tuple):
142201
"""Represents a sub-operation to perform."""
143202

@@ -230,10 +289,9 @@ def parse_subdocument_status(status, path, key): # noqa: C901
230289
raise CouchbaseException(f"Unknown status. Status={status}, path={path}, key={key}")
231290

232291

233-
def exists(
234-
path, # type: str
235-
xattr=False # type: Optional[bool]
236-
) -> Spec:
292+
def exists(path, # type: str
293+
xattr=False # type: Optional[bool]
294+
) -> Spec:
237295
"""Creates a :class:`.Spec` that returns whether a specific field exists in the document.
238296
239297
Args:
@@ -245,7 +303,6 @@ def exists(
245303
:class:`.Spec`: An instance of :class:`.Spec`.
246304
247305
"""
248-
249306
return Spec(SubDocOp.EXISTS, path, xattr)
250307

251308

@@ -284,7 +341,7 @@ def count(path, # type: str
284341

285342

286343
def insert(path, # type: str
287-
value, # type: JSONType
344+
value, # type: Union[JSONType, MutationMacro]
288345
create_parents=False, # type: Optional[bool]
289346
xattr=False, # type: Optional[bool]
290347
**kwargs # type: Dict[str, Any]
@@ -294,7 +351,7 @@ def insert(path, # type: str
294351
295352
Args:
296353
path (str): The path to the field.
297-
value (JSONType): The value to insert.
354+
value (Union[JSONType, MutationMacro]): The value to insert.
298355
create_parents (bool, optional): Whether or not the path to the field should be created
299356
if it does not already exist.
300357
xattr (bool, optional): Whether this operation should reference the document body or the
@@ -304,19 +361,16 @@ def insert(path, # type: str
304361
:class:`.Spec`: An instance of :class:`.Spec`.
305362
306363
"""
307-
return Spec(
308-
SubDocOp.DICT_ADD,
309-
path,
310-
create_parents,
311-
xattr,
312-
kwargs.get(
313-
"expand_macros",
314-
False),
315-
value)
364+
expand_macros = kwargs.get('expand_macros', False)
365+
if isinstance(value, MutationMacro):
366+
value = value.value
367+
xattr = True
368+
expand_macros = True
369+
return Spec(SubDocOp.DICT_ADD, path, create_parents, xattr, expand_macros, value)
316370

317371

318372
def upsert(path, # type: str
319-
value, # type: JSONType
373+
value, # type: Union[JSONType, MutationMacro]
320374
create_parents=False, # type: Optional[bool]
321375
xattr=False # type: Optional[bool]
322376
) -> Spec:
@@ -325,7 +379,7 @@ def upsert(path, # type: str
325379
326380
Args:
327381
path (str): The path to the field.
328-
value (JSONType): The value to upsert.
382+
value (Union[JSONType, MutationMacro]): The value to upsert.
329383
create_parents (bool, optional): Whether or not the path to the field should be created
330384
if it does not already exist.
331385
xattr (bool, optional): Whether this operation should reference the document body or the
@@ -335,35 +389,39 @@ def upsert(path, # type: str
335389
:class:`.Spec`: An instance of :class:`.Spec`.
336390
337391
"""
338-
return Spec(
339-
SubDocOp.DICT_UPSERT,
340-
path,
341-
create_parents,
342-
xattr,
343-
False,
344-
value)
392+
expand_macros = False
393+
if isinstance(value, MutationMacro):
394+
value = value.value
395+
xattr = True
396+
expand_macros = True
397+
return Spec(SubDocOp.DICT_UPSERT, path, create_parents, xattr, expand_macros, value)
345398

346399

347400
def replace(path, # type: str
348-
value, # type: JSONType
401+
value, # type: Union[JSONType, MutationMacro]
349402
xattr=False, # type: Optional[bool]
350403
) -> Spec:
351404
"""Creates a :class:`.Spec` for replacing a field into the document. Failing if the field already
352405
exists at the specified path.
353406
354407
Args:
355408
path (str): The path to the field.
356-
value (JSONType): The value to write.
409+
value (Union[JSONType, MutationMacro]): The value to write.
357410
xattr (bool, optional): Whether this operation should reference the document body or the
358411
extended attributes data for the document.
359412
360413
Returns:
361414
:class:`.Spec`: An instance of :class:`.Spec`.
362415
363416
"""
417+
expand_macros = False
418+
if isinstance(value, MutationMacro):
419+
value = value.value
420+
xattr = True
421+
expand_macros = True
364422
if not path:
365-
return Spec(SubDocOp.SET_DOC, '', False, xattr, False, value)
366-
return Spec(SubDocOp.REPLACE, path, False, xattr, False, value)
423+
return Spec(SubDocOp.SET_DOC, '', False, xattr, expand_macros, value)
424+
return Spec(SubDocOp.REPLACE, path, False, xattr, expand_macros, value)
367425

368426

369427
def remove(path, # type: str
@@ -404,14 +462,12 @@ def array_append(path, # type: str
404462
:class:`.Spec`: An instance of :class:`.Spec`.
405463
406464
"""
407-
return Spec(
408-
SubDocOp.ARRAY_PUSH_LAST,
409-
path,
410-
create_parents,
411-
xattr,
412-
False,
413-
ArrayValues(
414-
*values))
465+
expand_macros = False
466+
if any(map(lambda m: isinstance(m, MutationMacro), values)):
467+
values = [v.value if isinstance(v, MutationMacro) else v for v in values]
468+
xattr = True
469+
expand_macros = True
470+
return Spec(SubDocOp.ARRAY_PUSH_LAST, path, create_parents, xattr, expand_macros, ArrayValues(*values))
415471

416472

417473
def array_prepend(path, # type: str
@@ -433,15 +489,12 @@ def array_prepend(path, # type: str
433489
:class:`.Spec`: An instance of :class:`.Spec`.
434490
435491
"""
436-
437-
return Spec(
438-
SubDocOp.ARRAY_PUSH_FIRST,
439-
path,
440-
create_parents,
441-
xattr,
442-
False,
443-
ArrayValues(
444-
*values))
492+
expand_macros = False
493+
if any(map(lambda m: isinstance(m, MutationMacro), values)):
494+
values = [v.value if isinstance(v, MutationMacro) else v for v in values]
495+
xattr = True
496+
expand_macros = True
497+
return Spec(SubDocOp.ARRAY_PUSH_FIRST, path, create_parents, xattr, expand_macros, ArrayValues(*values))
445498

446499

447500
def array_insert(path, # type: str
@@ -464,14 +517,12 @@ def array_insert(path, # type: str
464517
:class:`.Spec`: An instance of :class:`.Spec`.
465518
466519
"""
467-
return Spec(
468-
SubDocOp.ARRAY_INSERT,
469-
path,
470-
create_parents,
471-
xattr,
472-
False,
473-
ArrayValues(
474-
*values))
520+
expand_macros = False
521+
if any(map(lambda m: isinstance(m, MutationMacro), values)):
522+
values = [v.value if isinstance(v, MutationMacro) else v for v in values]
523+
xattr = True
524+
expand_macros = True
525+
return Spec(SubDocOp.ARRAY_INSERT, path, create_parents, xattr, expand_macros, ArrayValues(*values))
475526

476527

477528
def array_addunique(path, # type: str
@@ -494,14 +545,12 @@ def array_addunique(path, # type: str
494545
:class:`.Spec`: An instance of :class:`.Spec`.
495546
496547
"""
497-
return Spec(
498-
SubDocOp.ARRAY_ADD_UNIQUE,
499-
path,
500-
create_parents,
501-
xattr,
502-
False,
503-
ArrayValues(
504-
*values))
548+
expand_macros = False
549+
if any(map(lambda m: isinstance(m, MutationMacro), values)):
550+
values = [v.value if isinstance(v, MutationMacro) else v for v in values]
551+
xattr = True
552+
expand_macros = True
553+
return Spec(SubDocOp.ARRAY_ADD_UNIQUE, path, create_parents, xattr, expand_macros, ArrayValues(*values))
505554

506555

507556
def counter(path, # type: str
@@ -594,8 +643,7 @@ def decrement(path, # type: str
594643
raise InvalidArgumentException(
595644
"Delta must be integer greater than or equal to 0")
596645

597-
return Spec(SubDocOp.COUNTER, path, create_parents,
598-
xattr, False, -1 * delta)
646+
return Spec(SubDocOp.COUNTER, path, create_parents, xattr, False, -1 * delta)
599647

600648

601649
def get_full() -> Spec:
@@ -613,7 +661,18 @@ def with_expiry() -> Spec:
613661
614662
:return: Spec
615663
"""
616-
return Spec(SubDocOp.GET, '$document.exptime', True)
664+
return Spec(SubDocOp.GET, LookupInMacro.expiry_time(), True)
665+
666+
667+
def convert_macro_cas_to_cas(cas # type: str
668+
) -> int:
669+
"""
670+
Utility method to help encode CAS coming from MutationMacro.cas() stored in the xattr.
671+
Due to a server bug, CAS is encoded backwards, but b/c of legacy users we cannot make a change.
672+
"""
673+
reversed_bytes = bytearray.fromhex(cas[2:] if cas.startswith('0x') else cas)
674+
reversed_bytes.reverse()
675+
return int(reversed_bytes.hex(), base=16)
617676

618677

619678
"""

0 commit comments

Comments
 (0)