How to run multiple expressions in sequence (AER-6473)

this is in the context of expression conditions, here’s the pseudo code of what i am trying to do

expressions.Cond(
  condition,
  (action1, action2),
  expressions.Unknown()
)

where each of the actions is some expression, e.g. action1 is removing an element from a list value of a map and action2 is checking if the list is empty and removing the corresponding key from the map if so, is there some way to do this?

a workaround right now, following the example above, is simply to add another expression operation that checks if the list is empty and removes the key if so, so it would reduce to two separate operations something like

expressions.Cond(
  condition,
  action1,
  expressions.Unknown()
)

and

expressions.Cond(
  condition,
  action2,
  expressions.Unknown()
)

which has the disadvantage of checking the condition again. is there some better alternative that i am missing? the usefulness of this makes sense right?

It seems you will need to use a separate expression operation for the second action. For a condition evaluation that is costly, you may save the condition result in a temp bin and use it multiple times, and delete the bin later. (Also, since the second action also has to perform an additional null list check, its condition would be: condition && null-list-check.)

Does action1 and action2 apply to the same bin?

@kporter yes

thanks @neelp i’ll probably double up on the expression operations since the condition is cheap, but taking a list of expressions would still be a cool feature for the future :slight_smile:

In that case, you should be able to chain the actions - similar to what you describe in your first example.

Would look something like:

expressions.Cond(
  condition,
  action2(action1),
  expressions.Unknown()
)

I could provide a better demonstration if you could provide a more concrete example.

Here is an example of what I’m describing. It’s written in the context of a filter-exp but the same principle would apply to an operate-exp.

so this may be a separate issue or i maybe be misunderstanding expressions, but i’m getting some weird behavior when doing the post-expression, “chained” bin passing, at least with expressions.Let

here’s a full repro with

python: 3.8.10
python aerospike client: 6.0.0
aerospike image: aerospike:ce-5.6.0.9
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', None, 0)

ac.put(
    key,
    {
        'bin': {
            'level1': {
                'level2_1': ['010101', '010102'],
                'level2_2': ['010102', '010103'],
            }
        }
    }
)
print(ac.get(key))
ac.operate(
    key,
    [
        expression_operations.expression_write(
            'bin',
            expressions.Let(
                expressions.Def(
                    'with_removed',
                    expressions.MapRemoveByKey(
                        ctx=[cdt_ctx.cdt_ctx_map_key('level1')],
                        key='level2_1',
                        bin='bin'
                    )
                ),
                expressions.Cond(
                    expressions.Eq(
                        expressions.MapSize(
                            ctx=[cdt_ctx.cdt_ctx_map_key('level1')],
                            bin=expressions.Var('with_removed')
                        ),
                        0
                    ),
                    expressions.MapRemoveByKey(
                        ctx=None,
                        key='level1',
                        bin=expressions.Var('with_removed')
                    ),
                    expressions.Var('with_removed')
                    # expressions.MapRemoveByKey(
                    #     ctx=[cdt_ctx.cdt_ctx_map_key('level1')],
                    #     key='level2_1',
                    #     bin='bin'
                    # )
                )
            ).compile()
        )
    ]
)
print(ac.get(key))

and i’m gettting this output

(('test', None, None, bytearray(b'\xb3F\x13{\xa9\x18\x95y\xdaR\x01b\xdf\xdc\xd6\x1a\x10\xfe7\xd6')), {'ttl': 2592000, 'gen': 21}, {'bin': {'level1': {'level2_1': ['010101', '010102'], 'level2_2': ['010102', '010103']}}})
(('test', None, None, bytearray(b'\xb3F\x13{\xa9\x18\x95y\xdaR\x01b\xdf\xdc\xd6\x1a\x10\xfe7\xd6')), {'ttl': 2592000, 'gen': 22}, {'bin': 6.9493343797729e-310})

i observed a few things:

  1. using the commented out expressions.MapRemoveByKey (repeating the expressions.Def) works as expected with output
(('test', None, None, bytearray(b'\xb3F\x13{\xa9\x18\x95y\xdaR\x01b\xdf\xdc\xd6\x1a\x10\xfe7\xd6')), {'ttl': 2592000, 'gen': 27}, {'bin': {'level1': {'level2_1': ['010101', '010102'], 'level2_2': ['010102', '010103']}}})
(('test', None, None, bytearray(b'\xb3F\x13{\xa9\x18\x95y\xdaR\x01b\xdf\xdc\xd6\x1a\x10\xfe7\xd6')), {'ttl': 2592000, 'gen': 28}, {'bin': {'level1': {'level2_2': ['010102', '010103']}}})
  1. only the non-“level1 removal path” is a problem, commenting out a level2 (to go down the level1 removal path) works as expected with output
(('test', None, None, bytearray(b'\xb3F\x13{\xa9\x18\x95y\xdaR\x01b\xdf\xdc\xd6\x1a\x10\xfe7\xd6')), {'ttl': 2592000, 'gen': 29}, {'bin': {'level1': {'level2_1': ['010101', '010102']}}})
(('test', None, None, bytearray(b'\xb3F\x13{\xa9\x18\x95y\xdaR\x01b\xdf\xdc\xd6\x1a\x10\xfe7\xd6')), {'ttl': 2592000, 'gen': 30}, {'bin': {}})
  1. not using a expressions.Let and just repeating everything within the expressions.Cond works as expected.

is the first output a bug?

Thanks for pinpointing the issue. There definitely is a bug (which we will file) - but it seems not in the “default” path, but likely in the way an unchanged var value is returned and gets assigned. If for example you return the var from a condition branch, it still does not work. A modified var (say, with MapPut or MapClear) however would work.

Thanks for reporting - this is a bug that affects namespaces that are data-in-memory or storage-engine memory. A fix has been committed internally and should be part of subsequent major and hotfix releases.

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