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
).