Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions python/aerospike_native/_native.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,14 @@ class BitOperation:
r"""
Bit operations. Create bit operations used by the client's `operate()` method.
"""
@property
def is_idempotent(self) -> builtins.bool:
r"""
``True`` when re-sending this operation after an in-doubt attempt
leaves the record as one application would. Reads are idempotent;
every modify operation of this class is not. See
:attr:`Operation.is_idempotent` for the rule.
"""
@staticmethod
def resize(bin_name: builtins.str, byte_size: builtins.int, resize_flags: typing.Optional[_native.BitwiseResizeFlags], policy: _native.BitPolicy) -> _native.BitOperation:
r"""
Expand Down Expand Up @@ -834,6 +842,14 @@ class CdtOperation:

Requires Aerospike Server version >= 8.1.1.
"""
@property
def is_idempotent(self) -> builtins.bool:
r"""
``True`` when re-sending this operation after an in-doubt attempt
leaves the record as one application would. Reads are idempotent;
every modify operation of this class is not. See
:attr:`Operation.is_idempotent` for the rule.
"""
@staticmethod
def select_by_path(bin_name: builtins.str, flag: builtins.int, ctx: typing.Sequence[_native.CTX]) -> _native.CdtOperation:
r"""
Expand Down Expand Up @@ -2324,6 +2340,14 @@ class ExpOperation:
Expression operations allow evaluating expressions on the server and optionally storing
the result in a bin.
"""
@property
def is_idempotent(self) -> builtins.bool:
r"""
``True`` when re-sending this operation after an in-doubt attempt
leaves the record as one application would. Reads are idempotent;
every modify operation of this class is not. See
:attr:`Operation.is_idempotent` for the rule.
"""
@staticmethod
def read(name: builtins.str, exp: _native.FilterExpression, flags: typing.Optional[typing.Any] = None) -> _native.ExpOperation:
r"""
Expand Down Expand Up @@ -4166,6 +4190,14 @@ class HllOperation:
r"""
HLL (HyperLogLog) operations. Create HLL operations used by the client's `operate()` method.
"""
@property
def is_idempotent(self) -> builtins.bool:
r"""
``True`` when re-sending this operation after an in-doubt attempt
leaves the record as one application would. Reads are idempotent;
every modify operation of this class is not. See
:attr:`Operation.is_idempotent` for the rule.
"""
@staticmethod
def init(bin_name: builtins.str, index_bit_count: builtins.int, min_hash_bit_count: builtins.int = -1, flags: typing.Optional[typing.Any] = None) -> _native.HllOperation:
r"""
Expand Down Expand Up @@ -4412,6 +4444,14 @@ class ListOperation:
r"""
List bin operations. Create list operations used by the client's `operate()` method.
"""
@property
def is_idempotent(self) -> builtins.bool:
r"""
``True`` when re-sending this operation after an in-doubt attempt
leaves the record as one application would. Reads are idempotent;
every modify operation of this class is not. See
:attr:`Operation.is_idempotent` for the rule.
"""
@staticmethod
def get(bin_name: builtins.str, index: builtins.int) -> _native.ListOperation:
r"""
Expand Down Expand Up @@ -4765,6 +4805,14 @@ class MapOperation:
r"""
Map bin operations. Create map operations used by the client's `operate()` method.
"""
@property
def is_idempotent(self) -> builtins.bool:
r"""
``True`` when re-sending this operation after an in-doubt attempt
leaves the record as one application would. Reads are idempotent;
every modify operation of this class is not. See
:attr:`Operation.is_idempotent` for the rule.
"""
@staticmethod
def size(bin_name: builtins.str) -> _native.MapOperation:
r"""
Expand Down Expand Up @@ -5281,6 +5329,19 @@ class Operation:
Python wrapper for Operation enum.
"""
@property
def is_idempotent(self) -> builtins.bool:
r"""
``True`` when re-sending this operation after an in-doubt attempt
leaves the record as one application would, so a retry is safe.

