def _commit_release(self, txn_id, new_head_offset=None, new_tail_offset=None):
     """
     If the lock is still held by this requester (txn-id), update the new positions of head/tail and r
     elease the lock. Otherwise abort the request as timed out.
     :param txn_id: lock owner id, must be unique among concurrent requests
     :param new_head_offset: new head offset to be updated
     :param new_tail_offset: new tail offset to be updated
     :return: throws ASAborted('Timed out')
     """
     metadata_key = (self.namespace, self.name, LargeQueue.META_REC_KEY)
     predexps = [ predexp.integer_bin("locked"),
                  predexp.integer_value(1),
                  predexp.integer_equal(),
                  predexp.integer_bin("lock-owner"),
                  predexp.integer_value(txn_id),
                  predexp.integer_equal(),
                  predexp.predexp_and(2)]
     ops = [ op_helpers.write('locked', 0),
             op_helpers.write('lock-owner', None),
             op_helpers.write('lock-time-ms', None) ]
     if new_head_offset is not None:
         ops.append(op_helpers.write('head-offset', new_head_offset))
     if new_tail_offset is not None:
         ops.append(op_helpers.write('tail-offset', new_tail_offset))
     try:
         _ = self.client.operate(metadata_key, ops, policy={'predexp': predexps})
     except exception.FilteredOut as ex:  # predexp failed
         raise ASAborted('Timed out')
     return
示例#2
0
    def test_background_execute_with_ops_and_predexp(self):
        """
        Ensure that Scan.execute_background() applies ops to records that match the predexp
        """
        test_bin = 'Stops_preds'
        keys = [(TEST_NS, TEST_SET, i) for i in range(50)]

        scan = self.as_connection.scan(TEST_NS, TEST_SET)
        # scan.apply(TEST_UDF_MODULE, TEST_UDF_FUNCTION, [test_bin])

        ops = [operations.write(test_bin, 'new_val')]

        predexp = [
            as_predexp.integer_bin('number'),
            as_predexp.integer_value(2),
            as_predexp.integer_equal(),
            as_predexp.integer_bin('number'),
            as_predexp.integer_value(3),
            as_predexp.integer_equal(),
            as_predexp.predexp_or(2)
        ]

        policy = {'predexp': predexp}

        scan.add_ops(ops)
        job_id = scan.execute_background(policy)
        # Give time for the scan to finish
        wait_for_job_completion(self.as_connection, job_id)

        for key in keys:
            _, _, bins = self.as_connection.get(key)
            if bins['number'] == 2 or bins['number'] == 3:
                assert (bins[test_bin] == 'new_val')
            else:
                assert (bins.get(test_bin) is None)
示例#3
0
    def test_background_execute_predexp_and_predicate(self):
        """
        Ensure that Scan.execute_background() gets applied to records that match the predicate
        NOTE: the predicate overrides the predexp
        """
        test_bin = 'Stpredold'
        keys = [(TEST_NS, TEST_SET, i) for i in range(50)]

        predexp = [
            as_predexp.integer_bin('number'),
            as_predexp.integer_value(2),
            as_predexp.integer_equal(),
            as_predexp.integer_bin('number'),
            as_predexp.integer_value(3),
            as_predexp.integer_equal(),
            as_predexp.predexp_or(2)
        ]

        number_predicate = predicates.equals('number', 4)

        policy = {'predexp': predexp}

        scan = self.as_connection.scan(TEST_NS, TEST_SET)
        scan.where(number_predicate)
        scan.apply(TEST_UDF_MODULE, TEST_UDF_FUNCTION, [test_bin])
        job_id = scan.execute_background(policy)
        # Give time for the scan to finish
        wait_for_job_completion(self.as_connection, job_id)

        for key in keys:
            _, _, bins = self.as_connection.get(key)
            if bins['number'] == 4:
                assert (bins[test_bin] == 'aerospike')
            else:
                assert (bins.get(test_bin) is None)
