diff options
Diffstat (limited to 'test/suite')
| -rw-r--r-- | test/suite/test_backup05.py | 35 | ||||
| -rw-r--r-- | test/suite/test_join01.py | 409 | ||||
| -rw-r--r-- | test/suite/test_join07.py | 548 | ||||
| -rw-r--r-- | test/suite/test_join08.py | 265 | ||||
| -rw-r--r-- | test/suite/test_reconfig02.py | 10 | ||||
| -rw-r--r-- | test/suite/test_stat05.py | 5 | ||||
| -rw-r--r-- | test/suite/test_txn04.py | 2 |
7 files changed, 1010 insertions, 264 deletions
diff --git a/test/suite/test_backup05.py b/test/suite/test_backup05.py index 991a9f71b19..fbe219d8de8 100644 --- a/test/suite/test_backup05.py +++ b/test/suite/test_backup05.py @@ -37,10 +37,12 @@ import fnmatch, os, shutil, time from suite_subprocess import suite_subprocess from wtscenario import multiply_scenarios, number_scenarios, prune_scenarios from helper import copy_wiredtiger_home -import wttest +import wiredtiger, wttest class test_backup05(wttest.WiredTigerTestCase, suite_subprocess): uri = 'table:test_backup05' + emptyuri = 'table:test_empty05' + newuri = 'table:test_new05' create_params = 'key_format=i,value_format=i' freq = 5 @@ -51,12 +53,35 @@ class test_backup05(wttest.WiredTigerTestCase, suite_subprocess): # With the connection still open, copy files to new directory. # Half the time use an unaligned copy. - aligned = (i % (self.freq * 2) != 0) or os.name == "nt" + even = i % (self.freq * 2) == 0 + aligned = even or os.name == "nt" copy_wiredtiger_home(olddir, newdir, aligned) + # Half the time try to rename a table and the other half try + # to remove a table. They should fail. + if not even: + self.assertRaises(wiredtiger.WiredTigerError, + lambda: self.session.rename( + self.emptyuri, self.newuri, None)) + else: + self.assertRaises(wiredtiger.WiredTigerError, + lambda: self.session.drop(self.emptyuri, None)) + # Now simulate fsyncUnlock by closing the backup cursor. cbkup.close() + # Once the backup cursor is closed we should be able to perform + # schema operations. Test that and then reset the files to their + # expected initial names. + if not even: + self.session.rename(self.emptyuri, self.newuri, None) + self.session.drop(self.newuri, None) + self.session.create(self.emptyuri, self.create_params) + else: + self.session.drop(self.emptyuri, None) + self.session.create(self.emptyuri, self.create_params) + + # Open the new directory and verify conn = self.setUpConnectionOpen(newdir) session = self.setUpSessionOpen(conn) @@ -77,6 +102,10 @@ class test_backup05(wttest.WiredTigerTestCase, suite_subprocess): # # If the metadata isn't flushed, eventually the metadata we copy will # be sufficiently out-of-sync with the data file that it won't verify. + + self.session.create(self.emptyuri, self.create_params) + self.reopen_conn() + self.session.create(self.uri, self.create_params) for i in range(100): c = self.session.open_cursor(self.uri) @@ -88,7 +117,7 @@ class test_backup05(wttest.WiredTigerTestCase, suite_subprocess): self.session.verify(self.uri) def test_backup(self): - with self.expectedStdoutPattern('Recreating metadata'): + with self.expectedStdoutPattern('recreating metadata'): self.backup() if __name__ == '__main__': diff --git a/test/suite/test_join01.py b/test/suite/test_join01.py index 4aa2bc6e269..f8d96a2718a 100644 --- a/test/suite/test_join01.py +++ b/test/suite/test_join01.py @@ -35,10 +35,44 @@ from wtscenario import check_scenarios, multiply_scenarios, number_scenarios class test_join01(wttest.WiredTigerTestCase): nentries = 100 - scenarios = [ + type_scen = [ ('table', dict(ref='table')), ('index', dict(ref='index')) ] + bloom0_scen = [ + ('bloom0=0', dict(joincfg0='')), + ('bloom0=1000', dict(joincfg0=',strategy=bloom,count=1000')), + ('bloom0=10000', dict(joincfg0=',strategy=bloom,count=10000')), + ] + bloom1_scen = [ + ('bloom1=0', dict(joincfg1='')), + ('bloom1=1000', dict(joincfg1=',strategy=bloom,count=1000')), + ('bloom1=10000', dict(joincfg1=',strategy=bloom,count=10000')), + ] + projection_scen = [ + ('no-projection', dict(do_proj=False)), + ('projection', dict(do_proj=True)) + ] + nested_scen = [ + ('simple', dict(do_nested=False)), + ('nested', dict(do_nested=True)) + ] + stats_scen = [ + ('no-stats', dict(do_stats=False)), + ('stats', dict(do_stats=True)) + ] + order_scen = [ + ('order=0', dict(join_order=0)), + ('order=1', dict(join_order=1)), + ('order=2', dict(join_order=2)), + ('order=3', dict(join_order=3)), + ] + scenarios = number_scenarios(multiply_scenarios('.', type_scen, + bloom0_scen, bloom1_scen, + projection_scen, + nested_scen, stats_scen, + order_scen)) + # We need statistics for these tests. conn_config = 'statistics=(all)' @@ -52,9 +86,29 @@ class test_join01(wttest.WiredTigerTestCase): return [s, rs, sort3] # Common function for testing iteration of join cursors - def iter_common(self, jc, do_proj): + def iter_common(self, jc, do_proj, do_nested, join_order): # See comments in join_common() - expect = [73, 82, 62, 83, 92] + # The order that the results are seen depends on + # the ordering of the joins. Specifically, the first + # join drives the order that results are seen. + if do_nested: + if join_order == 0: + expect = [73, 82, 83, 92] + elif join_order == 1: + expect = [73, 82, 83, 92] + elif join_order == 2: + expect = [82, 92, 73, 83] + elif join_order == 3: + expect = [92, 73, 82, 83] + else: + if join_order == 0: + expect = [73, 82, 62, 83, 92] + elif join_order == 1: + expect = [62, 73, 82, 83, 92] + elif join_order == 2: + expect = [62, 82, 92, 73, 83] + elif join_order == 3: + expect = [73, 82, 62, 83, 92] while jc.next() == 0: [k] = jc.get_keys() i = k - 1 @@ -64,7 +118,9 @@ class test_join01(wttest.WiredTigerTestCase): [v0,v1,v2] = jc.get_values() self.assertEquals(self.gen_values(i), [v0,v1,v2]) if len(expect) == 0 or i != expect[0]: - self.tty(' result ' + str(i) + ' is not in: ' + str(expect)) + self.tty('ERROR: ' + str(i) + ' is not next in: ' + + str(expect)) + self.tty('JOIN ORDER=' + str(join_order) + ', NESTED=' + str(do_nested)) self.assertTrue(i == expect[0]) expect.remove(i) self.assertEquals(0, len(expect)) @@ -81,6 +137,8 @@ class test_join01(wttest.WiredTigerTestCase): 'join: index:join01:index2: ' + statdesc ] if self.ref == 'index': expectstats.append('join: index:join01:index0: ' + statdesc) + elif self.do_proj: + expectstats.append('join: table:join01(v2,v1,v0): ' + statdesc) else: expectstats.append('join: table:join01: ' + statdesc) self.check_stats(statcur, expectstats) @@ -118,11 +176,46 @@ class test_join01(wttest.WiredTigerTestCase): self.assertTrue(len(expectstats) == 0, 'missing expected values in stats: ' + str(expectstats)) + def session_record_join(self, jc, refc, config, order, joins): + joins.append([order, [jc, refc, config]]) + + def session_play_one_join(self, firsturi, jc, refc, config): + if refc.uri == firsturi and config != None: + config = config.replace('strategy=bloom','') + #self.tty('->join(jc, uri="' + refc.uri + + # '", config="' + str(config) + '"') + self.session.join(jc, refc, config) + + def session_play_joins(self, joins, join_order): + #self.tty('->') + firsturi = None + for [i, joinargs] in joins: + if i >= join_order: + if firsturi == None: + firsturi = joinargs[1].uri + self.session_play_one_join(firsturi, *joinargs) + for [i, joinargs] in joins: + if i < join_order: + if firsturi == None: + firsturi = joinargs[1].uri + self.session_play_one_join(firsturi, *joinargs) + # Common function for testing the most basic functionality # of joins - def join_common(self, joincfg0, joincfg1, do_proj, do_stats): + def test_join(self): + joincfg0 = self.joincfg0 + joincfg1 = self.joincfg1 + do_proj = self.do_proj + do_nested = self.do_nested + do_stats = self.do_stats + join_order = self.join_order #self.tty('join_common(' + joincfg0 + ',' + joincfg1 + ',' + - # str(do_proj) + ')') + # str(do_proj) + ',' + str(do_nested) + ',' + + # str(do_stats) + ',' + str(join_order) + ')') + + closeme = [] + joins = [] # cursors to be joined + self.session.create('table:join01', 'key_format=r' + ',value_format=SSi,columns=(k,v0,v1,v2)') self.session.create('index:join01:index0','columns=(v0)') @@ -143,7 +236,7 @@ class test_join01(wttest.WiredTigerTestCase): # We join on index2 first, not using bloom indices. # This defines the order that items are returned. - # index2 is sorts multiples of 3 first (see gen_values()) + # index2 sorts multiples of 3 first (see gen_values()) # and by using 'gt' and key 99, we'll skip multiples of 3, # and examine primary keys 2,5,8,...,95,98,1,4,7,...,94,97. jc = self.session.open_cursor('join:table:join01' + proj_suffix, @@ -152,7 +245,7 @@ class test_join01(wttest.WiredTigerTestCase): c2 = self.session.open_cursor('index:join01:index2(v1)', None, None) c2.set_key(99) # skips all entries w/ primary key divisible by three self.assertEquals(0, c2.search()) - self.session.join(jc, c2, 'compare=gt') + self.session_record_join(jc, c2, 'compare=gt', 0, joins) # Then select all the numbers 0-99 whose string representation # sort >= '60'. @@ -163,285 +256,87 @@ class test_join01(wttest.WiredTigerTestCase): c0 = self.session.open_cursor('table:join01', None, None) c0.set_key(60) self.assertEquals(0, c0.search()) - self.session.join(jc, c0, 'compare=ge' + joincfg0) + self.session_record_join(jc, c0, 'compare=ge' + joincfg0, 1, joins) # Then select all numbers whose reverse string representation # is in '20' < x < '40'. c1a = self.session.open_cursor('index:join01:index1(v1)', None, None) c1a.set_key('21') self.assertEquals(0, c1a.search()) - self.session.join(jc, c1a, 'compare=gt' + joincfg1) + self.session_record_join(jc, c1a, 'compare=gt' + joincfg1, 2, joins) c1b = self.session.open_cursor('index:join01:index1(v1)', None, None) c1b.set_key('41') self.assertEquals(0, c1b.search()) - self.session.join(jc, c1b, 'compare=lt' + joincfg1) + self.session_record_join(jc, c1b, 'compare=lt' + joincfg1, 2, joins) # Numbers that satisfy these 3 conditions (with ordering implied by c2): # [73, 82, 62, 83, 92]. # # After iterating, we should be able to reset and iterate again. + if do_nested: + # To test nesting, we create two new levels of conditions: + # + # x == 72 or x == 73 or x == 82 or x == 83 or + # (x >= 90 and x <= 99) + # + # that will get AND-ed into our existing join. The expected + # result is [73, 82, 83, 92]. + # + # We don't specify the projection here, it should be picked up + # from the 'enclosing' join. + nest1 = self.session.open_cursor('join:table:join01', None, None) + nest2 = self.session.open_cursor('join:table:join01', None, None) + + nc = self.session.open_cursor('index:join01:index0', None, None) + nc.set_key('90') + self.assertEquals(0, nc.search()) + self.session.join(nest2, nc, 'compare=ge') # joincfg left out + closeme.append(nc) + + nc = self.session.open_cursor('index:join01:index0', None, None) + nc.set_key('99') + self.assertEquals(0, nc.search()) + self.session.join(nest2, nc, 'compare=le') + closeme.append(nc) + + self.session.join(nest1, nest2, "operation=or") + + for val in [ '72', '73', '82', '83' ]: + nc = self.session.open_cursor('index:join01:index0', None, None) + nc.set_key(val) + self.assertEquals(0, nc.search()) + self.session.join(nest1, nc, 'compare=eq,operation=or' + + joincfg0) + closeme.append(nc) + self.session_record_join(jc, nest1, None, 3, joins) + + self.session_play_joins(joins, join_order) + self.iter_common(jc, do_proj, do_nested, join_order) if do_stats: self.stats(jc, 0) - self.iter_common(jc, do_proj) + jc.reset() + self.iter_common(jc, do_proj, do_nested, join_order) if do_stats: self.stats(jc, 1) jc.reset() - self.iter_common(jc, do_proj) + self.iter_common(jc, do_proj, do_nested, join_order) if do_stats: self.stats(jc, 2) jc.reset() - self.iter_common(jc, do_proj) + self.iter_common(jc, do_proj, do_nested, join_order) jc.close() c2.close() c1a.close() c1b.close() c0.close() + if do_nested: + nest1.close() + nest2.close() + for c in closeme: + c.close() self.session.drop('table:join01') - # Test joins with basic functionality - def test_join(self): - bloomcfg1000 = ',strategy=bloom,count=1000' - bloomcfg10000 = ',strategy=bloom,count=10000' - for cfga in [ '', bloomcfg1000, bloomcfg10000 ]: - for cfgb in [ '', bloomcfg1000, bloomcfg10000 ]: - for do_proj in [ False, True ]: - #self.tty('cfga=' + cfga + - # ', cfgb=' + cfgb + - # ', doproj=' + str(do_proj)) - self.join_common(cfga, cfgb, do_proj, False) - - def test_join_errors(self): - self.session.create('table:join01', 'key_format=r,value_format=SS' - ',columns=(k,v0,v1)') - self.session.create('table:join01B', 'key_format=r,value_format=SS' - ',columns=(k,v0,v1)') - self.session.create('index:join01:index0','columns=(v0)') - self.session.create('index:join01:index1','columns=(v1)') - self.session.create('index:join01B:index0','columns=(v0)') - jc = self.session.open_cursor('join:table:join01', None, None) - tc = self.session.open_cursor('table:join01', None, None) - fc = self.session.open_cursor('file:join01.wt', None, None) - ic0 = self.session.open_cursor('index:join01:index0', None, None) - ic0again = self.session.open_cursor('index:join01:index0', None, None) - ic1 = self.session.open_cursor('index:join01:index1', None, None) - icB = self.session.open_cursor('index:join01B:index0', None, None) - tcB = self.session.open_cursor('table:join01B', None, None) - - tc.set_key(1) - tc.set_value('val1', 'val1') - tc.insert() - tcB.set_key(1) - tcB.set_value('val1', 'val1') - tcB.insert() - fc.next() - - # Joining using a non join-cursor - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(tc, ic0, 'compare=ge'), - '/not a join cursor/') - # Joining a table cursor, not index - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, fc, 'compare=ge'), - '/not an index or table cursor/') - # Joining a non positioned cursor - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic0, 'compare=ge'), - '/requires reference cursor be positioned/') - ic0.set_key('val1') - # Joining a non positioned cursor (no search or next has been done) - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic0, 'compare=ge'), - '/requires reference cursor be positioned/') - ic0.set_key('valXX') - self.assertEqual(ic0.search(), wiredtiger.WT_NOTFOUND) - # Joining a non positioned cursor after failed search - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic0, 'compare=ge'), - '/requires reference cursor be positioned/') - - # position the cursors now - ic0.set_key('val1') - ic0.search() - ic0again.next() - icB.next() - - # Joining non matching index - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, icB, 'compare=ge'), - '/table for join cursor does not match/') - - # The cursor must be positioned - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic1, 'compare=ge'), - '/requires reference cursor be positioned/') - ic1.next() - - # The first cursor joined cannot be bloom - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic1, - 'compare=ge,strategy=bloom,count=1000'), - '/first joined cursor cannot specify strategy=bloom/') - - # This succeeds. - self.session.join(jc, ic1, 'compare=ge'), - - # With bloom filters, a count is required - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic0, 'compare=ge,strategy=bloom'), - '/count must be nonzero/') - - # This succeeds. - self.session.join(jc, ic0, 'compare=ge,strategy=bloom,count=1000'), - - bloom_config = ',strategy=bloom,count=1000' - # Cannot use the same index cursor - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic0, - 'compare=le' + bloom_config), - '/index cursor already used in a join/') - - # When joining with the same index, need compatible compares - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic0again, 'compare=ge' + bloom_config), - '/join has overlapping ranges/') - - # Another incompatible compare - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic0again, 'compare=gt' + bloom_config), - '/join has overlapping ranges/') - - # Compare is compatible, but bloom args need to match - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic0again, 'compare=le'), - '/join has incompatible strategy/') - - # Counts need to match for bloom filters - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: self.session.join(jc, ic0again, 'compare=le,strategy=bloom,' - 'count=100'), '/count.* does not match previous count/') - - # This succeeds - self.session.join(jc, ic0again, 'compare=le,strategy=bloom,count=1000') - - # Need to do initial next() before getting key/values - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: jc.get_keys(), - '/join cursor must be advanced with next/') - - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: jc.get_values(), - '/join cursor must be advanced with next/') - - # Operations on the joined cursor are frozen until the join is closed. - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: ic0.next(), - '/index cursor is being used in a join/') - - # Operations on the joined cursor are frozen until the join is closed. - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: ic0.prev(), - '/index cursor is being used in a join/') - - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: ic0.reset(), - '/index cursor is being used in a join/') - - # Only a small number of operations allowed on a join cursor - msg = "/Unsupported cursor/" - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: jc.search(), msg) - - self.assertRaisesWithMessage(wiredtiger.WiredTigerError, - lambda: jc.prev(), msg) - - self.assertEquals(jc.next(), 0) - self.assertEquals(jc.next(), wiredtiger.WT_NOTFOUND) - - # Only after the join cursor is closed can we use the index cursor - # normally - jc.close() - self.assertEquals(ic0.next(), wiredtiger.WT_NOTFOUND) - self.assertEquals(ic0.prev(), 0) - - # common code for making sure that cursors can be - # implicitly closed, no matter the order they are created - def cursor_close_common(self, joinfirst): - self.session.create('table:join01', 'key_format=r' + - ',value_format=SS,columns=(k,v0,v1)') - self.session.create('index:join01:index0','columns=(v0)') - self.session.create('index:join01:index1','columns=(v1)') - c = self.session.open_cursor('table:join01', None, None) - for i in range(0, self.nentries): - c.set_key(*self.gen_key(i)) - c.set_value(*self.gen_values(i)) - c.insert() - c.close() - - if joinfirst: - jc = self.session.open_cursor('join:table:join01', None, None) - c0 = self.session.open_cursor('index:join01:index0', None, None) - c1 = self.session.open_cursor('index:join01:index1', None, None) - c0.next() # index cursors must be positioned - c1.next() - if not joinfirst: - jc = self.session.open_cursor('join:table:join01', None, None) - self.session.join(jc, c0, 'compare=ge') - self.session.join(jc, c1, 'compare=ge') - self.session.close() - self.session = None - - def test_cursor_close1(self): - self.cursor_close_common(True) - - def test_cursor_close2(self): - self.cursor_close_common(False) - - # test statistics using the framework set up for this test - def test_stats(self): - bloomcfg1000 = ',strategy=bloom,count=1000' - bloomcfg10 = ',strategy=bloom,count=10' - self.join_common(bloomcfg1000, bloomcfg1000, False, True) - - # Intentially run with an underconfigured Bloom filter, - # statistics should pick up some false positives. - self.join_common(bloomcfg10, bloomcfg10, False, True) - - # test statistics with a simple one index join cursor - def test_simple_stats(self): - self.session.create("table:join01b", - "key_format=i,value_format=i,columns=(k,v)") - self.session.create("index:join01b:index", "columns=(v)") - - cursor = self.session.open_cursor("table:join01b", None, None) - cursor[1] = 11 - cursor[2] = 12 - cursor[3] = 13 - cursor.close() - - cursor = self.session.open_cursor("index:join01b:index", None, None) - cursor.set_key(11) - cursor.search() - - jcursor = self.session.open_cursor("join:table:join01b", None, None) - self.session.join(jcursor, cursor, "compare=gt") - - while jcursor.next() == 0: - [k] = jcursor.get_keys() - [v] = jcursor.get_values() - - statcur = self.session.open_cursor("statistics:join", jcursor, None) - found = False - while statcur.next() == 0: - [desc, pvalue, value] = statcur.get_values() - #self.tty(str(desc) + "=" + str(pvalue)) - found = True - self.assertEquals(found, True) - - jcursor.close() - cursor.close() - - if __name__ == '__main__': wttest.run() diff --git a/test/suite/test_join07.py b/test/suite/test_join07.py new file mode 100644 index 00000000000..36e91361329 --- /dev/null +++ b/test/suite/test_join07.py @@ -0,0 +1,548 @@ +#!/usr/bin/env python +# +# Public Domain 2014-2016 MongoDB, Inc. +# Public Domain 2008-2014 WiredTiger, Inc. +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +import os, re, run +import wiredtiger, wttest, suite_random +from wtscenario import check_scenarios, multiply_scenarios, number_scenarios + +class ParseException(Exception): + def __init__(self, msg): + super(ParseException, self).__init__(msg) + +class Token: + UNKNOWN = '<unknown>' + NUMBER = 'Number' + STRING = 'String' + COLUMN = 'Column' + LPAREN = '(' + RPAREN = ')' + LBRACKET = '{' + RBRACKET = '}' + COMMA = ',' + OR = '||' + AND = '&&' + LT = '<' + GT = '>' + LE = '<=' + GE = '>=' + EQ = '==' + ATTRIBUTE = 'Attribute' # bracketed key value pair + + COMPARE_OPS = [LT, GT, LE, GE, EQ] + COMPARATORS = [NUMBER, STRING] + + def __init__(self, kind, tokenizer): + self.kind = kind + self.pos = tokenizer.off + tokenizer.pos + self.n = 0 + self.s = '' + self.index = '' + self.attr_key = '' + self.attr_value = '' + self.groups = None + + def __str__(self): + return '<Token ' + self.kind + ' at char ' + str(self.pos) + '>' + +class Tokenizer: + def __init__(self, s): + self.off = 0 + self.s = s + '?' # add a char that won't match anything + self.pos = 0 + self.end = len(s) + self.re_num = re.compile(r"(\d+)") + self.re_quote1 = re.compile(r"'([^']*)'") + self.re_quote2 = re.compile(r"\"([^\"]*)\"") + self.re_attr = re.compile(r"\[(\w+)=(\w+)\]") + self.pushed = None + + def newToken(self, kind, sz): + t = Token(kind, self) + self.pos += sz + return t + + def error(self, s): + raise ParseException(str(self.pos) + ': ' + s) + + def matched(self, kind, repat): + pos = self.pos + match = re.match(repat, self.s[pos:]) + if not match: + end = pos + 10 + if end > self.end: + end = self.end + self.error('matching ' + kind + ' at "' + + self.s[pos:end] + '..."') + t = self.newToken(kind, match.end()) + t.groups = match.groups() + t.s = self.s[pos:pos + match.end()] + return t + + def available(self): + if self.pushed == None: + self.pushback(self.token()) + return (self.pushed != None) + + def pushback(self, token): + if self.pushed != None: + raise AssertionError('pushback more than once') + self.pushed = token + + def peek(self): + token = self.token() + self.pushback(token) + return token + + def scan(self): + while self.pos < self.end and self.s[self.pos].isspace(): + self.pos += 1 + return '' if self.pos >= self.end else self.s[self.pos] + + def token(self): + if self.pushed != None: + ret = self.pushed + self.pushed = None + return ret + c = self.scan() + if self.pos >= self.end: + return None + lookahead = '' if self.pos + 1 >= self.end else self.s[self.pos+1] + #self.tty("Tokenizer.token char=" + c + ", lookahead=" + lookahead) + if c == "'": + t = self.matched(Token.STRING, self.re_quote1) + t.s = t.groups[0] + return t + if c == '"': + t = self.matched(Token.STRING, self.re_quote2) + t.s = t.groups[0] + return t + if c in "{}(),": + return self.newToken(c, 1) + if c == "|": + if lookahead != "|": + self.error('matching OR') + return self.newToken(Token.OR, 2) + if c == "&": + if lookahead != "&": + self.error('matching AND') + return self.newToken(Token.AND, 2) + if c in "0123456789": + t = self.matched(Token.NUMBER, self.re_num) + t.s = t.groups[0] + t.n = int(t.s) + return t + if c in "ABCDEFGHIJ": + t = self.newToken(Token.COLUMN, 1) + t.s = c + return t + if c == '<': + if lookahead == '=': + return self.newToken(Token.LE, 2) + else: + return self.newToken(Token.LT, 1) + if c == '>': + if lookahead == '=': + return self.newToken(Token.GE, 2) + else: + return self.newToken(Token.GT, 1) + if c in "=": + if lookahead != "=": + self.error('matching EQ') + return self.newToken(Token.EQ, 2) + if c in "[": + t = self.matched(Token.ATTRIBUTE, self.re_attr) + t.attr_key = t.groups[0] + t.attr_value = t.groups[1] + return t + return None + + def tty(self, s): + wttest.WiredTigerTestCase.tty(s) + +# test_join07.py +# Join interpreter +class test_join07(wttest.WiredTigerTestCase): + reverseop = { '==' : '==', '<=' : '>=', '<' : '>', '>=' : '<=', '>' : '<' } + compareop = { '==' : 'eq', '<=' : 'le', '<' : 'lt', '>=' : 'ge', + '>' : 'gt' } + columnmult = { 'A' : 1, 'B' : 2, 'C' : 3, 'D' : 4, 'E' : 5, + 'F' : 6, 'G' : 7, 'H' : 8, 'I' : 9, 'J' : 10 } + + extractscen = [ + ('extractor', dict(extractor=True)), + ('noextractor', dict(extractor=False)) + ] + + scenarios = number_scenarios(extractscen) + + # Return the wiredtiger_open extension argument for a shared library. + def extensionArg(self, exts): + extfiles = [] + for ext in exts: + (dirname, name, libname) = ext + if name != None and name != 'none': + testdir = os.path.dirname(__file__) + extdir = os.path.join(run.wt_builddir, 'ext', dirname) + extfile = os.path.join( + extdir, name, '.libs', 'libwiredtiger_' + libname + '.so') + if not os.path.exists(extfile): + self.skipTest('extension "' + extfile + '" not built') + if not extfile in extfiles: + extfiles.append(extfile) + if len(extfiles) == 0: + return '' + else: + return ',extensions=["' + '","'.join(extfiles) + '"]' + + # Override WiredTigerTestCase, we have extensions. + def setUpConnectionOpen(self, dir): + extarg = self.extensionArg([('extractors', 'csv', 'csv_extractor')]) + connarg = 'create,error_prefix="{0}: ",{1}'.format( + self.shortid(), extarg) + conn = self.wiredtiger_open(dir, connarg) + self.pr(`conn`) + return conn + + def expect(self, token, expected): + if token == None or token.kind not in expected: + self.err(token, 'expected one of: ' + str(expected)) + return token + + def err(self, token, msg): + self.assertTrue(False, 'ERROR at token ' + str(token) + ': ' + msg) + + def gen_key(self, i): + if self.keyformat == 'S': + return [ 'key%06d' % i ] # zero pad so it sorts expectedly + else: + return [ i ] + + def gen_values(self, i): + s = "" + ret = [] + for x in range(1, 11): + v = (i * x) % self.N + if x <= 5: + ret.append(v) + else: + ret.append(str(v)) + if s != "": + s += "," + s += str(v) + ret.insert(0, s) + return ret + + def iterate(self, jc, mbr): + mbr = set(mbr) # we need a mutable set + gotkeys = [] + #self.tty('iteration expects ' + str(len(mbr)) + + # ' entries: ' + str(mbr)) + while jc.next() == 0: + [k] = jc.get_keys() + values = jc.get_values() + if self.keyformat == 'S': + i = int(str(k[3:])) + else: + i = k + #self.tty('GOT key=' + str(k) + ', values=' + str(values)) + + # Duplicates may be returned when the disjunctions are used, + # so we ignore them. + if not i in gotkeys: + self.assertEquals(self.gen_values(i), values) + if not i in mbr: + self.tty('ERROR: result ' + str(i) + ' is not in: ' + + str(mbr)) + self.assertTrue(i in mbr) + mbr.remove(i) + gotkeys.append(i) + self.assertEquals(0, len(mbr)) + + def token_literal(self, token): + if token.kind == Token.STRING: + return token.s + elif token.kind == Token.NUMBER: + return token.n + + def idx_sim(self, x, mult, isstr): + if isstr: + return str(int(x) * mult % self.N) + else: + return (x * mult % self.N) + + def mkmbr(self, expr): + return frozenset([x for x in self.allN if expr(x)]) + + def join_one_side(self, jc, coltok, littok, optok, conjunction, + isright, mbr): + idxname = 'index:join07:' + coltok.s + cursor = self.session.open_cursor(idxname, None, None) + jc.cursors.append(cursor) + literal = self.token_literal(littok) + cursor.set_key(literal) + searchret = cursor.search() + if searchret != 0: + self.tty('ERROR: cannot find value ' + str(literal) + + ' in ' + idxname) + self.assertEquals(0, searchret) + op = optok.kind + if not isright: + op = self.reverseop[op] + mult = self.columnmult[coltok.s] + config = 'compare=' + self.compareop[op] + ',operation=' + \ + ('and' if conjunction else 'or') + if hasattr(coltok, 'bloom'): + config += ',strategy=bloom,count=' + str(coltok.bloom) + #self.tty('join(jc, cursor=' + str(literal) + ', ' + config) + self.session.join(jc, cursor, config) + isstr = type(literal) is str + if op == '==': + tmbr = self.mkmbr(lambda x: self.idx_sim(x, mult, isstr) == literal) + elif op == '<=': + tmbr = self.mkmbr(lambda x: self.idx_sim(x, mult, isstr) <= literal) + elif op == '<': + tmbr = self.mkmbr(lambda x: self.idx_sim(x, mult, isstr) < literal) + elif op == '>=': + tmbr = self.mkmbr(lambda x: self.idx_sim(x, mult, isstr) >= literal) + elif op == '>': + tmbr = self.mkmbr(lambda x: self.idx_sim(x, mult, isstr) > literal) + if conjunction: + mbr = mbr.intersection(tmbr) + else: + mbr = mbr.union(tmbr) + return mbr + + def parse_join(self, jc, tokenizer, conjunction, mbr): + left = None + right = None + leftop = None + rightop = None + col = None + token = tokenizer.token() + if token.kind == Token.LPAREN: + subjc = self.session.open_cursor('join:table:join07', None, None) + jc.cursors.append(subjc) + submbr = self.parse_junction(subjc, tokenizer) + config = 'operation=' + ('and' if conjunction else 'or') + self.session.join(jc, subjc, config) + if conjunction: + mbr = mbr.intersection(submbr) + else: + mbr = mbr.union(submbr) + return mbr + if token.kind in Token.COMPARATORS: + left = token + leftop = self.expect(tokenizer.token(), Token.COMPARE_OPS) + token = tokenizer.token() + col = self.expect(token, [Token.COLUMN]) + token = tokenizer.token() + if token.kind in Token.ATTRIBUTE: + tokenizer.pushback(token) + self.parse_column_attributes(tokenizer, col) + token = tokenizer.token() + if token.kind in Token.COMPARE_OPS: + rightop = token + right = self.expect(tokenizer.token(), Token.COMPARATORS) + token = tokenizer.token() + tokenizer.pushback(token) + + # Now we have everything we need to do a join. + if left != None: + mbr = self.join_one_side(jc, col, left, leftop, conjunction, + False, mbr) + if right != None: + mbr = self.join_one_side(jc, col, right, rightop, conjunction, + True, mbr) + return mbr + + # Parse a set of joins, grouped by && or || + def parse_junction(self, jc, tokenizer): + jc.cursors = [] + + # Take a peek at the tokenizer's stream to see if we + # have a conjunction or disjunction + token = tokenizer.peek() + s = tokenizer.s[token.pos:] + (andpos, orpos) = self.find_nonparen(s, ['&', '|']) + if orpos >= 0 and (andpos < 0 or orpos < andpos): + conjunction = False + mbr = frozenset() + else: + conjunction = True + mbr = frozenset(self.allN) + + while tokenizer.available(): + mbr = self.parse_join(jc, tokenizer, conjunction, mbr) + token = tokenizer.token() + if token != None: + if token.kind == Token.OR: + self.assertTrue(not conjunction) + elif token.kind == Token.AND: + self.assertTrue(conjunction) + elif token.kind == Token.RPAREN: + break + else: + self.err(token, 'unexpected token') + return mbr + + def parse_attributes(self, tokenizer): + attributes = [] + token = tokenizer.token() + while token != None and token.kind == Token.ATTRIBUTE: + attributes.append(token) + token = tokenizer.token() + tokenizer.pushback(token) + return attributes + + # Find a set of chars that aren't within parentheses. + # For this simple language, we don't allow parentheses in quoted literals. + def find_nonparen(self, s, matchlist): + pos = 0 + end = len(s) + nmatch = len(matchlist) + nfound = 0 + result = [-1 for i in range(0, nmatch)] + parennest = 0 + while pos < end and nfound < nmatch: + c = s[pos] + if c == '(': + parennest += 1 + elif c == ')': + parennest -= 1 + if parennest < 0: + break + elif parennest == 0 and c in matchlist: + m = matchlist.index(c) + if result[m] < 0: + result[m] = pos + nfound += 1 + pos += 1 + return result + + def parse_toplevel(self, jc, tokenizer): + return self.parse_junction(jc, tokenizer) + + def parse_toplevel_attributes(self, tokenizer): + for attrtoken in self.parse_attributes(tokenizer): + key = attrtoken.attr_key + value = attrtoken.attr_value + #self.tty('ATTR:' + str([key,value])) + if key == 'N': + self.N = int(value) + elif key == 'key': + self.keyformat = value + else: + tokenizer.error('bad attribute key: ' + str(key)) + + def parse_column_attributes(self, tokenizer, c): + for attrtoken in self.parse_attributes(tokenizer): + key = attrtoken.attr_key + value = attrtoken.attr_value + #self.tty('ATTR:' + str([key,value])) + if key == 'bloom': + c.bloom = int(value) + else: + tokenizer.error('bad column attribute key: ' + str(key)) + + def close_cursors(self, jc): + jc.close() + for c in jc.cursors: + if c.uri[0:5] == 'join:': + self.close_cursors(c) + else: + c.close() + + def interpret(self, s): + #self.tty('INTERPRET: ' + s) + self.N = 1000 + self.keyformat = "r" + self.keycols = 'k' + + # Grab attributes before creating anything, as some attributes + # may override needed parameters. + tokenizer = Tokenizer(s) + self.parse_toplevel_attributes(tokenizer) + self.allN = range(1, self.N + 1) + + self.session.create('table:join07', 'key_format=' + self.keyformat + + ',value_format=SiiiiiSSSSS,' + + 'columns=(' + self.keycols + + ',S,A,B,C,D,E,F,G,H,I,J)') + mdfieldnum = 0 + mdformat = 'i' + mdconfig = '' + for colname in [ 'A','B','C','D','E','F','G','H','I','J' ]: + if self.extractor: + if colname == 'F': + mdformat = 'S' + mdconfig = 'app_metadata={"format" : "%s","field" : "%d"}' % \ + (mdformat, mdfieldnum) + config = 'extractor=csv,key_format=%s' % mdformat + mdfieldnum += 1 + else: + config = 'columns=(%s)' % colname + self.session.create('index:join07:%s' % colname, + '%s,%s' % (config, mdconfig)) + c = self.session.open_cursor('table:join07', None, None) + for i in self.allN: + c.set_key(*self.gen_key(i)) + c.set_value(*self.gen_values(i)) + c.insert() + c.close() + + jc = self.session.open_cursor('join:table:join07', None, None) + mbr = self.parse_toplevel(jc, tokenizer) + self.iterate(jc, mbr) + + self.close_cursors(jc) + self.session.drop('table:join07') + + def test_join_string(self): + self.interpret("[N=1000][key=r] 7 < A <= 500 && B < 150 && C > 17") + self.interpret("[N=1001][key=r] 7 < A <= 500 && B < 150 && F > '234'") + self.interpret("[N=10000][key=r] 7 < A <= 500 && B < 150 && " + + "(F > '234' || G < '100')") + self.interpret("[N=7919][key=r](7 < A <= 9)&&(F > '234')") + self.interpret("[N=1000][key=S](A>=0 && A<0)||(A>999)") + self.interpret("[N=2000][key=S](A>=0 && A<0)||(A>1999)") + self.interpret("(7<A<=10 && B < 150)||(B>998)") + self.interpret("(7<A<=10 && B < 150)||(J=='990')") + clause1 = "(7 < A <= 500 && B < 150)" + clause2 = "(F > '234' || G < '100')" + self.interpret("[N=1000][key=r]" + clause1 + "&&" + clause2) + self.interpret("(7<A<=10)||(B>994||C<12)") + self.interpret("(7<A<=10 && B < 150)||(B>996||C<6)") + self.interpret("[N=1000][key=r]" + clause2 + "||" + clause1) + self.interpret("[N=1000][key=r]" + clause1 + "||" + clause2) + self.interpret("[N=1000][key=S]" + clause2 + "&&" + clause1) + clause1 = "(7 < A <= 500 && B[bloom=300] < 150)" + clause2 = "(F[bloom=500] > '234' || G[bloom=20] < '100')" + self.interpret("[N=1000][key=S]" + clause1 + "&&" + clause2) + +if __name__ == '__main__': + wttest.run() diff --git a/test/suite/test_join08.py b/test/suite/test_join08.py new file mode 100644 index 00000000000..6d674ab8193 --- /dev/null +++ b/test/suite/test_join08.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python +# +# Public Domain 2014-2016 MongoDB, Inc. +# Public Domain 2008-2014 WiredTiger, Inc. +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +import wiredtiger, wttest +from wtscenario import check_scenarios, multiply_scenarios, number_scenarios + +# test_join08.py +# Test join error paths +class test_join08(wttest.WiredTigerTestCase): + nentries = 100 + + # We need statistics for these tests. + conn_config = 'statistics=(all)' + + def gen_key(self, i): + return [ i + 1 ] + + def gen_values(self, i): + s = str(i) + rs = s[::-1] + sort3 = (self.nentries * (i % 3)) + i # multiples of 3 sort first + return [s, rs, sort3] + + def test_join_errors(self): + self.session.create('table:join08', 'key_format=r,value_format=SS' + ',columns=(k,v0,v1)') + self.session.create('table:join08B', 'key_format=r,value_format=SS' + ',columns=(k,v0,v1)') + self.session.create('index:join08:index0','columns=(v0)') + self.session.create('index:join08:index1','columns=(v1)') + self.session.create('index:join08B:index0','columns=(v0)') + jc = self.session.open_cursor('join:table:join08', None, None) + tc = self.session.open_cursor('table:join08', None, None) + fc = self.session.open_cursor('file:join08.wt', None, None) + ic0 = self.session.open_cursor('index:join08:index0', None, None) + ic0again = self.session.open_cursor('index:join08:index0', None, None) + ic1 = self.session.open_cursor('index:join08:index1', None, None) + icB = self.session.open_cursor('index:join08B:index0', None, None) + tcB = self.session.open_cursor('table:join08B', None, None) + + tc.set_key(1) + tc.set_value('val1', 'val1') + tc.insert() + tcB.set_key(1) + tcB.set_value('val1', 'val1') + tcB.insert() + fc.next() + + # Joining using a non join-cursor + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(tc, ic0, 'compare=ge'), + '/not a join cursor/') + # Joining a table cursor, not index + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, fc, 'compare=ge'), + '/must be an index, table or join cursor/') + # Joining a non positioned cursor + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, ic0, 'compare=ge'), + '/requires reference cursor be positioned/') + ic0.set_key('val1') + # Joining a non positioned cursor (no search or next has been done) + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, ic0, 'compare=ge'), + '/requires reference cursor be positioned/') + ic0.set_key('valXX') + self.assertEqual(ic0.search(), wiredtiger.WT_NOTFOUND) + # Joining a non positioned cursor after failed search + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, ic0, 'compare=ge'), + '/requires reference cursor be positioned/') + + # position the cursors now + ic0.set_key('val1') + ic0.search() + ic0again.next() + icB.next() + + # Joining non matching index + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, icB, 'compare=ge'), + '/table for join cursor does not match/') + + # The cursor must be positioned + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, ic1, 'compare=ge'), + '/requires reference cursor be positioned/') + ic1.next() + + # This succeeds. + self.session.join(jc, ic1, 'compare=ge'), + + # With bloom filters, a count is required + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, ic0, 'compare=ge,strategy=bloom'), + '/count must be nonzero/') + + # This succeeds. + self.session.join(jc, ic0, 'compare=ge,strategy=bloom,count=1000'), + + bloom_config = ',strategy=bloom,count=1000' + # Cannot use the same index cursor + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, ic0, + 'compare=le' + bloom_config), + '/cursor already used in a join/') + + # When joining with the same index, need compatible compares + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, ic0again, 'compare=ge' + bloom_config), + '/join has overlapping ranges/') + + # Another incompatible compare + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, ic0again, 'compare=gt' + bloom_config), + '/join has overlapping ranges/') + + # Compare is compatible, but bloom args need to match + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, ic0again, 'compare=le'), + '/join has incompatible strategy/') + + # Counts need to match for bloom filters + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: self.session.join(jc, ic0again, 'compare=le,strategy=bloom,' + 'count=100'), '/count.* does not match previous count/') + + # This succeeds + self.session.join(jc, ic0again, 'compare=le,strategy=bloom,count=1000') + + # Need to do initial next() before getting key/values + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: jc.get_keys(), + '/join cursor must be advanced with next/') + + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: jc.get_values(), + '/join cursor must be advanced with next/') + + # Operations on the joined cursor are frozen until the join is closed. + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: ic0.next(), + '/cursor is being used in a join/') + + # Operations on the joined cursor are frozen until the join is closed. + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: ic0.prev(), + '/cursor is being used in a join/') + + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: ic0.reset(), + '/cursor is being used in a join/') + + # Only a small number of operations allowed on a join cursor + msg = "/Unsupported cursor/" + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: jc.search(), msg) + + self.assertRaisesWithMessage(wiredtiger.WiredTigerError, + lambda: jc.prev(), msg) + + self.assertEquals(jc.next(), 0) + self.assertEquals(jc.next(), wiredtiger.WT_NOTFOUND) + + # Only after the join cursor is closed can we use the index cursor + # normally + jc.close() + self.assertEquals(ic0.next(), wiredtiger.WT_NOTFOUND) + self.assertEquals(ic0.prev(), 0) + + # common code for making sure that cursors can be + # implicitly closed, no matter the order they are created + def cursor_close_common(self, joinfirst): + self.session.create('table:join08', 'key_format=r' + + ',value_format=SS,columns=(k,v0,v1)') + self.session.create('index:join08:index0','columns=(v0)') + self.session.create('index:join08:index1','columns=(v1)') + c = self.session.open_cursor('table:join08', None, None) + for i in range(0, self.nentries): + c.set_key(*self.gen_key(i)) + c.set_value(*self.gen_values(i)) + c.insert() + c.close() + + if joinfirst: + jc = self.session.open_cursor('join:table:join08', None, None) + c0 = self.session.open_cursor('index:join08:index0', None, None) + c1 = self.session.open_cursor('index:join08:index1', None, None) + c0.next() # index cursors must be positioned + c1.next() + if not joinfirst: + jc = self.session.open_cursor('join:table:join08', None, None) + self.session.join(jc, c0, 'compare=ge') + self.session.join(jc, c1, 'compare=ge') + self.session.close() + self.session = None + + def test_cursor_close1(self): + self.cursor_close_common(True) + + def test_cursor_close2(self): + self.cursor_close_common(False) + + # test statistics with a simple one index join cursor + def test_simple_stats(self): + self.session.create("table:join01b", + "key_format=i,value_format=i,columns=(k,v)") + self.session.create("index:join01b:index", "columns=(v)") + + cursor = self.session.open_cursor("table:join01b", None, None) + cursor[1] = 11 + cursor[2] = 12 + cursor[3] = 13 + cursor.close() + + cursor = self.session.open_cursor("index:join01b:index", None, None) + cursor.set_key(11) + cursor.search() + + jcursor = self.session.open_cursor("join:table:join01b", None, None) + self.session.join(jcursor, cursor, "compare=gt") + + while jcursor.next() == 0: + [k] = jcursor.get_keys() + [v] = jcursor.get_values() + + statcur = self.session.open_cursor("statistics:join", jcursor, None) + found = False + while statcur.next() == 0: + [desc, pvalue, value] = statcur.get_values() + #self.tty(str(desc) + "=" + str(pvalue)) + found = True + self.assertEquals(found, True) + + jcursor.close() + cursor.close() + + +if __name__ == '__main__': + wttest.run() diff --git a/test/suite/test_reconfig02.py b/test/suite/test_reconfig02.py index aee8ee4458b..85a9ceb2a34 100644 --- a/test/suite/test_reconfig02.py +++ b/test/suite/test_reconfig02.py @@ -74,9 +74,15 @@ class test_reconfig02(wttest.WiredTigerTestCase): # Now turn on pre-allocation. Sleep to give the worker thread # a chance to run and verify pre-allocated log files exist. + # + # Potentially loop a few times in case it is a very slow system. self.conn.reconfigure("log=(prealloc=true)") - time.sleep(2) - prep_logs = fnmatch.filter(os.listdir('.'), "*Prep*") + for x in xrange(0, 20): + time.sleep(1) + prep_logs = fnmatch.filter(os.listdir('.'), "*Prep*") + if len(prep_logs) != 0: + break + self.assertNotEqual(0, len(prep_logs)) # Logging starts on, but archive is off. Verify it is off. diff --git a/test/suite/test_stat05.py b/test/suite/test_stat05.py index 6a93ec2c84d..9bcedd65089 100644 --- a/test/suite/test_stat05.py +++ b/test/suite/test_stat05.py @@ -37,9 +37,13 @@ from helper import complex_value_populate, key_populate, value_populate # Statistics cursor using size only class test_stat_cursor_config(wttest.WiredTigerTestCase): pfx = 'test_stat_cursor_size' + conn_config = 'statistics=(fast)' + uri = [ ('file', dict(uri='file:' + pfx, pop=simple_populate, cfg='')), ('table', dict(uri='table:' + pfx, pop=simple_populate, cfg='')), + ('inmem', dict(uri='table:' + pfx, pop=simple_populate, cfg='', + conn_config='in_memory,statistics=(fast)')), ('table-lsm', dict(uri='table:' + pfx, pop=simple_populate, cfg=',type=lsm,lsm=(chunk_size=1MB,merge_min=2)')), ('complex', dict(uri='table:' + pfx, pop=complex_populate, cfg='')), @@ -49,7 +53,6 @@ class test_stat_cursor_config(wttest.WiredTigerTestCase): ] scenarios = number_scenarios(uri) - conn_config = 'statistics=(fast)' def openAndWalkStatCursor(self): c = self.session.open_cursor( diff --git a/test/suite/test_txn04.py b/test/suite/test_txn04.py index bbd6ce8c4e2..9d9d2db62c6 100644 --- a/test/suite/test_txn04.py +++ b/test/suite/test_txn04.py @@ -193,7 +193,7 @@ class test_txn04(wttest.WiredTigerTestCase, suite_subprocess): self.hot_backup(self.uri, committed) def test_ops(self): - with self.expectedStdoutPattern('Recreating metadata'): + with self.expectedStdoutPattern('recreating metadata'): self.ops() if __name__ == '__main__': |
