How to make expression evaluate to nil in order to delete bin

the docs mention

aerospike.EXP_WRITE_ALLOW_DELETE

    If expression results in nil value, then delete the bin. Otherwise, return OpNotApplicable when EXP_WRITE_POLICY_NO_FAIL is not set.

how do we make an expression evaluate to nil on the python client? aerospike.null() doesn’t work and there is no expressions.Null()

edit: looks like the rust client has this

Apparently, from what I found out internally, one must leverage Unkown() in order to ‘fail’ the evaluation of an expression, which, I assume would then result in nil.

using expressions.Unknown() with aerospike.EXP_WRITE_ALLOW_DELETE | aerospike.EXP_WRITE_EVAL_NO_FAIL does not work, it just silently fails the operation. using just aerospike.EXP_WRITE_ALLOW_DELETE raises exception.OpNotApplicable

Sorry about my wrong assumption / understanding. Let us see what we find out. There definitely seems to be a gap somewhere, at least in the docs to make this clearer, if not something more serious.

Nil just isn’t supported by our Python client at the moment. A request has been raised to support it.

Correction - our Python client does support NIL, which is used by Map (see this example), List, and Aerospike Expressions. In the Python client aerospike.null() can be used both to express a NIL (for example with Map’s get_by_value_interval) as well as a 'C null` (for example to nuke a bin with a write operation).

Here’s an example of how you delete a bin conditionally with a write expression:

import aerospike
from aerospike import exception
from aerospike_helpers import expressions as exp
from aerospike_helpers.operations import expression_operations as opexp
import sys

config = {"hosts": [("127.0.0.1", 3000)]}
try:
    client = aerospike.client(config).connect()
except Exception as e:
    print(e, config)
    sys.exit(1)

key = ("test", "demo", "write-nil")
try:
    client.remove(key)
except:
    pass
policy = {"key": aerospike.POLICY_KEY_SEND}
client.put(key, {"a": 50, "imaloserbaby": "whydontyoukillme"}, policy=policy)
_, _, b = client.get(key)
print(b)

is_loser = exp.Cond(
    exp.LT(exp.IntBin("a"), 70), "loser",
    "winner").compile()
ops = [
    opexp.expression_read("whatami", is_loser),
]
_, _, b = client.operate(key, ops, policy=policy)
print("\n", b)

# The following will throw an exception with error code 4 AEROSPIKE_ERR_REQUEST_INVALID
# Because "All actions-expressions must evaluate to the same type or be the Unknown expression"
# A limitation that comes from Aerospike Expressions being strongly typed.
#
# One action-expression returns a string, the other returns a NIL value, via
# the Python client's aerospike.null(), which doubles as a C null for bin
# deletion, as well as an Aerospike NIL value.
killif = exp.Cond(
    exp.GE(exp.IntBin("a"), 70), exp.StrBin("imaloserbaby"),
    aerospike.null()).compile()
ops = [
    opexp.expression_write("imaloserbaby", killif, aerospike.EXP_WRITE_ALLOW_DELETE),
]
try:
    _, _, b = client.operate(key, ops, policy=policy)
except Exception as e:
    print("\n", e)

# Instead, this expression conditionally deletes the bin, because one
# action-expression returns a NIL value, the 'else' returns an Unknown
# and the write expression policy tells the server to not generate an error if
# the 'else' is evaluated.
killif = exp.Cond(
    exp.LT(exp.IntBin("a"), 70), aerospike.null(),
    exp.Unknown()).compile()
ops = [
    opexp.expression_write("imaloserbaby", killif,
        aerospike.EXP_WRITE_ALLOW_DELETE | aerospike.EXP_WRITE_EVAL_NO_FAIL),
]
_, _, b = client.operate(key, ops, policy=policy)
print("\n", b)

_, _, b = client.get(key)
print("\n", b)
client.close()

Which returns the following:

$ python3 write-nil.py 
{'a': 50, 'imaloserbaby': 'whydontyoukillme'}

 {'whatami': 'loser'}

 (4, '127.0.0.1:3000 AEROSPIKE_ERR_REQUEST_INVALID', 'src/main/client/operate.c', 813, False)

 {'imaloserbaby': None}

 {'a': 50}

If you look at the code example, I am emphasizing an important point in the comments. Expressions are strongly typed, so a Cond needs to return all the same data types (see the expressions.base.Cond). I’m giving an example of one way which fails due to a mix of types in the Cond and one that works to delete the bin conditionally.

I will add some examples to the Python client’s documentation around this.

(BTW, the fourth print statement shows that a NIL was written to the bin, and the Python client is casting it to a Python None).

3 Likes
© 2021 Copyright Aerospike, Inc. | All rights reserved. Creators of the Aerospike Database.