示例#4
0
    def test_query_apply_with_new_predexp(self):
        """
        Invoke query_apply() with correct policy and predexp
        """

        predexp = [
            as_predexp.integer_bin('age'),
            as_predexp.integer_value(2),
            as_predexp.integer_equal(),
            as_predexp.integer_bin('val'),
            as_predexp.integer_value(3),
            as_predexp.integer_equal(),
            as_predexp.predexp_or(2)
        ]

        policy = {'total_timeout': 0, 'predexp': predexp}
        query_id = self.as_connection.query_apply(
            "test", "demo", self.age_range_pred, "query_apply",
            "mark_as_applied", ['name', 2], policy)

        self._wait_for_query_complete(query_id)

        recs = []

        for i in range(1, 10):
            key = ('test', 'demo', i)
            _, _, bins = self.as_connection.get(key)
            if bins['name'] == 'aerospike':
                recs.append(bins)
        
        assert len(recs) == 2
        for rec in recs:
            assert rec['age'] == 2 or rec['val'] == 3
示例#5
0
    def test_background_execute_predexp_everywhere(self):
        """
        Ensure that Scan.execute_background() gets applied to records that match the predexp
        """
        test_bin = 'number'
        keys = [(TEST_NS, TEST_SET, i) for i in range(50)]

        predexp = [
            as_predexp.integer_bin('number'),
            as_predexp.integer_value(2),
            as_predexp.integer_equal(),
            as_predexp.integer_bin('number'),
            as_predexp.integer_value(3),
            as_predexp.integer_equal(),
            as_predexp.predexp_or(2)
        ]

        #number_predicate = predicates.equals('number', 3)

        policy = {'predexp': predexp}

        scan = self.as_connection.scan(TEST_NS, TEST_SET)
        #scan.where(number_predicate)
        scan.apply(TEST_UDF_MODULE, TEST_UDF_FUNCTION, [test_bin, 1])
        job_id = scan.execute_background(policy)
        # Give time for the scan to finish
        wait_for_job_completion(self.as_connection, job_id)

        for i, key in enumerate(keys):
            _, _, bins = self.as_connection.get(key)
            if i == 2 or i == 3:
                assert (bins[test_bin] == i + 1)
            else:
                assert (bins.get(test_bin) == i)
 def test_or(self):
     predexps = [
         predexp.integer_bin('positive_i'),
         predexp.integer_value(5),
         predexp.integer_equal(),
         predexp.integer_bin('positive_i'),
         predexp.integer_value(10),
         predexp.integer_equal(),
         predexp.predexp_or(2)
     ]
     self.query.predexp(predexps)
     results = self.query.results()
     assert len(results) == 2
     assert_each_record_bins(results, lambda b: b['positive_i'] in (5, 10))
 def test_and(self):
     predexps = [
         predexp.integer_bin('positive_i'),
         predexp.integer_value(10),
         predexp.integer_greater(),
         predexp.integer_bin('positive_i'),
         predexp.integer_value(20),
         predexp.integer_less(),
         predexp.predexp_and(2)
     ]
     self.query.predexp(predexps)
     results = self.query.results()
     assert len(results) == 9
     assert_each_record_bins(
         results, lambda b: b['positive_i'] > 10 and b['positive_i'] < 20)
 def test_or(self):
     predexps = [
         predexp.integer_bin('positive_i'),
         predexp.integer_value(5),
         predexp.integer_equal(),
         predexp.integer_bin('positive_i'),
         predexp.integer_value(10),
         predexp.integer_equal(),
         predexp.predexp_or(2)
     ]
     self.query.predexp(predexps)
     results = self.query.results()
     assert len(results) == 2
     assert_each_record_bins(
         results,
         lambda b: b['positive_i'] in (5, 10))
 def test_and(self):
     predexps = [
         predexp.integer_bin('positive_i'),
         predexp.integer_value(10),
         predexp.integer_greater(),
         predexp.integer_bin('positive_i'),
         predexp.integer_value(20),
         predexp.integer_less(),
         predexp.predexp_and(2)
     ]
     self.query.predexp(predexps)
     results = self.query.results()
     assert len(results) == 9
     assert_each_record_bins(
         results,
         lambda b: b['positive_i'] > 10 and b['positive_i'] < 20)
