13
13
# See the License for the specific language governing permissions and
14
14
# limitations under the License.
15
15
16
+ from __future__ import annotations
17
+
16
18
from enum import IntEnum
17
19
from typing import (TYPE_CHECKING ,
18
20
Any ,
19
21
Dict ,
20
22
Iterable ,
21
23
List ,
22
- Optional )
24
+ Optional ,
25
+ Union )
23
26
24
27
from couchbase .exceptions import (CouchbaseException ,
25
28
DeltaInvalidException ,
@@ -138,6 +141,62 @@ class StoreSemantics(IntEnum):
138
141
INSERT = 2
139
142
140
143
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
+
141
200
class Spec (tuple ):
142
201
"""Represents a sub-operation to perform."""
143
202
@@ -230,10 +289,9 @@ def parse_subdocument_status(status, path, key): # noqa: C901
230
289
raise CouchbaseException (f"Unknown status. Status={ status } , path={ path } , key={ key } " )
231
290
232
291
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 :
237
295
"""Creates a :class:`.Spec` that returns whether a specific field exists in the document.
238
296
239
297
Args:
@@ -245,7 +303,6 @@ def exists(
245
303
:class:`.Spec`: An instance of :class:`.Spec`.
246
304
247
305
"""
248
-
249
306
return Spec (SubDocOp .EXISTS , path , xattr )
250
307
251
308
@@ -284,7 +341,7 @@ def count(path, # type: str
284
341
285
342
286
343
def insert (path , # type: str
287
- value , # type: JSONType
344
+ value , # type: Union[ JSONType, MutationMacro]
288
345
create_parents = False , # type: Optional[bool]
289
346
xattr = False , # type: Optional[bool]
290
347
** kwargs # type: Dict[str, Any]
@@ -294,7 +351,7 @@ def insert(path, # type: str
294
351
295
352
Args:
296
353
path (str): The path to the field.
297
- value (JSONType): The value to insert.
354
+ value (Union[ JSONType, MutationMacro] ): The value to insert.
298
355
create_parents (bool, optional): Whether or not the path to the field should be created
299
356
if it does not already exist.
300
357
xattr (bool, optional): Whether this operation should reference the document body or the
@@ -304,19 +361,16 @@ def insert(path, # type: str
304
361
:class:`.Spec`: An instance of :class:`.Spec`.
305
362
306
363
"""
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 )
316
370
317
371
318
372
def upsert (path , # type: str
319
- value , # type: JSONType
373
+ value , # type: Union[ JSONType, MutationMacro]
320
374
create_parents = False , # type: Optional[bool]
321
375
xattr = False # type: Optional[bool]
322
376
) -> Spec :
@@ -325,7 +379,7 @@ def upsert(path, # type: str
325
379
326
380
Args:
327
381
path (str): The path to the field.
328
- value (JSONType): The value to upsert.
382
+ value (Union[ JSONType, MutationMacro] ): The value to upsert.
329
383
create_parents (bool, optional): Whether or not the path to the field should be created
330
384
if it does not already exist.
331
385
xattr (bool, optional): Whether this operation should reference the document body or the
@@ -335,35 +389,39 @@ def upsert(path, # type: str
335
389
:class:`.Spec`: An instance of :class:`.Spec`.
336
390
337
391
"""
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 )
345
398
346
399
347
400
def replace (path , # type: str
348
- value , # type: JSONType
401
+ value , # type: Union[ JSONType, MutationMacro]
349
402
xattr = False , # type: Optional[bool]
350
403
) -> Spec :
351
404
"""Creates a :class:`.Spec` for replacing a field into the document. Failing if the field already
352
405
exists at the specified path.
353
406
354
407
Args:
355
408
path (str): The path to the field.
356
- value (JSONType): The value to write.
409
+ value (Union[ JSONType, MutationMacro] ): The value to write.
357
410
xattr (bool, optional): Whether this operation should reference the document body or the
358
411
extended attributes data for the document.
359
412
360
413
Returns:
361
414
:class:`.Spec`: An instance of :class:`.Spec`.
362
415
363
416
"""
417
+ expand_macros = False
418
+ if isinstance (value , MutationMacro ):
419
+ value = value .value
420
+ xattr = True
421
+ expand_macros = True
364
422
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 )
367
425
368
426
369
427
def remove (path , # type: str
@@ -404,14 +462,12 @@ def array_append(path, # type: str
404
462
:class:`.Spec`: An instance of :class:`.Spec`.
405
463
406
464
"""
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 ))
415
471
416
472
417
473
def array_prepend (path , # type: str
@@ -433,15 +489,12 @@ def array_prepend(path, # type: str
433
489
:class:`.Spec`: An instance of :class:`.Spec`.
434
490
435
491
"""
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 ))
445
498
446
499
447
500
def array_insert (path , # type: str
@@ -464,14 +517,12 @@ def array_insert(path, # type: str
464
517
:class:`.Spec`: An instance of :class:`.Spec`.
465
518
466
519
"""
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 ))
475
526
476
527
477
528
def array_addunique (path , # type: str
@@ -494,14 +545,12 @@ def array_addunique(path, # type: str
494
545
:class:`.Spec`: An instance of :class:`.Spec`.
495
546
496
547
"""
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 ))
505
554
506
555
507
556
def counter (path , # type: str
@@ -594,8 +643,7 @@ def decrement(path, # type: str
594
643
raise InvalidArgumentException (
595
644
"Delta must be integer greater than or equal to 0" )
596
645
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 )
599
647
600
648
601
649
def get_full () -> Spec :
@@ -613,7 +661,18 @@ def with_expiry() -> Spec:
613
661
614
662
:return: Spec
615
663
"""
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 )
617
676
618
677
619
678
"""
0 commit comments