#!/usr/bin/env python # # Public Domain 2014-present 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 wtdataset import SimpleDataSet, ComplexLSMDataSet from wtscenario import make_scenarios # test_checkpoint01.py # Checkpoint tests # General checkpoint test: create an object containing sets of data associated # with a set of checkpoints, then confirm the checkpoint's values are correct, # including after other checkpoints are dropped. class test_checkpoint(wttest.WiredTigerTestCase): scenarios = make_scenarios([ ('file', dict(uri='file:checkpoint',fmt='S')), ('table', dict(uri='table:checkpoint',fmt='S')) ]) # Each checkpoint has a key range and a "is dropped" flag. checkpoints = { "checkpoint-1": ((100, 200), 0), "checkpoint-2": ((200, 220), 0), "checkpoint-3": ((300, 320), 0), "checkpoint-4": ((400, 420), 0), "checkpoint-5": ((500, 520), 0), "checkpoint-6": ((100, 620), 0), "checkpoint-7": ((200, 250), 0), "checkpoint-8": ((300, 820), 0), "checkpoint-9": ((400, 920), 0) } # Add a set of records for a checkpoint. def add_records(self, name): cursor = self.session.open_cursor(self.uri, None, "overwrite") start, stop = self.checkpoints[name][0] for i in range(start, stop+1): cursor["%010d KEY------" % i] = ("%010d VALUE " % i) + name cursor.close() self.checkpoints[name] = (self.checkpoints[name][0], 1) # For each checkpoint entry, add/overwrite the specified records, then # checkpoint the object, and verify it (which verifies all underlying # checkpoints individually). def build_file_with_checkpoints(self): for checkpoint_name, entry in self.checkpoints.items(): self.add_records(checkpoint_name) self.session.checkpoint("name=" + checkpoint_name) # Create a dictionary of sorted records a checkpoint should include. def list_expected(self, name): records = {} for checkpoint_name, entry in self.checkpoints.items(): start, stop = entry[0] for i in range(start, stop+1): records['%010d KEY------' % i] =\ '%010d VALUE ' % i + checkpoint_name if name == checkpoint_name: break return records # Create a dictionary of sorted records a checkpoint does include. def list_checkpoint(self, name): records = {} cursor = self.session.open_cursor(self.uri, None, 'checkpoint=' + name) while cursor.next() == 0: records[cursor.get_key()] = cursor.get_value() cursor.close() return records # For each existing checkpoint entry, verify it contains the records it # should, and no other checkpoints exist. def check(self): # Physically verify the file, including the individual checkpoints. self.session.verify(self.uri, None) for checkpoint_name, entry in self.checkpoints.items(): if entry[1] == 0: self.assertRaises(wiredtiger.WiredTigerError, lambda: self.session.open_cursor( self.uri, None, "checkpoint=" + checkpoint_name)) else: list_expected = self.list_expected(checkpoint_name) list_checkpoint = self.list_checkpoint(checkpoint_name) self.assertEqual(list_expected, list_checkpoint) # Main checkpoint test driver. def test_checkpoint(self): # Build a file with a set of checkpoints, and confirm they all have # the correct key/value pairs. self.session.create(self.uri, "key_format=" + self.fmt +\ ",value_format=S,allocation_size=512,leaf_page_max=512") self.build_file_with_checkpoints() self.check() # Drop a set of checkpoints sequentially, and each time confirm the # contents of remaining checkpoints, and that dropped checkpoints # don't appear. for i in [1,3,7,9]: checkpoint_name = 'checkpoint-' + str(i) self.session.checkpoint('drop=(' + checkpoint_name + ')') self.checkpoints[checkpoint_name] =\ (self.checkpoints[checkpoint_name][0], 0) self.check() # Drop remaining checkpoints, all subsequent checkpoint opens should # fail. self.session.checkpoint("drop=(from=all)") for checkpoint_name, entry in self.checkpoints.items(): self.checkpoints[checkpoint_name] =\ (self.checkpoints[checkpoint_name][0], 0) self.check() # Check some specific cursor checkpoint combinations. class test_checkpoint_cursor(wttest.WiredTigerTestCase): scenarios = make_scenarios([ ('file', dict(uri='file:checkpoint',fmt='S')), ('table', dict(uri='table:checkpoint',fmt='S')) ]) # Check that you cannot open a checkpoint that doesn't exist. def test_checkpoint_dne(self): SimpleDataSet(self, self.uri, 100, key_format=self.fmt).populate() self.assertRaises(wiredtiger.WiredTigerError, lambda: self.session.open_cursor( self.uri, None, "checkpoint=checkpoint-1")) self.assertRaises(wiredtiger.WiredTigerError, lambda: self.session.open_cursor( self.uri, None, "checkpoint=WiredTigerCheckpoint")) # Check that you can open checkpoints more than once. def test_checkpoint_multiple_open(self): SimpleDataSet(self, self.uri, 100, key_format=self.fmt).populate() self.session.checkpoint("name=checkpoint-1") c1 = self.session.open_cursor(self.uri, None, "checkpoint=checkpoint-1") c2 = self.session.open_cursor(self.uri, None, "checkpoint=checkpoint-1") c3 = self.session.open_cursor(self.uri, None, "checkpoint=checkpoint-1") c4 = self.session.open_cursor(self.uri, None, None) c4.close, c3.close, c2.close, c1.close self.session.checkpoint("name=checkpoint-2") c1 = self.session.open_cursor(self.uri, None, "checkpoint=checkpoint-1") c2 = self.session.open_cursor(self.uri, None, "checkpoint=checkpoint-2") c3 = self.session.open_cursor(self.uri, None, "checkpoint=checkpoint-2") c4 = self.session.open_cursor(self.uri, None, None) c4.close, c3.close, c2.close, c1.close # Check that you cannot drop a checkpoint if it's in use. def test_checkpoint_inuse(self): SimpleDataSet(self, self.uri, 100, key_format=self.fmt).populate() self.session.checkpoint("name=checkpoint-1") self.session.checkpoint("name=checkpoint-2") self.session.checkpoint("name=checkpoint-3") cursor = self.session.open_cursor( self.uri, None, "checkpoint=checkpoint-2") # Check creating an identically named checkpoint fails. */ # Check dropping the specific checkpoint fails. # Check dropping all checkpoints fails. msg = '/checkpoints cannot be dropped/' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.checkpoint("force,name=checkpoint-2"), msg) self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.checkpoint("drop=(checkpoint-2)"), msg) self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.checkpoint("drop=(from=all)"), msg) # Check dropping other checkpoints succeeds (which also tests that you # can create new checkpoints while other checkpoints are in-use). self.session.checkpoint("drop=(checkpoint-1,checkpoint-3)") # Close the cursor and repeat the failing commands, they should now # succeed. cursor.close() self.session.checkpoint("name=checkpoint-2") self.session.checkpoint("drop=(checkpoint-2)") self.session.checkpoint("drop=(from=all)") # Check that you can checkpoint targets. class test_checkpoint_target(wttest.WiredTigerTestCase): scenarios = make_scenarios([ ('file', dict(uri='file:checkpoint',fmt='S')), ('table', dict(uri='table:checkpoint',fmt='S')) ]) def update(self, uri, ds, value): cursor = self.session.open_cursor(uri, None, "overwrite") cursor[ds.key(10)] = value cursor.close() def check(self, uri, ds, value): cursor = self.session.open_cursor(uri, None, "checkpoint=checkpoint-1") self.assertEquals(cursor[ds.key(10)], value) cursor.close() def test_checkpoint_target(self): # Create 3 objects, change one record to an easily recognizable string. uri = self.uri + '1' ds1 = SimpleDataSet(self, uri, 100, key_format=self.fmt) ds1.populate() self.update(uri, ds1, 'ORIGINAL') uri = self.uri + '2' ds2 = SimpleDataSet(self, uri, 100, key_format=self.fmt) ds2.populate() self.update(uri, ds2, 'ORIGINAL') uri = self.uri + '3' ds3 = SimpleDataSet(self, uri, 100, key_format=self.fmt) ds3.populate() self.update(uri, ds3, 'ORIGINAL') # Checkpoint all three objects. self.session.checkpoint("name=checkpoint-1") # Update all 3 objects, then checkpoint two of the objects with the # same checkpoint name. self.update(self.uri + '1', ds1, 'UPDATE') self.update(self.uri + '2', ds2, 'UPDATE') self.update(self.uri + '3', ds3, 'UPDATE') target = 'target=("' + self.uri + '1"' + ',"' + self.uri + '2")' self.session.checkpoint("name=checkpoint-1," + target) # Confirm the checkpoint has the old value in objects that weren't # checkpointed, and the new value in objects that were checkpointed. self.check(self.uri + '1', ds1, 'UPDATE') self.check(self.uri + '2', ds2, 'UPDATE') self.check(self.uri + '3', ds3, 'ORIGINAL') # Check that you can't write checkpoint cursors. class test_checkpoint_cursor_update(wttest.WiredTigerTestCase): scenarios = make_scenarios([ ('file-r', dict(uri='file:checkpoint',fmt='r')), ('file-S', dict(uri='file:checkpoint',fmt='S')), ('table-r', dict(uri='table:checkpoint',fmt='r')), ('table-S', dict(uri='table:checkpoint',fmt='S')) ]) def test_checkpoint_cursor_update(self): ds = SimpleDataSet(self, self.uri, 100, key_format=self.fmt) ds.populate() self.session.checkpoint("name=ckpt") cursor = self.session.open_cursor(self.uri, None, "checkpoint=ckpt") cursor.set_key(ds.key(10)) cursor.set_value("XXX") msg = "/Unsupported cursor/" self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: cursor.insert(), msg) self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: cursor.remove(), msg) self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: cursor.update(), msg) cursor.close() # Check that WiredTigerCheckpoint works as a checkpoint specifier. class test_checkpoint_last(wttest.WiredTigerTestCase): scenarios = make_scenarios([ ('file', dict(uri='file:checkpoint',fmt='S')), ('table', dict(uri='table:checkpoint',fmt='S')) ]) def test_checkpoint_last(self): # Create an object, change one record to an easily recognizable string, # then checkpoint it and open a cursor, confirming we see the correct # value. Repeat this action, we want to be sure the engine gets the # latest checkpoint information each time. uri = self.uri ds = SimpleDataSet(self, uri, 100, key_format=self.fmt) ds.populate() for value in ('FIRST', 'SECOND', 'THIRD', 'FOURTH', 'FIFTH'): # Update the object. cursor = self.session.open_cursor(uri, None, "overwrite") cursor[ds.key(10)] = value cursor.close() # Checkpoint the object. self.session.checkpoint() # Verify the "last" checkpoint sees the correct value. cursor = self.session.open_cursor( uri, None, "checkpoint=WiredTigerCheckpoint") self.assertEquals(cursor[ds.key(10)], value) # Don't close the checkpoint cursor, we want it to remain open until # the test completes. # Check we can't use the reserved name as an application checkpoint name. class test_checkpoint_illegal_name(wttest.WiredTigerTestCase): def test_checkpoint_illegal_name(self): ds = SimpleDataSet(self, "file:checkpoint", 100, key_format='S') ds.populate() msg = '/the checkpoint name.*is reserved/' for conf in ( 'name=WiredTigerCheckpoint', 'name=WiredTigerCheckpoint.', 'name=WiredTigerCheckpointX', 'drop=(from=WiredTigerCheckpoint)', 'drop=(from=WiredTigerCheckpoint.)', 'drop=(from=WiredTigerCheckpointX)', 'drop=(to=WiredTigerCheckpoint)', 'drop=(to=WiredTigerCheckpoint.)', 'drop=(to=WiredTigerCheckpointX)'): self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.checkpoint(conf), msg) msg = '/WiredTiger objects should not include grouping/' for conf in ( 'name=check{point', 'name=check\\point'): self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.checkpoint(conf), msg) # Check we can't name checkpoints that include LSM tables. class test_checkpoint_lsm_name(wttest.WiredTigerTestCase): def test_checkpoint_lsm_name(self): ds = ComplexLSMDataSet(self, "table:checkpoint", 1000) ds.populate() msg = '/object does not support named checkpoints/' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.checkpoint("name=ckpt"), msg) class test_checkpoint_empty(wttest.WiredTigerTestCase): scenarios = make_scenarios([ ('file', dict(uri='file:checkpoint')), ('table', dict(uri='table:checkpoint')), ]) # Create an empty file, do one of 4 cases of checkpoint, then verify the # checkpoints exist. The reason for the 4 cases is we must create all # checkpoints an application can explicitly reference using a cursor, and # I want to test when other types of checkpoints are created before the # checkpoint we really need. # Case 1: a named checkpoint def test_checkpoint_empty_one(self): self.session.create(self.uri, "key_format=S,value_format=S") self.session.checkpoint('name=ckpt') cursor = self.session.open_cursor(self.uri, None, "checkpoint=ckpt") # Case 2: an internal checkpoint def test_checkpoint_empty_two(self): self.session.create(self.uri, "key_format=S,value_format=S") self.session.checkpoint() cursor = self.session.open_cursor( self.uri, None, "checkpoint=WiredTigerCheckpoint") # Case 3: a named checkpoint, then an internal checkpoint def test_checkpoint_empty_three(self): self.session.create(self.uri, "key_format=S,value_format=S") self.session.checkpoint('name=ckpt') self.session.checkpoint() cursor = self.session.open_cursor(self.uri, None, "checkpoint=ckpt") cursor = self.session.open_cursor( self.uri, None, "checkpoint=WiredTigerCheckpoint") # Case 4: an internal checkpoint, then a named checkpoint def test_checkpoint_empty_four(self): self.session.create(self.uri, "key_format=S,value_format=S") self.session.checkpoint() self.session.checkpoint('name=ckpt') cursor = self.session.open_cursor(self.uri, None, "checkpoint=ckpt") cursor = self.session.open_cursor( self.uri, None, "checkpoint=WiredTigerCheckpoint") # Check that we can create an empty checkpoint, change the underlying # object, checkpoint again, and still see the original empty tree, for # both named and unnamed checkpoints. def test_checkpoint_empty_five(self): self.session.create(self.uri, "key_format=S,value_format=S") self.session.checkpoint('name=ckpt') cursor = self.session.open_cursor(self.uri, None, "checkpoint=ckpt") self.assertEquals(cursor.next(), wiredtiger.WT_NOTFOUND) cursor.close() cursor = self.session.open_cursor(self.uri, None) cursor["key"] = "value" self.session.checkpoint() cursor = self.session.open_cursor(self.uri, None, "checkpoint=ckpt") self.assertEquals(cursor.next(), wiredtiger.WT_NOTFOUND) def test_checkpoint_empty_six(self): self.session.create(self.uri, "key_format=S,value_format=S") self.session.checkpoint() cursor = self.session.open_cursor( self.uri, None, "checkpoint=WiredTigerCheckpoint") self.assertEquals(cursor.next(), wiredtiger.WT_NOTFOUND) cursor.close() cursor = self.session.open_cursor(self.uri, None) cursor["key"] = "value" self.session.checkpoint('name=ckpt') cursor = self.session.open_cursor( self.uri, None, "checkpoint=WiredTigerCheckpoint") self.assertEquals(cursor.next(), wiredtiger.WT_NOTFOUND) if __name__ == '__main__': wttest.run()