示例#10
0
    def test_background_execute_sindex_predexp(self, clean_test_background):
        """
        Ensure that Query.execute_background() only applies to records matched by
        the specified predicate
        """
        test_bin = 't4'
        keys = [(TEST_NS, TEST_SET, i) for i in range(500)]

        #  rec['number'] < 10
        predexps = [
            predexp.integer_bin('number'),
            predexp.integer_value(10),
            predexp.integer_less()
        ]

        query = self.as_connection.query(TEST_NS, TEST_SET)
        query.predexp(predexps)
        query.apply(TEST_UDF_MODULE, TEST_UDF_FUNCTION, [test_bin])
        query.execute_background()
        # Give time for the query to finish
        time.sleep(5)

        # Records with number > 10 should not have had the UDF applied
        validate_records(self.as_connection, keys[10:],
                         lambda rec: test_bin not in rec)
        #  Records with number < 10 should have had the udf applied
        validate_records(self.as_connection, keys[:10],
                         lambda rec: rec[test_bin] == 'aerospike')
示例#11
0
    def test_scan_apply_with_none_set_and_predexp(self):
        """
        Invoke scan_apply() with set argument as None
        It should invoke the function on all records in NS that match the predexp
        """
        predexp = [
            as_predexp.string_bin('name'),
            as_predexp.string_value('name2'),
            as_predexp.string_equal(),
            as_predexp.integer_bin('age'),
            as_predexp.integer_value(3),
            as_predexp.integer_unequal(),
            as_predexp.predexp_and(2)
        ]

        policy = {'timeout': 1000, 'predexp': predexp}
        scan_id = self.as_connection.scan_apply("test", None, "bin_lua",
                                                "mytransform", ['age', 2],
                                                policy)

        wait_for_job_completion(self.as_connection, scan_id)

        for i in range(5):
            key = ('test', 'demo', i)
            _, _, bins = self.as_connection.get(key)
            if bins['name'] == 'name2':
                assert bins['age'] == i + 2
            else:
                assert bins['age'] == i

        _, _, rec = self.as_connection.get(('test', None, 'no_set'))
        assert rec['age'] == 10
    def test_exists_many_with_large_predexp(self):
        '''
        Proper call to exists_many with predexp in policy.
        '''
        predexp = [
            as_predexp.integer_bin('account_id'),
            as_predexp.integer_value(4),
            as_predexp.integer_equal(),
            as_predexp.string_bin('user_name'),
            as_predexp.string_value('user3'),
            as_predexp.string_equal(),
            as_predexp.integer_var('list_val'),
            as_predexp.integer_value(12),
            as_predexp.integer_less(),
            as_predexp.list_bin('charges'),
            as_predexp.list_iterate_and('list_val'),
            as_predexp.predexp_or(3)
        ]

        matched_recs = []
        records = self.as_connection.exists_many(self.keys,
                                                 {'predexp': predexp})
        for rec in records:
            if rec[1] is not None:
                matched_recs.append(rec[1])

        assert len(matched_recs) == 3
class TestApply(TestBaseClass):
    def setup_class(cls):
        # Register setup and teardown functions
        cls.connection_setup_functions = [add_indexes_and_udfs]
        cls.connection_teardown_functions = [remove_indexes_and_udfs]

    @pytest.fixture(autouse=True)
    def setup(self, request, connection_with_config_funcs):
        as_connection = connection_with_config_funcs

        for i in range(5):
            key = ('test', 'demo', i)
            rec = {
                'name': ['name%s' % (str(i))],
                'addr': 'name%s' % (str(i)),
                'age': i,
                'no': i,
                'basic_map': {
                    "k30": 6,
                    "k20": 5,
                    "k10": 1
                }
            }
            as_connection.put(key, rec)

        def teardown():
            for i in range(5):
                key = ('test', 'demo', i)
                as_connection.remove(key)

        request.addfinalizer(teardown)

    @pytest.mark.parametrize("func_args, test_bin, predexp, expected",
                             ((['name', 1], 'name', [
                                 as_predexp.integer_bin('age'),
                                 as_predexp.integer_value(1),
                                 as_predexp.integer_equal()
                             ], ['name1', 1]), ),
                             ids=[
                                 "Integer",
                             ])
    def test_apply_causing_list_append_with_correct_params_with_predexp(
            self, func_args, test_bin, predexp, expected):

        key = ('test', 'demo', 1)
        retval = self.as_connection.apply(key,
                                          'sample',
                                          'list_append',
                                          func_args,
                                          policy={'predexp': predexp})

        _, _, bins = self.as_connection.get(key)

        print(expected)
        print(bins[test_bin])
        assert bins[test_bin] == expected
        assert retval == 0  # the list_append UDF returns 0
