#!/usr/bin/env python # # Public Domain 2014-2019 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. # # test_truncate01.py # session level operations on tables # import wiredtiger, wttest from helper import confirm_empty from wtdataset import SimpleDataSet, ComplexDataSet, simple_key from wtscenario import make_scenarios # Test truncation arguments. class test_truncate_arguments(wttest.WiredTigerTestCase): name = 'test_truncate' scenarios = make_scenarios([ ('file', dict(type='file:')), ('table', dict(type='table:')) ]) # Test truncation without URI or cursors specified, or with a URI and # either cursor specified, expect errors. def test_truncate_bad_args(self): uri = self.type + self.name SimpleDataSet(self, uri, 100).populate() msg = '/either a URI or start/stop cursors/' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.truncate(None, None, None, None), msg) cursor = self.session.open_cursor(uri, None) self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.truncate(uri, cursor, None, None), msg) self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.truncate(uri, None, cursor, None), msg) # Test truncation of cursors where no key is set, expect errors. def test_truncate_cursor_notset(self): uri = self.type + self.name msg = '/requires key be set/' ds = SimpleDataSet(self, uri, 100) ds.populate() c1 = self.session.open_cursor(uri, None) c2 = self.session.open_cursor(uri, None) c2.set_key(ds.key(10)) self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.truncate(None, c1, c2, None), msg) self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.truncate(None, c2, c1, None), msg) c1.close() c2.close() # Test truncation of an object using its URI. class test_truncate_uri(wttest.WiredTigerTestCase): name = 'test_truncate' scenarios = make_scenarios([ ('file', dict(type='file:')), ('table', dict(type='table:')) ]) # Populate an object, truncate it by URI, and confirm it's empty. def test_truncate_uri(self): uri = self.type + self.name # A simple, one-file file or table object. SimpleDataSet(self, uri, 100).populate() self.session.truncate(uri, None, None, None) confirm_empty(self, uri) self.session.drop(uri, None) if self.type == "table:": ComplexDataSet(self, uri, 100).populate() self.session.truncate(uri, None, None, None) confirm_empty(self, uri) self.session.drop(uri, None) # Test truncation of cursors in an illegal order. class test_truncate_cursor_order(wttest.WiredTigerTestCase): name = 'test_truncate' types = [ ('file', dict(type='file:')), ('table', dict(type='table:')) ] keyfmt = [ ('integer', dict(keyfmt='i')), ('recno', dict(keyfmt='r')), ('string', dict(keyfmt='S')), ] scenarios = make_scenarios(types, keyfmt) # Test an illegal order, then confirm that equal cursors works. def test_truncate_cursor_order(self): uri = self.type + self.name ds = SimpleDataSet(self, uri, 100, key_format=self.keyfmt) ds.populate() c1 = self.session.open_cursor(uri, None) c2 = self.session.open_cursor(uri, None) c1.set_key(ds.key(20)) c2.set_key(ds.key(10)) msg = '/the start cursor position is after the stop cursor position/' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.truncate(None, c1, c2, None), msg) c1.set_key(ds.key(10)) c2.set_key(ds.key(20)) self.session.truncate(None, c1, c2, None) # Test truncation of cursors past the end of the object. class test_truncate_cursor_end(wttest.WiredTigerTestCase): name = 'test_truncate' types = [ ('file', dict(type='file:')), ('table', dict(type='table:')) ] keyfmt = [ ('integer', dict(keyfmt='i')), ('recno', dict(keyfmt='r')), ('string', dict(keyfmt='S')), ] scenarios = make_scenarios(types, keyfmt) # Test truncation of cursors past the end of the object. def test_truncate_cursor_order(self): uri = self.type + self.name # A simple, one-file file or table object. ds = SimpleDataSet(self, uri, 100, key_format=self.keyfmt) ds.populate() c1 = self.session.open_cursor(uri, None) c1.set_key(ds.key(1000)) c2 = self.session.open_cursor(uri, None) c2.set_key(ds.key(2000)) self.session.truncate(None, c1, c2, None) self.assertEqual(c1.close(), 0) self.assertEqual(c2.close(), 0) self.session.drop(uri) if self.type == "table:": ds = ComplexDataSet(self, uri, 100, key_format=self.keyfmt) ds.populate() c1 = self.session.open_cursor(uri, None) c1.set_key(ds.key(1000)) c2 = self.session.open_cursor(uri, None) c2.set_key(ds.key(2000)) self.session.truncate(None, c1, c2, None) self.assertEqual(c1.close(), 0) self.assertEqual(c2.close(), 0) self.session.drop(uri) # Test truncation of empty objects. class test_truncate_empty(wttest.WiredTigerTestCase): name = 'test_truncate_empty' types = [ ('file', dict(type='file:')), ('table', dict(type='table:')) ] keyfmt = [ ('integer', dict(keyfmt='i')), ('recno', dict(keyfmt='r')), ('string', dict(keyfmt='S')), ] scenarios = make_scenarios(types, keyfmt) # Test truncation of empty objects using a cursor def test_truncate_empty_cursor(self): uri = self.type + self.name self.session.create(uri, ',key_format=' + self.keyfmt + ',value_format=S') c1 = self.session.open_cursor(uri, None) c1.set_key(simple_key(c1, 1000)) c2 = self.session.open_cursor(uri, None) c2.set_key(simple_key(c2, 2000)) self.assertEqual(self.session.truncate(None, c1, c2, None), 0) # Test truncation of empty objects using a URI def test_truncate_empty_uri(self): uri = self.type + self.name self.session.create(uri, ',key_format=' + self.keyfmt + ',value_format=S') self.assertEqual(self.session.truncate(uri, None, None, None), 0) # Test session.truncate. class test_truncate_cursor(wttest.WiredTigerTestCase): name = 'test_truncate' # Use a small page size because we want to create lots of pages. # The underlying table routines don't easily support 8t value types, limit # those tests to file objects. types = [ ('file', dict(type='file:', valuefmt='S', config='allocation_size=512,leaf_page_max=512', P=0.25)), ('file8t', dict(type='file:', valuefmt='8t', config='allocation_size=512,leaf_page_max=512', P=0.25)), ('table', dict(type='table:', valuefmt='S', config='allocation_size=512,leaf_page_max=512', P=0.5)), ] keyfmt = [ ('integer', dict(keyfmt='i')), ('recno', dict(keyfmt='r')), ('string', dict(keyfmt='S')), ] reopen = [ ('in-memory', dict(reopen=False)), ('on-disk', dict(reopen=True)), ] size = [ ('small', dict(nentries=100,skip=7)), ('big', dict(nentries=1000,skip=37)), ] scenarios = make_scenarios(types, keyfmt, size, reopen, prune=10, prunelong=1000) # Set a cursor key. def cursorKey(self, ds, uri, key): if key == -1: return None cursor = self.session.open_cursor(uri, None) cursor.set_key(ds.key(key)) return cursor # Truncate a range using cursors, and check the results. def truncateRangeAndCheck(self, ds, uri, begin, end, expected): self.pr('truncateRangeAndCheck: ' + str(begin) + ',' + str(end)) cur1 = self.cursorKey(ds, uri, begin) cur2 = self.cursorKey(ds, uri, end) self.session.truncate(None, cur1, cur2, None) if not cur1: begin = 1 else: cur1.close() if not cur2: end = self.nentries else: cur2.close() # If the object should be empty, confirm that. if begin == 1 and end == self.nentries: confirm_empty(self, uri) return # Check the expected values against the object. cursor = self.session.open_cursor(uri, None) for i in range(begin, end + 1): expected[ds.key(i)] = [0] for k, v in expected.items(): cursor.set_key(k) if v == [0] and \ cursor.key_format == 'r' and cursor.value_format == '8t': cursor.search() self.assertEqual(cursor.get_values(), [0]) elif v == [0]: self.assertEqual(cursor.search(), wiredtiger.WT_NOTFOUND) else: cursor.search() self.assertEqual(cursor.get_values(), v) cursor.close() # Test truncation of files and simple tables using cursors. def test_truncate_simple(self): uri = self.type + self.name # layout: # the number of initial skipped records # the number of initial inserted records # the number of trailing skipped records # the number of trailing inserted records layout = [ # simple set of rows (0, 0, 0, 0), # trailing append list, no delete point overlap (0, 0, 0, self.skip - 3), # trailing append list, delete point overlap (0, 0, 0, self.skip + 3), # trailing skipped list, no delete point overlap (0, 0, self.skip - 3, 1), # trailing skipped list, delete point overlap (0, 0, self.skip + 3, 1), # leading insert list, no delete point overlap (0, self.skip - 3, 0, 0), # leading insert list, delete point overlap (0, self.skip + 3, 0, 0), # leading skipped list, no delete point overlap (self.skip - 3, 1, 0, 0), # leading skipped list, delete point overlap (self.skip + 3, 1, 0, 0), ] # list: truncation patterns applied on top of the layout. # # begin and end: -1 means pass None for the cursor arg to truncate. An # integer N, with 1 <= N < self.nentries, truncates from/to a cursor # positioned at that row. list = [ (-1, self.nentries), # begin to end, begin = None (1, -1), # begin to end, end = None (1, self.nentries), # begin to end (-1, self.nentries - self.skip), # begin to middle, begin = None (1, self.nentries - self.skip), # begin to middle (self.skip, -1), # middle to end, end = None (self.skip, self.nentries), # middle to end (self.skip, # middle to different middle self.nentries - self.skip), (1, 1), # begin to begin (self.nentries, self.nentries), # end to end (self.skip, self.skip) # middle to same middle ] # Using this data set to compare only, it doesn't create or populate. ds = SimpleDataSet(self, uri, 0, key_format=self.keyfmt, value_format=self.valuefmt, config=self.config) # Build the layout we're going to test total = self.nentries for begin_skipped,begin_insert,end_skipped,end_insert in layout: # skipped records require insert/append records if begin_skipped and not begin_insert or \ end_skipped and not end_insert: raise AssertionError('test error: skipped set without insert') for begin,end in list: ''' print '===== run:' print 'key:', self.keyfmt, 'begin:', begin, 'end:', end print 'total: ', total, \ 'begin_skipped:', begin_skipped, \ 'begin_insert:', begin_insert, \ 'end_skipped:', end_skipped, \ 'end_insert:', end_insert ''' # Build a dictionary of what the object should look like for # later comparison expected = {} # Create the object. self.session.create( uri, self.config + ',key_format=' + self.keyfmt + ',value_format=' + self.valuefmt) # Insert the records that aren't skipped or inserted. start = begin_skipped + begin_insert stop = self.nentries - (end_skipped + end_insert) cursor = self.session.open_cursor(uri, None) for i in range(start + 1, stop + 1): k = ds.key(i) v = ds.value(i) cursor[k] = v expected[k] = [v] cursor.close() # Optionally close and re-open the object to get a disk image # instead of a big insert list. if self.reopen: self.reopen_conn() # Optionally insert initial skipped records. cursor = self.session.open_cursor(uri, None, "overwrite") start = 0 for i in range(0, begin_skipped): start += 1 k = ds.key(start) expected[k] = [0] # Optionally insert leading records. for i in range(0, begin_insert): start += 1 k = ds.key(start) v = ds.value(start) cursor[k] = v expected[k] = [v] # Optionally insert trailing skipped records. for i in range(0, end_skipped): stop += 1 k = ds.key(stop) expected[k] = [0] # Optionally insert trailing records. for i in range(0, end_insert): stop += 1 k = ds.key(stop) v = ds.value(stop) cursor[k] = v expected[k] = [v] cursor.close() self.truncateRangeAndCheck(ds, uri, begin, end, expected) self.session.drop(uri, None) # Test truncation of complex tables using cursors. We can't do the kind of # layout and detailed testing as we can with files, but this will at least # smoke-test the handling of indexes and column-groups. def test_truncate_complex(self): # We only care about tables. if self.type == 'file:': return uri = self.type + self.name # list: truncation patterns # # begin and end: -1 means pass None for the cursor arg to truncate. An # integer N, with 1 <= N < self.nentries, truncates from/to a cursor # positioned at that row. list = [ (-1, self.nentries), # begin to end, begin = None (1, -1), # begin to end, end = None (1, self.nentries), # begin to end (-1, self.nentries - self.skip), # begin to middle, begin = None (1, self.nentries - self.skip), # begin to middle (self.skip, -1), # middle to end, end = None (self.skip, self.nentries), # middle to end (self.skip, # middle to different middle self.nentries - self.skip), (1, 1), # begin to begin (self.nentries, self.nentries), # end to end (self.skip, self.skip) # middle to same middle ] # Build the layout we're going to test for begin,end in list: ''' print '===== run:', uri print 'key:', self.keyfmt, 'begin:', begin, 'end:', end ''' # Create the object. ds = ComplexDataSet(self, uri, self.nentries, config=self.config, key_format=self.keyfmt) ds.populate() # Build a dictionary of what the object should look like for # later comparison cursor = self.session.open_cursor(uri, None) expected = {} for i in range(1, self.nentries + 1): expected[ds.key(i)] = ds.comparable_value(i) cursor.close() # Optionally close and re-open the object to get a disk image # instead of a big insert list. if self.reopen: self.reopen_conn() self.truncateRangeAndCheck(ds, uri, begin, end, expected) self.session.drop(uri, None) if __name__ == '__main__': wttest.run()