Reads, ``put``, ``delete``, and ``touch`` are idempotent. ``add``,
``append``, ``prepend``, and every list, map, bit, HLL, string, and
expression modify operation are not, as a family: a few members are
idempotent in effect (list ``clear``, HLL ``add``), but retry
classification keys on the family. Every operation class carries
this property.
"""
@property
def bin_name(self) -> typing.Optional[builtins.str]:
r"""
Name of the bin this operation targets, or ``None`` when it targets
Expand Down Expand Up @@ -6203,6 +6264,14 @@ class StringOperation:
trailing ``ctx`` argument selecting a string element nested inside a
list/map bin. With ``ctx=None`` the op targets the bin itself.
"""
@property
def is_idempotent(self) -> builtins.bool:
r"""
``True`` when re-sending this operation after an in-doubt attempt
leaves the record as one application would. Reads and ``to_string``
are idempotent; every modify operation is not. See
:attr:`Operation.is_idempotent` for the rule.
"""
@staticmethod
def strlen(bin: builtins.str, *, ctx: typing.Optional[typing.Sequence[_native.CTX]] = None) -> _native.StringOperation:
r"""
Expand Down
65 changes: 65 additions & 0 deletions python/tests/unit/operation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@
MapOperation,
BitOperation,
HllOperation,
CdtOperation,
ExpOperation,
StringOperation,
FilterExpression as fe,
ListPolicy,
ListOrderType,
ListReturnType,
ListSortFlags,
ModifyFlags,
SelectFlags,
MapPolicy,
MapOrder,
MapReturnType,
Expand Down Expand Up @@ -412,3 +418,62 @@ def test_get_intersect_count(self):
def test_get_similarity(self):
op = HllOperation.get_similarity("hllbin", [])
assert isinstance(op, HllOperation)


_IDEMPOTENT = [
pytest.param(lambda: Operation.get(), id="get"),
pytest.param(lambda: Operation.get_bin("v"), id="get_bin"),
pytest.param(lambda: Operation.get_header(), id="get_header"),
pytest.param(lambda: Operation.put("v", 1), id="put"),
pytest.param(lambda: Operation.delete(), id="delete"),
pytest.param(lambda: Operation.touch(), id="touch"),
pytest.param(lambda: ListOperation.size("l"), id="list_size"),
pytest.param(lambda: ListOperation.get_by_index("l", 0, ListReturnType.VALUE), id="list_get_by_index"),
pytest.param(lambda: MapOperation.size("m"), id="map_size"),
pytest.param(lambda: MapOperation.get_by_key("m", "k", MapReturnType.VALUE), id="map_get_by_key"),
pytest.param(lambda: BitOperation.get("b", 0, 8), id="bit_get"),
pytest.param(lambda: BitOperation.count("b", 0, 8), id="bit_count"),
pytest.param(lambda: HllOperation.get_count("h"), id="hll_get_count"),
pytest.param(lambda: ExpOperation.read("r", fe.int_bin("v")), id="exp_read"),
pytest.param(
lambda: CdtOperation.select_by_path("c", SelectFlags.VALUE, [CTX.map_key("k")]),
id="cdt_select_by_path",
),
pytest.param(lambda: StringOperation.strlen("s"), id="string_strlen"),
pytest.param(lambda: StringOperation.to_string("s"), id="string_to_string"),
]

_NOT_IDEMPOTENT = [
pytest.param(lambda: Operation.add("v", 1), id="add"),
pytest.param(lambda: Operation.append("v", "x"), id="append"),
pytest.param(lambda: Operation.prepend("v", "x"), id="prepend"),
pytest.param(lambda: ListOperation.append("l", 1, ListPolicy()), id="list_append"),
pytest.param(lambda: ListOperation.clear("l"), id="list_clear"),
pytest.param(lambda: ListOperation.remove_by_value("l", 1, ListReturnType.NONE), id="list_remove_by_value"),
pytest.param(lambda: MapOperation.put("m", "k", 1, MapPolicy()), id="map_put"),
pytest.param(lambda: MapOperation.clear("m"), id="map_clear"),
pytest.param(lambda: MapOperation.increment_value("m", "k", 1, MapPolicy()), id="map_increment"),
pytest.param(lambda: BitOperation.set("b", 0, 8, b"\xff", BitPolicy()), id="bit_set"),
pytest.param(lambda: HllOperation.add("h", [1, 2]), id="hll_add"),
pytest.param(lambda: HllOperation.init("h", 8), id="hll_init"),
pytest.param(lambda: ExpOperation.write("v", fe.int_val(1)), id="exp_write"),
pytest.param(
lambda: CdtOperation.modify_by_path(
"c", ModifyFlags.DEFAULT, fe.int_val(1), [CTX.map_key("k")]),
id="cdt_modify_by_path",
),
pytest.param(lambda: StringOperation.append("s", "x"), id="string_append"),
pytest.param(lambda: StringOperation.upper("s"), id="string_upper"),
]


class TestIsIdempotent:
"""Reads, put, delete, and touch may be re-sent; every modify family may not."""

@pytest.mark.parametrize("make", _IDEMPOTENT)
def test_idempotent(self, make):
assert make().is_idempotent is True

@pytest.mark.parametrize("make", _NOT_IDEMPOTENT)
def test_not_idempotent(self, make):
assert make().is_idempotent is False
Loading
Loading