示例#14
0
 def test_integer_equals(self):
     predexps = [
         predexp.integer_bin('positive_i'),
         predexp.integer_value(5),
         predexp.integer_equal()
     ]
     self.query.predexp(predexps)
     results = self.query.results()
     assert len(results) == 1
     assert_each_record_bins(results, lambda b: b['positive_i'] == 5)
示例#15
0
 def test_integer_greatereq(self):
     predexps = [
         predexp.integer_bin('positive_i'),
         predexp.integer_value(49),
         predexp.integer_greatereq()
     ]
     self.query.predexp(predexps)
     results = self.query.results()
     assert len(results) == 51
     assert_each_record_bins(results, lambda b: b['positive_i'] >= 49)
示例#16
0
 def test_integer_lesseq(self):
     predexps = [
         predexp.integer_bin('positive_i'),
         predexp.integer_value(10),
         predexp.integer_lesseq()
     ]
     self.query.predexp(predexps)
     results = self.query.results()
     assert len(results) == 11
     assert_each_record_bins(results, lambda b: b['positive_i'] <= 10)
 def test_get_with_predexp_filtered_out(self):
     '''
     Call to get with predexp in policy with expected failures.
     '''
     predexp = [
         as_predexp.integer_bin('account_id'),
         as_predexp.integer_value(3),
         as_predexp.integer_equal()
     ]
     with pytest.raises(e.FilteredOut):
         self.as_connection.get(self.keys[0], {'predexp': predexp})
示例#18
0
    def test_query_apply_with_bad_new_predexp(self):
        """
        Invoke query_apply() with correct policy and predexp
        """

        predexp = [
            as_predexp.integer_bin('age'),
            as_predexp.string_value(2),
            as_predexp.integer_equal(),
            as_predexp.integer_bin('val'),
            as_predexp.integer_value(3),
            as_predexp.integer_equal(),
            as_predexp.predexp_or(2)
        ]

        policy = {'total_timeout': 0, 'predexp': predexp}
        with pytest.raises(e.ParamError):
            query_id = self.as_connection.query_apply(
                "test", "demo", self.age_range_pred, "query_apply",
                "mark_as_applied", ['name', 2], policy)
