diff --git a/BUILD.md b/BUILD.md index 9e5654474..f19da166a 100644 --- a/BUILD.md +++ b/BUILD.md @@ -33,12 +33,12 @@ sudo yum install python-setuptools To get `python26-devel` on older distros such as CentOS 5, see [Stack Overflow](http://stackoverflow.com/a/11684053/582436). -### Debian 6+ and Ubuntu 12.04+ +### Debian 6+ and Ubuntu 14.04+ The following are dependencies for: - Debian 6 or newer - - Ubuntu 12.04 or newer + - Ubuntu 14.04 or newer - Related distributions which use the `apt` package manager ``` diff --git a/README.rst b/README.rst index 44cc318bf..8a9e4b06b 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ Debian 6+ and Ubuntu 14.04+ The following are dependencies for: - Debian 6 or newer -- Ubuntu 12.04 or newer +- Ubuntu 14.04 or newer - Related distributions which use the ``apt`` package manager :: diff --git a/VERSION b/VERSION index 15a279981..18091983f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.3.0 +3.4.0 diff --git a/aerospike_helpers/__init__.py b/aerospike_helpers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/aerospike_helpers/operations/__init__.py b/aerospike_helpers/operations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/aerospike_helpers/operations/list_operations.py b/aerospike_helpers/operations/list_operations.py new file mode 100644 index 000000000..7bd4e1a91 --- /dev/null +++ b/aerospike_helpers/operations/list_operations.py @@ -0,0 +1,830 @@ +""" +This module provides helper functions to produce dictionaries to be used with the +`client.operate` and `client.operate_ordered` methods of the aerospike module. +""" +import aerospike + + +OP_KEY = "op" +BIN_KEY = "bin" +VALUE_KEY = "val" +INDEX_KEY = "index" +LIST_POLICY_KEY = "list_policy" +INVERTED_KEY = "inverted" +RETURN_TYPE_KEY = "return_type" +COUNT_KEY = "count" +RANK_KEY = "rank" +VALUE_BEGIN_KEY = "value_begin" +VALUE_END_KEY = "value_end" +VALUE_LIST_KEY = "value_list" +LIST_ORDER_KEY = "list_order" +SORT_FLAGS_KEY = "sort_flags" + + +def list_append(bin_name, value, policy=None): + """Creates a list append operation to be used with operate, or operate_ordered + + The list append operation instructs the aerospike server to append an item to the + end of a list bin. + + Args: + bin_name (str): The name of the bin to be operated on. + value: The value to be appended to the end of the list. + policy (dict): An optional dictionary of list write options. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_APPEND, + BIN_KEY: bin_name, + VALUE_KEY: value + } + + if policy: + op_dict[LIST_POLICY_KEY] = policy + + return op_dict + + +def list_append_items(bin_name, values, policy=None): + """Creates a list append items operation to be used with operate, or operate_ordered + + The list append items operation instructs the aerospike server to append multiple items to the + end of a list bin. + + Args: + bin_name (str): The name of the bin to be operated on. + values: (list): A sequence of items to be appended to the end of the list. + policy (dict): An optional dictionary of list write options. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_APPEND_ITEMS, + BIN_KEY: bin_name, + VALUE_KEY: values + } + + if policy: + op_dict[LIST_POLICY_KEY] = policy + + return op_dict + +def list_insert(bin_name, index, value, policy=None): + """Creates a list insert operation to be used with operate, or operate_ordered + + The list insert operation inserts an item at index: `index` into the list contained + in the specified bin. + + Args: + bin_name (str): The name of the bin to be operated on. + index (int): The index at which to insert an item. The value may be positive to use + zero based indexing or negative to index from the end of the list. + value: The value to be inserted into the list. + policy (dict): An optional dictionary of list write options. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_INSERT, + BIN_KEY: bin_name, + INDEX_KEY: index, + VALUE_KEY: value + } + + if policy: + op_dict[LIST_POLICY_KEY] = policy + + return op_dict + +def list_insert_items(bin_name, index, values, policy=None): + """Creates a list insert items operation to be used with operate, or operate_ordered + + The list insert items operation inserts items at index: `index` into the list contained + in the specified bin. + + Args: + bin_name (str): The name of the bin to be operated on. + index (int): The index at which to insert the items. The value may be positive to use + zero based indexing or negative to index from the end of the list. + values (list): The values to be inserted into the list. + policy (dict): An optional dictionary of list write options. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_INSERT_ITEMS, + BIN_KEY: bin_name, + INDEX_KEY: index, + VALUE_KEY: values + } + + if policy: + op_dict[LIST_POLICY_KEY] = policy + + return op_dict + +def list_increment(bin_name, index, value, policy=None): + """Creates a list increment operation to be used with operate, or operate_ordered + + The list insert operation inserts an item at index: `index` into the list contained + in the specified bin. + + Args: + bin_name (str): The name of the bin to be operated on. + index (int): The index of the list item to increment. + value (int, float) : The value to be added to the list item. + policy (dict): An optional dictionary of list write options. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_INCREMENT, + BIN_KEY: bin_name, + INDEX_KEY: index, + VALUE_KEY: value + } + + if policy: + op_dict[LIST_POLICY_KEY] = policy + + return op_dict + + +def list_pop(bin_name, index): + """Creates a list pop operation to be used with operate, or operate_ordered + + The list insert operation removes and returns an item index: `index` from list contained + in the specified bin. + + Args: + bin_name (str): The name of the bin to be operated on. + index (int): The index of the item to be removed. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_POP, + BIN_KEY: bin_name, + INDEX_KEY: index + } + + +def list_pop_range(bin_name, index, count): + """Creates a list pop range operation to be used with operate, or operate_ordered + + The list insert range operation removes and returns `count` items + starting from index: `index` from the list contained in the specified bin. + + Args: + bin_name (str): The name of the bin to be operated on. + index (int): The index of the first item to be removed. + count (int): A positive number indicating how many items, including the first, + to be removed and returned + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_POP_RANGE, + BIN_KEY: bin_name, + INDEX_KEY: index, + VALUE_KEY: count + } + + +def list_remove(bin_name, index): + """Create list remove operation. + + The list remove operation removes an item located at `index` in the list specified by `bin_name` + + Args: + bin_name (str): The name of the bin containing the item to be removed. + index (int): The index at which to remove the item. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_REMOVE, + BIN_KEY: bin_name, + INDEX_KEY: index + } + + +def list_remove_range(bin_name, index, count): + """Create list remove range operation. + + The list remove range operation removes `count` items starting at `index` + in the list specified by `bin_name` + + Args: + bin_name (str): The name of the bin containing the items to be removed. + index (int): The index of the first item to remove. + count (int): A positive number representing the number of items to be removed. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_REMOVE_RANGE, + BIN_KEY: bin_name, + INDEX_KEY: index, + VALUE_KEY: count + } + + +def list_clear(bin_name): + """Create list clear operation. + + The list clear operation removes all items from the list specified by `bin_name` + + Args: + bin_name (str): The name of the bin containing the list to be cleared + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_CLEAR, + BIN_KEY: bin_name + } + + +def list_set(bin_name, index, value, policy=None): + """Create a list set operation. + + The list set operations sets the value of the item at `index` to `value` + + Args: + bin_name (str): The name of the bin containing the list to be operated on. + index (int): The index of the item to be set. + value: The value to be assigned to the list item. + policy (dict): An optional dictionary of list write options. + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_SET, + BIN_KEY: bin_name, + INDEX_KEY: index, + VALUE_KEY: value + } + if policy: + op_dict[LIST_POLICY_KEY] = policy + + return op_dict + + +def list_get(bin_name, index): + """Create a list get operation. + + The list get operation gets the value of the item at `index` and returns the value + + Args: + bin_name (str): The name of the bin containing the list to fetch items from. + index (int): The index of the item to be returned. + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_GET, + BIN_KEY: bin_name, + INDEX_KEY: index, + } + + +def list_get_range(bin_name, index, count): + """Create a list get range operation. + + The list get range operation gets `count` items starting `index` and returns the values. + + Args: + bin_name (str): The name of the bin containing the list to fetch items from. + index (int): The index of the item to be returned. + count (int): A positive number of items to be returned. + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_GET_RANGE, + BIN_KEY: bin_name, + INDEX_KEY: index, + VALUE_KEY: count + } + + +def list_trim(bin_name, index, count): + """Create a list trim operation. + + Server removes items in list bin that do not fall into range specified by index and count range. + + Args: + bin_name (str): The name of the bin containing the list to be trimmed. + index (int): The index of the items to be kept. + count (int): A positive number of items to be kept. + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_TRIM, + BIN_KEY: bin_name, + INDEX_KEY: index, + VALUE_KEY: count + } + + +def list_size(bin_name): + """Create a list size operation. + + Server returns the size of the list in the specified bin. + + Args: + bin_name (str): The name of the bin containing the list. + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_SIZE, + BIN_KEY: bin_name + } + + +# Post 3.4.0 Operations. Require Server >= 3.16.0.1 + +def list_get_by_index(bin_name, index, return_type): + """Create a list get index operation. + + The list get operation gets the item at `index` and returns a value + specified by `return_type` + + Args: + bin_name (str): The name of the bin containing the list to fetch items from. + index (int): The index of the item to be returned. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_GET_BY_INDEX, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + INDEX_KEY: index + } + + +def list_get_by_index_range(bin_name, index, return_type, count=None, inverted=False): + """Create a list get index range operation. + + The list get by index range operation gets `count` items starting at `index` and returns a value + specified by `return_type` + + Args: + bin_name (str): The name of the bin containing the list to fetch items from. + index (int): The index of the first item to be returned. + count (int): The number of list items to be selected. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values. + inverted (bool): Optional bool specifying whether to invert the return type. + If set to true, all items outside of the specified range will be returned. + Default: `False` + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_GET_BY_INDEX_RANGE, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + INDEX_KEY: index, + INVERTED_KEY: inverted + } + + if count is not None: + op_dict[COUNT_KEY] = count + + return op_dict + + +def list_get_by_rank(bin_name, rank, return_type): + """Create a list get by rank operation. + + Server selects list item identified by `rank` and returns selected data + specified by `return_type`. + + Args: + bin_name (str): The name of the bin containing the list to fetch a value from. + rank (int): The rank of the item to be fetched. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_GET_BY_RANK, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + RANK_KEY: rank + } + + +def list_get_by_rank_range(bin_name, rank, return_type, count=None, inverted=False): + """Create a list get by rank range operation. + + Server selects `count` items starting at the specified `rank` and returns selected data + specified by `return_type`. + + Args: + bin_name (str): The name of the bin containing the list to fetch items from. + rank (int): The rank of the first items to be returned. + count (int): A positive number indicating number of items to be returned. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + inverted (bool): Optional bool specifying whether to invert the return type. + If set to true, all items outside of the specified rank range will be returned. + Default: `False` + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_GET_BY_RANK_RANGE, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + RANK_KEY: rank, + INVERTED_KEY: inverted + } + + if count is not None: + op_dict[COUNT_KEY] = count + + return op_dict + + +def list_get_by_value(bin_name, value, return_type, inverted=False): + """Create a list get by value operation. + + Server selects list items with a value equal to `value` and returns selected data specified by + `return_type`. + + Args: + bin_name (str): The name of the bin containing the list to fetch items from. + value: The server returns all items matching this value + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + inverted (bool): Optional bool specifying whether to invert the return type. + If set to true, all items not equal to `value` will be selected. Default: `False` + Returns: + A dictionary usable in operate or operate_ordered.The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_GET_BY_VALUE, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + VALUE_KEY: value, + INVERTED_KEY: inverted + } + + return op_dict + + +def list_get_by_value_list(bin_name, value_list, return_type, inverted=False): + """Create a list get by value list operation. + + Server selects list items with a value contained in `value_list` and returns selected data + specified by `return_type`. + + Args: + bin_name (str): The name of the bin containing the list to fetch items from. + value_list (list): Return items from the list matching an item in this list. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + inverted (bool): Optional bool specifying whether to invert the return type. + If set to `True`, all items not matching an entry in `value_list` will be selected. + Default: `False` + Returns: + A dictionary usable in operate or operate_ordered.The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_GET_BY_VALUE_LIST, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + VALUE_LIST_KEY: value_list, + INVERTED_KEY: inverted + } + + return op_dict + + +def list_get_by_value_range(bin_name, return_type, value_begin, value_end, inverted=False): + """Create a list get by value list operation. + + Server selects list items with a value greater than or equal to `value_begin` + and less than `value_end`. Server returns selected data specified by `return_type`. + + Args: + bin_name (str): The name of the bin containing the list to fetch items from. + value_begin: The start of the value range. + value_end: The end of the value range. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + inverted (bool): Optional bool specifying whether to invert the return type. + If set to `True`, all items not included in the specified range will be returned. + Default: `False` + Returns: + A dictionary usable in operate or operate_ordered.The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_GET_BY_VALUE_RANGE, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + if value_begin is not None: + op_dict[VALUE_BEGIN_KEY] = value_begin + + if value_end is not None: + op_dict[VALUE_END_KEY] = value_end + + return op_dict + + +def list_remove_by_index(bin_name, index, return_type): + """Create a list remove by index operation. + + The list get operation removes the value of the item at `index` and returns a value + specified by `return_type` + + Args: + bin_name (str): The name of the bin containing the list to remove an item from. + index (int): The index of the item to be removed. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_REMOVE_BY_INDEX, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + INDEX_KEY: index + } + + +def list_remove_by_index_range(bin_name, index, return_type, count=None, inverted=False): + """Create a list remove by index range operation. + + The list remove by index range operation removes `count` starting at `index` and returns a value + specified by `return_type` + + Args: + bin_name (str): The name of the bin containing the list to remove items from. + index (int): The index of the first item to be removed. + count (int): The number of items to be removed + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values. + inverted (bool): Optional bool specifying whether to invert the operation. + If set to true, all items outside of the specified range will be removed. + Default: `False` + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_REMOVE_BY_INDEX_RANGE, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + INDEX_KEY: index, + INVERTED_KEY: inverted + } + + if count is not None: + op_dict[COUNT_KEY] = count + + return op_dict + + +def list_remove_by_rank(bin_name, rank, return_type): + """Create a list remove by rank operation. + + Server removes a list item identified by `rank` and returns selected data + specified by `return_type`. + + Args: + bin_name (str): The name of the bin containing the list to fetch a value from. + rank (int): The rank of the item to be removed. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_REMOVE_BY_RANK, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + RANK_KEY: rank + } + + +def list_remove_by_rank_range(bin_name, rank, return_type, count=None, inverted=False): + """Create a list remove by rank range operation. + + Server removes `count` items starting at the specified `rank` and returns selected data + specified by `return_type`. + + Args: + bin_name (str): The name of the bin containing the list to fetch items from. + rank (int): The rank of the first item to removed. + count (int): A positive number indicating number of items to be removed. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + inverted (bool): Optional bool specifying whether to invert the operation. + If set to true, all items outside of the specified rank range will be removed. + Default: `False` + + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_REMOVE_BY_RANK_RANGE, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + RANK_KEY: rank, + INVERTED_KEY: inverted + } + + if count is not None: + op_dict[COUNT_KEY] = count + + return op_dict + + +def list_remove_by_value(bin_name, value, return_type, inverted=False): + """Create a list remove by value operation. + + Server removes list items with a value equal to `value` and returns selected data specified by + `return_type`. + + Args: + bin_name (str): The name of the bin containing the list to remove items from. + value: The server removes all list items matching this value. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + inverted (bool): Optional bool specifying whether to invert the operation. + If set to true, all items not equal to `value` will be removed. + Default: `False` + Returns: + A dictionary usable in operate or operate_ordered.The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_REMOVE_BY_VALUE, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + VALUE_KEY: value, + INVERTED_KEY: inverted + } + + return op_dict + + +def list_remove_by_value_list(bin_name, value_list, return_type, inverted=False): + """Create a list remove by value list operation. + + Server removes list items with a value matching one contained in `value_list` + and returns selected data specified by `return_type`. + + Args: + bin_name (str): The name of the bin containing the list to remove items from. + value_list (list): The server removes all list items matching one of these values. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + inverted (bool): Optional bool specifying whether to invert the operation. + If set to true, all items not equal to a value contained in + `value_list` will be removed. + Default: `False` + Returns: + A dictionary usable in operate or operate_ordered.The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_REMOVE_BY_VALUE_LIST, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + VALUE_LIST_KEY: value_list, + INVERTED_KEY: inverted + } + + return op_dict + + +def list_remove_by_value_range(bin_name, return_type, value_begin=None, + value_end=None, inverted=False): + """Create a list remove by value range operation. + + Server removes list items with a value greater than or equal to `value_begin` + and less than `value_end`. Server returns selected data specified by `return_type`. + + Args: + bin_name (str): The name of the bin containing the list to fetch items from. + value_begin: The start of the value range. + value_end: The end of the value range. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.LIST_RETURN_* values + inverted (bool): Optional bool specifying whether to invert the operation. + If set to `True`, all items not included in the specified range will be removed. + Default: `False` + Returns: + A dictionary usable in operate or operate_ordered.The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_REMOVE_BY_VALUE_RANGE, + BIN_KEY: bin_name, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + if value_begin is not None: + op_dict[VALUE_BEGIN_KEY] = value_begin + + if value_end is not None: + op_dict[VALUE_END_KEY] = value_end + + return op_dict + + +def list_set_order(bin_name, list_order): + """Create a list set order operation. + + The list_set_order operation sets an order on a specified list bin. + + Args: + bin_name (str): The name of the list bin. + list_order: The ordering to apply to the list. Should be aerospike.LIST_ORDERED or + aerospike.LIST_UNORDERED . + Returns: + A dictionary usable in operate or operate_ordered.The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_LIST_SET_ORDER, + BIN_KEY: bin_name, + LIST_ORDER_KEY: list_order + } + + +def list_sort(bin_name, sort_flags=aerospike.LIST_SORT_DEFAULT): + """Create a list sort operation + + The list sort operation will sort the specified list bin. + + Args: + bin_name (str): The name of the bin to sort. + sort_flags: Optional. A list of flags bitwise or'd together. + Available flags are currently `aerospike.LIST_SORT_DROP_DUPLICATES` + Returns: + A dictionary usable in operate or operate_ordered.The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_LIST_SORT, + BIN_KEY: bin_name, + SORT_FLAGS_KEY: sort_flags + } + + return op_dict diff --git a/aerospike_helpers/operations/map_operations.py b/aerospike_helpers/operations/map_operations.py new file mode 100644 index 000000000..77ed9b476 --- /dev/null +++ b/aerospike_helpers/operations/map_operations.py @@ -0,0 +1,694 @@ +''' +Helper functions to create map operation dictionaries arguments for +the operate and operate_ordered methods of the aerospike client. +''' +import aerospike + +OP_KEY = "op" +BIN_KEY = "bin" +POLICY_KEY = "map_policy" +VALUE_KEY = "val" +KEY_KEY = "key" +INDEX_KEY = "index" +RETURN_TYPE_KEY = "return_type" +INVERTED_KEY = "inverted" +RANGE_KEY = "range" + + +def map_set_policy(bin_name, policy): + """Creates a map_set_policy_operation to be used with operate or operate_ordered + + The operation allows a user to set the policy for the map. + + Args: + bin_name (str): The name of the bin containing the map. + policy (dict): The map policy dictionary + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_MAP_SET_POLICY, + BIN_KEY: bin_name, + POLICY_KEY: policy + } + + +def map_put(bin_name, key, value): + """Creates a map_put operation to be used with operate or operate_ordered + + The operation allows a user to set the value of an item in the map stored + on the server. + + Args: + bin_name (str): The name of the bin containing the map. + key: The key for the map. + value: The item to store in the map with the corresponding key. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_MAP_PUT, + BIN_KEY: bin_name, + KEY_KEY: key, + VALUE_KEY: value + } + + +def map_put_items(bin_name, item_dict): + """Creates a map_put_items operation to be used with operate or operate_ordered + + The operation allows a user to add or update items in the map stored on the server. + + Args: + bin_name (str): The name of the bin containing the map. + item_dict (dict): A dictionary of key value pairs to be added to the map on the server. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_MAP_PUT_ITEMS, + BIN_KEY: bin_name, + VALUE_KEY: item_dict + } + + +def map_increment(bin_name, key, amount): + """Creates a map_increment operation to be used with operate or operate_ordered + + The operation allows a user to increment the value of a value stored in the map on the server. + + Args: + bin_name (str): The name of the bin containing the map. + key: The key for the value to be incremented. + amount: The amount by which to increment the value stored in map[key] + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_MAP_INCREMENT, + BIN_KEY: bin_name, + KEY_KEY: key, + VALUE_KEY: amount + } + + +def map_decrement(bin_name, key, amount): + """Creates a map_decrement operation to be used with operate or operate_ordered + + The operation allows a user to decrement the value of a value stored in the map on the server. + + Args: + bin_name (str): The name of the bin containing the map. + key: The key for the value to be decremented. + amount: The amount by which to decrement the value stored in map[key] + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_MAP_DECREMENT, + BIN_KEY: bin_name, + KEY_KEY: key, + VALUE_KEY: amount + } + + +def map_size(bin_name): + """Creates a map_size operation to be used with operate or operate_ordered + + The operation returns the size of the map stored in the specified bin. + + Args: + bin_name (str): The name of the bin containing the map. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_MAP_SIZE, + BIN_KEY: bin_name + } + + +def map_clear(bin_name): + """Creates a map_clear operation to be used with operate or operate_ordered + + The operation removes all items from the map stored in the specified bin. + + Args: + bin_name (str): The name of the bin containing the map. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + return { + OP_KEY: aerospike.OP_MAP_CLEAR, + BIN_KEY: bin_name + } + + +def map_remove_by_key(bin_name, key, return_type): + """Creates a map_remove_by_key operation to be used with operate or operate_ordered + + The operation removes an item, specified by the key from the map stored in the specified bin. + + Args: + bin_name (str): The name of the bin containing the map. + key: The key to be removed from the map + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_REMOVE_BY_KEY, + BIN_KEY: bin_name, + KEY_KEY: key, + RETURN_TYPE_KEY: return_type + } + + return op_dict + + +def map_remove_by_key_list(bin_name, key_list, return_type, inverted=False): + """Creates a map_remove_by_key operation to be used with operate or operate_ordered + + The operation removes items, specified by the keys in key_list from the map stored in the specified bin. + + Args: + bin_name (str): The name of the bin containing the map. + key_list (list): A list of keys to be removed from the map. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If true, keys with values not specified in the key_list will be removed, + and those keys specified in the key_list will be kept. Default: False + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_REMOVE_BY_KEY_LIST, + BIN_KEY: bin_name, + VALUE_KEY: key_list, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_remove_by_key_range(bin_name, key_range_start, + key_range_end, return_type, inverted=False): + """Creates a map_remove_by_key_range operation to be used with operate or operate_ordered + + The operation removes items, with keys between key_range_start(inclusive) and + key_range_end(exclusive) from the map + + Args: + bin_name (str): The name of the bin containing the map. + key_range_start: The start of the range of keys to be removed. (Inclusive) + key_range_end: The end of the range of keys to be removed. (Exclusive) + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If True, values outside of the specified range will be removed, and + values inside of the range will be kept. Default: False + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_REMOVE_BY_KEY_RANGE, + BIN_KEY: bin_name, + KEY_KEY: key_range_start, + VALUE_KEY: key_range_end, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_remove_by_value(bin_name, value, return_type, inverted=False): + """Creates a map_remove_by_value operation to be used with operate or operate_ordered + + The operation removes key value pairs whose value matches the specified value. + + Args: + bin_name (str): The name of the bin containing the map. + value: Entries with a value matching this argument will be removed from the map. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If True, entries with a value different than the specified value will be removed. + Default: False + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_REMOVE_BY_VALUE, + BIN_KEY: bin_name, + VALUE_KEY: value, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_remove_by_value_list(bin_name, value_list, return_type, inverted=False): + """Creates a map_remove_by_value_list operation to be used with operate or operate_ordered + + The operation removes key value pairs whose values are specified in the value_list. + + Args: + bin_name (str): The name of the bin containing the map. + value_list (list): Entries with a value contained in this list will be removed from the map. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If True, entries with a value contained in value_list will be kept, and all others + will be removed and returned. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_REMOVE_BY_VALUE_LIST, + BIN_KEY: bin_name, + VALUE_KEY: value_list, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_remove_by_value_range(bin_name, value_start, value_end, return_type, inverted=False): + """Creates a map_remove_by_value_range operation to be used with operate or operate_ordered + + The operation removes items, with values between value_start(inclusive) and + value_end(exclusive) from the map + + Args: + bin_name (str): The name of the bin containing the map. + value_start: The start of the range of values to be removed. (Inclusive) + value_end: The end of the range of values to be removed. (Exclusive) + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If True, values outside of the specified range will be removed, and + values inside of the range will be kept. Default: False + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_REMOVE_BY_VALUE_RANGE, + BIN_KEY: bin_name, + VALUE_KEY: value_start, + RANGE_KEY: value_end, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_remove_by_index(bin_name, index, return_type): + """Creates a map_remove_by_index operation to be used with operate or operate_ordered + + The operation removes the entry at index from the map. + + Args: + bin_name (str): The name of the bin containing the map. + index (int): The index of the entry to remove. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_REMOVE_BY_INDEX, + BIN_KEY: bin_name, + INDEX_KEY: index, + RETURN_TYPE_KEY: return_type + } + + return op_dict + + +def map_remove_by_index_range(bin_name, index_start, remove_amt, return_type, inverted=False): + """Creates a map_remove_by_index_range operation to be used with operate or operate_ordered + + The operation removes remove_amt entries starting at index_start from the map. + + Args: + bin_name (str): The name of the bin containing the map. + index_start (int): The index of the first entry to remove. + remove_amt (int): The number of entries to remove from the map. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If true, entries in the specified index range should be kept, and all other + entries removed. Default: False + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_REMOVE_BY_INDEX_RANGE, + BIN_KEY: bin_name, + INDEX_KEY: index_start, + VALUE_KEY: remove_amt, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_remove_by_rank(bin_name, rank, return_type): + """Creates a map_remove_by_rank operation to be used with operate or operate_ordered + + The operation removes the item with the specified rank from the map. + + Args: + bin_name (str): The name of the bin containing the map. + rank (int): The rank of the entry to remove. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_REMOVE_BY_RANK, + BIN_KEY: bin_name, + INDEX_KEY: rank, + RETURN_TYPE_KEY: return_type + } + + return op_dict + + +def map_remove_by_rank_range(bin_name, rank_start, remove_amt, return_type, inverted=False): + """Creates a map_remove_by_rank_range operation to be used with operate or operate_ordered + + The operation removes `remove_amt` items beginning with the item with the specified rank from the map. + + Args: + bin_name (str): The name of the bin containing the map. + rank_start (int): The rank of the entry to remove. + remove_amt (int): The number of entries to remove. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If True, items with ranks inside the specified range should be kept, + and all other entries removed. Default: False. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_REMOVE_BY_RANK_RANGE, + BIN_KEY: bin_name, + INDEX_KEY: rank_start, + VALUE_KEY: remove_amt, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_get_by_key(bin_name, key, return_type): + """Creates a map_get_by_key operation to be used with operate or operate_ordered + + The operation returns an item, specified by the key from the map stored in the specified bin. + + Args: + bin_name (str): The name of the bin containing the map. + key: The key of the item to be returned from the map + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_GET_BY_KEY, + BIN_KEY: bin_name, + KEY_KEY: key, + RETURN_TYPE_KEY: return_type + } + + return op_dict + + +def map_get_by_key_range(bin_name, key_range_start, + key_range_end, return_type, inverted=False): + """Creates a map_get_by_key_range operation to be used with operate or operate_ordered + + The operation returns items with keys between key_range_start(inclusive) and + key_range_end(exclusive) from the map + + Args: + bin_name (str): The name of the bin containing the map. + key_range_start: The start of the range of keys to be returned. (Inclusive) + key_range_end: The end of the range of keys to be returned. (Exclusive) + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If True, values outside of the specified range will be returned, and + values inside of the range will be ignored. Default: False + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_GET_BY_KEY_RANGE, + BIN_KEY: bin_name, + KEY_KEY: key_range_start, + RANGE_KEY: key_range_end, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_get_by_key_list(bin_name, key_list, return_type, inverted=False): + """Creates a map_get_by_key_list operation to be used with operate or operate_ordered + + The operation returns items, specified by the keys in key_list from the map stored in the specified bin. + + Args: + bin_name (str): The name of the bin containing the map. + key_list (list): A list of keys to be returned from the map. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If true, keys with values not specified in the key_list will be returned, + and those keys specified in the key_list will be ignored. Default: False + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_GET_BY_KEY_LIST, + BIN_KEY: bin_name, + VALUE_KEY: key_list, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + +def map_get_by_value(bin_name, value, return_type, inverted=False): + """Creates a map_get_by_value operation to be used with operate or operate_ordered + + The operation returns entries whose value matches the specified value. + + Args: + bin_name (str): The name of the bin containing the map. + value: Entries with a value matching this argument will be returned from the map. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If True, entries with a value different than the specified value will be returned. + Default: False + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_GET_BY_VALUE, + BIN_KEY: bin_name, + VALUE_KEY: value, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_get_by_value_range(bin_name, value_start, value_end, return_type, inverted=False): + """Creates a map_get_by_value_range operation to be used with operate or operate_ordered + + The operation returns items, with values between value_start(inclusive) and + value_end(exclusive) from the map + + Args: + bin_name (str): The name of the bin containing the map. + value_start: The start of the range of values to be returned. (Inclusive) + value_end: The end of the range of values to be returned. (Exclusive) + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If True, values outside of the specified range will be returned, and + values inside of the range will be ignored. Default: False + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_GET_BY_VALUE_RANGE, + BIN_KEY: bin_name, + VALUE_KEY: value_start, + RANGE_KEY: value_end, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_get_by_value_list(bin_name, key_list, return_type, inverted=False): + """Creates a map_get_by_value_list operation to be used with operate or operate_ordered + + The operation returns entries whose values are specified in the value_list. + + Args: + bin_name (str): The name of the bin containing the map. + value_list (list): Entries with a value contained in this list will be returned from the map. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If True, entries with a value contained in value_list will be ignored, and all others + will be returned. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_GET_BY_VALUE_LIST, + BIN_KEY: bin_name, + VALUE_KEY: key_list, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_get_by_index(bin_name, index, return_type): + """Creates a map_get_by_index operation to be used with operate or operate_ordered + + The operation returns the entry at index from the map. + + Args: + bin_name (str): The name of the bin containing the map. + index (int): The index of the entry to return. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_GET_BY_INDEX, + BIN_KEY: bin_name, + INDEX_KEY: index, + RETURN_TYPE_KEY: return_type + } + + return op_dict + + +def map_get_by_index_range(bin_name, index_start, get_amt, return_type, inverted=False): + """Creates a map_get_by_index_range operation to be used with operate or operate_ordered + + The operation returns get_amt entries starting at index_start from the map. + + Args: + bin_name (str): The name of the bin containing the map. + index_start (int): The index of the first entry to return. + get_amt (int): The number of entries to return from the map. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If true, entries in the specified index range should be ignored, and all other + entries returned. Default: False + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_GET_BY_INDEX_RANGE, + BIN_KEY: bin_name, + INDEX_KEY: index_start, + VALUE_KEY: get_amt, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict + + +def map_get_by_rank(bin_name, rank, return_type): + """Creates a map_get_by_rank operation to be used with operate or operate_ordered + + The operation returns the item with the specified rank from the map. + + Args: + bin_name (str): The name of the bin containing the map. + rank (int): The rank of the entry to return. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_GET_BY_RANK, + BIN_KEY: bin_name, + INDEX_KEY: rank, + RETURN_TYPE_KEY: return_type + } + + return op_dict + + +def map_get_by_rank_range(bin_name, rank_start, get_amt, return_type, inverted=False): + """Creates a map_get_by_rank_range operation to be used with operate or operate_ordered + + The operation returns item within the specified rank range from the map. + + Args: + bin_name (str): The name of the bin containing the map. + rank_start (int): The start of the rank of the entries to return. + get_amt (int): The number of entries to return. + return_type (int): Value specifying what should be returned from the operation. + This should be one of the aerospike.MAP_RETURN_* values. + inverted (bool): If True, items with ranks inside the specified range should be ignored, + and all other entries returned. Default: False. + Returns: + A dictionary usable in operate or operate_ordered. The format of the dictionary + should be considered an internal detail, and subject to change. + """ + op_dict = { + OP_KEY: aerospike.OP_MAP_GET_BY_RANK_RANGE, + BIN_KEY: bin_name, + INDEX_KEY: rank_start, + VALUE_KEY: get_amt, + RETURN_TYPE_KEY: return_type, + INVERTED_KEY: inverted + } + + return op_dict diff --git a/aerospike_helpers/operations/operations.py b/aerospike_helpers/operations/operations.py new file mode 100644 index 000000000..e151c0016 --- /dev/null +++ b/aerospike_helpers/operations/operations.py @@ -0,0 +1,119 @@ +''' +Module with helper functions to create dictionaries consumed by +the operate and operate_ordered methods for the aerospike.client class. +''' +import warnings + +import aerospike + + + +def read(bin_name): + """Create a read operation dictionary + + The read operation reads and returns the value in `bin_name` + + Args: + bin: String the name of the bin from which to read + Returns: + A dictionary to be passed to operate or operate_ordered + """ + + return { + "op": aerospike.OPERATOR_READ, + "bin": bin_name, + } + + +def write(bin_name, write_item): + """Create a read operation dictionary + + The write operation writes `write_item` into the bin specified by bin_name + + Args: + bin (string): The name of the bin into which `write_item` will be stored. + write_item: The value which will be written into the bin + Returns: + A dictionary to be passed to operate or operate_ordered + """ + return { + "op": aerospike.OPERATOR_WRITE, + "bin": bin_name, + "val": write_item + } + + +def append(bin_name, append_item): + """Create an append operation dictionary + + The append operation appends `append_item` to the value in bin_name + + Args: + bin (string): The name of the bin to be used. + append_item: The value which will be appended to the item contained in the specified bin. + Returns: + A dictionary to be passed to operate or operate_ordered + """ + return { + "op": aerospike.OPERATOR_APPEND, + "bin": bin_name, + "val": append_item + } + + +def prepend(bin_name, prepend_item): + """Create a prepend operation dictionary + + The prepend operation prepends `prepend_item` to the value in bin_name + + Args: + bin (string): The name of the bin to be used. + prepend_item: The value which will be prepended to the item contained in the specified bin. + Returns: + A dictionary to be passed to operate or operate_ordered + """ + return { + "op": aerospike.OPERATOR_PREPEND, + "bin": bin_name, + "val": prepend_item + } + + +def increment(bin_name, amount): + """Create a prepend operation dictionary + + The increment operation increases a value in bin_name by the specified amount, + or creates a bin with the value of amount + + Args: + bin (string): The name of the bin to be incremented. + amount: The amount by which to increment the item in the specified bin. + Returns: + A dictionary to be passed to operate or operate_ordered + """ + return { + "op": aerospike.OPERATOR_INCR, + "bin": bin_name, + "val": amount + } + + +def touch(ttl=None): + """Create a touch operation dictionary + + Using ttl here is deprecated. It should be set in the record metadata for the operate method + + Args: + ttl (int): Deprecated. The ttl that should be set for the record. + This should be set in the metadata passed to the operate or + operate_ordered methods. + amount: The amount by which to increment the item in the specified bin. + Returns: + A dictionary to be passed to operate or operate_ordered + """ + op_dict = {"op": aerospike.OPERATOR_TOUCH} + if ttl: + warnings.warn( + "TTL should be specified in the meta dictionary for operate", DeprecationWarning) + op_dict["val"] = ttl + return op_dict diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 5446806c5..f6ec7a44b 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -83,6 +83,7 @@ in an in-memory primary index. * **protocols** Specifies enabled protocols. This format is the same as Apache's SSLProtocol documented at https://httpd.apache.org/docs/current/mod/mod_ssl.html#sslprotocol . If not specified the client will use "-all +TLSv1.2". * **cipher_suite** :class:`str` Specifies enabled cipher suites. The format is the same as OpenSSL's Cipher List Format documented at https://www.openssl.org/docs/manmaster/apps/ciphers.html .If not specified the OpenSSL default cipher suite described in the ciphers documentation will be used. If you are not sure what cipher suite to select this option is best left unspecified * **keyfile** :class:`str` Path to the client's key for mutual authentication. By default mutual authentication is disabled. + * **keyfile_pw** :class:`str` Decryption password for the client's key for mutual authentication. By default the key is assumed not to be encrypted. * **cert_blacklist** :class:`str` Path to a certificate blacklist file. The file should contain one line for each blacklisted certificate. Each line starts with the certificate serial number expressed in hex. Each entry may optionally specify the issuer name of the certificate (serial numbers are only required to be unique per issuer). Example records: 867EC87482B2 /C=US/ST=CA/O=Acme/OU=Engineering/CN=Test Chain CA E2D4B0E570F9EF8E885C065899886461 * **certfile** :class:`str` Path to the client's certificate chain file for mutual authentication. By default mutual authentication is disabled. * **crl_check** :class:`bool` Enable CRL checking for the certificate chain leaf certificate. An error occurs if a suitable CRL cannot be found. By default CRL checking is disabled. @@ -428,6 +429,11 @@ Operators Operators for the multi-ops method :py:meth:`~aerospike.Client.operate`. +.. note:: + + Beginning in version 3.4.0, it is recommended to use the operation helpers module :ref:`aerospike_operation_helpers` + To create the arguments for the :py:meth:`~aerospike.Client.operate` and :py:meth:`~aerospike.Client.operate` + .. data:: OPERATOR_WRITE Write a value into a bin @@ -506,9 +512,12 @@ Operators for the multi-ops method :py:meth:`~aerospike.Client.operate`. { "op" : aerospike.OP_LIST_APPEND, "bin": "events", - "val": 1234 + "val": 1234, + "list_policy": {"write_flags": aerospike.LIST_WRITE_ADD_UNIQUE} # Optional, new in client 3.4.0 } + .. versionchanged:: 3.4.0 + .. data:: OP_LIST_APPEND_ITEMS Extend a bin with :class:`list` type data with a list of items @@ -518,9 +527,12 @@ Operators for the multi-ops method :py:meth:`~aerospike.Client.operate`. { "op" : aerospike.OP_LIST_APPEND_ITEMS, "bin": "events", - "val": [ 123, 456 ] + "val": [ 123, 456 ], + "list_policy": {"write_flags": aerospike.LIST_WRITE_ADD_UNIQUE} # Optional, new in client 3.4.0 } + .. versionchanged:: 3.4.0 + .. data:: OP_LIST_INSERT Insert an element at a specified index of a bin with :class:`list` type data @@ -531,9 +543,12 @@ Operators for the multi-ops method :py:meth:`~aerospike.Client.operate`. "op" : aerospike.OP_LIST_INSERT, "bin": "events", "index": 2, - "val": 1234 + "val": 1234, + "list_policy": {"write_flags": aerospike.LIST_WRITE_ADD_UNIQUE} # Optional, new in client 3.4.0 } + .. versionchanged:: 3.4.0 + .. data:: OP_LIST_INSERT_ITEMS Insert the items at a specified index of a bin with :class:`list` type data @@ -545,8 +560,11 @@ Operators for the multi-ops method :py:meth:`~aerospike.Client.operate`. "bin": "events", "index": 2, "val": [ 123, 456 ] + "list_policy": {"write_flags": aerospike.LIST_WRITE_ADD_UNIQUE} # Optional, new in client 3.4.0 } + .. versionchanged:: 3.4.0 + .. data:: OP_LIST_INCREMENT Increment the value of an item at the given index in a list stored in the specified bin @@ -557,9 +575,12 @@ Operators for the multi-ops method :py:meth:`~aerospike.Client.operate`. "op": aerospike.OP_LIST_INCREMENT, "bin": "bin_name", "index": 2, - "val": 5 + "val": 5, + "list_policy": {"write_flags": aerospike.LIST_WRITE_ADD_UNIQUE} # Optional, new in client 3.4.0 } + .. versionchanged:: 3.4.0 + .. data:: OP_LIST_POP Remove and return the element at a specified index of a bin with :class:`list` type data @@ -631,9 +652,12 @@ Operators for the multi-ops method :py:meth:`~aerospike.Client.operate`. "op" : aerospike.OP_LIST_SET, "bin": "events", "index": 2, - "val": "latest event at index 2" # set this value at index 2 + "val": "latest event at index 2" # set this value at index 2, + "list_policy": {"write_flags": aerospike.LIST_WRITE_ADD_UNIQUE} # Optional, new in client 3.4.0 } + .. versionchanged:: 3.4.0 + .. data:: OP_LIST_GET Get the element at a specified index of a bin with :class:`list` type data @@ -683,6 +707,290 @@ Operators for the multi-ops method :py:meth:`~aerospike.Client.operate`. "bin": "events" # gets the size of a list contained in the bin } +.. data:: OP_LIST_GET_BY_INDEX + + Get the item at the specified index from a list bin. Server selects list item identified by index + and returns selected data specified by ``return_type``. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_GET_BY_INDEX, + "bin": "events", + "index": 2, # Index of the item to fetch + "return_type": aerospike.LIST_RETURN_VALUE + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_GET_BY_INDEX_RANGE + + Server selects ``count`` list items starting at specified index and returns selected data specified by return_type. + if ``count`` is omitted, the server returns all items from ``index`` to the end of list. + + If ``inverted`` is set to ``True``, return all items outside of the specified range. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_GET_BY_INDEX_RANGE, + "bin": "events", + "index": 2, # Beginning index of range, + "count": 2, # Optional Count. + "return_type": aerospike.LIST_RETURN_VALUE, + "inverted": False # Optional. + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_GET_BY_RANK + + Server selects list item identified by ``rank`` and returns selected data specified by return_type. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_GET_BY_RANK, + "bin": "events", + "rank": 2, # Rank of the item to fetch + "return_type": aerospike.LIST_RETURN_VALUE + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_GET_BY_RANK_RANGE + + Server selects ``count`` list items starting at specified rank and returns selected data specified by return_type. + If ``count`` is not specified, the server returns items starting at the specified rank to the last ranked item. + + If ``inverted`` is set to ``True``, return all items outside of the specified range. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_GET_BY_RANK_RANGE, + "bin": "events", + "rank": 2, # Rank of the item to fetch + "count": 3, + "return_type": aerospike.LIST_RETURN_VALUE, + "inverted": False # Optional, defaults to False + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_GET_BY_VALUE + + Server selects list items identified by ``val`` and returns selected data specified by return_type. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_GET_BY_VALUE, + "bin": "events", + "val": 5, + "return_type": aerospike.LIST_RETURN_COUNT + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_GET_BY_VALUE_LIST + + Server selects list items contained in by ``value_list`` and returns selected data specified by return_type. + + If ``inverted`` is set to ``True``, returns items not included in ``value_list`` + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_GET_BY_VALUE_LIST, + "bin": "events", + "value_list": [5, 6, 7], + "return_type": aerospike.LIST_RETURN_COUNT, + "inverted": False # Optional, defaults to False + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_GET_BY_VALUE_RANGE + + Create list get by value range operation. Server selects list items identified by value range (begin inclusive, end exclusive). + If ``value_begin`` is not present the range is less than ``value_end``. If ``value_end`` is not specified, the range is greater + than or equal to ``value_begin``. + + If ``inverted`` is set to ``True``, returns items not included in the specified range. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_GET_BY_VALUE_RANGE, + "bin": "events", + "value_begin": 3, # Optional + "value_end": 6, Optional + "return_type": aerospike.LIST_RETURN_VALUE, + "inverted": False # Optional, defaults to False + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_REMOVE_BY_INDEX + + Remove and return the item at the specified index from a list bin. Server selects list item identified by index + and returns selected data specified by ``return_type``. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_REMOVE_BY_INDEX, + "bin": "events", + "index": 2, # Index of the item to fetch + "return_type": aerospike.LIST_RETURN_VALUE + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_REMOVE_BY_INDEX_RANGE + + Server remove ``count`` list items starting at specified index and returns selected data specified by return_type. + if ``count`` is omitted, the server removes and returns all items from ``index`` to the end of list. + + If ``inverted`` is set to ``True``, remove and return all items outside of the specified range. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_REMOVE_BY_INDEX_RANGE, + "bin": "events", + "index": 2, # Beginning index of range, + "count": 2, # Optional Count. + "return_type": aerospike.LIST_RETURN_VALUE, + "inverted": False # Optional. + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_REMOVE_BY_RANK + + Server removes and returns list item identified by ``rank`` and returns selected data specified by return_type. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_REMOVE_BY_RANK, + "bin": "events", + "rank": 2, # Rank of the item to fetch + "return_type": aerospike.LIST_RETURN_VALUE + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_REMOVE_BY_RANK_RANGE + + Server removes and returns ``count`` list items starting at specified rank and returns selected data specified by return_type. + If ``count`` is not specified, the server removes and returns items starting at the specified rank to the last ranked item. + + If ``inverted`` is set to ``True``, removes return all items outside of the specified range. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_REMOVE_BY_RANK_RANGE, + "bin": "events", + "rank": 2, # Rank of the item to fetch + "count": 3, + "return_type": aerospike.LIST_RETURN_VALUE, + "inverted": False # Optional, defaults to False + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_REMOVE_BY_VALUE + + Server removes and returns list items identified by ``val`` and returns selected data specified by return_type. + + If ``inverted`` is set to ``True``, removes and returns list items with a value not equal to ``val``. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_REMOVE_BY_VALUE, + "bin": "events", + "val": 5, + "return_type": aerospike.LIST_RETURN_COUNT, + "inverted", # Optional, defaults to False + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_REMOVE_BY_VALUE_LIST + + Server removes and returns list items contained in by ``value_list`` and returns selected data specified by return_type. + + If ``inverted`` is set to ``True``, removes and returns items not included in ``value_list`` + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_REMOVE_BY_VALUE_LIST, + "bin": "events", + "value_list": [5, 6, 7], + "return_type": aerospike.LIST_RETURN_COUNT, + "inverted": False # Optional, defaults to False + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_REMOVE_BY_VALUE_RANGE + + Create list remove by value range operation. Server removes and returns list items identified by value range (begin inclusive, end exclusive). + If ``value_begin`` is not present the range is less than ``value_end``. If ``value_end`` is not specified, the range is greater + than or equal to ``value_begin``. + + If ``inverted`` is set to ``True``, removes and returns items not included in the specified range. + + .. code-block:: python + + { + "op" : aerospike.OP_LIST_REMOVE_BY_VALUE_RANGE, + "bin": "events", + "value_begin": 3, # Optional + "value_end": 6, Optional + "return_type": aerospike.LIST_RETURN_VALUE, + "inverted": False # Optional, defaults to False + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_SET_ORDER + + Assign an ordering to the specified list bin. + ``list_order`` should be one of ``aerospike.LIST_ORDERED``, ``aerospike.LIST_UNORDERED``. + + .. code-block:: python + + { + "op": aerospike.OP_LIST_SET_ORDER, + "list_order": aerospike.LIST_ORDERED, + "bin": "events" + } + + .. versionadded:: 3.4.0 + +.. data:: OP_LIST_SORT + + Perform a sort operation on the bin. + ``sort_flags``, if provided, can be one of: ``aerospike.LIST_SORT_DROP_DUPLICATES`` indicating that duplicate elements + should be removed from the sorted list. + + .. code-block:: python + + { + 'op': aerospike.OP_LIST_SORT, + 'sort_flags': aerospike.LIST_SORT_DROP_DUPLICATES, # Optional flags or'd together specifying behavior + 'bin': self.test_bin + } + + .. versionadded:: 3.4.0 + .. data:: OP_MAP_SET_POLICY Set the policy for a map bin. The policy controls the write mode and the ordering of the map entries. @@ -1352,6 +1660,42 @@ Return types used by various map operations Return key/value items. Note that key/value pairs will be returned as a list of tuples (i.e. [(key1, value1), (key2, value2)]) +.. _list_return_types: + +List Return Types +------------------ + +Return types used by various map operations + +.. data:: LIST_RETURN_NONE + + Do not return any value. + +.. data:: LIST_RETURN_INDEX + + Return key index order. + +.. data:: LIST_RETURN_REVERSE_INDEX + + Return reverse key order. + +.. data:: LIST_RETURN_RANK + + Return value order. + +.. data:: LIST_RETURN_REVERSE_RANK + + Return reserve value order. + +.. data:: LIST_RETURN_COUNT + + Return count of items selected. + +.. data:: LIST_RETURN_VALUE + + Return value for single key read and value list for range read. + + .. _regex_constants: Regex Flag Values diff --git a/doc/aerospike_helpers.operations.rst b/doc/aerospike_helpers.operations.rst new file mode 100644 index 000000000..b8b684885 --- /dev/null +++ b/doc/aerospike_helpers.operations.rst @@ -0,0 +1,40 @@ +.. _aerospike_operation_helpers: + +aerospike\_helpers\.operations package +====================================== + +Submodules +---------- + +aerospike\_helpers\.operations\.list\_operations module +------------------------------------------------------- + +.. automodule:: aerospike_helpers.operations.list_operations + :members: + :undoc-members: + :show-inheritance: + +aerospike\_helpers\.operations\.map\_operations module +------------------------------------------------------ + +.. automodule:: aerospike_helpers.operations.map_operations + :members: + :undoc-members: + :show-inheritance: + +aerospike\_helpers\.operations\.operations module +------------------------------------------------- + +.. automodule:: aerospike_helpers.operations.operations + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: aerospike_helpers.operations + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/aerospike_helpers.rst b/doc/aerospike_helpers.rst new file mode 100644 index 000000000..dff80f9e1 --- /dev/null +++ b/doc/aerospike_helpers.rst @@ -0,0 +1,17 @@ +aerospike\_helpers package +========================== + +Subpackages +----------- + +.. toctree:: + + aerospike_helpers.operations + +Module contents +--------------- + +.. automodule:: aerospike_helpers + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/client.rst b/doc/client.rst index d1d0e3f92..be405f235 100644 --- a/doc/client.rst +++ b/doc/client.rst @@ -1546,6 +1546,7 @@ Multi-Ops (Operate) from __future__ import print_function import aerospike + from aerospike_helpers.operations import operations as op_helpers from aerospike import exception as ex import sys @@ -1556,34 +1557,13 @@ Multi-Ops (Operate) key = ('test', 'demo', 1) client.put(key, {'age': 25, 'career': 'delivery boy'}) ops = [ - { - "op" : aerospike.OPERATOR_INCR, - "bin": "age", - "val": 1000 - }, - { - "op" : aerospike.OPERATOR_WRITE, - "bin": "name", - "val": "J." - }, - { - "op" : aerospike.OPERATOR_PREPEND, - "bin": "name", - "val": "Phillip " - }, - { - "op" : aerospike.OPERATOR_APPEND, - "bin": "name", - "val": " Fry" - }, - { - "op" : aerospike.OPERATOR_READ, - "bin": "name" - }, - { - "op" : aerospike.OPERATOR_READ, - "bin": "career" - } + op_helpers.increment("age", 1000), + op_helpers.write("name", "J."), + op_helpers.prepend("name", "Phillip "), + op_helpers.append("name", " Fry"), + op_helpers.read("name"), + op_helpers.read("career"), + op_helpers.read("age") ] (key, meta, bins) = client.operate(key, ops, {'ttl':360}, {'total_timeout':500}) @@ -1677,6 +1657,7 @@ Multi-Ops (Operate) from __future__ import print_function import aerospike from aerospike import exception as ex + from aerospike_helpers.operations import operations as op_helpers import sys config = { 'hosts': [('127.0.0.1', 3000)] } @@ -1690,14 +1671,12 @@ Multi-Ops (Operate) 'commit_level': aerospike.POLICY_COMMIT_LEVEL_MASTER } - llist = [{"op": aerospike.OPERATOR_APPEND, - "bin": "name", - "val": "aa"}, - {"op": aerospike.OPERATOR_READ, - "bin": "name"}, - {"op": aerospike.OPERATOR_INCR, - "bin": "age", - "val": 3}] + llist = [ + op_helpers.append("name", "aa"), + op_helpers.read("name"), + op_helpers.increment("age", 3), + op_helpers.read("age") + ] client.operate_ordered(key, llist, {}, policy) except ex.AerospikeError as e: @@ -2936,6 +2915,32 @@ Admin Policies * **timeout** admin operation timeout in milliseconds + +.. _aerospike_list_policies: + +List Policies +------------- + +.. object:: policy + + A :class:`dict` of optional list policies, which are applicable to list operations. + + .. hlist:: + :columns: 1 + + * **write_flags** Write flags for the operation. Valid values: ``aerospike.LIST_WRITE_ADD_UNIQUE``, ``aerospike.LIST_WRITE_INSERT_BOUNDED``, ``aerospike.LIST_WRITE_DEFAULT`` + values should be or'd together: ``aerospike.LIST_WRITE_ADD_UNIQUE | aerospike.LIST_WRITE_INSERT_BOUNDED`` + * **list_order** ordering to maintain for the list. Valid values: values are ``aerospike.LIST_ORDERED``, ``aerospike.LIST_UNORDERED`` + + Example: + + .. code-block:: python + + list_policy = { + "write_flags": aerospike.LIST_WRITE_ADD_UNIQUE | aerospike.LIST_WRITE_INSERT_BOUNDED, + "list_order": aerospike.LIST_ORDERED + } + .. _aerospike_map_policies: Map Policies diff --git a/doc/conf.py b/doc/conf.py index 57523e0d7..97a826008 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,18 +1,21 @@ # -*- coding: utf-8 -*- import sys, os - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.append( + os.path.abspath( + os.path.join(os.path.dirname(__name__), '..')) +) #sys.path.append(os.path.abspath('/usr/local/lib/python2.7/site-packages/aerospike-1.0.44-py2.7-macosx-10.9-x86_64.egg/')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.todo', 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] - +extensions = ['sphinx.ext.todo', 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon'] +napoleon_google_docstring = True intersphinx_mapping = {'python': ('https://docs.python.org/2.7', None)} # Add any paths that contain templates here, relative to this directory. @@ -29,7 +32,7 @@ # General information about the project. project = u'aerospike' -copyright = u'2014-2017, Aerospike' +copyright = u'2014-2018, Aerospike' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/doc/index.rst b/doc/index.rst index f3614277a..51ccb76a5 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -16,6 +16,8 @@ the Aerospike C client. * :ref:`Data_Mapping` How Python types map to Aerospike Server types +* :mod:`aerospike_helpers.operations` Operation helper functions. + .. seealso:: The `Python Client Manual `_ for a quick guide. @@ -35,6 +37,7 @@ Content predexp geojson exception + modules Indices and tables diff --git a/doc/modules.rst b/doc/modules.rst new file mode 100644 index 000000000..d66ee5c72 --- /dev/null +++ b/doc/modules.rst @@ -0,0 +1,7 @@ +aerospike_helpers +================= + +.. toctree:: + :maxdepth: 4 + + aerospike_helpers diff --git a/doc/query.rst b/doc/query.rst index 92423106d..e63d139a0 100644 --- a/doc/query.rst +++ b/doc/query.rst @@ -304,6 +304,8 @@ Query Policies | :class:`bool` Should raw bytes representing a list or map be deserialized to a list or dictionary. | Set to `False` for backup programs that just need access to raw bytes. | Default: ``True`` + * **fail_on_cluster_change** + | :class:`bool` Terminate query if cluster is in migration state. Default ``False`` .. _aerospike_query_options: diff --git a/examples/client/operate.py b/examples/client/operate.py index 3acdf90d9..6be860411 100644 --- a/examples/client/operate.py +++ b/examples/client/operate.py @@ -16,11 +16,12 @@ ########################################################################## from __future__ import print_function +import sys +from optparse import OptionParser import aerospike -import sys +from aerospike_helpers.operations import operations as op_helpers -from optparse import OptionParser ########################################################################## # Option Parsing @@ -109,8 +110,9 @@ try: namespace = options.namespace if options.namespace and options.namespace != 'None' else None - set = options.set if options.set and options.set != 'None' else None + setname = options.set if options.set and options.set != 'None' else None key = args.pop() + record_key = (namespace, setname, key) record = { 'example_name': 'John', @@ -122,41 +124,30 @@ # invoke operation - client.put((namespace, set, key), record, meta, policy) + client.put(record_key, record, meta, policy) print("---") print("OK, 1 record written.") - (returnedkey, meta, bins) = client.get((namespace, set, key)) + _, _, bins = client.get(record_key) print("---") print("Before operate operation") print(bins) operation_list = [ - { - "op": aerospike.OPERATOR_PREPEND, - "bin": "example_name", - "val": "Mr " - }, - { - "op": aerospike.OPERATOR_INCR, - "bin": "example_age", - "val": 3 - }, - { - "op": aerospike.OPERATOR_READ, - "bin": "example_name" - } + op_helpers.prepend("example_name", "Mr "), + op_helpers.increment("example_age", 3), + op_helpers.read("example_name") ] - (returnedkey, meta, bins) = client.operate( - (namespace, set, key), operation_list, meta, policy) + _, _, bins = client.operate( + record_key, operation_list, meta, policy) print("---") print("Record returned on operate completion") print(bins) - (returnedkey, meta, bins) = client.get((namespace, set, key)) + _, _, bins = client.get(record_key) print("---") print("After operate operation") diff --git a/setup.py b/setup.py index e1043b3ce..c9f96f9a9 100644 --- a/setup.py +++ b/setup.py @@ -72,7 +72,7 @@ def run(self): os.environ['ARCHFLAGS'] = '-arch x86_64' AEROSPIKE_C_VERSION = os.getenv('AEROSPIKE_C_VERSION') if not AEROSPIKE_C_VERSION: - AEROSPIKE_C_VERSION = '4.3.12' + AEROSPIKE_C_VERSION = '4.3.13' DOWNLOAD_C_CLIENT = os.getenv('DOWNLOAD_C_CLIENT') AEROSPIKE_C_HOME = os.getenv('AEROSPIKE_C_HOME') PREFIX = None @@ -308,6 +308,7 @@ def resolve_c_client(lua_src_path): 'src/main/log.c', 'src/main/client/type.c', 'src/main/client/apply.c', + 'src/main/client/cdt_list_operate.c', 'src/main/client/close.c', 'src/main/client/connect.c', 'src/main/client/exists.c', @@ -370,4 +371,6 @@ def resolve_c_client(lua_src_path): extra_link_args=extra_link_args, ) ], + packages=['aerospike_helpers', 'aerospike_helpers.operations'] + ) diff --git a/src/include/cdt_list_operations.h b/src/include/cdt_list_operations.h new file mode 100644 index 000000000..4caa2b276 --- /dev/null +++ b/src/include/cdt_list_operations.h @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright 2013-2018 Aerospike, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +#pragma once +#include +#include +#include +#include + +as_status add_new_list_op(AerospikeClient * self, as_error * err, PyObject * op_dict, as_vector * unicodeStrVector, + as_static_pool * static_pool, as_operations * ops, long operation_code, long * ret_type, + int serializer_type); diff --git a/src/include/conversions.h b/src/include/conversions.h index a9a175f30..bf1dc2373 100644 --- a/src/include/conversions.h +++ b/src/include/conversions.h @@ -99,3 +99,6 @@ as_status as_batch_read_results_to_pyobject(as_error* err, AerospikeClient* clie uint32_t size, PyObject** py_records); as_status batch_read_records_to_pyobject(AerospikeClient *self, as_error *err, as_batch_read_records* records, PyObject **py_recs); + +as_status +string_and_pyuni_from_pystring(PyObject* py_string, PyObject** pyuni_r, char** c_str_ptr, as_error* err); \ No newline at end of file diff --git a/src/include/policy.h b/src/include/policy.h index 5e1bd68c0..79477051b 100644 --- a/src/include/policy.h +++ b/src/include/policy.h @@ -20,6 +20,7 @@ #include #include #include +#include #define MAX_CONSTANT_STR_SIZE 512 @@ -51,7 +52,23 @@ enum Aerospike_list_operations { OP_LIST_GET_RANGE, OP_LIST_TRIM, OP_LIST_SIZE, - OP_LIST_INCREMENT + OP_LIST_INCREMENT, + OP_LIST_GET_BY_INDEX, + OP_LIST_GET_BY_INDEX_RANGE, + OP_LIST_GET_BY_RANK, + OP_LIST_GET_BY_RANK_RANGE, + OP_LIST_GET_BY_VALUE, + OP_LIST_GET_BY_VALUE_LIST, + OP_LIST_GET_BY_VALUE_RANGE, + OP_LIST_REMOVE_BY_INDEX, + OP_LIST_REMOVE_BY_INDEX_RANGE, + OP_LIST_REMOVE_BY_RANK, + OP_LIST_REMOVE_BY_RANK_RANGE, + OP_LIST_REMOVE_BY_VALUE, + OP_LIST_REMOVE_BY_VALUE_LIST, + OP_LIST_REMOVE_BY_VALUE_RANGE, + OP_LIST_SET_ORDER, + OP_LIST_SORT }; enum Aerospike_map_operations { @@ -80,6 +97,8 @@ enum Aerospike_map_operations { OP_MAP_GET_BY_INDEX_RANGE, OP_MAP_GET_BY_RANK, OP_MAP_GET_BY_RANK_RANGE, + OP_MAP_GET_BY_VALUE_LIST, + OP_MAP_GET_BY_KEY_LIST }; typedef struct Aerospike_Constants { @@ -153,3 +172,6 @@ void set_scan_options(as_error *err, as_scan* scan_p, PyObject * py_options); as_status set_query_options(as_error* err, PyObject* query_options, as_query* query); +as_status pyobject_to_list_policy(as_error * err, PyObject * py_policy, + as_list_policy * policy); + diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 72f91550d..213e7d3d8 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -93,7 +93,7 @@ AerospikeConstants operator_constants[] = { MOD_INIT(aerospike) { - const char version[8] = "3.3.0"; + const char version[8] = "3.4.0"; // Makes things "thread-safe" PyEval_InitThreads(); int i = 0; diff --git a/src/main/client/cdt_list_operate.c b/src/main/client/cdt_list_operate.c new file mode 100644 index 000000000..f22e38c62 --- /dev/null +++ b/src/main/client/cdt_list_operate.c @@ -0,0 +1,1318 @@ +/******************************************************************************* + * Copyright 2013-2018 Aerospike, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + + +#include +#include +#include +#include +#include +#include + +#include "client.h" +#include "conversions.h" +#include "exceptions.h" +#include "policy.h" +#include "serializer.h" +#include "cdt_list_operations.h" + +#define AS_PY_BIN_KEY "bin" +#define AS_PY_VAL_KEY "val" +#define AS_PY_VALUES_KEY "value_list" +#define AS_PY_VAL_BEGIN_KEY "value_begin" +#define AS_PY_VAL_END_KEY "value_end" +#define AS_PY_INDEX_KEY "index" +#define AS_PY_COUNT_KEY "count" +#define AS_PY_RANK_KEY "rank" +#define AS_PY_LIST_RETURN_KEY "return_type" +#define AS_PY_LIST_ORDER "list_order" +#define AS_PY_LIST_SORT_FLAGS "sort_flags" +#define AS_PY_LIST_POLICY "list_policy" +/* +This handles + (_op) == OP_LIST_GET_BY_INDEX ||\ + (_op) == OP_LIST_BY_INDEX_RANGE ||\ + (_op) == OP_LIST_GET_BY_RANK ||\ + (_op) == OP_LIST_BY_RANK_RANGE ||\ + (_op) == OP_LIST_GET_BY_VALUE ||\ + (_op) == OP_LIST_GET_BY_VALUE_LIST ||\ + (_op) == OP_LIST_GET_BY_VALUE_RANGE ||\ + (_op) == OP_LIST_REMOVE_BY_INDEX ||\ + (_op) == OP_LIST_REMOVE_BY_INDEX_RANGE ||\ + (_op) == OP_LIST_REMOVE_BY_RANK ||\ + (_op) == OP_LIST_REMOVE_BY_RANK_RANGE ||\ + (_op) == OP_LIST_REMOVE_BY_VALUE ||\ + (_op) == OP_LIST_REMOVE_BY_VALUE_LIST ||\ + (_op) == OP_LIST_REMOVE_BY_VALUE_RANGE ||) +*/ + +/* Dictionary field extraction functions */ +static as_status +get_bin(as_error * err, PyObject * op_dict, as_vector * unicodeStrVector, char** binName); + +static as_status +get_asval(AerospikeClient * self, as_error * err, char* key, PyObject * op_dict, as_val** val, + as_static_pool * static_pool, int serializer_type, bool required); + +static as_status +get_val_list(AerospikeClient * self, as_error * err, const char* list_key, PyObject * op_dict, as_list** list, as_static_pool * static_pool, int serializer_type); + +static as_status +get_int64_t(as_error * err, const char* key, PyObject * op_dict, int64_t* count); + +static as_status +get_list_policy(as_error * err, PyObject * op_dict, as_list_policy* policy, bool* found); + +static as_status +get_optional_int64_t(as_error * err, const char* key, PyObject * op_dict, int64_t* count, bool* found); + +static as_status +get_list_return_type(as_error * err, PyObject * op_dict, int* return_type); + +/* + * Previously implemented list operations + */ +static as_status +add_op_list_append(AerospikeClient* self, as_error * err, char* bin, + PyObject * op_dict, as_operations * ops, + as_static_pool* static_pool, int serializer_type); + +static as_status +add_op_list_append_items(AerospikeClient* self, as_error* err, char* bin, + PyObject* op_dict, as_operations* ops, + as_static_pool* static_pool, int serializer_type); + +static as_status +add_op_list_insert(AerospikeClient* self, as_error * err, char* bin, + PyObject * op_dict, as_operations * ops, + as_static_pool* static_pool, int serializer_type); + +static as_status +add_op_list_insert_items(AerospikeClient* self, as_error* err, char* bin, + PyObject* op_dict, as_operations* ops, + as_static_pool* static_pool, int serializer_type); + +static as_status +add_op_list_increment(AerospikeClient* self, as_error * err, char* bin, + PyObject * op_dict, as_operations * ops, + as_static_pool* static_pool, int serializer_type); + +static as_status +add_op_list_size(as_error * err, char* bin, as_operations * ops); + +static as_status +add_op_list_pop(as_error * err, char* bin, PyObject * op_dict, as_operations * ops); + +static as_status +add_op_list_pop_range(as_error * err, char* bin, PyObject * op_dict, as_operations * ops); + +static as_status +add_op_list_remove(as_error * err, char* bin, PyObject * op_dict, as_operations * ops); + +static as_status +add_op_list_remove_range(as_error * err, char* bin, PyObject * op_dict, as_operations * ops); + +static as_status +add_op_list_clear(as_error * err, char* bin,as_operations * ops); + +static as_status +add_op_list_get(as_error * err, char* bin, PyObject * op_dict, as_operations * ops); + +static as_status +add_op_list_get_range(as_error * err, char* bin, PyObject * op_dict, as_operations * ops); + +static as_status +add_op_list_trim(as_error * err, char* bin, PyObject * op_dict, as_operations * ops); + +static as_status +add_op_list_set(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, as_operations * ops, + as_static_pool* static_pool, int serializer_type); + +/* NEW CDT LIST OPERATIONS Post 3.16.0.1*/ +/* GET BY */ +static as_status +add_op_list_get_by_index(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops); + +static as_status +add_op_list_get_by_index_range(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops); + +static as_status +add_op_list_get_by_rank(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops); + +static as_status +add_op_list_get_by_rank_range(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops); + +static as_status +add_op_list_get_by_value(AerospikeClient* self, as_error * err, char* bin, + PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type); + +static as_status +add_op_list_get_by_value_list(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, + as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type); + +static as_status +add_op_list_get_by_value_range(AerospikeClient* self, as_error * err, char* bin, + PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type); + +/* remove by */ + +static as_status +add_op_list_remove_by_index(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops); + +static as_status +add_op_list_remove_by_index_range(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops); + +static as_status +add_op_list_remove_by_rank(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops); + +static as_status +add_op_list_remove_by_rank_range(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops); + + +static as_status +add_op_list_remove_by_value(AerospikeClient* self, as_error * err, char* bin, + PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type); + +static as_status +add_op_list_remove_by_value_list(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, + as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type); + +static as_status +add_op_list_remove_by_value_range(AerospikeClient* self, as_error * err, char* bin, + PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type); + + +/* Set Order */ +static as_status +add_op_list_set_order(as_error * err, char* bin, PyObject * op_dict, as_operations * ops); + +/* List sort */ +static as_status +add_op_list_sort(as_error * err, char* bin, PyObject * op_dict, as_operations * ops); + +/* End forwards */ +as_status +add_new_list_op(AerospikeClient * self, as_error * err, PyObject * op_dict, as_vector * unicodeStrVector, + as_static_pool * static_pool, as_operations * ops, long operation_code, long * ret_type, int serializer_type) + +{ + char* bin = NULL; + + if (get_bin(err, op_dict, unicodeStrVector, &bin) != AEROSPIKE_OK) { + return err->code; + } + + switch(operation_code) { + case OP_LIST_APPEND: + return add_op_list_append(self, err, bin, op_dict, ops, static_pool, serializer_type); + + case OP_LIST_APPEND_ITEMS: + return add_op_list_append_items(self, err, bin, op_dict, ops, static_pool, serializer_type); + + case OP_LIST_SIZE: + return add_op_list_size(err, bin, ops); + + case OP_LIST_INSERT: + return add_op_list_insert(self, err, bin, op_dict, ops, static_pool, serializer_type); + + case OP_LIST_INSERT_ITEMS: + return add_op_list_insert_items(self, err, bin, op_dict, ops, static_pool, serializer_type); + + case OP_LIST_INCREMENT: + return add_op_list_increment(self, err, bin, op_dict, ops, static_pool, serializer_type); + + case OP_LIST_POP: + return add_op_list_pop(err, bin, op_dict, ops); + + case OP_LIST_POP_RANGE: + return add_op_list_pop_range(err, bin, op_dict, ops); + + case OP_LIST_REMOVE: + return add_op_list_remove(err, bin, op_dict, ops); + + case OP_LIST_REMOVE_RANGE: + return add_op_list_remove_range(err, bin, op_dict, ops); + + case OP_LIST_CLEAR: + return add_op_list_clear(err, bin, ops); + + case OP_LIST_SET: + return add_op_list_set(self, err, bin, op_dict, ops, static_pool, serializer_type); + + case OP_LIST_GET: + return add_op_list_get(err, bin, op_dict, ops); + + case OP_LIST_GET_RANGE: + return add_op_list_get_range(err, bin, op_dict, ops); + + case OP_LIST_TRIM: + return add_op_list_trim(err, bin, op_dict, ops); + /***** New List ops ****/ + + case OP_LIST_GET_BY_INDEX: { + return add_op_list_get_by_index(err, bin, op_dict, unicodeStrVector, ops); + } + + case OP_LIST_GET_BY_INDEX_RANGE: { + return add_op_list_get_by_index_range(err, bin, op_dict, unicodeStrVector, ops); + } + + case OP_LIST_GET_BY_RANK: { + return add_op_list_get_by_rank(err, bin, op_dict, unicodeStrVector, ops); + } + + case OP_LIST_GET_BY_RANK_RANGE: { + return add_op_list_get_by_rank_range(err, bin, op_dict, unicodeStrVector, ops); + } + + case OP_LIST_GET_BY_VALUE: { + return add_op_list_get_by_value(self, err, bin, op_dict, unicodeStrVector, ops, static_pool, serializer_type); + } + + case OP_LIST_GET_BY_VALUE_LIST: { + return add_op_list_get_by_value_list(self, err, bin, op_dict, unicodeStrVector, ops, static_pool, serializer_type); + } + + case OP_LIST_GET_BY_VALUE_RANGE: { + return add_op_list_get_by_value_range(self, err, bin, op_dict, unicodeStrVector, ops, static_pool, serializer_type); + } + + case OP_LIST_REMOVE_BY_INDEX: { + return add_op_list_remove_by_index(err, bin, op_dict, unicodeStrVector, ops); + } + + case OP_LIST_REMOVE_BY_INDEX_RANGE: { + return add_op_list_remove_by_index_range(err, bin, op_dict, unicodeStrVector, ops); + } + + case OP_LIST_REMOVE_BY_RANK: { + return add_op_list_remove_by_rank(err, bin, op_dict, unicodeStrVector, ops); + } + + case OP_LIST_REMOVE_BY_RANK_RANGE: { + return add_op_list_remove_by_rank_range(err, bin, op_dict, unicodeStrVector, ops); + } + + case OP_LIST_REMOVE_BY_VALUE: { + return add_op_list_remove_by_value(self, err, bin, op_dict, unicodeStrVector, ops, static_pool, serializer_type); + } + + case OP_LIST_REMOVE_BY_VALUE_LIST: { + return add_op_list_remove_by_value_list(self, err, bin, op_dict, unicodeStrVector, ops, static_pool, serializer_type); + } + + case OP_LIST_REMOVE_BY_VALUE_RANGE: { + return add_op_list_remove_by_value_range(self, err, bin, op_dict, unicodeStrVector, ops, static_pool, serializer_type); + } + + case OP_LIST_SET_ORDER: { + return add_op_list_set_order(err, bin, op_dict, ops); + } + + case OP_LIST_SORT: { + return add_op_list_sort(err, bin, op_dict, ops); // Sort the thing + } + + default: + // This should never be possible since we only get here if we know that the operation is valid. + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Unknown operation"); + } + + + + return err->code; +} + +/* +The caller of this does not own the pointer to binName, and should not free it. It is either +held by Python, or is added to the list of chars to free later. +*/ +static as_status +get_bin(as_error * err, PyObject * op_dict, as_vector * unicodeStrVector, char** binName) +{ + PyObject* intermediateUnicode = NULL; + + PyObject* py_bin = PyDict_GetItemString(op_dict, AS_PY_BIN_KEY); + + if (!py_bin) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation must contain a \"bin\" entry"); + } + + if (string_and_pyuni_from_pystring(py_bin, &intermediateUnicode, binName, err) != AEROSPIKE_OK) { + return err->code; + } + + if (intermediateUnicode) { + /* + If this happened, we have an extra pyobject. For historical reasons, we are strduping it's char value, + then decref'ing the item itself. + and storing the char* on a list of items to delete. + */ + char* dupStr = strdup(*binName); + *binName = dupStr; + as_vector_append(unicodeStrVector, dupStr); + Py_DecRef(intermediateUnicode); + } + return AEROSPIKE_OK; +} + +static as_status +get_asval(AerospikeClient * self, as_error * err, char* key, PyObject * op_dict, as_val** val, + as_static_pool * static_pool, int serializer_type, bool required) +{ + *val = NULL; + PyObject* py_val = PyDict_GetItemString(op_dict, key); + if (!py_val) { + if (required) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation must contain a \"%s\" entry", key); + } + else { + *val = NULL; + return AEROSPIKE_OK; + } + } + + /* If the value isn't required, None indicates that it isn't provided */ + if (py_val == Py_None && !required) { + *val = NULL; + return AEROSPIKE_OK; + } + return pyobject_to_val(self, err, py_val, val, static_pool, serializer_type); +} + +static as_status +get_val_list(AerospikeClient * self, as_error * err, const char* list_key, PyObject * op_dict, as_list** list_val, as_static_pool * static_pool, int serializer_type) +{ + *list_val = NULL; + PyObject* py_val = PyDict_GetItemString(op_dict, list_key); + if (!py_val) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation must contain a \"values\" entry"); + } + if (!PyList_Check(py_val)) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Value must be a list"); + } + return pyobject_to_list(self, err, py_val, list_val, static_pool, serializer_type); +} + +static as_status +get_int64_t(as_error * err, const char* key, PyObject * op_dict, int64_t* count) +{ + bool found = false; + if (get_optional_int64_t(err, key, op_dict, count, &found) != AEROSPIKE_OK) { + return err->code; + } + if (!found) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation missing required entry %s", key); + } + return AEROSPIKE_OK; +} + +static as_status +get_optional_int64_t(as_error * err, const char* key, PyObject * op_dict, int64_t* count, bool* found) +{ + *found = false; + PyObject* py_val = PyDict_GetItemString(op_dict, key); + if (!py_val) { + return AEROSPIKE_OK; + } + + if (PyInt_Check(py_val)) { + *count = (int64_t)PyInt_AsLong(py_val); + if (PyErr_Occurred()) { + if(PyErr_ExceptionMatches(PyExc_OverflowError)) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "%s too large", key); + } + + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert %s", key); + + } + } + else if (PyLong_Check(py_val)) { + *count = (int64_t)PyLong_AsLong(py_val); + if (PyErr_Occurred()) { + if(PyErr_ExceptionMatches(PyExc_OverflowError)) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "%s too large", key); + } + + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert %s", key); + } + } + else { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "%s must be an integer", key); + } + + *found = true; + return AEROSPIKE_OK; +} + +static as_status +get_list_return_type(as_error * err, PyObject * op_dict, int* return_type) +{ + int64_t int64_return_type; + int py_bool_val = -1; + + if (get_int64_t(err, AS_PY_LIST_RETURN_KEY, op_dict, &int64_return_type) != AEROSPIKE_OK) { + return err->code; + } + *return_type = int64_return_type; + PyObject* py_inverted = PyDict_GetItemString(op_dict, "inverted"); //NOT A MAGIC STRING + + if (py_inverted) { + py_bool_val = PyObject_IsTrue(py_inverted); + /* Essentially bool(py_bool_val) failed, so we raise an exception */ + if (py_bool_val == -1) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid inverted option"); + } + if (py_bool_val == 1) { + *return_type |= AS_LIST_RETURN_INVERTED; + } + } + + return AEROSPIKE_OK; +} + +static as_status +add_op_list_get_by_index(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops) +{ + int64_t index; + int return_type = AS_LIST_RETURN_VALUE; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_get_by_index(ops, bin, index, return_type)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add get_by_list_index operation"); + } + + return err->code; +} + +static as_status +add_op_list_get_by_index_range(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops) +{ + int64_t index; + int64_t count; + bool range_specified = false; + bool success = false; + int return_type = AS_LIST_RETURN_VALUE; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + /* Get the count of items, and store whether it was found in range_specified*/ + if (get_optional_int64_t(err, AS_PY_COUNT_KEY, op_dict, &count, &range_specified) != AEROSPIKE_OK) { + return err->code; + } + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (range_specified) { + success = as_operations_add_list_get_by_index_range(ops, bin, index, (uint64_t)count, return_type); + } + else { + success = as_operations_add_list_get_by_index_range_to_end(ops, bin, index, return_type); + } + + if (!success) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add get_by_list_index_range operation"); + } + + return err->code; +} + +static as_status +add_op_list_get_by_rank(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops) +{ + int64_t rank; + int return_type = AS_LIST_RETURN_VALUE; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_RANK_KEY, op_dict, &rank) != AEROSPIKE_OK) { + return err->code; + } + + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_get_by_rank(ops, bin, rank, return_type)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add get_by_list_index operation"); + } + + return err->code; +} + +static as_status +add_op_list_get_by_rank_range(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops) +{ + int64_t rank; + int64_t count; + bool range_specified = false; + bool success = false; + int return_type = AS_LIST_RETURN_VALUE; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_RANK_KEY, op_dict, &rank) != AEROSPIKE_OK) { + return err->code; + } + + /* Get the count of items, and store whether it was found in range_specified*/ + if (get_optional_int64_t(err, AS_PY_COUNT_KEY, op_dict, &count, &range_specified) != AEROSPIKE_OK) { + return err->code; + } + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (range_specified) { + success = as_operations_add_list_get_by_rank_range(ops, bin, rank, (uint64_t)count, return_type); + } + else { + success = as_operations_add_list_get_by_rank_range_to_end(ops, bin, rank, return_type); + } + + if (!success) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_get_by_rank_range operation"); + } + + return err->code; +} + +static as_status +add_op_list_get_by_value(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type) +{ + as_val* val = NULL; + int return_type = AS_LIST_RETURN_VALUE; + + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (get_asval(self, err, AS_PY_VAL_KEY, op_dict, &val, static_pool, serializer_type, true) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_get_by_value(ops, bin, val, return_type)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_get_by_value operation"); + } + + return err->code; +} + +static as_status +add_op_list_get_by_value_list(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type) +{ + as_list* value_list = NULL; + int return_type = AS_LIST_RETURN_VALUE; + + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (get_val_list(self, err, AS_PY_VALUES_KEY, op_dict, &value_list, static_pool, serializer_type) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_get_by_value_list(ops, bin, value_list, return_type)) { + /* Failed to add the operation, we need to destroy the list of values */ + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_get_by_value_list operation"); + as_val_destroy(value_list); + } + + return err->code; +} + +static as_status +add_op_list_get_by_value_range(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type) +{ + as_val* val_begin = NULL; + as_val* val_end = NULL; + + int return_type = AS_LIST_RETURN_VALUE; + + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (get_asval(self, err, AS_PY_VAL_BEGIN_KEY, op_dict, &val_begin, static_pool, serializer_type, false) != AEROSPIKE_OK) { + return err->code; + } + + if (get_asval(self, err, AS_PY_VAL_END_KEY, op_dict, &val_end, static_pool, serializer_type, false) != AEROSPIKE_OK) { + goto ERROR; + } + + if (!as_operations_add_list_get_by_value_range(ops, bin, val_begin, val_end, return_type)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_get_by_value_range operation"); + goto ERROR; + } + return err->code; + +ERROR: + /* Free the as_vals if they exists */ + if (val_begin) { + as_val_destroy(val_begin); + } + if (val_end) { + as_val_destroy(val_end); + } + return err->code; +} + +static as_status +add_op_list_remove_by_index(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops) +{ + int64_t index; + int return_type = AS_LIST_RETURN_VALUE; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_remove_by_index(ops, bin, index, return_type)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add remove_by_list_index operation"); + } + + return err->code; +} + +static as_status +add_op_list_remove_by_index_range(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops) +{ + int64_t index; + int64_t count; + bool range_specified = false; + bool success = false; + int return_type = AS_LIST_RETURN_VALUE; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + /* Get the count of items, and store whether it was found in range_specified*/ + if (get_optional_int64_t(err, AS_PY_COUNT_KEY, op_dict, &count, &range_specified) != AEROSPIKE_OK) { + return err->code; + } + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (range_specified) { + success = as_operations_add_list_remove_by_index_range(ops, bin, index, (uint64_t)count, return_type); + } + else { + success = as_operations_add_list_remove_by_index_range_to_end(ops, bin, index, return_type); + } + + if (!success) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add remove_by_list_index_range operation"); + } + + return err->code; +} + + +static as_status +add_op_list_remove_by_rank(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops) +{ + int64_t rank; + int return_type = AS_LIST_RETURN_VALUE; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_RANK_KEY, op_dict, &rank) != AEROSPIKE_OK) { + return err->code; + } + + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_remove_by_rank(ops, bin, rank, return_type)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_remove_by_rank operation"); + } + + return err->code; +} + +static as_status +add_op_list_remove_by_rank_range(as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops) +{ + int64_t rank; + int64_t count; + bool range_specified = false; + bool success = false; + int return_type = AS_LIST_RETURN_VALUE; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_RANK_KEY, op_dict, &rank) != AEROSPIKE_OK) { + return err->code; + } + + /* Get the count of items, and store whether it was found in range_specified*/ + if (get_optional_int64_t(err, AS_PY_COUNT_KEY, op_dict, &count, &range_specified) != AEROSPIKE_OK) { + return err->code; + } + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (range_specified) { + success = as_operations_add_list_remove_by_rank_range(ops, bin, rank, (uint64_t)count, return_type); + } + else { + success = as_operations_add_list_remove_by_rank_range_to_end(ops, bin, rank, return_type); + } + + if (!success) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_remove_by_rank_range operation"); + } + + return err->code; +} + +static as_status +add_op_list_remove_by_value(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type) +{ + as_val* val = NULL; + int return_type = AS_LIST_RETURN_VALUE; + + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (get_asval(self, err, AS_PY_VAL_KEY, op_dict, &val, static_pool, serializer_type, true) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_remove_by_value(ops, bin, val, return_type)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_remove_by_value operation"); + } + + return err->code; +} + +static as_status +add_op_list_remove_by_value_list(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type) +{ + as_list* value_list = NULL; + int return_type = AS_LIST_RETURN_VALUE; + + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (get_val_list(self, err, AS_PY_VALUES_KEY, op_dict, &value_list, static_pool, serializer_type) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_remove_by_value_list(ops, bin, value_list, return_type)) { + /* Failed to add the operation, we need to destroy the list of values */ + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_get_by_value_list operation"); + as_val_destroy(value_list); + } + + return err->code; +} + +static as_status +add_op_list_remove_by_value_range(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, as_vector * unicodeStrVector, as_operations * ops, + as_static_pool* static_pool, int serializer_type) +{ + as_val* val_begin = NULL; + as_val* val_end = NULL; + + int return_type = AS_LIST_RETURN_VALUE; + + if (get_list_return_type(err, op_dict, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (get_asval(self, err, AS_PY_VAL_BEGIN_KEY, op_dict, &val_begin, static_pool, serializer_type, false) != AEROSPIKE_OK) { + return err->code; + } + + if (get_asval(self, err, AS_PY_VAL_END_KEY, op_dict, &val_end, static_pool, serializer_type, false) != AEROSPIKE_OK) { + goto ERROR; + } + + if (!as_operations_add_list_remove_by_value_range(ops, bin, val_begin, val_end, return_type)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_remove_by_value_range operation"); + goto ERROR; + } + return err->code; + +ERROR: + /* Free the as_vals if they exists */ + if (val_begin) { + as_val_destroy(val_begin); + } + if (val_end) { + as_val_destroy(val_end); + } + return err->code; +} + +static as_status +add_op_list_set_order(as_error * err, char* bin, PyObject * op_dict, as_operations * ops) +{ + int64_t order_type_int; + if (get_int64_t(err, AS_PY_LIST_ORDER, op_dict, &order_type_int) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_set_order(ops, bin, (as_list_order)order_type_int)) { + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_set_order operation"); + } + + return AEROSPIKE_OK; +} + + +/* List sort */ +static as_status +add_op_list_sort(as_error * err, char* bin, PyObject * op_dict, as_operations * ops) { + int64_t sort_flags; + if (get_int64_t(err, AS_PY_LIST_SORT_FLAGS, op_dict, &sort_flags) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_sort(ops, bin, (as_list_sort_flags)sort_flags)) { + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_sort operation"); + } + + return AEROSPIKE_OK; +} + +/* Previously implemented list operations */ + +static as_status +add_op_list_append(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, as_operations * ops, + as_static_pool* static_pool, int serializer_type) +{ + as_val* val = NULL; + as_list_policy list_policy; + bool policy_in_use = false; + + if (get_list_policy(err, op_dict, &list_policy, &policy_in_use) != AEROSPIKE_OK) { + return err->code; + } + + if (get_asval(self, err, AS_PY_VAL_KEY, op_dict, &val, static_pool, serializer_type, true) != AEROSPIKE_OK) { + return err->code; + } + + if (policy_in_use) { + if (!as_operations_add_list_append_with_policy(ops, bin, &list_policy, val)) { + as_val_destroy(val); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_append operation"); + } + } + else { + if (!as_operations_add_list_append(ops, bin, val)) { + as_val_destroy(val); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_append operation"); + } + } + + return AEROSPIKE_OK; +} + +static as_status +add_op_list_append_items(AerospikeClient* self, as_error* err, char* bin, + PyObject* op_dict, as_operations* ops, + as_static_pool* static_pool, int serializer_type) { + as_list* items_list = NULL; + as_list_policy list_policy; + bool policy_in_use = false; + + if (get_list_policy(err, op_dict, &list_policy, &policy_in_use) != AEROSPIKE_OK) { + return err->code; + } + + if (get_val_list(self, err, AS_PY_VAL_KEY, op_dict, &items_list, static_pool, serializer_type) != AEROSPIKE_OK) { + return err->code; + } + + if (policy_in_use) { + if (!as_operations_add_list_append_items_with_policy(ops, bin, &list_policy, items_list)) { + as_val_destroy(items_list); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_append_items operation"); + } + } + else { + if (!as_operations_add_list_append_items(ops, bin, items_list)) { + as_val_destroy(items_list); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_append_items operation"); + } + } + + + return AEROSPIKE_OK; +} + +static as_status +add_op_list_insert(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, as_operations * ops, + as_static_pool* static_pool, int serializer_type) +{ + as_val* val = NULL; + int64_t index; + as_list_policy list_policy; + bool policy_in_use = false; + + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + if (get_list_policy(err, op_dict, &list_policy, &policy_in_use) != AEROSPIKE_OK) { + return err->code; + } + + if (get_asval(self, err, AS_PY_VAL_KEY, op_dict, &val, static_pool, serializer_type, true) != AEROSPIKE_OK) { + return err->code; + } + + if (policy_in_use) { + if (!as_operations_add_list_insert_with_policy(ops, bin, &list_policy, index, val)) { + as_val_destroy(val); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_insert operation"); + } + } + else { + if (!as_operations_add_list_insert(ops, bin, index, val)) { + as_val_destroy(val); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_insert operation"); + } + } + return AEROSPIKE_OK; +} + +static as_status +add_op_list_insert_items(AerospikeClient* self, as_error* err, char* bin, + PyObject* op_dict, as_operations* ops, + as_static_pool* static_pool, int serializer_type) { + as_list* items_list = NULL; + int64_t index; + as_list_policy list_policy; + bool policy_in_use = false; + + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + if (get_list_policy(err, op_dict, &list_policy, &policy_in_use) != AEROSPIKE_OK) { + return err->code; + } + + if (get_val_list(self, err, AS_PY_VAL_KEY, op_dict, &items_list, static_pool, serializer_type) != AEROSPIKE_OK) { + return err->code; + } + + if (policy_in_use) { + if (!as_operations_add_list_insert_items_with_policy(ops, bin, &list_policy, index, items_list)) { + as_val_destroy(items_list); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_insert_items operation"); + } + } + else { + if (!as_operations_add_list_insert_items(ops, bin, index, items_list)) { + as_val_destroy(items_list); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_insert_items operation"); + } + } + + return AEROSPIKE_OK; +} + +static as_status +add_op_list_increment(AerospikeClient* self, as_error * err, char* bin, + PyObject * op_dict, as_operations * ops, + as_static_pool* static_pool, int serializer_type) +{ + as_val* incr = NULL; + int64_t index; + as_list_policy list_policy; + bool policy_in_use = false; + + if (get_list_policy(err, op_dict, &list_policy, &policy_in_use) != AEROSPIKE_OK) { + return err->code; + } + + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + if (get_asval(self, err, AS_PY_VAL_KEY, op_dict, &incr, static_pool, serializer_type, true) != AEROSPIKE_OK) { + return err->code; + } + + if (policy_in_use) { + if (!as_operations_add_list_increment_with_policy(ops, bin, &list_policy, index, incr)) { + as_val_destroy(incr); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_increment operation"); + } + } + else { + if (!as_operations_add_list_increment(ops, bin, index, incr)) { + as_val_destroy(incr); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_increment operation"); + } + } + + return AEROSPIKE_OK; +} + +static as_status +add_op_list_pop(as_error * err, char* bin, PyObject * op_dict, as_operations * ops) +{ + int64_t index; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_pop(ops, bin, index)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_pop operation"); + } + + return err->code; +} + +static as_status +add_op_list_pop_range(as_error * err, char* bin, PyObject * op_dict, as_operations * ops) +{ + int64_t index; + int64_t count; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + /* Get the count*/ + if (get_int64_t(err, AS_PY_VAL_KEY, op_dict, &count) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_pop_range(ops, bin, index, (uint64_t)count)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to list_pop_range operation"); + } + + return err->code; +} + +static as_status +add_op_list_remove(as_error * err, char* bin, PyObject * op_dict, as_operations * ops) +{ + int64_t index; + + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_remove(ops, bin, index)) { + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_remove operation"); + } + + return AEROSPIKE_OK; +} + +static as_status +add_op_list_remove_range(as_error * err, char* bin, PyObject * op_dict, as_operations * ops) +{ + int64_t index; + int64_t count; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + /* Get the count*/ + if (get_int64_t(err, AS_PY_VAL_KEY, op_dict, &count) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_remove_range(ops, bin, index, (uint64_t)count)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to list_remove_range operation"); + } + + return err->code; +} + +static as_status +add_op_list_clear(as_error * err, char* bin, as_operations * ops) +{ + + if (!as_operations_add_list_clear(ops, bin)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_clear operation"); + } + + return AEROSPIKE_OK; +} + +static as_status +add_op_list_set(AerospikeClient* self, as_error * err, char* bin, PyObject * op_dict, as_operations * ops, + as_static_pool* static_pool, int serializer_type) +{ + as_val* val = NULL; + int64_t index; + as_list_policy list_policy; + bool policy_in_use = false; + + if (get_list_policy(err, op_dict, &list_policy, &policy_in_use) != AEROSPIKE_OK) { + return err->code; + } + + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + if (get_asval(self, err, AS_PY_VAL_KEY, op_dict, &val, static_pool, serializer_type, true) != AEROSPIKE_OK) { + return err->code; + } + + if (policy_in_use) { + if (!as_operations_add_list_set_with_policy(ops, bin, &list_policy, index, val)) { + as_val_destroy(val); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_set operation"); + } + } + else { + if (!as_operations_add_list_set(ops, bin, index, val)) { + as_val_destroy(val); + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_set operation"); + } + } + return AEROSPIKE_OK; +} + +static as_status +add_op_list_get(as_error * err, char* bin, PyObject * op_dict, as_operations * ops) +{ + int64_t index; + + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_get(ops, bin, index)) { + return as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_get operation"); + } + + return AEROSPIKE_OK; +} + +static as_status +add_op_list_get_range(as_error * err, char* bin, PyObject * op_dict, as_operations * ops) +{ + int64_t index; + int64_t count; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + /* Get the count*/ + if (get_int64_t(err, AS_PY_VAL_KEY, op_dict, &count) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_get_range(ops, bin, index, (uint64_t)count)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to list_get_range operation"); + } + + return err->code; +} + +static as_status +add_op_list_trim(as_error * err, char* bin, PyObject * op_dict, as_operations * ops) +{ + int64_t index; + int64_t count; + + /* Get the index*/ + if (get_int64_t(err, AS_PY_INDEX_KEY, op_dict, &index) != AEROSPIKE_OK) { + return err->code; + } + + /* Get the count*/ + if (get_int64_t(err, AS_PY_VAL_KEY, op_dict, &count) != AEROSPIKE_OK) { + return err->code; + } + + if (!as_operations_add_list_trim(ops, bin, index, (uint64_t)count)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to list_trim operation"); + } + + return err->code; +} + +static as_status +add_op_list_size(as_error * err, char* bin, as_operations * ops) +{ + + if (!as_operations_add_list_size(ops, bin)) { + as_error_update(err, AEROSPIKE_ERR_CLIENT, "Failed to add list_size operation"); + } + + return AEROSPIKE_OK; +} + + +static as_status +get_list_policy(as_error * err, PyObject * op_dict, as_list_policy* policy, bool* found) { + *found = false; + + PyObject* list_policy = PyDict_GetItemString(op_dict, AS_PY_LIST_POLICY); + + if (list_policy) { + if (pyobject_to_list_policy(err, list_policy, policy) != AEROSPIKE_OK) { + return err->code; + } + /* We succesfully converted the policy*/ + *found = true; + } + + return AEROSPIKE_OK; +} diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 45b95d125..b331011e6 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "client.h" #include "conversions.h" @@ -30,6 +31,7 @@ #include "policy.h" #include "serializer.h" #include "geo.h" +#include "cdt_list_operations.h" #include #include @@ -37,6 +39,12 @@ #include + +static as_status +get_operation(as_error* err, PyObject* op_dict, long* operation_ptr); + +#define PY_OPERATION_KEY "op" + #define BASE_VARIABLES\ as_error err;\ as_error_init(&err);\ @@ -102,6 +110,8 @@ static_pool, SERIALIZER_PYTHON) != AEROSPIKE_OK) {\ return err->code;\ } + +static as_status invertIfSpecified(as_error* err, PyObject* op_dict, uint64_t* return_value); /** ******************************************************************************************************* * This function will check whether operation can be performed @@ -167,6 +177,39 @@ int check_type(AerospikeClient * self, PyObject * py_value, int op, as_error *er return 0; } +static inline bool isListOp(int op) { + return (op == OP_LIST_APPEND || + op == OP_LIST_APPEND_ITEMS || + op == OP_LIST_INSERT || + op == OP_LIST_INSERT_ITEMS || + op == OP_LIST_POP || + op == OP_LIST_POP_RANGE || + op == OP_LIST_REMOVE || + op == OP_LIST_REMOVE_RANGE || + op == OP_LIST_CLEAR || + op == OP_LIST_SET || + op == OP_LIST_GET || + op == OP_LIST_GET_RANGE || + op == OP_LIST_TRIM || + op == OP_LIST_SIZE || + op == OP_LIST_INCREMENT || + op == OP_LIST_GET_BY_INDEX || + op == OP_LIST_GET_BY_INDEX_RANGE || + op == OP_LIST_GET_BY_RANK || + op == OP_LIST_GET_BY_RANK_RANGE || + op == OP_LIST_GET_BY_VALUE || + op == OP_LIST_GET_BY_VALUE_LIST || + op == OP_LIST_GET_BY_VALUE_RANGE || + op == OP_LIST_REMOVE_BY_INDEX || + op == OP_LIST_REMOVE_BY_INDEX_RANGE || + op == OP_LIST_REMOVE_BY_RANK || + op == OP_LIST_REMOVE_BY_RANK_RANGE || + op == OP_LIST_REMOVE_BY_VALUE || + op == OP_LIST_REMOVE_BY_VALUE_LIST || + op == OP_LIST_REMOVE_BY_VALUE_RANGE || + op == OP_LIST_SET_ORDER|| + op == OP_LIST_SORT); +} bool opRequiresIndex(int op) { return (op == OP_LIST_INSERT || op == OP_LIST_INSERT_ITEMS || op == OP_LIST_POP || op == OP_LIST_POP_RANGE || @@ -248,13 +291,25 @@ as_status add_op(AerospikeClient * self, as_error * err, PyObject * py_val, as_v PyObject * py_map_policy = NULL; PyObject * py_return_type = NULL; Py_ssize_t pos = 0; + + if (get_operation(err, py_val, &operation) != AEROSPIKE_OK) { + return err->code; + } + + /* Handle the list operations with a helper in the cdt_list_operate.c file */ + if (isListOp(operation)) { + return add_new_list_op(self, err, py_val, unicodeStrVector, static_pool, + ops, operation, ret_type, SERIALIZER_PYTHON); //This hardcoding matches current behavior + + } + while (PyDict_Next(py_val, &pos, &key_op, &value)) { if (!PyString_Check(key_op)) { return as_error_update(err, AEROSPIKE_ERR_CLIENT, "An operation key must be a string."); } else { char * name = PyString_AsString(key_op); - if (!strcmp(name,"op") && (PyInt_Check(value) || PyLong_Check(value))) { - operation = PyInt_AsLong(value); + if (!strcmp(name,"op")) { + continue; } else if (!strcmp(name, "bin")) { py_bin = value; } else if (!strcmp(name, "index")) { @@ -269,6 +324,9 @@ as_status add_op(AerospikeClient * self, as_error * err, PyObject * py_val, as_v py_map_policy = value; } else if (!strcmp(name, "return_type")) { py_return_type = value; + } + else if (strcmp(name, "inverted") == 0) { + continue; } else { return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation can contain only op, bin, index, key, val, return_type and map_policy keys"); @@ -333,6 +391,17 @@ as_status add_op(AerospikeClient * self, as_error * err, PyObject * py_val, as_v } return_type = PyInt_AsLong(py_return_type); } + + /* Add the inverted flag to the return type if it's present */ + if (invertIfSpecified(err, py_val, &return_type) != AEROSPIKE_OK) { + return err->code; + } + + if (err->code != AEROSPIKE_OK) { + return err->code; + } + + *ret_type = return_type; if (py_index) { @@ -448,71 +517,6 @@ as_status add_op(AerospikeClient * self, as_error * err, PyObject * py_val, as_v as_operations_add_write(ops, bin, (as_bin_value *) put_val); break; - //------- LIST OPERATIONS --------- - case OP_LIST_APPEND: - CONVERT_VAL_TO_AS_VAL(); - as_operations_add_list_append(ops, bin, put_val); - break; - case OP_LIST_APPEND_ITEMS: - CONVERT_VAL_TO_AS_VAL(); - as_operations_add_list_append_items(ops, bin, (as_list*)put_val); - break; - case OP_LIST_INSERT: - CONVERT_VAL_TO_AS_VAL(); - as_operations_add_list_insert(ops, bin, index, put_val); - break; - case OP_LIST_INSERT_ITEMS: - CONVERT_VAL_TO_AS_VAL(); - as_operations_add_list_insert_items(ops, bin, index, (as_list*)put_val); - break; - case OP_LIST_INCREMENT: - CONVERT_VAL_TO_AS_VAL(); - as_operations_add_list_increment(ops, bin, index, put_val); - break; - case OP_LIST_POP: - as_operations_add_list_pop(ops, bin, index); - break; - case OP_LIST_POP_RANGE: - if (py_value && (pyobject_to_index(self, err, py_value, &offset) != AEROSPIKE_OK)) { - return err->code; - } - as_operations_add_list_pop_range(ops, bin, index, offset); - break; - case OP_LIST_REMOVE: - as_operations_add_list_remove(ops, bin, index); - break; - case OP_LIST_REMOVE_RANGE: - if (py_value && (pyobject_to_index(self, err, py_value, &offset) != AEROSPIKE_OK)) { - return err->code; - } - as_operations_add_list_remove_range(ops, bin, index, offset); - break; - case OP_LIST_CLEAR: - as_operations_add_list_clear(ops, bin); - break; - case OP_LIST_SET: - CONVERT_VAL_TO_AS_VAL(); - as_operations_add_list_set(ops, bin, index, put_val); - break; - case OP_LIST_GET: - as_operations_add_list_get(ops, bin, index); - break; - case OP_LIST_GET_RANGE: - if (py_value && (pyobject_to_index(self, err, py_value, &offset) != AEROSPIKE_OK)) { - return err->code; - } - as_operations_add_list_get_range(ops, bin, index, offset); - break; - case OP_LIST_TRIM: - if (py_value && (pyobject_to_index(self, err, py_value, &offset) != AEROSPIKE_OK)) { - return err->code; - } - as_operations_add_list_trim(ops, bin, index, offset); - break; - case OP_LIST_SIZE: - as_operations_add_list_size(ops, bin); - break; - //------- MAP OPERATIONS --------- case OP_MAP_SET_POLICY: as_operations_add_map_set_policy(ops, bin, &map_policy); @@ -595,6 +599,10 @@ as_status add_op(AerospikeClient * self, as_error * err, PyObject * py_val, as_v CONVERT_KEY_TO_AS_VAL(); as_operations_add_map_get_by_key_range(ops, bin, put_key, put_range, return_type); break; + case OP_MAP_GET_BY_KEY_LIST: + CONVERT_VAL_TO_AS_VAL(); + as_operations_add_map_get_by_key_list(ops, bin, (as_list *)put_val, return_type); + break; case OP_MAP_GET_BY_VALUE: CONVERT_VAL_TO_AS_VAL(); as_operations_add_map_get_by_value(ops, bin, put_val, return_type); @@ -604,6 +612,10 @@ as_status add_op(AerospikeClient * self, as_error * err, PyObject * py_val, as_v CONVERT_RANGE_TO_AS_VAL(); as_operations_add_map_get_by_value_range(ops, bin, put_val, put_range, return_type); break; + case OP_MAP_GET_BY_VALUE_LIST: + CONVERT_VAL_TO_AS_VAL(); + as_operations_add_map_get_by_value_list(ops, bin, (as_list *)put_val, return_type); + break; case OP_MAP_GET_BY_INDEX: as_operations_add_map_get_by_index(ops, bin, index, return_type); break; @@ -1195,3 +1207,46 @@ PyObject * AerospikeClient_Touch(AerospikeClient * self, PyObject * args, PyObje return PyLong_FromLong(0); } +static as_status +get_operation(as_error* err, PyObject* op_dict, long* operation_ptr) +{ + PyObject* py_operation = PyDict_GetItemString(op_dict, PY_OPERATION_KEY); + if (!py_operation) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation must contain an \"op\" entry"); + } + if (!PyInt_Check(py_operation)) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation must be an integer"); + } + + *operation_ptr = PyLong_AsLong(py_operation); + if (PyErr_Occurred()) { + if (*operation_ptr == -1 && PyErr_ExceptionMatches(PyExc_OverflowError)) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Operation code too large"); + } + else { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid operation"); + } + } + return AEROSPIKE_OK; +} + +static as_status +invertIfSpecified(as_error* err, PyObject* op_dict, uint64_t* return_value) { + PyObject* pyInverted = PyDict_GetItemString(op_dict, "inverted"); + int truthValue; + if (!pyInverted) { + return AEROSPIKE_OK; + } + truthValue = PyObject_IsTrue(pyInverted); + + /* An error ocurred, update the flag */ + if (truthValue == -1) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid inverted value"); + } + + if (truthValue) { + *return_value |= AS_MAP_RETURN_INVERTED; + } + + return AEROSPIKE_OK; +} \ No newline at end of file diff --git a/src/main/client/operate_map.c b/src/main/client/operate_map.c index ed9bbc1cd..d459e5641 100644 --- a/src/main/client/operate_map.c +++ b/src/main/client/operate_map.c @@ -119,6 +119,9 @@ return NULL;\ } +/* Forward declaration for function which inverts an operation */ +static as_status invertIfSpecified(as_error* err, PyObject* py_inverted, uint64_t* returnType); + PyObject * AerospikeClient_MapSetPolicy(AerospikeClient * self, PyObject * args, PyObject * kwds) { BASE_VARIABLES @@ -440,6 +443,8 @@ PyObject * AerospikeClient_MapRemoveByKey(AerospikeClient * self, PyObject * arg PyObject * py_mapKey = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject * py_inverted = NULL; + uint64_t returnType; as_record *rec = NULL; as_val * key_put; @@ -448,9 +453,13 @@ PyObject * AerospikeClient_MapRemoveByKey(AerospikeClient * self, PyObject * arg CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "map_key", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OO:map_remove_by_key", kwlist, - &py_key, &py_bin, &py_mapKey, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "map_key", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OOO:map_remove_by_key", kwlist, + &py_key, &py_bin, &py_mapKey, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -483,6 +492,7 @@ PyObject * AerospikeClient_MapRemoveByKeyList(AerospikeClient * self, PyObject * PyObject * py_list = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; as_record *rec = NULL; as_val * list_put; @@ -491,9 +501,13 @@ PyObject * AerospikeClient_MapRemoveByKeyList(AerospikeClient * self, PyObject * CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "list", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OO:map_remove_by_key_list", kwlist, - &py_key, &py_bin, &py_list, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "list", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OOO:map_remove_by_key_list", kwlist, + &py_key, &py_bin, &py_list, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -532,6 +546,8 @@ PyObject * AerospikeClient_MapRemoveByKeyRange(AerospikeClient * self, PyObject PyObject * py_range = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; + uint64_t returnType; as_record *rec = NULL; as_val * key_put; @@ -541,9 +557,13 @@ PyObject * AerospikeClient_MapRemoveByKeyRange(AerospikeClient * self, PyObject CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "map_key", "range", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOOl|OO:map_remove_by_key_range", kwlist, - &py_key, &py_bin, &py_mapKey, &py_range, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "map_key", "range", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOOl|OOO:map_remove_by_key_range", kwlist, + &py_key, &py_bin, &py_mapKey, &py_range, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -580,6 +600,7 @@ PyObject * AerospikeClient_MapRemoveByValue(AerospikeClient * self, PyObject * a PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; as_record *rec = NULL; as_val * value_put; @@ -588,9 +609,13 @@ PyObject * AerospikeClient_MapRemoveByValue(AerospikeClient * self, PyObject * a CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "val", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OO:map_remove_by_value", kwlist, - &py_key, &py_bin, &py_mapValue, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "val", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OOO:map_remove_by_value", kwlist, + &py_key, &py_bin, &py_mapValue, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -624,6 +649,8 @@ PyObject * AerospikeClient_MapRemoveByValueList(AerospikeClient * self, PyObject PyObject * py_list = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; + uint64_t returnType; as_record *rec = NULL; as_val * list_put; @@ -632,9 +659,13 @@ PyObject * AerospikeClient_MapRemoveByValueList(AerospikeClient * self, PyObject CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "list", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OO:map_remove_by_value_list", kwlist, - &py_key, &py_bin, &py_list, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "list", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OOO:map_remove_by_value_list", kwlist, + &py_key, &py_bin, &py_list, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -674,6 +705,8 @@ PyObject * AerospikeClient_MapRemoveByValueRange(AerospikeClient * self, PyObjec PyObject * py_range = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; + uint64_t returnType; as_record *rec = NULL; as_val * value_put; @@ -683,9 +716,13 @@ PyObject * AerospikeClient_MapRemoveByValueRange(AerospikeClient * self, PyObjec CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "val", "range", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOOl|OO:map_remove_by_value_range", kwlist, - &py_key, &py_bin, &py_mapValue, &py_range, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "val", "range", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOOl|OOO:map_remove_by_value_range", kwlist, + &py_key, &py_bin, &py_mapValue, &py_range, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -718,6 +755,8 @@ PyObject * AerospikeClient_MapRemoveByIndex(AerospikeClient * self, PyObject * a PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; + uint64_t index; uint64_t returnType; as_record *rec = NULL; @@ -726,9 +765,13 @@ PyObject * AerospikeClient_MapRemoveByIndex(AerospikeClient * self, PyObject * a CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "index", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOll|OO:map_remove_by_index", kwlist, - &py_key, &py_bin, &index, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "index", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOll|OOO:map_remove_by_index", kwlist, + &py_key, &py_bin, &index, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -752,6 +795,7 @@ PyObject * AerospikeClient_MapRemoveByIndexRange(AerospikeClient * self, PyObjec PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; uint64_t index; uint64_t range; @@ -761,9 +805,13 @@ PyObject * AerospikeClient_MapRemoveByIndexRange(AerospikeClient * self, PyObjec CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "index", "range", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOlll|OO:map_remove_by_index_range", kwlist, - &py_key, &py_bin, &index, &range, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "index", "range", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOlll|OOO:map_remove_by_index_range", kwlist, + &py_key, &py_bin, &index, &range, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -787,6 +835,7 @@ PyObject * AerospikeClient_MapRemoveByRank(AerospikeClient * self, PyObject * ar PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; uint64_t rank; uint64_t returnType; as_record *rec = NULL; @@ -795,9 +844,13 @@ PyObject * AerospikeClient_MapRemoveByRank(AerospikeClient * self, PyObject * ar CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "rank", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOll|OO:map_remove_by_rank", kwlist, - &py_key, &py_bin, &rank, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "rank", "return_type", "meta", "policy","inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOll|OOO:map_remove_by_rank", kwlist, + &py_key, &py_bin, &rank, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -821,6 +874,8 @@ PyObject * AerospikeClient_MapRemoveByRankRange(AerospikeClient * self, PyObject PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; + uint64_t returnType; uint64_t rank; uint64_t range; @@ -830,9 +885,13 @@ PyObject * AerospikeClient_MapRemoveByRankRange(AerospikeClient * self, PyObject CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "rank", "range", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOlll|OO:map_remove_by_rank_range", kwlist, - &py_key, &py_bin, &rank, &range, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "rank", "range", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOlll|OOO:map_remove_by_rank_range", kwlist, + &py_key, &py_bin, &rank, &range, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -860,6 +919,7 @@ PyObject * AerospikeClient_MapGetByKey(AerospikeClient * self, PyObject * args, PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; as_record* rec = NULL; as_val * key_put; @@ -868,9 +928,13 @@ PyObject * AerospikeClient_MapGetByKey(AerospikeClient * self, PyObject * args, CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "map_key", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OO:map_get_by_key", kwlist, - &py_key, &py_bin, &py_mapKey, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "map_key", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OOO:map_get_by_key", kwlist, + &py_key, &py_bin, &py_mapKey, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -904,6 +968,8 @@ PyObject * AerospikeClient_MapGetByValue(AerospikeClient * self, PyObject * args PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; + uint64_t returnType; as_record *rec = NULL; as_val * value_put; @@ -912,9 +978,13 @@ PyObject * AerospikeClient_MapGetByValue(AerospikeClient * self, PyObject * args CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "val", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OO:map_get_by_value", kwlist, - &py_key, &py_bin, &py_mapValue, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "val", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OOO:map_get_by_value", kwlist, + &py_key, &py_bin, &py_mapValue, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -948,6 +1018,7 @@ PyObject * AerospikeClient_MapGetByKeyRange(AerospikeClient * self, PyObject * a PyObject * py_meta = NULL; PyObject * py_policy = NULL; PyObject * py_range = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; as_record *rec = NULL; as_val * map_key; @@ -957,9 +1028,13 @@ PyObject * AerospikeClient_MapGetByKeyRange(AerospikeClient * self, PyObject * a CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "map_key", "range", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOOl|OO:map_get_by_key_range", kwlist, - &py_key, &py_bin, &py_mapKey, &py_range, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "map_key", "range", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOOl|OOO:map_get_by_key_range", kwlist, + &py_key, &py_bin, &py_mapKey, &py_range, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -997,6 +1072,7 @@ PyObject * AerospikeClient_MapGetByValueRange(AerospikeClient * self, PyObject * PyObject * py_meta = NULL; PyObject * py_policy = NULL; PyObject * py_range = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; as_record *rec = NULL; as_val * value_put; @@ -1006,9 +1082,13 @@ PyObject * AerospikeClient_MapGetByValueRange(AerospikeClient * self, PyObject * CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "val", "range", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOOl|OO:map_get_by_value_range", kwlist, - &py_key, &py_bin, &py_mapValue, &py_range, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "val", "range", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOOl|OOO:map_get_by_value_range", kwlist, + &py_key, &py_bin, &py_mapValue, &py_range, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -1045,6 +1125,7 @@ PyObject* AerospikeClient_MapGetByValueList(AerospikeClient* self, PyObject* arg PyObject * py_meta = NULL; PyObject * py_policy = NULL; PyObject* py_value_list = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; // C client function arg vars @@ -1062,9 +1143,13 @@ PyObject* AerospikeClient_MapGetByValueList(AerospikeClient* self, PyObject* arg CHECK_CONNECTED(); - static char* kwlist[] = {"key", "bin", "value_list", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OO:map_get_by_value_list", - kwlist, &py_key, &py_bin, &py_value_list, &returnType, &py_meta, &py_policy) == false) { + static char* kwlist[] = {"key", "bin", "value_list", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OOO:map_get_by_value_list", + kwlist, &py_key, &py_bin, &py_value_list, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -1109,6 +1194,7 @@ PyObject* AerospikeClient_MapGetByKeyList(AerospikeClient* self, PyObject* args, PyObject * py_meta = NULL; PyObject * py_policy = NULL; PyObject* py_key_list = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; // C client function arg vars @@ -1126,9 +1212,13 @@ PyObject* AerospikeClient_MapGetByKeyList(AerospikeClient* self, PyObject* args, CHECK_CONNECTED(); - static char* kwlist[] = {"key", "bin", "key_list", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OO:map_get_by_key_list", - kwlist, &py_key, &py_bin, &py_key_list, &returnType, &py_meta, &py_policy) == false) { + static char* kwlist[] = {"key", "bin", "key_list", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOOl|OOO:map_get_by_key_list", + kwlist, &py_key, &py_bin, &py_key_list, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -1169,6 +1259,7 @@ PyObject * AerospikeClient_MapGetByIndex(AerospikeClient * self, PyObject * args PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; uint64_t index; as_record *rec = NULL; @@ -1177,9 +1268,13 @@ PyObject * AerospikeClient_MapGetByIndex(AerospikeClient * self, PyObject * args CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "index", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOll|OO:map_get_by_index", kwlist, - &py_key, &py_bin, &index, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "index", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOll|OOO:map_get_by_index", kwlist, + &py_key, &py_bin, &index, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -1203,6 +1298,7 @@ PyObject * AerospikeClient_MapGetByIndexRange(AerospikeClient * self, PyObject * PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; uint64_t index; uint64_t range; @@ -1212,9 +1308,13 @@ PyObject * AerospikeClient_MapGetByIndexRange(AerospikeClient * self, PyObject * CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "index", "range", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOlll|OO:map_get_by_index_range", kwlist, - &py_key, &py_bin, &index, &range, &returnType, &py_meta, &py_policy ) == false) { + static char * kwlist[] = {"key", "bin", "index", "range", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOlll|OOO:map_get_by_index_range", kwlist, + &py_key, &py_bin, &index, &range, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -1238,6 +1338,8 @@ PyObject * AerospikeClient_MapGetByRank(AerospikeClient * self, PyObject * args, PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; + uint64_t returnType; uint64_t rank; as_record *rec = NULL; @@ -1246,9 +1348,13 @@ PyObject * AerospikeClient_MapGetByRank(AerospikeClient * self, PyObject * args, CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "rank", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOll|OO:map_get_by_rank", kwlist, - &py_key, &py_bin, &rank, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "rank", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOll|OOO:map_get_by_rank", kwlist, + &py_key, &py_bin, &rank, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -1272,6 +1378,7 @@ PyObject * AerospikeClient_MapGetByRankRange(AerospikeClient * self, PyObject * PyObject * py_result = NULL; PyObject * py_meta = NULL; PyObject * py_policy = NULL; + PyObject* py_inverted = NULL; uint64_t returnType; uint64_t rank; uint64_t range; @@ -1281,9 +1388,13 @@ PyObject * AerospikeClient_MapGetByRankRange(AerospikeClient * self, PyObject * CHECK_CONNECTED(); - static char * kwlist[] = {"key", "bin", "rank", "range", "return_type", "meta", "policy", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kwds, "OOlll|OO:map_get_by_rank_range", kwlist, - &py_key, &py_bin, &rank, &range, &returnType, &py_meta, &py_policy) == false) { + static char * kwlist[] = {"key", "bin", "rank", "range", "return_type", "meta", "policy", "inverted", NULL}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "OOlll|OOO:map_get_by_rank_range", kwlist, + &py_key, &py_bin, &rank, &range, &returnType, &py_meta, &py_policy, &py_inverted) == false) { + goto CLEANUP; + } + + if (invertIfSpecified(&err, py_inverted, &returnType) != AEROSPIKE_OK) { goto CLEANUP; } @@ -1299,3 +1410,23 @@ PyObject * AerospikeClient_MapGetByRankRange(AerospikeClient * self, PyObject * return py_result; } + +static as_status +invertIfSpecified(as_error* err, PyObject* py_inverted, uint64_t* returnType) { + if (!py_inverted) { + return AEROSPIKE_OK; + } + + int truthValue = PyObject_IsTrue(py_inverted); + + /* An error ocurred, update the flag */ + if (truthValue == -1) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid inverted value"); + } + + if (truthValue) { + *returnType |= AS_MAP_RETURN_INVERTED; + } + + return AEROSPIKE_OK; +} diff --git a/src/main/conversions.c b/src/main/conversions.c index f29dc6671..571f0a2be 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -1879,3 +1879,30 @@ batch_read_records_to_pyobject(AerospikeClient *self, as_error *err, as_batch_re } return AEROSPIKE_OK; } + +/* +This fetches a string from a Python String like. If it is a unicode in Python27, we need to convert it +to a bytes like object first, and keep track of the intermediate object for later deletion. +*/ +as_status +string_and_pyuni_from_pystring(PyObject* py_string, PyObject** pyuni_r, char** c_str_ptr, as_error* err) { + /* Not needed if we drop support for Python > 3 < 3.3 */ + + PyObject* intermediate_uni = NULL; + *c_str_ptr = NULL; + if (PyString_Check(py_string)) { + *c_str_ptr = PyString_AsString(py_string); + return AEROSPIKE_OK; + + } else if (PyUnicode_Check(py_string)) { + intermediate_uni = PyUnicode_AsUTF8String(py_string); + if (!intermediate_uni) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid unicode value"); + } + + *c_str_ptr = PyBytes_AsString(intermediate_uni); + *pyuni_r = intermediate_uni; + return AEROSPIKE_OK; + } + return as_error_update(err, AEROSPIKE_ERR_PARAM, "String value required"); +} \ No newline at end of file diff --git a/src/main/policy.c b/src/main/policy.c index fce02d09c..c5031ce6b 100644 --- a/src/main/policy.c +++ b/src/main/policy.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include "aerospike/as_scan.h" #include "aerospike/as_job.h" @@ -189,6 +188,8 @@ AerospikeConstants aerospike_constants[] = { { OP_MAP_GET_BY_INDEX_RANGE , "OP_MAP_GET_BY_INDEX_RANGE"}, { OP_MAP_GET_BY_RANK , "OP_MAP_GET_BY_RANK"}, { OP_MAP_GET_BY_RANK_RANGE , "OP_MAP_GET_BY_RANK_RANGE"}, + { OP_MAP_GET_BY_VALUE_LIST, "OP_MAP_GET_BY_VALUE_LIST"}, + { OP_MAP_GET_BY_KEY_LIST, "OP_MAP_GET_BY_KEY_LIST" }, { AS_MAP_UNORDERED , "MAP_UNORDERED"}, { AS_MAP_KEY_ORDERED , "MAP_KEY_ORDERED"}, @@ -213,7 +214,38 @@ AerospikeConstants aerospike_constants[] = { { AS_RECORD_NO_CHANGE_TTL , "TTL_DONT_UPDATE"}, { AS_AUTH_INTERNAL, "AUTH_INTERNAL"}, { AS_AUTH_EXTERNAL, "AUTH_EXTERNAL"}, - { AS_AUTH_EXTERNAL_INSECURE, "AUTH_EXTERNAL_INSECURE"} + { AS_AUTH_EXTERNAL_INSECURE, "AUTH_EXTERNAL_INSECURE"}, + /* New CDT Operations, post 3.16.0.1 */ + {OP_LIST_GET_BY_INDEX, "OP_LIST_GET_BY_INDEX"}, + {OP_LIST_GET_BY_INDEX_RANGE, "OP_LIST_GET_BY_INDEX_RANGE"}, + {OP_LIST_GET_BY_RANK, "OP_LIST_GET_BY_RANK"}, + {OP_LIST_GET_BY_RANK_RANGE, "OP_LIST_GET_BY_RANK_RANGE"}, + {OP_LIST_GET_BY_VALUE, "OP_LIST_GET_BY_VALUE"}, + {OP_LIST_GET_BY_VALUE_LIST, "OP_LIST_GET_BY_VALUE_LIST"}, + {OP_LIST_GET_BY_VALUE_RANGE, "OP_LIST_GET_BY_VALUE_RANGE"}, + {OP_LIST_REMOVE_BY_INDEX, "OP_LIST_REMOVE_BY_INDEX"}, + {OP_LIST_REMOVE_BY_INDEX_RANGE, "OP_LIST_REMOVE_BY_INDEX_RANGE"}, + {OP_LIST_REMOVE_BY_RANK, "OP_LIST_REMOVE_BY_RANK"}, + {OP_LIST_REMOVE_BY_RANK_RANGE, "OP_LIST_REMOVE_BY_RANK_RANGE"}, + {OP_LIST_REMOVE_BY_VALUE, "OP_LIST_REMOVE_BY_VALUE"}, + {OP_LIST_REMOVE_BY_VALUE_LIST, "OP_LIST_REMOVE_BY_VALUE_LIST"}, + {OP_LIST_REMOVE_BY_VALUE_RANGE, "OP_LIST_REMOVE_BY_VALUE_RANGE"}, + {OP_LIST_SET_ORDER, "OP_LIST_SET_ORDER"}, + {OP_LIST_SORT, "OP_LIST_SORT"}, + {AS_LIST_RETURN_NONE, "LIST_RETURN_NONE"}, + {AS_LIST_RETURN_INDEX, "LIST_RETURN_INDEX"}, + {AS_LIST_RETURN_REVERSE_INDEX, "LIST_RETURN_REVERSE_INDEX"}, + {AS_LIST_RETURN_RANK, "LIST_RETURN_RANK"}, + {AS_LIST_RETURN_REVERSE_RANK, "LIST_RETURN_REVERSE_RANK"}, + {AS_LIST_RETURN_COUNT, "LIST_RETURN_COUNT"}, + {AS_LIST_RETURN_VALUE, "LIST_RETURN_VALUE"}, + {AS_LIST_SORT_DROP_DUPLICATES, "LIST_SORT_DROP_DUPLICATES"}, + {AS_LIST_SORT_DEFAULT, "LIST_SORT_DEFAULT"}, + {AS_LIST_WRITE_ADD_UNIQUE, "LIST_WRITE_ADD_UNIQUE"}, + {AS_LIST_WRITE_INSERT_BOUNDED, "LIST_WRITE_INSERT_BOUNDED"}, + {AS_LIST_ORDERED, "LIST_ORDERED"}, + {AS_LIST_UNORDERED, "LIST_UNORDERED"} + }; static @@ -468,6 +500,7 @@ as_status pyobject_to_policy_query(as_error * err, PyObject * py_policy, POLICY_SET_FIELD(deserialize, bool); + POLICY_SET_FIELD(fail_on_cluster_change, bool); // Update the policy POLICY_UPDATE(); @@ -721,3 +754,64 @@ as_status pyobject_to_map_policy(as_error * err, PyObject * py_policy, return err->code; } + +as_status +pyobject_to_list_policy(as_error* err, PyObject* py_policy, as_list_policy* list_policy) +{ + as_list_policy_init(list_policy); + PyObject* py_val = NULL; + long list_order = AS_LIST_UNORDERED; + long flags = AS_LIST_WRITE_DEFAULT; + + if (!py_policy || py_policy == Py_None) { + return AEROSPIKE_OK; + } + + if (!PyDict_Check(py_policy)) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "List policy must be a dictionary."); + } + + py_val = PyDict_GetItemString(py_policy, "list_order"); + if (py_val && py_val != Py_None) { + if (PyInt_Check(py_val)) { + list_order = (int64_t)PyInt_AsLong(py_val); + if (PyErr_Occurred()) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert list_order"); + + } + } + else if (PyLong_Check(py_val)) { + list_order = (int64_t)PyLong_AsLong(py_val); + if (PyErr_Occurred()) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert list_order"); + } + } + else { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid List order"); + } + } + + py_val = PyDict_GetItemString(py_policy, "write_flags"); + if (py_val && py_val != Py_None) { + if (PyInt_Check(py_val)) { + flags = (int64_t)PyInt_AsLong(py_val); + if (PyErr_Occurred()) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert write_flags"); + + } + } + else if (PyLong_Check(py_val)) { + flags = (int64_t)PyLong_AsLong(py_val); + if (PyErr_Occurred()) { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert write_flags"); + } + } + else { + return as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid write_flags"); + } + } + + as_list_policy_set(list_policy, (as_list_order)list_order, (as_list_write_flags)flags); + + return AEROSPIKE_OK; +} diff --git a/src/main/query/predexp.c b/src/main/query/predexp.c index 8174b562a..d217a6e53 100644 --- a/src/main/query/predexp.c +++ b/src/main/query/predexp.c @@ -59,13 +59,6 @@ typedef as_predexp_base* (single_string_predexp_constructor) (char const*); typedef as_predexp_base* (no_arg_predexp_constructor) (void); -/* - * Convert either a PyString or Py_Unicode into a char*, return a pointer to any intermediate - * PyObject which need to be cleaned up. - */ -as_status -string_and_pyuni_from_pystring(PyObject* py_string, PyObject** pyuni_r, char** c_str_ptr, as_error* err); - /* Predexp constructor function which takes a single char* argument */ as_status add_single_string_arg_predicate(as_query* query, PyObject* predicate, as_error* err, @@ -642,30 +635,6 @@ as_status add_mapval_iterate_and(as_query* query, PyObject* predicate, as_error* return add_single_string_arg_predicate(query, predicate, err, as_predexp_mapval_iterate_and, "mapval_iterate_and"); } -as_status -string_and_pyuni_from_pystring(PyObject* py_string, PyObject** pyuni_r, char** c_str_ptr, as_error* err) { - /* Not needed if we drop support for Python > 3 < 3.3 */ - - PyObject* intermediate_uni = NULL; - *c_str_ptr = NULL; - if (PyString_Check(py_string)) { - *c_str_ptr = PyString_AsString(py_string); - return AEROSPIKE_OK; - - } else if (PyUnicode_Check(py_string)) { - intermediate_uni = PyUnicode_AsUTF8String(py_string); - if (!intermediate_uni) { - return as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid unicode value"); - } - - *c_str_ptr = PyBytes_AsString(intermediate_uni); - *pyuni_r = intermediate_uni; - return AEROSPIKE_OK; - } - /* This should never be reached */ - return as_error_update(err, AEROSPIKE_ERR_PARAM, "String value required"); -} - as_status add_single_string_arg_predicate(as_query* query, PyObject* predicate, as_error* err, single_string_predexp_constructor* constructor, const char* predicate_name) { diff --git a/src/main/tls_config.c b/src/main/tls_config.c index d6e815287..2cc0f3f9f 100644 --- a/src/main/tls_config.c +++ b/src/main/tls_config.c @@ -44,6 +44,7 @@ setup_tls_config(as_config* config, PyObject* tls_config) _set_config_str_if_present(config, tls_config, "cert_blacklist"); _set_config_str_if_present(config, tls_config, "keyfile"); _set_config_str_if_present(config, tls_config, "certfile"); + _set_config_str_if_present(config, tls_config, "keyfile_pw"); // Setup The boolean values of the struct if they are present config_value = PyDict_GetItemString(tls_config, "enable"); @@ -148,6 +149,9 @@ _set_config_str_if_present(as_config* config, PyObject* tls_config, as_config_tls_set_certfile(config, (const char*)config_value_str); } + else if (strcmp("keyfile_pw", key) == 0) { + as_config_tls_set_keyfile_pw(config, (const char*) config_value_str); + } } } } diff --git a/test/new_tests/test_inverted_map_operations.py b/test/new_tests/test_inverted_map_operations.py new file mode 100644 index 000000000..c7f95b45b --- /dev/null +++ b/test/new_tests/test_inverted_map_operations.py @@ -0,0 +1,213 @@ +# -*- coding: utf-8 -*- +import pytest +import sys +from aerospike import exception as e +from aerospike_helpers.operations import map_operations as map_ops + +aerospike = pytest.importorskip("aerospike") +try: + import aerospike +except: + print("Please install aerospike python client.") + sys.exit(1) + + +def get_map_result_from_operation(client, key, operations, res_bin): + ''' + Map operations return a 1 bin record. + This operation extracts the dictionary from it. + ''' + _, _, result_bins = client.operate(key, operations) + return result_bins[res_bin] + + +def maps_have_same_keys(map1, map2): + ''' + Return True iff the two maps have an identical set of keys + ''' + if len(map1) != len(map2): + return False + + for key in map1: + if key not in map2: + return False + return True + + +def maps_have_same_values(map1, map2): + if len(map1) != len(map2): + return False + + for key in map1: + if key not in map2 or map1[key] != map2[key]: + return False + return True + + +def sort_map(client, test_key, test_bin): + map_policy = {"map_write_mode": aerospike.MAP_CREATE_ONLY, "map_order": aerospike.MAP_KEY_ORDERED} + operations = [map_ops.map_set_policy(test_bin, map_policy)] + client.operate(test_key, operations) + +class TestNewListOperationsHelpers(object): + + @pytest.fixture(autouse=True) + def setup(self, request, as_connection): + """ + Setup Method + """ + self.keys = [] + self.test_map = { + "a" : 5, + "b" : 4, + "c" : 3, + "d" : 2, + "e" : 1 + } + + self.test_key = 'test', 'demo', 'new_map_op' + self.test_bin = 'map' + self.as_connection.put(self.test_key, {self.test_bin: self.test_map}) + self.keys.append(self.test_key) + + yield + + for key in self.keys: + try: + self.as_connection.remove(key) + except e.AerospikeError: + pass + + def test_map_remove_by_key_list(self): + operations = [map_ops.map_remove_by_key_list( + self.test_bin, ["a", "b", "c"], return_type=aerospike.MAP_RETURN_VALUE, inverted=True)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set([self.test_map["e"], self.test_map["d"]]) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "d" not in res_map + assert "e" not in res_map + + def test_map_remove_by_key_range(self): + operations = [map_ops.map_remove_by_key_range( + self.test_bin, "a", "d", return_type=aerospike.MAP_RETURN_VALUE, inverted=True)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set([self.test_map["e"], self.test_map["d"]]) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "d" not in res_map + assert "e" not in res_map + + def test_map_remove_by_value(self): + operations = [map_ops.map_remove_by_value( + self.test_bin, self.test_map["a"], return_type=aerospike.MAP_RETURN_KEY, inverted=True)] + assert (set(get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin)) + == set(["b", "c", "d", "e"])) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "b" not in res_map + assert "c" not in res_map + assert "d" not in res_map + assert "e" not in res_map + + def test_map_remove_by_value_list(self): + operations = [map_ops.map_remove_by_value_list( + self.test_bin, + [self.test_map["a"], self.test_map["b"], self.test_map["c"]], + return_type=aerospike.MAP_RETURN_KEY, inverted=True)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set(["d", "e"]) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "d" not in res_map + assert "e" not in res_map + + def test_map_remove_by_value_range(self): + operations = [map_ops.map_remove_by_value_range( + self.test_bin, 1, 4, return_type=aerospike.MAP_RETURN_KEY, inverted=True)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set(["a","b"]) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "a" not in res_map + assert "b" not in res_map + + def test_map_remove_by_index_range(self): + sort_map(self.as_connection, self.test_key, self.test_bin) + operations = [ + map_ops.map_remove_by_index_range( + self.test_bin, 1, 3, return_type=aerospike.MAP_RETURN_KEY, inverted=True) + ] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert ret_vals == ["a", "e"] + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "a" not in res_map + assert "e" not in res_map + + def test_map_remove_by_rank_range(self): + operations = [map_ops.map_remove_by_rank_range( + self.test_bin, 1, 3, return_type=aerospike.MAP_RETURN_KEY, inverted=True)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert set(ret_vals) == set(["a", "e"]) + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "a" not in res_map + assert "e" not in res_map + + + def test_map_get_by_key_list(self): + operations = [map_ops.map_get_by_key_list( + self.test_bin, ["a", "b", "c"], return_type=aerospike.MAP_RETURN_VALUE, inverted=True)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set([self.test_map["d"], self.test_map["e"]]) + + def test_map_get_by_key_range(self): + operations = [map_ops.map_get_by_key_range( + self.test_bin, "a", "d", return_type=aerospike.MAP_RETURN_VALUE, inverted=True)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set([self.test_map["d"], self.test_map["e"]]) + + def test_map_get_by_value(self): + operations = [map_ops.map_get_by_value( + self.test_bin, self.test_map["a"], return_type=aerospike.MAP_RETURN_KEY, inverted=True)] + assert (set(get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin)) + == set(["b", "c", "d", "e"])) + + def test_map_get_by_value_list(self): + operations = [map_ops.map_get_by_value_list( + self.test_bin, + [self.test_map["a"], self.test_map["b"], self.test_map["c"]], + return_type=aerospike.MAP_RETURN_KEY, inverted=True)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set(["d", "e"]) + + def test_map_get_by_value_range(self): + operations = [map_ops.map_get_by_value_range( + self.test_bin, 1, 4, return_type=aerospike.MAP_RETURN_KEY, inverted=True)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set(["a", "b"]) + + def test_map_get_by_index_range(self): + sort_map(self.as_connection, self.test_key, self.test_bin) + operations = [ + map_ops.map_get_by_index_range( + self.test_bin, 1, 2, return_type=aerospike.MAP_RETURN_KEY, inverted=True) + ] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert set(ret_vals) == set(["a", "d", "e"]) + + def test_map_get_by_rank_range(self): + sort_map(self.as_connection, self.test_key, self.test_bin) + operations = [ + map_ops.map_get_by_rank_range( + self.test_bin, 1, 2, return_type=aerospike.MAP_RETURN_KEY, inverted=True) + ] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert set(ret_vals) == set(["a", "b", "e"]) diff --git a/test/new_tests/test_map_operation_helpers.py b/test/new_tests/test_map_operation_helpers.py new file mode 100644 index 000000000..6a5382de0 --- /dev/null +++ b/test/new_tests/test_map_operation_helpers.py @@ -0,0 +1,313 @@ +# -*- coding: utf-8 -*- +import pytest +import sys +from aerospike import exception as e +from aerospike_helpers.operations import map_operations as map_ops + +aerospike = pytest.importorskip("aerospike") +try: + import aerospike +except: + print("Please install aerospike python client.") + sys.exit(1) + + +def get_map_result_from_operation(client, key, operations, res_bin): + ''' + Map operations return a 1 bin record. + This operation extracts the dictionary from it. + ''' + _, _, result_bins = client.operate(key, operations) + return result_bins[res_bin] + + +def maps_have_same_keys(map1, map2): + ''' + Return True iff the two maps have an identical set of keys + ''' + if len(map1) != len(map2): + return False + + for key in map1: + if key not in map2: + return False + return True + + +def maps_have_same_values(map1, map2): + if len(map1) != len(map2): + return False + + for key in map1: + if key not in map2 or map1[key] != map2[key]: + return False + return True + + +def sort_map(client, test_key, test_bin): + map_policy = {"map_write_mode": aerospike.MAP_CREATE_ONLY, "map_order": aerospike.MAP_KEY_ORDERED} + operations = [map_ops.map_set_policy(test_bin, map_policy)] + client.operate(test_key, operations) + +class TestNewListOperationsHelpers(object): + + @pytest.fixture(autouse=True) + def setup(self, request, as_connection): + """ + Setup Method + """ + self.keys = [] + self.test_map = { + "a" : 5, + "b" : 4, + "c" : 3, + "d" : 2, + "e" : 1 + } + + self.test_key = 'test', 'demo', 'new_map_op' + self.test_bin = 'map' + self.as_connection.put(self.test_key, {self.test_bin: self.test_map}) + self.keys.append(self.test_key) + + yield + + for key in self.keys: + try: + self.as_connection.remove(key) + except e.AerospikeError: + pass + + def test_map_set_policy(self): + ''' + Test setting map policy with an operation + ''' + map_policy = {"map_write_mode": aerospike.MAP_CREATE_ONLY, "map_order": aerospike.MAP_KEY_VALUE_ORDERED} + operations = [map_ops.map_set_policy(self.test_bin, map_policy)] + + self.as_connection.operate(self.test_key, operations) + + def test_map_put(self): + operations =[map_ops.map_put(self.test_bin, "new", "map_put")] + self.as_connection.operate(self.test_key, operations) + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert res_map["new"] == "map_put" + + def test_map_put_items(self): + new_items = {"new": 1, "new2": 3} + expected_items = { + "a" : 5, + "b" : 4, + "c" : 3, + "d" : 2, + "e" : 1, + "new": 1, + "new2": 3 + } + operations = [map_ops.map_put_items(self.test_bin, new_items)] + self.as_connection.operate(self.test_key, operations) + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + + assert res_map == expected_items + + def test_map_increment(self): + operations = [map_ops.map_increment(self.test_bin, "a", 100)] + self.as_connection.operate(self.test_key, operations) + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert res_map["a"] == self.test_map["a"] + 100 + + def test_map_decrement(self): + operations = [map_ops.map_decrement(self.test_bin, "a", 100)] + self.as_connection.operate(self.test_key, operations) + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert res_map["a"] == self.test_map["a"] - 100 + + def test_map_size(self): + operations = [map_ops.map_size(self.test_bin)] + assert ( + get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + == len(self.test_map) + ) + + def test_map_clear(self): + operations = [map_ops.map_clear(self.test_bin)] + self.as_connection.operate(self.test_key, operations) + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert res_map == {} + + def test_map_remove_by_key(self): + operations = [map_ops.map_remove_by_key( + self.test_bin, "a", return_type=aerospike.MAP_RETURN_VALUE)] + assert (get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + == self.test_map["a"]) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "a" not in res_map + + def test_map_remove_by_key_list(self): + operations = [map_ops.map_remove_by_key_list( + self.test_bin, ["a", "b", "c"], return_type=aerospike.MAP_RETURN_VALUE)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set([self.test_map["a"], self.test_map["b"], self.test_map["c"]]) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "a" not in res_map + assert "b" not in res_map + assert "c" not in res_map + + def test_map_remove_by_key_range(self): + operations = [map_ops.map_remove_by_key_range( + self.test_bin, "a", "d", return_type=aerospike.MAP_RETURN_VALUE)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set([self.test_map["a"], self.test_map["b"], self.test_map["c"]]) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "a" not in res_map + assert "b" not in res_map + assert "c" not in res_map + + def test_map_remove_by_value(self): + operations = [map_ops.map_remove_by_value( + self.test_bin, self.test_map["a"], return_type=aerospike.MAP_RETURN_KEY)] + assert (get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + == ["a"]) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "a" not in res_map + + + def test_map_remove_by_value_list(self): + operations = [map_ops.map_remove_by_value_list( + self.test_bin, + [self.test_map["a"], self.test_map["b"], self.test_map["c"]], + return_type=aerospike.MAP_RETURN_KEY)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set(["a", "b", "c"]) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "a" not in res_map + assert "b" not in res_map + assert "c" not in res_map + + def test_map_remove_by_value_range(self): + operations = [map_ops.map_remove_by_value_range( + self.test_bin, 1, 4, return_type=aerospike.MAP_RETURN_KEY)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set(["e", "d", "c"]) + + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "e" not in res_map + assert "d" not in res_map + assert "c" not in res_map + + def test_map_remove_by_index(self): + sort_map(self.as_connection, self.test_key, self.test_bin) + operations = [ + map_ops.map_remove_by_index(self.test_bin, 1, return_type=aerospike.MAP_RETURN_KEY) + ] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert ret_vals == "b" + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "b" not in res_map + + def test_map_remove_by_index_range(self): + sort_map(self.as_connection, self.test_key, self.test_bin) + operations = [ + map_ops.map_remove_by_index_range(self.test_bin, 1, 2, return_type=aerospike.MAP_RETURN_KEY) + ] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert ret_vals == ["b", "c"] + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "b" not in res_map + assert "c" not in res_map + + def test_map_remove_by_rank(self): + operations = [map_ops.map_remove_by_rank(self.test_bin, 1, return_type=aerospike.MAP_RETURN_KEY)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert ret_vals == "d" + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "d" not in res_map + + def test_map_remove_by_rank_range(self): + operations = [map_ops.map_remove_by_rank_range(self.test_bin, 1, 2, return_type=aerospike.MAP_RETURN_KEY)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert set(ret_vals) == set(["d", "c"]) + res_map = self.as_connection.get(self.test_key)[2][self.test_bin] + assert "d" not in res_map + assert "c" not in res_map + + + def test_map_get_by_key(self): + operations = [map_ops.map_get_by_key( + self.test_bin, "a", return_type=aerospike.MAP_RETURN_VALUE)] + assert (get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + == self.test_map["a"]) + + def test_map_get_by_key_list(self): + operations = [map_ops.map_get_by_key_list( + self.test_bin, ["a", "b", "c"], return_type=aerospike.MAP_RETURN_VALUE)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set([self.test_map["a"], self.test_map["b"], self.test_map["c"]]) + + def test_map_get_by_key_range(self): + operations = [map_ops.map_get_by_key_range( + self.test_bin, "a", "d", return_type=aerospike.MAP_RETURN_VALUE)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set([self.test_map["a"], self.test_map["b"], self.test_map["c"]]) + + def test_map_get_by_value(self): + operations = [map_ops.map_get_by_value( + self.test_bin, self.test_map["a"], return_type=aerospike.MAP_RETURN_KEY)] + assert (get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + == ["a"]) + + def test_map_get_by_value_list(self): + operations = [map_ops.map_get_by_value_list( + self.test_bin, + [self.test_map["a"], self.test_map["b"], self.test_map["c"]], + return_type=aerospike.MAP_RETURN_KEY)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set(["a", "b", "c"]) + + def test_map_get_by_value_range(self): + operations = [map_ops.map_get_by_value_range( + self.test_bin, 1, 4, return_type=aerospike.MAP_RETURN_KEY)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + + assert set(ret_vals) == set(["e", "d", "c"]) + + def test_map_get_by_index(self): + sort_map(self.as_connection, self.test_key, self.test_bin) + operations = [ + map_ops.map_get_by_index(self.test_bin, 1, return_type=aerospike.MAP_RETURN_KEY) + ] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert ret_vals == "b" + + def test_map_get_by_index_range(self): + sort_map(self.as_connection, self.test_key, self.test_bin) + operations = [ + map_ops.map_get_by_index_range(self.test_bin, 1, 2, return_type=aerospike.MAP_RETURN_KEY) + ] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert ret_vals == ["b", "c"] + + def test_map_get_by_rank(self): + operations = [map_ops.map_get_by_rank(self.test_bin, 1, return_type=aerospike.MAP_RETURN_KEY)] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert ret_vals == "d" + + def test_map_get_by_rank_range(self): + sort_map(self.as_connection, self.test_key, self.test_bin) + operations = [ + map_ops.map_get_by_rank_range(self.test_bin, 1, 2, return_type=aerospike.MAP_RETURN_KEY) + ] + ret_vals = get_map_result_from_operation(self.as_connection, self.test_key, operations, self.test_bin) + assert ret_vals == ["d", "c"] \ No newline at end of file diff --git a/test/new_tests/test_new_list_operation_helpers.py b/test/new_tests/test_new_list_operation_helpers.py new file mode 100644 index 000000000..5fa92a7c2 --- /dev/null +++ b/test/new_tests/test_new_list_operation_helpers.py @@ -0,0 +1,528 @@ +# -*- coding: utf-8 -*- +import pytest +import sys +from aerospike import exception as e +from aerospike_helpers.operations import list_operations + +aerospike = pytest.importorskip("aerospike") +try: + import aerospike +except: + print("Please install aerospike python client.") + sys.exit(1) + + +def get_list_result_from_operation(client, key, operation, bin): + _, _, result_bins = client.operate(key, [operation]) + return result_bins[bin] + + +class TestNewListOperationsHelpers(object): + + @pytest.fixture(autouse=True) + def setup(self, request, as_connection): + """ + Setup Method + """ + self.keys = [] + # INDEXES 0, 1, 2, 3, 4, 05 + # RINDEX 5, 4, 3, 2, 1, 0 + # RANK 2, 1, 0, 3, 4, 5 + # RRANK -4,-5,-6,-3,-2,-1 + self.test_list = [7, 6, 5, 8, 9, 10] + + self.test_key = 'test', 'demo', 'new_list_op' + self.test_bin = 'list' + self.as_connection.put(self.test_key, {self.test_bin: self.test_list}) + self.keys.append(self.test_key) + + yield + + for key in self.keys: + try: + self.as_connection.remove(key) + except e.AerospikeError: + pass + + @pytest.mark.parametrize( + "index, expected", + ( + (2, 5), + (-2, 9) + )) + def test_get_by_index(self, index, expected): + ''' + Without a return type this should return the value + ''' + operation = list_operations.list_get_by_index( + self.test_bin, index, aerospike.LIST_RETURN_VALUE) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == expected + + @pytest.mark.parametrize( + "return_type, expected", + ( + (aerospike.LIST_RETURN_VALUE, 5), + (aerospike.LIST_RETURN_INDEX, 2), + (aerospike.LIST_RETURN_REVERSE_INDEX, 3), + (aerospike.LIST_RETURN_RANK, 0), + (aerospike.LIST_RETURN_REVERSE_RANK, 5), + )) + def test_list_get_by_index_return_types(self, return_type, expected): + ''' + Without a return type this should return the value + ''' + operation = list_operations.list_get_by_index( + self.test_bin, 2, return_type) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == expected + + @pytest.mark.parametrize( + "index, count", + ( + (0, 3), + (-2, 1), + (0, 100) + )) + def test_get_by_index_range_both_present(self, index, count): + expected = self.test_list[index: index + count] + operation = list_operations.list_get_by_index_range( + self.test_bin, index, aerospike.LIST_RETURN_VALUE, count) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == expected + + def test_get_by_index_range_no_count(self): + operation = list_operations.list_get_by_index_range( + self.test_bin, 2, aerospike.LIST_RETURN_VALUE) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == self.test_list[2:] + + def test_get_by_index_range_inverted(self): + start = 0 + count = 3 + expected = self.test_list[start + count:] + + operation = list_operations.list_get_by_index_range( + self.test_bin, start, aerospike.LIST_RETURN_VALUE, count=3, + inverted=True) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == expected + + @pytest.mark.parametrize( + "rank, expected", + ( + (0, 5), + (-1, 10) + )) + def test_get_by_rank(self, rank, expected): + ''' + Without a return type this should return the value + ''' + operation = list_operations.list_get_by_rank( + self.test_bin, rank, aerospike.LIST_RETURN_VALUE) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == expected + + @pytest.mark.parametrize( + "return_type, expected", + ( + (aerospike.LIST_RETURN_VALUE, 5), + (aerospike.LIST_RETURN_INDEX, 2), + (aerospike.LIST_RETURN_REVERSE_INDEX, 3), + (aerospike.LIST_RETURN_RANK, 0), + (aerospike.LIST_RETURN_REVERSE_RANK, 5), + )) + def test_list_get_by_rank_return_types(self, return_type, expected): + ''' + Without a return type this should return the value + ''' + operation = list_operations.list_get_by_rank( + self.test_bin, 0, return_type) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == expected + + @pytest.mark.parametrize( + "rank, count", + ( + (0, 3), + (-2, 1), + (0, 100) + )) + def test_get_by_rank_range_both_present(self, rank, count): + expected = sorted(self.test_list)[rank: rank + count] + + operation = list_operations.list_get_by_rank_range( + self.test_bin, rank, aerospike.LIST_RETURN_VALUE, count=count) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert set(result) == set(expected) + + def test_get_by_rank_range_no_count(self): + + operation = list_operations.list_get_by_rank_range( + self.test_bin, 2, aerospike.LIST_RETURN_VALUE); + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == sorted(self.test_list)[2:] + + def test_get_by_rank_range_inverted(self): + rank_start = 0 + rank_count = 3 + + # All of the values except for those in the specified rank range + expected = sorted(self.test_list)[rank_start + rank_count:] + + operation = list_operations.list_get_by_rank_range( + self.test_bin, rank_start, aerospike.LIST_RETURN_VALUE, + count=rank_count, inverted=True) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert set(result) == set(expected) + + def test_get_by_value_no_duplicates(self): + ''' + 7 is in the 0th position, so we expect [0] as the result + ''' + operation = list_operations.list_get_by_value( + self.test_bin, 7, aerospike.LIST_RETURN_INDEX) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == [0] + + def test_get_by_value_no_duplicates_inverted(self): + ''' + 7 is in the 0th position, so we expect [0] as the result + ''' + operation = list_operations.list_get_by_value( + self.test_bin, 7, aerospike.LIST_RETURN_VALUE, inverted=True) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + # Every value except for 7 + assert result == [6, 5, 8, 9, 10] + + def test_get_by_value_with_duplicates(self): + ''' + Add a list [0, 1, 0, 2, 0] with 3 0's + We expect [0, 2, 4] as the results + ''' + dup_list = [0, 1, 0, 2, 0] + dup_key = 'test', 'list', 'dup' + self.keys.append(dup_key) + + self.as_connection.put(dup_key, {self.test_bin: dup_list}) + + operation = list_operations.list_get_by_value( + self.test_bin, 0, aerospike.LIST_RETURN_INDEX + ) + + result = get_list_result_from_operation( + self.as_connection, dup_key, operation, self.test_bin) + assert result == [0, 2, 4] + + def test_get_by_value_list(self): + values = [7, 5, 9] + operation = list_operations.list_get_by_value_list( + self.test_bin, values, aerospike.LIST_RETURN_INDEX) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == [0, 2, 4] + + def test_get_by_value_list_inverted(self): + values = [7, 5, 9] + operation = list_operations.list_get_by_value_list( + self.test_bin, values, aerospike.LIST_RETURN_VALUE, inverted=True + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert set(result) == set([6, 8, 10]) + + + def test_get_by_value_range(self): + operation = list_operations.list_get_by_value_range( + self.test_bin, aerospike.LIST_RETURN_INDEX, 5, 8 + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 3 and set(result) == set([0, 1, 2]) + + def test_get_by_value_range_inverted(self): + operation = list_operations.list_get_by_value_range( + self.test_bin, aerospike.LIST_RETURN_VALUE, + 6, 8, inverted=True + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 4 and set(result) == set([5, 8, 9, 10]) + + # REMOVE Family of operations + def test_remove_by_index(self): + ''' + Remove the 3rd item, a 5 + ''' + operation = list_operations.list_remove_by_index( + self.test_bin, 2, aerospike.LIST_RETURN_VALUE + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == 5 + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[:2] + self.test_list[3:] + + def test_remove_by_index_range(self): + ''' + Remove the 3rd item, a 5 + ''' + operation = list_operations.list_remove_by_index_range( + self.test_bin, 2, aerospike.LIST_RETURN_VALUE, count=2 + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == [5, 8] + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[:2] + self.test_list[4:] + + def test_remove_by_index_range_inverted(self): + ''' + Remove the 3rd item, a 5 + ''' + operation = list_operations.list_remove_by_index_range( + self.test_bin, 2, aerospike.LIST_RETURN_VALUE, count=2, inverted=True) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert set(result) == set([7, 6, 9, 10]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [5, 8] + + def test_remove_by_rank(self): + ''' + Remove the 3rd smallest item, a 7 at index 0 + ''' + operation = list_operations.list_remove_by_rank( + self.test_bin, 2, aerospike.LIST_RETURN_VALUE + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == 7 + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[1:] + + def test_remove_by_rank_range(self): + ''' + Remove the 3 smallest items, a 7, 6, 6 + ''' + + operation = list_operations.list_remove_by_rank_range( + self.test_bin, 0, aerospike.LIST_RETURN_VALUE, + count=3 + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == [7, 6, 5] + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[3:] + + def test_remove_by_rank_range_inverted(self): + ''' + Remove the 3rd smallest item, a 7 at index 0 + ''' + operation = list_operations.list_remove_by_rank_range( + self.test_bin, 0, aerospike.LIST_RETURN_VALUE, + count=3, inverted=True + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert set(result) == set([8, 9, 10]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[0:3] + + def test_remove_by_value_no_duplicates(self): + ''' + 7 is in the 0th position, so we expect [0] as the result + ''' + operation = list_operations.list_remove_by_value( + self.test_bin, 7, aerospike.LIST_RETURN_INDEX + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == [0] + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[1:] + + def test_remove_by_value_inverted(self): + ''' + 7 is in the 0th position, so we expect [0] as the result + ''' + operation = list_operations.list_remove_by_value( + self.test_bin, 7, aerospike.LIST_RETURN_VALUE, + inverted=True + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == [6, 5, 8, 9, 10] + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[0:1] + + def test_remove_by_value_with_duplicates(self): + ''' + Add a list [0, 1, 0, 2, 0] with 3 0's + We expect [0, 2, 4] as the results + ''' + dup_list = [0, 1, 0, 2, 0] + dup_key = 'test', 'list', 'dup' + self.keys.append(dup_key) + + self.as_connection.put(dup_key, {self.test_bin: dup_list}) + + operation = list_operations.list_remove_by_value( + self.test_bin, 0, aerospike.LIST_RETURN_INDEX + ) + + result = get_list_result_from_operation( + self.as_connection, dup_key, operation, self.test_bin) + assert result == [0, 2, 4] + + _, _, bins = self.as_connection.get(dup_key) + assert bins[self.test_bin] == [1, 2] + + def test_remove_by_value_list(self): + values = [7, 5, 9] + + operation = list_operations.list_remove_by_value_list( + self.test_bin, values, aerospike.LIST_RETURN_INDEX + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == [0, 2, 4] + + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [6, 8, 10] + + def test_remove_by_value_list_inverted(self): + values = [7, 5, 9] + + operation = list_operations.list_remove_by_value_list( + self.test_bin, values, aerospike.LIST_RETURN_VALUE, + inverted=True + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert set(result) == set([6, 8, 10]) + + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [7, 5, 9] + + def test_remove_by_value_range(self): + operation = list_operations.list_remove_by_value_range( + self.test_bin, aerospike.LIST_RETURN_INDEX, + value_begin=5, value_end=8 + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 3 and set(result) == set([0, 1, 2]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [8, 9, 10] + + def test_remove_by_value_range_inverted(self): + + operation = list_operations.list_remove_by_value_range( + self.test_bin, aerospike.LIST_RETURN_VALUE, + value_begin=6, value_end=8, inverted=True + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 4 and set(result) == set([5, 8, 9, 10]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [7, 6] + + def test_remove_by_value_range_no_begin(self): + operation = list_operations.list_remove_by_value_range( + self.test_bin, aerospike.LIST_RETURN_INDEX, value_end=8 + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 3 and set(result) == set([0, 1, 2]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [8, 9, 10] + + def test_remove_by_value_range_no_end(self): + + operation = list_operations.list_remove_by_value_range( + self.test_bin, aerospike.LIST_RETURN_INDEX, value_begin=7 + ) + + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 4 and set(result) == set([0, 3, 4, 5]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [6, 5] + + def test_list_set_order(self): + operation = list_operations.list_set_order( + self.test_bin, aerospike.LIST_ORDERED + ) + + self.as_connection.operate(self.test_key, [operation]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == sorted(self.test_list) + + def test_list_sort(self): + unsorted_dups = [2, 5, 2, 5] + sort_key = 'test', 'demo', 'dup_list' + self.keys.append(sort_key) + self.as_connection.put(sort_key, {self.test_bin: unsorted_dups}) + + operation = list_operations.list_sort( + self.test_bin, sort_flags=aerospike.LIST_SORT_DROP_DUPLICATES + ) + + self.as_connection.operate(sort_key, [operation]) + _, _, bins = self.as_connection.get(sort_key) + assert bins[self.test_bin] == [2, 5] \ No newline at end of file diff --git a/test/new_tests/test_new_list_operations.py b/test/new_tests/test_new_list_operations.py new file mode 100644 index 000000000..df1b76363 --- /dev/null +++ b/test/new_tests/test_new_list_operations.py @@ -0,0 +1,609 @@ +# -*- coding: utf-8 -*- +import pytest +import sys +from aerospike import exception as e + +aerospike = pytest.importorskip("aerospike") +try: + import aerospike +except: + print("Please install aerospike python client.") + sys.exit(1) + + +def get_list_result_from_operation(client, key, operation, bin): + _, _, result_bins = client.operate(key, [operation]) + return result_bins[bin] + + +class TestNewListOperations(object): + + @pytest.fixture(autouse=True) + def setup(self, request, as_connection): + """ + Setup Method + """ + self.keys = [] + # INDEXES 0, 1, 2, 3, 4, 05 + # RINDEX 5, 4, 3, 2, 1, 0 + # RANK 2, 1, 0, 3, 4, 5 + # RRANK -4,-5,-6,-3,-2,-1 + self.test_list = [7, 6, 5, 8, 9, 10] + + self.test_key = 'test', 'demo', 'new_list_op' + self.test_bin = 'list' + self.as_connection.put(self.test_key, {self.test_bin: self.test_list}) + self.keys.append(self.test_key) + + yield + + for key in self.keys: + try: + self.as_connection.remove(key) + except e.AerospikeError: + pass + + @pytest.mark.parametrize( + "index, expected", + ( + (2, 5), + (-2, 9) + )) + def test_get_by_index(self, index, expected): + ''' + Without a return type this should return the value + ''' + operation = { + 'op': aerospike.OP_LIST_GET_BY_INDEX, + 'bin': 'list', + 'index': index, + 'return_type': aerospike.LIST_RETURN_VALUE + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == expected + + @pytest.mark.parametrize( + "return_type, expected", + ( + (aerospike.LIST_RETURN_VALUE, 5), + (aerospike.LIST_RETURN_INDEX, 2), + (aerospike.LIST_RETURN_REVERSE_INDEX, 3), + (aerospike.LIST_RETURN_RANK, 0), + (aerospike.LIST_RETURN_REVERSE_RANK, 5), + )) + def test_list_get_by_index_return_types(self, return_type, expected): + ''' + Without a return type this should return the value + ''' + operation = { + 'op': aerospike.OP_LIST_GET_BY_INDEX, + 'bin': 'list', + 'index': 2, + 'return_type': return_type + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == expected + + @pytest.mark.parametrize( + "index, count", + ( + (0, 3), + (-2, 1), + (0, 100) + )) + def test_get_by_index_range_both_present(self, index, count): + expected = self.test_list[index: index + count] + operation = { + 'op': aerospike.OP_LIST_GET_BY_INDEX_RANGE, + 'bin': self.test_bin, + 'index': index, + 'count': count, + 'return_type': aerospike.LIST_RETURN_VALUE + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == expected + + def test_get_by_index_range_no_count(self): + operation = { + 'op': aerospike.OP_LIST_GET_BY_INDEX_RANGE, + 'bin': self.test_bin, + 'index': 2, + 'return_type': aerospike.LIST_RETURN_VALUE + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == self.test_list[2:] + + def test_get_by_index_range_inverted(self): + start = 0 + count = 3 + expected = self.test_list[start + count:] + operation = { + 'op': aerospike.OP_LIST_GET_BY_INDEX_RANGE, + 'bin': self.test_bin, + 'index': start, + 'count': count, + 'return_type': aerospike.LIST_RETURN_VALUE, + 'inverted': True + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == expected + + @pytest.mark.parametrize( + "rank, expected", + ( + (0, 5), + (-1, 10) + )) + def test_get_by_rank(self, rank, expected): + ''' + Without a return type this should return the value + ''' + operation = { + 'op': aerospike.OP_LIST_GET_BY_RANK, + 'bin': 'list', + 'rank': rank, + 'return_type': aerospike.LIST_RETURN_VALUE + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == expected + + @pytest.mark.parametrize( + "return_type, expected", + ( + (aerospike.LIST_RETURN_VALUE, 5), + (aerospike.LIST_RETURN_INDEX, 2), + (aerospike.LIST_RETURN_REVERSE_INDEX, 3), + (aerospike.LIST_RETURN_RANK, 0), + (aerospike.LIST_RETURN_REVERSE_RANK, 5), + )) + def test_list_get_by_rank_return_types(self, return_type, expected): + ''' + Without a return type this should return the value + ''' + operation = { + 'op': aerospike.OP_LIST_GET_BY_RANK, + 'bin': 'list', + 'rank': 0, + 'return_type': return_type + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == expected + + @pytest.mark.parametrize( + "rank, count", + ( + (0, 3), + (-2, 1), + (0, 100) + )) + def test_get_by_rank_range_both_present(self, rank, count): + expected = sorted(self.test_list)[rank: rank + count] + operation = { + 'op': aerospike.OP_LIST_GET_BY_RANK_RANGE, + 'bin': self.test_bin, + 'rank': rank, + 'count': count, + 'return_type': aerospike.LIST_RETURN_VALUE + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert set(result) == set(expected) + + def test_get_by_rank_range_no_count(self): + operation = { + 'op': aerospike.OP_LIST_GET_BY_RANK_RANGE, + 'bin': self.test_bin, + 'rank': 2, + 'return_type': aerospike.LIST_RETURN_VALUE + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == sorted(self.test_list)[2:] + + def test_get_by_rank_range_inverted(self): + rank_start = 0 + rank_count = 3 + # All of the values except for those in the specified rank range + expected = sorted(self.test_list)[rank_start + rank_count:] + operation = { + 'op': aerospike.OP_LIST_GET_BY_RANK_RANGE, + 'bin': self.test_bin, + 'rank': rank_start, + 'count': rank_count, + 'return_type': aerospike.LIST_RETURN_VALUE, + 'inverted': True + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert set(result) == set(expected) + + def test_get_by_value_no_duplicates(self): + ''' + 7 is in the 0th position, so we expect [0] as the result + ''' + operation = { + 'op': aerospike.OP_LIST_GET_BY_VALUE, + 'bin': self.test_bin, + 'val': 7, + 'return_type': aerospike.LIST_RETURN_INDEX + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == [0] + + def test_get_by_value_no_duplicates_inverted(self): + ''' + 7 is in the 0th position, so we expect [0] as the result + ''' + operation = { + 'op': aerospike.OP_LIST_GET_BY_VALUE, + 'bin': self.test_bin, + 'val': 7, + 'return_type': aerospike.LIST_RETURN_VALUE, + 'inverted': True + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + # Every value except for 7 + assert result == [6, 5, 8, 9, 10] + + def test_get_by_value_with_duplicates(self): + ''' + Add a list [0, 1, 0, 2, 0] with 3 0's + We expect [0, 2, 4] as the results + ''' + dup_list = [0, 1, 0, 2, 0] + dup_key = 'test', 'list', 'dup' + self.keys.append(dup_key) + + self.as_connection.put(dup_key, {self.test_bin: dup_list}) + + operation = { + 'op': aerospike.OP_LIST_GET_BY_VALUE, + 'bin': self.test_bin, + 'val': 0, + 'return_type': aerospike.LIST_RETURN_INDEX + } + result = get_list_result_from_operation( + self.as_connection, dup_key, operation, self.test_bin) + assert result == [0, 2, 4] + + def test_get_by_value_list(self): + values = [7, 5, 9] + operation = { + 'op': aerospike.OP_LIST_GET_BY_VALUE_LIST, + 'bin': self.test_bin, + 'value_list': values, + 'return_type': aerospike.LIST_RETURN_INDEX + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == [0, 2, 4] + + def test_get_by_value_list_inverted(self): + values = [7, 5, 9] + operation = { + 'op': aerospike.OP_LIST_GET_BY_VALUE_LIST, + 'bin': self.test_bin, + 'value_list': values, + 'return_type': aerospike.LIST_RETURN_VALUE, + 'inverted': True + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert set(result) == set([6, 8, 10]) + + def test_get_by_value_range(self): + operation = { + 'op': aerospike.OP_LIST_GET_BY_VALUE_RANGE, + 'bin': self.test_bin, + 'value_begin': 5, + 'value_end': 8, + 'return_type': aerospike.LIST_RETURN_INDEX + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 3 and set(result) == set([0, 1, 2]) + + def test_get_by_value_range_inverted(self): + operation = { + 'op': aerospike.OP_LIST_GET_BY_VALUE_RANGE, + 'bin': self.test_bin, + 'value_begin': 6, + 'value_end': 8, + 'return_type': aerospike.LIST_RETURN_VALUE, + 'inverted': True + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 4 and set(result) == set([5, 8, 9, 10]) + + # REMOVE Family of operations + def test_remove_by_index(self): + ''' + Remove the 3rd item, a 5 + ''' + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_INDEX, + 'bin': 'list', + 'index': 2, + 'return_type': aerospike.LIST_RETURN_VALUE + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == 5 + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[:2] + self.test_list[3:] + + def test_remove_by_index_range(self): + ''' + Remove the 3rd item, a 5 + ''' + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_INDEX_RANGE, + 'bin': 'list', + 'index': 2, + 'count': 2, + 'return_type': aerospike.LIST_RETURN_VALUE + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == [5, 8] + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[:2] + self.test_list[4:] + + def test_remove_by_index_range_inverted(self): + ''' + Remove the 3rd item, a 5 + ''' + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_INDEX_RANGE, + 'bin': 'list', + 'index': 2, + 'count': 2, + 'return_type': aerospike.LIST_RETURN_VALUE, + 'inverted': True + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert set(result) == set([7, 6, 9, 10]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [5, 8] + + def test_remove_by_rank(self): + ''' + Remove the 3rd smallest item, a 7 at index 0 + ''' + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_RANK, + 'bin': 'list', + 'rank': 2, + 'return_type': aerospike.LIST_RETURN_VALUE + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == 7 + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[1:] + + def test_remove_by_rank_range(self): + ''' + Remove the 3rd smallest item, a 7 at index 0 + ''' + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_RANK_RANGE, + 'bin': 'list', + 'rank': 0, + 'count': 3, + 'return_type': aerospike.LIST_RETURN_VALUE + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert result == [7, 6, 5] + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[3:] + + def test_remove_by_rank_range_inverted(self): + ''' + Remove the 3rd smallest item, a 7 at index 0 + ''' + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_RANK_RANGE, + 'bin': 'list', + 'rank': 0, + 'count': 3, + 'return_type': aerospike.LIST_RETURN_VALUE, + 'inverted': True + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + + assert set(result) == set([8, 9, 10]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[0:3] + + def test_remove_by_value_no_duplicates(self): + ''' + 7 is in the 0th position, so we expect [0] as the result + ''' + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_VALUE, + 'bin': self.test_bin, + 'val': 7, + 'return_type': aerospike.LIST_RETURN_INDEX + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == [0] + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[1:] + + def test_remove_by_value_inverted(self): + ''' + 7 is in the 0th position, so we expect [0] as the result + ''' + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_VALUE, + 'bin': self.test_bin, + 'val': 7, + 'return_type': aerospike.LIST_RETURN_VALUE, + 'inverted': True + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == [6, 5, 8, 9, 10] + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == self.test_list[0:1] + + def test_remove_by_value_with_duplicates(self): + ''' + Add a list [0, 1, 0, 2, 0] with 3 0's + We expect [0, 2, 4] as the results + ''' + dup_list = [0, 1, 0, 2, 0] + dup_key = 'test', 'list', 'dup' + self.keys.append(dup_key) + + self.as_connection.put(dup_key, {self.test_bin: dup_list}) + + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_VALUE, + 'bin': self.test_bin, + 'val': 0, + 'return_type': aerospike.LIST_RETURN_INDEX + } + result = get_list_result_from_operation( + self.as_connection, dup_key, operation, self.test_bin) + assert result == [0, 2, 4] + + _, _, bins = self.as_connection.get(dup_key) + assert bins[self.test_bin] == [1, 2] + + def test_remove_by_value_list(self): + values = [7, 5, 9] + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_VALUE_LIST, + 'bin': self.test_bin, + 'value_list': values, + 'return_type': aerospike.LIST_RETURN_INDEX + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert result == [0, 2, 4] + + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [6, 8, 10] + + def test_remove_by_value_list_inverted(self): + values = [7, 5, 9] + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_VALUE_LIST, + 'bin': self.test_bin, + 'value_list': values, + 'return_type': aerospike.LIST_RETURN_VALUE, + 'inverted': True + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert set(result) == set([6, 8, 10]) + + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [7, 5, 9] + + def test_remove_by_value_range(self): + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_VALUE_RANGE, + 'bin': self.test_bin, + 'value_begin': 5, + 'value_end': 8, + 'return_type': aerospike.LIST_RETURN_INDEX + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 3 and set(result) == set([0, 1, 2]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [8, 9, 10] + + def test_remove_by_value_range_inverted(self): + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_VALUE_RANGE, + 'bin': self.test_bin, + 'value_begin': 6, + 'value_end': 8, + 'return_type': aerospike.LIST_RETURN_VALUE, + 'inverted': True + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 4 and set(result) == set([5, 8, 9, 10]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [7, 6] + + def test_remove_by_value_range_no_begin(self): + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_VALUE_RANGE, + 'bin': self.test_bin, + 'value_end': 8, + 'return_type': aerospike.LIST_RETURN_INDEX + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 3 and set(result) == set([0, 1, 2]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [8, 9, 10] + + def test_remove_by_value_range_no_end(self): + operation = { + 'op': aerospike.OP_LIST_REMOVE_BY_VALUE_RANGE, + 'bin': self.test_bin, + 'value_begin': 7, + 'return_type': aerospike.LIST_RETURN_INDEX + } + result = get_list_result_from_operation( + self.as_connection, self.test_key, operation, self.test_bin) + assert len(result) == 4 and set(result) == set([0, 3, 4, 5]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == [6, 5] + + def test_list_set_order(self): + operation = { + 'op': aerospike.OP_LIST_SET_ORDER, + 'list_order': aerospike.LIST_ORDERED, + 'bin': self.test_bin + } + self.as_connection.operate(self.test_key, [operation]) + _, _, bins = self.as_connection.get(self.test_key) + assert bins[self.test_bin] == sorted(self.test_list) + + def test_list_sort(self): + unsorted_dups = [2, 5, 2, 5] + sort_key = 'test', 'demo', 'dup_list' + self.keys.append(sort_key) + self.as_connection.put(sort_key, {self.test_bin: unsorted_dups}) + + operation = { + 'op': aerospike.OP_LIST_SORT, + 'sort_flags': aerospike.LIST_SORT_DROP_DUPLICATES, + 'bin': self.test_bin + } + + self.as_connection.operate(sort_key, [operation]) + _, _, bins = self.as_connection.get(sort_key) + assert bins[self.test_bin] == [2, 5] \ No newline at end of file diff --git a/test/new_tests/test_operate_helpers.py b/test/new_tests/test_operate_helpers.py new file mode 100644 index 000000000..1b063d002 --- /dev/null +++ b/test/new_tests/test_operate_helpers.py @@ -0,0 +1,1023 @@ +# -*- coding: utf-8 -*- +import pytest +import sys +from .test_base_class import TestBaseClass +from aerospike_helpers.operations import operations, list_operations, map_operations +aerospike = pytest.importorskip("aerospike") +try: + import aerospike + from aerospike import exception as e +except: + print("Please install aerospike python client.") + sys.exit(1) + +# OPERATIONS +# aerospike.OPERATOR_WRITE +# aerospike.OPERATOR_APPEND +# aerospike.OPERATOR_PREPEND +# aerospike.OPERATOR_INCR +# aerospike.OPERATOR_READ +# aerospike.OPERATOR_TOUCH +# aerospike.OP_LIST_APPEND +# aerospike.OP_LIST_APPEND_ITEMS +# aerospike.OP_LIST_INSERT +# aerospike.OP_LIST_INSERT_ITEMS +# aerospike.OP_LIST_POP +# aerospike.OP_LIST_POP_RANGE +# aerospike.OP_LIST_REMOVE +# aerospike.OP_LIST_REMOVE_RANGE +# aerospike.OP_LIST_CLEAR +# aerospike.OP_LIST_SET +# aerospike.OP_LIST_GET +# aerospike.OP_LIST_GET_RANGE +# aerospike.OP_LIST_TRIM +# aerospike.OP_LIST_SIZE +# aerospike.OP_MAP_SET_POLICY +# aerospike.OP_MAP_PUT +# aerospike.OP_MAP_PUT_ITEMS +# aerospike.OP_MAP_INCREMENT +# aerospike.OP_MAP_DECREMENT +# aerospike.OP_MAP_SIZE +# aerospike.OP_MAP_CLEAR +# aerospike.OP_MAP_REMOVE_BY_KEY +# aerospike.OP_MAP_REMOVE_BY_KEY_LIST +# aerospike.OP_MAP_REMOVE_BY_KEY_RANGE +# aerospike.OP_MAP_REMOVE_BY_VALUE +# aerospike.OP_MAP_REMOVE_BY_VALUE_LIST +# aerospike.OP_MAP_REMOVE_BY_VALUE_RANGE +# aerospike.OP_MAP_REMOVE_BY_INDEX +# aerospike.OP_MAP_REMOVE_BY_INDEX_RANGE +# aerospike.OP_MAP_REMOVE_BY_RANK +# aerospike.OP_MAP_REMOVE_BY_RANK_RANGE +# aerospike.OP_MAP_GET_BY_KEY +# aerospike.OP_MAP_GET_BY_KEY_RANGE +# aerospike.OP_MAP_GET_BY_VALUE +# aerospike.OP_MAP_GET_BY_VALUE_RANGE +# aerospike.OP_MAP_GET_BY_INDEX +# aerospike.OP_MAP_GET_BY_INDEX_RANGE +# aerospike.OP_MAP_GET_BY_RANK +# aerospike.OP_MAP_GET_BY_RANK_RANGE + + +class TestOperate(object): + + def setup_class(cls): + """ + Setup class. + """ + # hostlist, user, password = TestBaseClass.get_hosts() + + # config_no_typechecks = {'hosts': hostlist, 'strict_types': False} + # if user is None and password is None: + # TestOperate.client_no_typechecks = aerospike.client( + # config_no_typechecks).connect() + # else: + # TestOperate.client_no_typechecks = aerospike.client( + # config_no_typechecks).connect(user, password) + cls.client_no_typechecks = TestBaseClass.get_new_connection( + {'strict_types': False}) + + def teardown_class(cls): + TestOperate.client_no_typechecks.close() + + @pytest.fixture(autouse=True) + def setup(self, request, as_connection): + """ + Setup Method + """ + keys = [] + + for i in range(5): + key = ('test', 'demo', i) + rec = {'name': 'name%s' % (str(i)), 'age': i} + as_connection.put(key, rec) + + key = ('test', 'demo', 6) + rec = {"age": 6.3} + as_connection.put(key, rec) + keys.append(key) + + key = ('test', 'demo', 'bytearray_key') + rec = {"bytearray_bin": bytearray("asd;as[d'as;d", "utf-8")} + as_connection.put(key, rec) + + keys.append(key) + + key = ('test', 'demo', 'list_key') + rec = {"int_bin": [1, 2, 3, 4], "string_bin": ['a', 'b', 'c', 'd']} + as_connection.put(key, rec) + + keys.append(key) + + key = ('test', 'demo', 'existing_key') + rec = {"dict": {"a": 1}, "bytearray": bytearray("abc", "utf-8"), + "float": 3.4, "list": ['a']} + as_connection.put(key, rec) + + keys.append(key) + + def teardown(): + """ + Teardown Method + """ + for i in range(5): + key = ('test', 'demo', i) + try: + as_connection.remove(key) + except e.RecordNotFound: + pass + for key in keys: + try: + as_connection.remove(key) + except e.RecordNotFound: + pass + + request.addfinalizer(teardown) + + @pytest.mark.parametrize("key, llist, expected", [ + ( + ('test', 'demo', 1), + [ + operations.prepend("name", u"ram"), + operations.increment("age", 3), + operations.read("name") + ], + {'name': 'ramname1'}), + ( + ('test', 'demo', 1), # with_write_float_value + [ + operations.write("write_bin", {"no": 89.8}), + operations.read("write_bin") + ], + {'write_bin': {u'no': 89.8}}), + ( + ('test', 'demo', 1), # write positive + [ + operations.write("write_bin", {"no": 89}), + operations.read("write_bin") + ], + {'write_bin': {u'no': 89}}), + ( + ('test', 'demo', 1), # write_tuple_positive + [ + operations.write("write_bin", ('a', 'b', 'c')), + operations.read("write_bin") + ], + {'write_bin': ('a', 'b', 'c')}), + ( + ('test', 'demo', 1), # with_bin_bytearray + [ + operations.prepend("asd[;asjk", "ram"), + operations.read("asd[;asjk") + ], + {'asd[;asjk': 'ram'}), + ( + ('test', 'demo', 'bytearray_key'), # with_operator append_val bytearray + [ + operations.append("bytearray_bin", bytearray("abc", "utf-8")), + operations.read("bytearray_bin") + ], + {'bytearray_bin': bytearray("asd;as[d'as;dabc", "utf-8")}), + ( + ('test', 'demo', 'bytearray_new'), # with_operator append_val bytearray_newrecord + [ + operations.append("bytearray_bin", bytearray("asd;as[d'as;d", "utf-8")), + operations.read("bytearray_bin") + ], + {'bytearray_bin': bytearray("asd;as[d'as;d", "utf-8")}), + ( + ('test', 'demo', 'bytearray_key'), # with_operatorprepend_valbytearray + [ + operations.prepend("bytearray_bin", bytearray("abc", "utf-8")), + operations.read("bytearray_bin") + ], + {'bytearray_bin': bytearray("abcasd;as[d'as;d", "utf-8")}), + ( + ('test', 'demo', 'bytearray_new'), # with_operatorprepend_valbytearray_newrecord + [ + operations.prepend("bytearray_bin", bytearray("asd;as[d'as;d", "utf-8")), + operations.read("bytearray_bin") + ], + {'bytearray_bin': bytearray("asd;as[d'as;d", "utf-8")}), + + ]) + def test_pos_operate_with_correct_paramters(self, key, llist, expected): + """ + Invoke operate() with correct parameters + """ + + key, _, bins = self.as_connection.operate(key, llist) + + assert bins == expected + self.as_connection.remove(key) + + def test_pos_operate_with_increment_positive_float_value(self): + """ + Invoke operate() with correct parameters + """ + if TestOperate.skip_old_server is True: + pytest.skip("Server does not support increment on float type") + key = ('test', 'demo', 6) + llist = [ + operations.increment("age", 3.5), + operations.read("age") + ] + + _, _, bins = self.as_connection.operate(key, llist) + + assert bins == {'age': 9.8} + + def test_pos_operate_with_correct_policy(self): + """ + Invoke operate() with correct policy + """ + key = ('test', 'demo', 1) + policy = { + 'timeout': 1000, + 'key': aerospike.POLICY_KEY_SEND, + 'commit_level': aerospike.POLICY_COMMIT_LEVEL_MASTER + } + + llist = [ + operations.append("name", "aa"), + operations.increment("age", 3), + operations.read("name") + ] + + _, _, bins = self.as_connection.operate(key, llist, {}, policy) + + assert bins == {'name': 'name1aa'} + + self.as_connection.remove(key) + + + @pytest.mark.parametrize("key, policy, meta, llist", [ + (('test', 'demo', 1), + {'total_timeout': 1000, + 'key': aerospike.POLICY_KEY_SEND, + 'gen': aerospike.POLICY_GEN_IGNORE, + 'commit_level': aerospike.POLICY_COMMIT_LEVEL_ALL}, + {'gen': 10, 'ttl': 1200}, + [ + operations.append("name","aa"), + operations.increment("age", 3), + operations.read("name") + ]), + ]) + def test_pos_operate_with_policy_gen_ignore( + self, key, policy, meta, llist): + """ + Invoke operate() with gen ignore. + """ + + key, meta, bins = self.as_connection.operate(key, llist, meta, policy) + + assert bins == {'name': 'name1aa'} + + + def test_pos_operate_with_policy_gen_EQ(self): + """ + Invoke operate() with gen EQ positive. + """ + key = ('test', 'demo', 1) + policy = { + 'gen': aerospike.POLICY_GEN_EQ + } + (key, meta) = self.as_connection.exists(key) + gen = meta['gen'] + meta = {'gen': gen} + + llist = [ + operations.append("name", "aa"), + operations.increment("age", 3), + operations.read("name") + ] + (key, meta, bins) = self.as_connection.operate( + key, llist, meta, policy) + + assert bins == {'name': 'name1aa'} + + @pytest.mark.parametrize("key, llist", [ + ( + ('test', 'demo', 1), + [ + operations.touch(4000) + ] + ), + ( + ('test', 'demo', 1), + [operations.touch(4000)] + ) + ]) + def test_pos_operate_touch_operation_with_bin_and_value_combination( + self, key, llist): + """ + Invoke operate() with touch value with bin and value combination. + """ + + self.as_connection.operate(key, llist) + + (key, meta) = self.as_connection.exists(key) + + assert meta['ttl'] is not None + + def test_pos_operate_with_policy_gen_EQ_not_equal(self): + """ + Invoke operate() with a mismatched generation, and verify + it does not succeed + """ + key = ('test', 'demo', 1) + policy = { + 'gen': aerospike.POLICY_GEN_EQ + } + + (_, meta) = self.as_connection.exists(key) + gen = meta['gen'] + meta = { + 'gen': gen + 5, + } + llist = [ + operations.append("name", "aa"), + operations.increment("age", 3), + ] + + with pytest.raises(e.RecordGenerationError): + self.as_connection.operate(key, llist, meta, policy) + + _, _, bins = self.as_connection.get(key) + assert bins == {"age": 1, 'name': 'name1'} + + + def test_pos_operate_with_policy_gen_GT_mismatch(self): + """ + Invoke operate() with gen GT policy, with amatching generation + then verify that the operation was rejected properly + """ + key = ('test', 'demo', 1) + policy = { + 'gen': aerospike.POLICY_GEN_GT + } + _, meta = self.as_connection.exists(key) + gen = meta['gen'] + meta = {'gen': gen} + + llist = [ + operations.append("name", "aa"), + operations.increment("age", 3) + ] + + with pytest.raises(e.RecordGenerationError): + self.as_connection.operate( + key, llist, meta, policy) + + _, _, bins = self.as_connection.get(key) + assert bins == {'age': 1, 'name': 'name1'} + + + def test_pos_operate_touch_with_meta(self): + """ + Invoke operate() OPERATE_TOUCH using meta to pass in ttl. + """ + key = ('test', 'demo', 1) + meta = {'ttl': 1200} + + llist = [operations.touch()] + + self.as_connection.operate(key, llist, meta) + + _, meta = self.as_connection.exists(key) + + assert meta['ttl'] <= 1200 and meta['ttl'] >= 1150 + + def test_pos_operate_with_policy_gen_GT(self): + """ + Invoke operate() with gen GT positive. + """ + key = ('test', 'demo', 1) + policy = { + 'gen': aerospike.POLICY_GEN_GT + } + _, meta = self.as_connection.exists(key) + gen = meta['gen'] + meta = {'gen': gen + 5} + + llist = [ + operations.append("name", "aa"), + operations.increment("age", 3), + operations.read("name") + ] + _, _, bins = self.as_connection.operate( + key, llist, meta, policy) + + assert bins == {'name': 'name1aa'} + + def test_pos_operate_with_nonexistent_key(self): + """ + Invoke operate() with non-existent key + """ + new_key = ('test', 'demo', "key11") + llist = [ + operations.prepend("loc", "mumbai"), + operations.read("loc") + ] + _, _, bins = self.as_connection.operate(new_key, llist) + + assert bins == {'loc': 'mumbai'} + self.as_connection.remove(new_key) + + def test_pos_operate_with_nonexistent_bin(self): + """ + Invoke operate() with non-existent bin + """ + key = ('test', 'demo', 1) + llist = [ + operations.append("addr", "pune"), + operations.read("addr") + ] + _, _, bins = self.as_connection.operate(key, llist) + + assert bins == {'addr': 'pune'} + + def test_pos_operate_increment_nonexistent_key(self): + """ + Invoke operate() with increment with nonexistent_key + """ + key = ('test', 'demo', "non_existentkey") + llist = [operations.increment("age", 5)] + + self.as_connection.operate(key, llist) + + _, _, bins = self.as_connection.get(key) + + assert bins == {"age": 5} + + self.as_connection.remove(key) + + + def test_pos_operate_with_correct_paramters_without_connection(self): + """ + Invoke operate() with correct parameters without connection + """ + key = ('test', 'demo', 1) + config = {'hosts': [('127.0.0.1', 3000)]} + client1 = aerospike.client(config) + llist = [ + operations.touch() + ] + + with pytest.raises(e.ClusterError): + client1.operate(key, llist) + + + def test_pos_operate_write_set_to_aerospike_null(self): + """ + Invoke operate() with write command with bin set to aerospike_null + """ + key = ('test', 'demo', 'null_record') + + bins = {"name": "John", "no": 3} + + self.as_connection.put(key, bins) + + llist = [ + operations.write("no", aerospike.null()), + operations.read("no") + ] + + _, _, bins = self.as_connection.operate(key, llist) + assert {} == bins + + self.as_connection.remove(key) + + @pytest.mark.parametrize("key, llist, expected", [ + ( + ('test', 'demo', 'prepend_int'), # prepend_with_int + [ + operations.prepend("age", 4), + operations.read("age") + ], + {'age': 4}), + ( + ('test', 'demo', 'append_dict'), # append_with_dict + [ + operations.append("dict", {"a": 1, "b": 2}), + operations.read("dict") + ], + {'dict': {"a": 1, "b": 2}}), + ( + ('test', 'demo', 'incr_string'), # incr_with_string + [ + operations.increment("name", "aerospike"), + operations.read("name") + ], + {'name': 'aerospike'}), + ]) + def test_pos_operate_new_record(self, key, llist, expected): + """ + Invoke operate() with prepend command on a new record + """ + _, _, bins = TestOperate.client_no_typechecks.operate(key, llist) + assert expected == bins + TestOperate.client_no_typechecks.remove(key) + + @pytest.mark.parametrize("key, llist", [ + ( + ('test', 'demo', 1), + [ + operations.prepend("age", 4), + operations.read("age") + ] + ), + ( + ('test', 'demo', 'existing_key'), # Existing list + [ + operations.prepend("list", ['c']), + operations.read("list") + ] + ), + ( + ('test', 'demo', 'existing_key'), # Existing dict + [ + operations.append("dict", {"c": 2}), + operations.read("dict") + ] + ), + ( + ('test', 'demo', 'existing_key'), # Exiting float + [ + operations.append("float", 3.4), + operations.read("float") + ] + ), + ( + ('test', 'demo', 1), # Existing string + [ + operations.increment("name", "aerospike"), + operations.read("name") + ] + ), + ( + ('test', 'demo', 'existing_key'), # Existing Bytearray + [ + operations.increment("bytearray", bytearray("abc", "utf-8")), + operations.read("bytearray") + ]), + ]) + def test_pos_operate_prepend_with_existing_record(self, key, llist): + """ + Invoke operate() with prepend command on a existing record + """ + + with pytest.raises(e.BinIncompatibleType): + TestOperate.client_no_typechecks.operate(key, llist) + + TestOperate.client_no_typechecks.remove(key) + + def test_pos_operate_incr_with_geospatial_new_record(self): + """ + Invoke operate() with incr command on a new record + """ + key = ('test', 'demo', 'geospatial_key') + + llist = [ + operations.increment("geospatial", aerospike.GeoJSON({"type": "Point", "coordinates": + [42.34, 58.62]})), + operations.read("geospatial") + ] + + _, _, bins = TestOperate.client_no_typechecks.operate(key, llist) + + assert bins['geospatial'].unwrap() == { + 'coordinates': [42.34, 58.62], 'type': 'Point'} + TestOperate.client_no_typechecks.remove(key) + + def test_pos_operate_with_bin_length_extra_nostricttypes(self): + """ + Invoke operate() with bin length extra. Strict types disabled + """ + key = ('test', 'demo', 1) + + max_length = 'a' * 21 + + llist = [ + operations.prepend("name", "ram"), + operations.increment(max_length, 3) + ] + + TestOperate.client_no_typechecks.operate(key, llist) + + _, _, bins = TestOperate.client_no_typechecks.get(key) + + assert bins == {"name": "ramname1", "age": 1} + + def test_pos_operate_prepend_set_to_aerospike_null(self): + """ + Invoke operate() with prepend command with bin set to aerospike_null + """ + key = ('test', 'demo', 'null_record') + + bins = {"name": "John", "no": 3} + + assert 0 == self.as_connection.put(key, bins) + + (key, _, bins) = self.as_connection.get(key) + + assert {"name": "John", "no": 3} == bins + + llist = [ + operations.prepend("no", aerospike.null()), + operations.read("no") + ] + + try: + (key, _, bins) = self.as_connection.operate(key, llist) + + except e.InvalidRequest as exception: + assert exception.code == 4 + self.as_connection.remove(key) + + @pytest.mark.parametrize("list, result, bin, expected", [ + ( + [ + list_operations.list_append("int_bin", 7), + list_operations.list_get("int_bin", 4) + ], + {"int_bin": 7}, + "int_bin", + [1, 2, 3, 4, 7]), + ( + [ + list_operations.list_append_items("int_bin", [7, 9]), + list_operations.list_get_range("int_bin", 3, 3), + ], + {'int_bin': [4, 7, 9]}, + "int_bin", + [1, 2, 3, 4, 7, 9] + ), + ( + [ + list_operations.list_insert("int_bin", 2, 7), + list_operations.list_pop("int_bin", 2) + ], + {'int_bin': 7}, + "int_bin", + [1, 2, 3, 4] + ), + ( + [ + list_operations.list_insert_items("int_bin", 2, [7, 9]), + list_operations.list_pop_range("int_bin", 2, 2) + + ], + {'int_bin': [7, 9]}, + "int_bin", [1, 2, 3, 4] + ), + ( + [ + list_operations.list_set("int_bin", 2, 18), + list_operations.list_get("int_bin", 2) + ], + {'int_bin': 18}, + "int_bin", + [1, 2, 18, 4] + ), + ( + [ + list_operations.list_set("int_bin", 6, 10), + list_operations.list_get("int_bin", 6) + ], + {'int_bin': 10}, + "int_bin", + [1, 2, 3, 4, None, None, 10] # Inserting outside of the range adds nils in between + ) + ]) + def test_pos_operate_with_list_addition_operations(self, list, result, bin, + expected): + """ + Invoke operate() with list addition operations + """ + key = ('test', 'demo', 'list_key') + + key, _, bins = self.as_connection.operate(key, list) + + assert bins == result + + key, _, bins = self.as_connection.get(key) + assert bins[bin] == expected + + @pytest.mark.parametrize("list, bin, expected", [ + ( + [ + list_operations.list_remove("int_bin", 2) + ], + "int_bin", [1, 2, 4] + ), + ( + [ + list_operations.list_remove_range("int_bin", 2, 2) + ], + "int_bin", + [1, 2] + ), + ( + [ + list_operations.list_trim("int_bin", 2, 2) + ], + "int_bin", + [3, 4] + ), + ( + [ + list_operations.list_clear("int_bin") + ], + "int_bin", + [] + ) + ]) + def test_pos_operate_with_list_remove_operations(self, list, bin, + expected): + """ + Invoke operate() with list remove operations + """ + key = ('test', 'demo', 'list_key') + + self.as_connection.operate(key, list) + + key, _, bins = self.as_connection.get(key) + + assert bins[bin] == expected + + def test_pos_operate_with_list_size(self): + """ + Invoke operate() with list_size operation + """ + key = ('test', 'demo', 'list_key') + list = [ + list_operations.list_size("int_bin") + ] + + key, _, bins = self.as_connection.operate(key, list) + + assert bins == {'int_bin': 4} + + def test_list_increment_with_valid_value(self): + ''' + previous list was [1, 2, 3, 4] + new should be [1, 2, 23, 4] + ''' + key = ('test', 'demo', 'list_key') + list = [ + list_operations.list_increment("int_bin", 2, 20) + ] + + _, _, bins = self.as_connection.operate(key, list) + + assert bins == {'int_bin': 23} + _, _, bins = self.as_connection.get(key) + + assert bins['int_bin'] == [1, 2, 23, 4] + + def test_list_increment_with_incorrect_value_type(self): + ''' + previous list was [1, 2, 3, 4] + new should be [1, 2, 23, 4] + ''' + key = ('test', 'demo', 'list_key') + list = [ + list_operations.list_increment("int_bin", 2, "twenty") + ] + + with pytest.raises(e.AerospikeError): + self.as_connection.operate(key, list) + + def test_pos_operate_with_list_get_range_val_out_of_bounds(self): + """ + Invoke operate() with list_get_range operation and value out of bounds + """ + key = ('test', 'demo', 'list_key') + list = [ + list_operations.list_get_range("int_bin", 2, 9) + ] + + _, _, bins = self.as_connection.operate(key, list) + + assert bins == {'int_bin': [3, 4]} + + def test_pos_operate_with_list_trim_val_with_negative_value(self): + """ + Invoke operate() with list_trimoperation and value is negative + """ + key = ('test', 'demo', 'list_key') + list = [ + list_operations.list_trim("int_bin", 1, -9) + ] + + self.as_connection.operate(key, list) + + _, _, bins = self.as_connection.get(key) + + assert bins['int_bin'] == [2, 3, 4] + + def test_pos_operate_with_list_insert_index_negative(self): + """ + Invoke operate() with list_insert and item index is a negative value + """ + key = ('test', 'demo', 'list_key') + list = [ + list_operations.list_insert("int_bin", -2, 9) + ] + + self.as_connection.operate(key, list) + + _, _, bins = self.as_connection.get(key) + + assert bins['int_bin'] == [1, 2, 9, 3, 4] + + @pytest.mark.parametrize("list, result, bin, expected", [ + ([ + list_operations.list_append("string_bin", {"new_val": 1}), + list_operations.list_get("string_bin", 4) + ], {"string_bin": {"new_val": 1}}, "string_bin", ['a', 'b', 'c', + 'd', {'new_val': 1}]), + ([ + list_operations.list_append_items("string_bin", [['z', 'x'], ('y', 'w')]), + list_operations.list_get_range("string_bin", 3, 3) + ], {"string_bin": ['d', ['z', 'x'], ('y', 'w')]}, "string_bin", ['a', 'b', 'c', + 'd', ['z', 'x'], ('y', 'w')]), + ([ + list_operations.list_insert("string_bin", 2, True), + list_operations.list_pop("string_bin", 2) + ], {'string_bin': True}, "string_bin", ['a', 'b', 'c', 'd']), + ([ + list_operations.list_insert_items("string_bin", 2, [bytearray("abc", "utf-8"), u"xyz"]), + list_operations.list_pop_range("string_bin", 2, 2) + ], {'string_bin': [bytearray(b'abc'), 'xyz']}, "string_bin", ['a', 'b', + 'c', 'd']), + ]) + def test_pos_operate_with_list_operations_different_datatypes(self, list, result, bin, + expected): + """ + Invoke operate() with list operations using different datatypes + """ + key = ('test', 'demo', 'list_key') + + _, _, bins = self.as_connection.operate(key, list) + + assert bins == result + + _, _, bins = self.as_connection.get(key) + + assert bins[bin] == expected + + # Negative Tests + def test_neg_operate_with_no_parameters(self): + """ + Invoke opearte() without any mandatory parameters. + """ + with pytest.raises(TypeError) as typeError: + self.as_connection.operate() + assert "Required argument 'key' (pos 1) not found" in str( + typeError.value) + + def test_neg_operate_list_operation_bin_notlist(self): + """ + Invoke operate() with a list operation and bin does not contain list + """ + key = ('test', 'demo', 1) + list = [ + list_operations.list_insert("age", 2, 9) + ] + + with pytest.raises(e.BinIncompatibleType): + self.as_connection.operate(key, list) + + def test_neg_operate_append_items_not_a_list(self): + """ + Invoke operate() with list addition operations negative + """ + key = ('test', 'demo', 1) + ops = [list_operations.list_append_items("int_bin", 7)] + with pytest.raises(e.ParamError): + self.as_connection.operate(key, ops) + + @pytest.mark.parametrize("list", [ + ( + [list_operations.list_get("int_bin", 7)] + ), + ( + [ + list_operations.list_clear("int_bin"), + list_operations.list_pop("int_bin", 2) + ] + ), + ( + [ + list_operations.list_clear("int_bin"), + list_operations.list_remove("int_bin", 2) + ] + ) + ]) + def test_neg_operate_list_invalid_requests(self, list): + """ + Invoke operate() with list addition operations negative + """ + key = ('test', 'demo', 'list_key') + with pytest.raises(e.InvalidRequest): + self.as_connection.operate(key, list) + + + def test_neg_operate_with_bin_length_extra(self): + """ + Invoke operate() with bin length extra. Strict types enabled + """ + key = ('test', 'demo', 1) + + max_length = 'a' * 21 + + + llist = [ + operations.prepend("name", "ram"), + operations.increment(max_length, 3) + ] + + with pytest.raises(e.BinNameError): + self.as_connection.operate(key, llist) + + def test_neg_operate_empty_string_key(self): + """ + Invoke operate() with empty string key + """ + llist = [ + operations.prepend("name", "ram") + ] + with pytest.raises(e.ParamError): + self.as_connection.operate("", llist) + + + def test_neg_operate_with_extra_parameter(self): + """ + Invoke operate() with extra parameter. + """ + key = ('test', 'demo', 1) + policy = {'timeout': 1000} + llist = [ + operations.prepend("name", "ram") + ] + with pytest.raises(TypeError) as typeError: + self.as_connection.operate(key, llist, {}, policy, "") + + def test_neg_operate_policy_is_string(self): + """ + Invoke operate() with policy is string + """ + key = ('test', 'demo', 1) + llist = [ + operations.prepend("name", "ram") + ] + try: + self.as_connection.operate(key, llist, {}, "") + + except e.ParamError as exception: + assert exception.code == -2 + + def test_neg_operate_key_is_none(self): + """ + Invoke operate() with key is none + """ + llist = [ + operations.prepend("name", "ram") + ] + try: + self.as_connection.operate(None, llist) + + except e.ParamError as exception: + assert exception.code == -2 + + + def test_neg_operate_append_value_integer(self): + """ + Invoke operate() with append value is of type integer + """ + key = ('test', 'demo', 1) + llist = [ + operations.append("name", 12) + ] + + try: + self.as_connection.operate(key, llist) + except e.ParamError as exception: + assert exception.code == -2 + + def test_neg_operate_with_incorrect_polic(self): + """ + Invoke operate() with incorrect policy + """ + key = ('test', 'demo', 1) + policy = {'total_timeout': 0.5} + llist = [ + operations.prepend("name", "ram") + ] + + with pytest.raises(e.ParamError): + self.as_connection.operate(key, llist, {}, policy) + diff --git a/test/new_tests/test_operate_map.py b/test/new_tests/test_operate_map.py index c07c493dd..7303a6898 100644 --- a/test/new_tests/test_operate_map.py +++ b/test/new_tests/test_operate_map.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -import pytest import sys -import pdb -from .test_base_class import TestBaseClass +import pytest + +from .test_base_class import TestBaseClass aerospike = pytest.importorskip("aerospike") + try: import aerospike from aerospike import exception as e