summaryrefslogtreecommitdiff
path: root/test/suite
diff options
context:
space:
mode:
Diffstat (limited to 'test/suite')
-rw-r--r--test/suite/test_backup05.py35
-rw-r--r--test/suite/test_join01.py409
-rw-r--r--test/suite/test_join07.py548
-rw-r--r--test/suite/test_join08.py265
-rw-r--r--test/suite/test_reconfig02.py10
-rw-r--r--test/suite/test_stat05.py5
-rw-r--r--test/suite/test_txn04.py2
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__':