示例#19
0
def main():
    # variables to connect to Aerospike
    host = '127.0.0.1'
    port = 3000
    namespace = 'ns2'
    set_name = 'example'

    # connect to Aerospike
    client = aerospike.client({'hosts': [(host, port)]}).connect()

    # variables to control which account and locations are corrected
    account_to_correct = '00007'
    incorrect_location = 5
    correct_location = 2

    # only update records that match on 'acct' AND 'loc'
    predicate_expressions = [
        predexp.integer_bin('loc'),
        predexp.integer_value(incorrect_location),
        predexp.integer_equal(),
        predexp.string_bin('acct'),
        predexp.string_value(account_to_correct),
        predexp.string_equal(),
        predexp.predexp_and(2)
    ]

    policy = {'predexp': predicate_expressions}

    # Do a standard scan with the predicate expressions and count the results.
    scan = client.scan(namespace, set_name)
    records = scan.results(policy)
    print("{} records found".format(len(records)))

    # Do a background scan, which runs server-side, to update the records that
    # match the predicate expression with the correct value for 'loc'.
    ops = [operations.write('loc', correct_location)]

    bgscan = client.scan(namespace, set_name)
    bgscan.add_ops(ops)
    scan_id = bgscan.execute_background(policy)
    print("Running background read/write scan. ID: {}".format(scan_id))

    # Wait for the background scan to complete.
    while True:
        response = client.job_info(scan_id, aerospike.JOB_SCAN)
        if response["status"] != aerospike.JOB_STATUS_INPROGRESS:
            break
        sleep(0.25)

    # Do a standard scan with the predicate expressions and count the results.
    scan = client.scan(namespace, set_name)
    records = scan.results(policy)
    print("{} records found".format(len(records)))
    def test_get_with_predexp(self):
        '''
        Call to get with predexp in policy.
        '''
        predexp = [
            as_predexp.integer_bin('account_id'),
            as_predexp.integer_value(1),
            as_predexp.integer_equal()
        ]
        record = self.as_connection.get(self.keys[0], {'predexp': predexp})

        assert record[2]['account_id'] == 1
 def test_put_with_predexp_filtered_out(self):
     '''
     Call put with predexp in policy with expected failure.
     '''
     predexp = [
         as_predexp.integer_bin('account_id'),
         as_predexp.integer_value(4),
         as_predexp.integer_equal()
     ]
     with pytest.raises(e.FilteredOut):
         self.as_connection.put(self.keys[0], {'newkey': 'newval'},
                                policy={'predexp': predexp})
 def test_remove_bin_with_predexp_filtered_out(self):
     '''
     Call remove_bin with predexp in policy with expected failure.
     '''
     predexp = [
         as_predexp.integer_bin('account_id'),
         as_predexp.integer_value(4),
         as_predexp.integer_equal()
     ]
     with pytest.raises(e.FilteredOut):
         self.as_connection.remove_bin(self.keys[0],
                                       ['account_id', 'user_name'],
                                       policy={'predexp': predexp})
    def test_pos_remove_with_predexp(self):
        '''
        Call remove with predexp in policy.
        '''
        predexp = [
            as_predexp.integer_bin('account_id'),
            as_predexp.integer_value(1),
            as_predexp.integer_equal()
        ]
        records = self.as_connection.remove(self.keys[0])

        rec = self.as_connection.exists(self.keys[0])
        assert rec[1] is None
    def test_put_with_predexp(self):
        '''
        Call put with predexp in policy.
        '''
        predexp = [
            as_predexp.integer_bin('account_id'),
            as_predexp.integer_value(1),
            as_predexp.integer_equal()
        ]
        self.as_connection.put(self.keys[0], {'newkey': 'newval'},
                               policy={'predexp': predexp})

        rec = self.as_connection.get(self.keys[0])
        assert rec[2]['newkey'] == 'newval'
 def _lock(self, txn_id, op):
     """
     Atomically check if the queue is locked, break an expired lock, lock the queue and
     set the lock-owner and lock-time, and if the operation is enqueue, also increment and
     return the fencing counter.
     Try multiple times if the lock is not available, and wait before subsequent attempt.
     :param txn_id: lock owner id, must be unique among concurrent requests
     :param op: enqueue or dequeue
     :return: dict with head and tail positions on success
         throws ASAborted('Failed to acquire lock') on failure
     """
     metadata_key = (self.namespace, self.name, LargeQueue.META_REC_KEY)
     for _ in range(LargeQueue.LOCK_MAX_RETRIES):
         curr_time_ms = LargeQueue._curr_time_milliseconds()
         predexps = [ predexp.integer_bin("locked"),
                      predexp.integer_value(0),
                      predexp.integer_equal(),
                      predexp.integer_bin("lock-time-ms"),
                      predexp.integer_value(curr_time_ms-LargeQueue.LOCK_EXPIRATION_MS),
                      predexp.integer_less(),
                      predexp.predexp_or(2) ]
         ops = [ op_helpers.read('head-offset'),
                 op_helpers.read('tail-offset'),
                 op_helpers.write('locked', 1),
                 op_helpers.write('lock-owner', txn_id),
                 op_helpers.write('lock-time-ms', curr_time_ms) ]
         if op == LargeQueue.Ops.Enqueue:
             ops.append(op_helpers.increment('fencing-ctr', 1))
             ops.append(op_helpers.read('fencing-ctr'))
         try:
             _, _, record = self.client.operate(metadata_key, ops, policy={'predexp': predexps})
         except exception.FilteredOut as ex:     # predexp failed
             time.sleep(LargeQueue.LOCK_POLL_WAIT_MS/1000.0)
             continue
         return record
     raise ASAborted('Failed to acquire lock')
示例#26
0
    def test_background_execute_predexp_and_predicate(self,
                                                      clean_test_background):
        """
        Ensure that Query.execute_background() gets applied to records that match the predicate
        NOTE: the predicate overrides the predexp
        """
        test_bin = 'tpredold'
        keys = [(TEST_NS, TEST_SET, i) for i in range(500)]

        predexp = [
            as_predexp.integer_bin('number'),
            as_predexp.integer_value(2),
            as_predexp.integer_equal(),
            as_predexp.integer_bin('number'),
            as_predexp.integer_value(3),
            as_predexp.integer_equal(),
            as_predexp.predexp_or(2)
        ]

        number_predicate = predicates.equals('number', 4)

        policy = {'predexp': predexp}

        query = self.as_connection.query(TEST_NS, TEST_SET)
        query.where(number_predicate)
        query.apply(TEST_UDF_MODULE, TEST_UDF_FUNCTION, [test_bin])
        query.execute_background(policy)
        # Give time for the query to finish
        time.sleep(5)

        for key in keys:
            _, _, bins = self.as_connection.get(key)
            if bins['number'] == 4:
                assert (bins[test_bin] == 'aerospike')
            else:
                assert (bins.get(test_bin) is None)
