Context within expressions not working as expected

here is a reproduction using python 3.9 and client version 6.0.0

import aerospike
from aerospike_helpers import cdt_ctx, expressions
from aerospike_helpers.operations import expression_operations

ac = aerospike.client({'hosts': [('localhost', 3000)]})
ac.connect()
key = ('test', 'test', 'test')
try:
    ac.remove(key)
except:
    pass
ac.put(key, {'bin': [{'key': 'value0'}]})

print(ac.get(key)[2])
ac.operate(
    key,
    [
        expression_operations.expression_write(
            bin_name='bin',
            expression=expressions.Cond(
                expressions.Eq(
                    # expressions.MapGetByKey(
                    #     ctx=None,
                    #     return_type=aerospike.MAP_RETURN_VALUE,
                    #     value_type=expressions.resources.ResultType.STRING,
                    #     key='key',
                    #     bin=expressions.ListGetByIndex(
                    #         ctx=None,
                    #         return_type=aerospike.LIST_RETURN_VALUE,
                    #         value_type=expressions.resources.ResultType.MAP,
                    #         index=0,
                    #         bin='bin'
                    #     )
                    # ),
                    expressions.MapGetByKey(
                        ctx=[cdt_ctx.cdt_ctx_list_index(0)],
                        return_type=aerospike.MAP_RETURN_VALUE,
                        value_type=expressions.resources.ResultType.STRING,
                        key='key',
                        bin='bin'
                    ),
                    'value0',
                ),
                [{'key': 'value1'}],
                expressions.Unknown()
            ).compile()
        ),
    ]
)
print(ac.get(key)[2])

i get output

exception.OpNotApplicable: (26, '127.0.0.1:3000 AEROSPIKE_ERR_OP_NOT_APPLICABLE', 'src/main/client/operate.c', 813, False)

when using the commented out MapGetByKey above which uses ListGetByIndex instead, i get the expected

{'bin': [{'key': 'value0'}]}
{'bin': [{'key': 'value1'}]}

but these should be equivalent operations correct? are there advantages to using the cdt_ctx (i do think the cdt_ctx is syntactically better at the very least)?

Thanks for pointing out the issue with a working example. It seems the context may be working, but there may be another issue. Per the docs, the “bin” parameter of MapGetByKey is either a “Map bin name or map expression”. When the list bin is changed to a map bin - {‘bin’: {1: {‘key’: ‘value0’}}, for example - it seems to work. We will have engineering look into it.

@avi this is due to the helper behavior of the Python client for deducing a bin type being simplistic, but there’s an easy fix. The Python client API for expressions.map.MapGetByKey defines the param bin ( TypeBinName ) – Map bin name or map expression. If you just give a string bin name it’ll assume that it’s an expressions.MapBin(bin-name).

On the server-side Expressions are strongly typed. It expects an expression that tells it the type and name of the bin. You should explicitly state that this is a List bin: bin=expressions.ListBin('bin').

Here’s my test code:

import aerospike
from aerospike import exception
from aerospike_helpers import expressions as exp
from aerospike_helpers import cdt_ctx as context
from aerospike_helpers.operations import list_operations as lh
from aerospike_helpers.operations import map_operations as mh
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", "context-bug")
try:
    client.remove(key)
except:
    pass
policy = {"key": aerospike.POLICY_KEY_SEND}
client.put(
    key,
    {"listbin": [{"mapkey0": "v0"}], "mapbin": {"1": {"mapkey1": "v1"}}},
    policy=policy,
)
_, _, b = client.get(key)
print(b)

ctx = [context.cdt_ctx_map_key("1")]
v1_exp = exp.map.MapGetByKey(
    ctx, aerospike.MAP_RETURN_VALUE, exp.ResultType.STRING, "mapkey1",
    exp.MapBin("mapbin")
).compile()
# these should be equivalent (and are)
ops = [
    mh.map_get_by_key("mapbin", "mapkey1", aerospike.MAP_RETURN_VALUE, ctx),
    opexp.expression_read("same_mapval", v1_exp),
]
_, _, b = client.operate(key, ops, policy=policy)
print("\n")
print(b)
# gives: {'mapbin': 'v1', 'same_mapval': 'v1'}

ctx = [context.cdt_ctx_list_index(0)]
v0_exp = exp.map.MapGetByKey(
    ctx, aerospike.MAP_RETURN_VALUE, exp.ResultType.STRING, "mapkey0",
    exp.ListBin("listbin")
).compile()
# these should also be equivalent
# it fails to deduce the bin type due to the context (map operation inside a list bin)
# if the bin name is given as a string. When it's an explicit bin type expression it works
ops = [
    mh.map_get_by_key("listbin", "mapkey0", aerospike.MAP_RETURN_VALUE, ctx),
    opexp.expression_read("same_mapval", v0_exp),
]
try:
    _, _, b = client.operate(key, ops, policy=policy)
    print("\n")
    print(b)
    # gives: {'listbin': 'v0', 'same_mapval': 'v0'}
except Exception as e:
    print("\n", e)
    # if the expression has "listbin" instead of exp.ListBin("listbin") we get
    # (26, '127.0.0.1:3000 AEROSPIKE_ERR_OP_NOT_APPLICABLE', 'src/main/client/operate.c', 813, False)

client.close()

The output is as expected:

$ python3 context-bug.py 
{'listbin': [{'mapkey0': 'v0'}], 'mapbin': {'1': {'mapkey1': 'v1'}}}


{'mapbin': 'v1', 'same_mapval': 'v1'}


{'listbin': 'v0', 'same_mapval': 'v0'}
1 Like
© 2021 Copyright Aerospike, Inc. | All rights reserved. Creators of the Aerospike Database.