#!/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. # # test_assert04.py # Timestamps: verify consistency usage on keys # from suite_subprocess import suite_subprocess import wiredtiger, wttest def timestamp_str(t): return '%x' % t class test_assert04(wttest.WiredTigerTestCase, suite_subprocess): session_config = 'isolation=snapshot' def test_timestamp_alter(self): base = 'assert04' uri = 'file:' + base cfg_on = 'write_timestamp_usage=key_consistent,assert=(write_timestamp=on)' cfg_off = 'assert=(write_timestamp=off)' msg_ooo='/out of order/' msg_usage='/used inconsistently/' # Create the table without the key consistency checking turned on. # Create a few items breaking the rules. Then alter the setting and # verify the inconsistent usage is detected. self.session.create(uri, 'key_format=S,value_format=S') # Insert a data item at timestamp 2. c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(2)) c['key_ts1'] = 'value2' self.session.commit_transaction() c.close() # Modify the data item at timestamp 1. c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(1)) c['key_ts1'] = 'value1' self.session.commit_transaction() c.close() # Insert a non-timestamped item. Then modify with a timestamp. And # again modify without a timestamp. c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_nots'] = 'value_nots' self.session.commit_transaction() c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(2)) c['key_nots'] = 'value2' self.session.commit_transaction() c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_nots'] = 'value_nots2' self.session.commit_transaction() c.close() # We must move the oldest timestamp forward in order to alter. # Otherwise alter closing the file will fail with EBUSY. self.conn.set_timestamp('oldest_timestamp=' + timestamp_str(2)) # Now alter the setting and make sure we detect incorrect usage. self.session.alter(uri, cfg_on) # Detect decreasing timestamp. c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(5)) c['key_ts1'] = 'value5' self.session.commit_transaction() c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(4)) c['key_ts1'] = 'value4' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_ooo) c.close() # Detect not using a timestamp. c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_ts1'] = 'value_nots3' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_usage) c.close() # Detect using a timestamp on the non-timestamp key. # We must first use a non timestamped operation on the key # in order to violate the key consistency condition in the # following transaction. c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_nots'] = 'value_nots3' self.session.commit_transaction() c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(3)) c['key_nots'] = 'value3' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_usage) c.close() c = self.session.open_cursor(uri) self.assertEquals(c['key_ts1'], 'value5') self.assertEquals(c['key_nots'], 'value_nots3') c.close() # Now alter the setting again and detection is off. self.conn.set_timestamp('oldest_timestamp=' + timestamp_str(5)) self.session.alter(uri, cfg_off) # Detection is off we can successfully change the same key with and # without a timestamp. c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_nots'] = 'value_nots4' self.session.commit_transaction() c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(6)) c['key_nots'] = 'value6' self.session.commit_transaction() c.close() def test_timestamp_usage(self): base = 'assert04' uri = 'file:' + base msg_ooo='/out of order/' msg_usage='/used inconsistently/' # Create the table with the key consistency checking turned on. # That checking will verify any individual key is always or never # used with a timestamp. And if it is used with a timestamp that # the timestamps are in increasing order for that key. self.session.create(uri, 'key_format=S,value_format=S,write_timestamp_usage=key_consistent,assert=(write_timestamp=on)') # Insert a data item at timestamp 2. c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(2)) c['key_ts1'] = 'value2' self.session.commit_transaction() c.close() # Modify the data item at timestamp 1. We should detect it is wrong. c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(1)) c['key_ts1'] = 'value1' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_ooo) c.close() # Make sure we can successfully add a different key at timestamp 1. c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(1)) c['key_ts2'] = 'value1' self.session.commit_transaction() c.close() # # Insert key_ts3 at timestamp 10 and key_ts4 at 15. # Then modify both keys in one transaction at timestamp 13. # We should not be allowed to modify the one from 15. # So the whole transaction should fail. # c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(10)) c['key_ts3'] = 'value10' self.session.commit_transaction() self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(15)) c['key_ts4'] = 'value15' self.session.commit_transaction() c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(13)) c['key_ts3'] = 'value13' c['key_ts4'] = 'value13' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_ooo) c.close() c = self.session.open_cursor(uri) self.assertEquals(c['key_ts3'], 'value10') self.assertEquals(c['key_ts4'], 'value15') c.close() # # Separately, we should be able to update key_ts3 at timestamp 10 # but not update key_ts4 inserted at timestamp 15. # c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(13)) c['key_ts3'] = 'value13' self.session.commit_transaction() c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(13)) c['key_ts4'] = 'value13' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_ooo) c.close() # Make sure multiple update attempts still fail and eventually # succeed with a later timestamp. This tests that aborted entries # in the update chain are not considered for the timestamp check. c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(14)) c['key_ts4'] = 'value14' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_ooo) c.close() c = self.session.open_cursor(uri) self.assertEquals(c['key_ts4'], 'value15') c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(16)) c['key_ts4'] = 'value16' self.session.commit_transaction() c.close() c = self.session.open_cursor(uri) self.assertEquals(c['key_ts4'], 'value16') c.close() # Now try to modify a key previously used with timestamps without # one. We should get the inconsistent usage message. c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_ts4'] = 'value_nots' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_usage) c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_ts4'] = 'value_nots' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_usage) c.close() c = self.session.open_cursor(uri) self.assertEquals(c['key_ts4'], 'value16') c.close() # Now confirm the other way. Create a key without a timestamp and then # attempt to modify it with a timestamp. The only error checking that # makes sense here is the inconsistent usage. c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_nots'] = 'value_nots' self.session.commit_transaction() c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(16)) c['key_nots'] = 'value16' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_usage) c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_nots'] = 'value_nots1' self.session.commit_transaction() c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(17)) c['key_nots'] = 'value17' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction(), msg_usage) c.close() c = self.session.open_cursor(uri) self.assertEquals(c['key_nots'], 'value_nots1') c.close() # Confirm it is okay to set the timestamp in the middle or end of the # transaction. That should set the timestamp for the whole thing. c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_ts5'] = 'value_notsyet' self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(20)) c['key_ts5'] = 'value20' self.session.commit_transaction() c.close() c = self.session.open_cursor(uri) self.assertEquals(c['key_ts5'], 'value20') c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_ts6'] = 'value_notsyet' c['key_ts6'] = 'value21_after' self.session.timestamp_transaction( 'commit_timestamp=' + timestamp_str(21)) self.session.commit_transaction() c.close() c = self.session.open_cursor(uri) self.assertEquals(c['key_ts6'], 'value21_after') c.close() # Confirm it is okay to set the timestamp on the commit call. # That should set the timestamp for the whole thing. c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_ts6'] = 'value_committs1' c['key_ts6'] = 'value22' self.session.commit_transaction('commit_timestamp=' + timestamp_str(22)) c.close() c = self.session.open_cursor(uri) self.session.begin_transaction() c['key_nots'] = 'value23' self.assertRaisesWithMessage(wiredtiger.WiredTigerError, lambda: self.session.commit_transaction( 'commit_timestamp=' + timestamp_str(23)), msg_usage) c.close() if __name__ == '__main__': wttest.run()