示例#27
0
    def test_background_execute_with_ops_and_predexp(self,
                                                     clean_test_background):
        """
        Ensure that Query.execute_background() applies ops to records that match the predexp
        """
        test_bin = 'tops_preds'
        keys = [(TEST_NS, TEST_SET, i) for i in range(500)]

        query = self.as_connection.query(TEST_NS, TEST_SET)
        # query.apply(TEST_UDF_MODULE, TEST_UDF_FUNCTION, [test_bin])

        ops = [operations.write(test_bin, 'new_val')]

        predexp = [
            as_predexp.integer_bin('number'),
            as_predexp.integer_value(2),
            as_predexp.integer_equal(),
            as_predexp.integer_bin('number'),
            as_predexp.integer_value(3),
            as_predexp.integer_equal(),
            as_predexp.predexp_or(2)
        ]

        policy = {'predexp': predexp}

        query.add_ops(ops)
        query.execute_background(policy)
        # Give time for the query to finish
        time.sleep(5)

        for key in keys:
            _, _, bins = self.as_connection.get(key)
            if bins['number'] == 2 or bins['number'] == 3:
                assert (bins[test_bin] == 'new_val')
            else:
                assert (bins.get(test_bin) is None)
    def test_remove_bin_with_predexp(self):
        '''
        Call remove_bin with predexp in policy.
        '''
        predexp = [
            as_predexp.integer_bin('account_id'),
            as_predexp.integer_value(1),
            as_predexp.integer_equal()
        ]
        self.as_connection.remove_bin(self.keys[0],
                                      ['account_id', 'user_name'],
                                      policy={'predexp': predexp})

        rec = self.as_connection.get(self.keys[0])
        assert rec[2].get('account_id') is None and rec[2].get(
            'user_name') is None
    def test_put_new_record_with_predexp(self):  # should this fail?
        '''
        Call put a new record with predexp in policy.
        '''
        predexp = [
            as_predexp.integer_bin('account_id'),
            as_predexp.integer_value(1),
            as_predexp.integer_equal()
        ]
        key = ("test", "demo", 10)
        self.as_connection.put(key, {'newkey': 'newval'},
                               policy={'predexp': predexp})

        rec = self.as_connection.get(key)
        self.as_connection.remove(key)
        assert rec[2]['newkey'] == 'newval'
    def test_query_with_results_method_and_invalid_predexp(self):
        """
            Invoke query() with correct arguments
        """
        predexp = [
            as_predexp.integer_bin('test_age'),
            as_predexp.integer_value('1'),
            as_predexp.integer_equal()
        ]

        policy = {'predexp': predexp}

        query = self.as_connection.query('test', 'demo')
        query.select('name', 'test_age')

        with pytest.raises(e.ParamError):
            query.results(policy)
    def test_query_with_results_method_and_predexp(self):
        """
            Invoke query() with correct arguments
        """
        predexp = [
            as_predexp.integer_bin('test_age'),
            as_predexp.integer_value(1),
            as_predexp.integer_equal()
        ]

        policy = {'predexp': predexp}

        query = self.as_connection.query('test', 'demo')
        query.select('name', 'test_age')

        records = query.results(policy)
        assert len(records) == 1
    def test_select_with_predexp(self):
        '''
        Call to select with predexp in policy.
        '''
        predexp = [
            as_predexp.integer_bin('acct_balance'),
            as_predexp.integer_value(20),
            as_predexp.integer_equal(),
            as_predexp.integer_var('charge'),
            as_predexp.integer_value(20),
            as_predexp.integer_less(),
            as_predexp.list_bin('charges'),
            as_predexp.list_iterate_and('charge'),
            as_predexp.predexp_and(2)
        ]

        result = self.as_connection.select(self.keys[1],
                                           ['account_id', 'acct_balance'],
                                           {'predexp': predexp})
        assert result[2]['account_id'] == 2 and result[2]['acct_balance'] == 20