#!/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 wiredtiger import stat from wtscenario import make_scenarios def timestamp_str(t): return '%x' % t # test_hs09.py # Verify that we write the newest committed version to data store and the # second newest committed version to history store. class test_hs09(wttest.WiredTigerTestCase): # Force a small cache. conn_config = 'cache_size=20MB,statistics=(fast)' session_config = 'isolation=snapshot' uri = "table:test_hs09" key_format_values = [ ('column', dict(key_format='r')), ('integer', dict(key_format='i')), ('string', dict(key_format='S')), ] scenarios = make_scenarios(key_format_values) def create_key(self, i): if self.key_format == 'S': return str(i) return i def check_ckpt_hs(self, expected_data_value, expected_hs_value, expected_hs_start_ts, expected_hs_stop_ts, expect_prepared_in_datastore = False): session = self.conn.open_session(self.session_config) session.checkpoint() # Check the data file value. cursor = session.open_cursor(self.uri, None, 'checkpoint=WiredTigerCheckpoint') # If we are expecting prepapred updates in the datastore, start an explicit transaction with # ignore prepare flag to avoid getting a WT_PREPARE_CONFLICT error. if expect_prepared_in_datastore: session.begin_transaction("ignore_prepare=true") for _, value in cursor: self.assertEqual(value, expected_data_value) if expect_prepared_in_datastore: session.rollback_transaction() cursor.close() # Check the history store file value. cursor = session.open_cursor("file:WiredTigerHS.wt", None, 'checkpoint=WiredTigerCheckpoint') for _, _, hs_start_ts, _, hs_stop_ts, _, type, value in cursor: # No WT_UPDATE_TOMBSTONE in the history store. self.assertNotEqual(type, 5) # No WT_UPDATE_BIRTHMARK in the history store. self.assertNotEqual(type, 1) # WT_UPDATE_STANDARD if (type == 4): self.assertEqual(value.decode(), expected_hs_value + '\x00') self.assertEqual(hs_start_ts, expected_hs_start_ts) self.assertEqual(hs_stop_ts, expected_hs_stop_ts) cursor.close() session.close() def test_uncommitted_updates_not_written_to_hs(self): # Create a small table. create_params = 'key_format={},value_format=S'.format(self.key_format) self.session.create(self.uri, create_params) value1 = 'a' * 500 value2 = 'b' * 500 value3 = 'c' * 500 # Load 500KB of data. self.conn.set_timestamp('oldest_timestamp=' + timestamp_str(1)) cursor = self.session.open_cursor(self.uri) self.session.begin_transaction() for i in range(1, 1000): cursor[self.create_key(i)] = value1 self.session.commit_transaction('commit_timestamp=' + timestamp_str(2)) # Load another 500KB of data with a later timestamp. self.session.begin_transaction() for i in range(1, 1000): cursor[self.create_key(i)] = value2 self.session.commit_transaction('commit_timestamp=' + timestamp_str(3)) # Uncommitted changes. self.session.begin_transaction() for i in range(1, 11): cursor[self.create_key(i)] = value3 self.check_ckpt_hs(value2, value1, 2, 3) def test_prepared_updates_not_written_to_hs(self): # FIXME-WT-6061: Prepare reads currently not supported with columnar store. # Remove this once prepare reads is supported. if self.key_format == 'r': return # Create a small table. create_params = 'key_format={},value_format=S'.format(self.key_format) self.session.create(self.uri, create_params) value1 = 'a' * 500 value2 = 'b' * 500 value3 = 'c' * 500 # Load 1MB of data. self.conn.set_timestamp('oldest_timestamp=' + timestamp_str(1)) cursor = self.session.open_cursor(self.uri) self.session.begin_transaction() for i in range(1, 2000): cursor[self.create_key(i)] = value1 self.session.commit_transaction('commit_timestamp=' + timestamp_str(2)) # Load another 1MB of data with a later timestamp. self.session.begin_transaction() for i in range(1, 2000): cursor[self.create_key(i)] = value2 self.session.commit_transaction('commit_timestamp=' + timestamp_str(3)) # Prepare some updates. self.session.begin_transaction() for i in range(1, 11): cursor[self.create_key(i)] = value3 self.session.prepare_transaction('prepare_timestamp=' + timestamp_str(4)) # We can expect prepared values to show up in data store if the eviction runs between now # and the time when we open a cursor on the user table. self.check_ckpt_hs(value2, value1, 2, 3, True) self.session.commit_transaction('commit_timestamp=' + timestamp_str(5) + ',durable_timestamp=' + timestamp_str(5)) def test_write_newest_version_to_data_store(self): # Create a small table. create_params = 'key_format={},value_format=S'.format(self.key_format) self.session.create(self.uri, create_params) value1 = 'a' * 500 value2 = 'b' * 500 # Load 500KB of data. self.conn.set_timestamp('oldest_timestamp=' + timestamp_str(1)) cursor = self.session.open_cursor(self.uri) self.session.begin_transaction() for i in range(1, 1000): cursor[self.create_key(i)] = value1 self.session.commit_transaction('commit_timestamp=' + timestamp_str(2)) # Load another 500KB of data with a later timestamp. self.session.begin_transaction() for i in range(1, 1000): cursor[self.create_key(i)] = value2 self.session.commit_transaction('commit_timestamp=' + timestamp_str(3)) self.check_ckpt_hs(value2, value1, 2, 3) def test_write_deleted_version_to_data_store(self): # Create a small table. create_params = 'key_format={},value_format=S'.format(self.key_format) self.session.create(self.uri, create_params) value1 = 'a' * 500 value2 = 'b' * 500 # Load 500KB of data. self.conn.set_timestamp('oldest_timestamp=' + timestamp_str(1)) cursor = self.session.open_cursor(self.uri) self.session.begin_transaction() for i in range(1, 1000): cursor[self.create_key(i)] = value1 self.session.commit_transaction('commit_timestamp=' + timestamp_str(2)) # Load another 500KB of data with a later timestamp. self.session.begin_transaction() for i in range(1, 1000): cursor[self.create_key(i)] = value2 self.session.commit_transaction('commit_timestamp=' + timestamp_str(3)) # Delete records. self.session.begin_transaction() for i in range(1, 1000): cursor = self.session.open_cursor(self.uri) cursor.set_key(self.create_key(i)) self.assertEqual(cursor.remove(), 0) self.session.commit_transaction('commit_timestamp=' + timestamp_str(4)) self.check_ckpt_hs(value2, value1, 2, 3) if __name__ == '__main__': wttest.run()