#!/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 wttest
from wiredtiger import stat, WiredTigerError, wiredtiger_strerror, WT_ROLLBACK
from wtdataset import SimpleDataSet
from wtscenario import make_scenarios

# test_truncate10.py
#
# Check that nothing comes unstuck if we commit a truncate with durable > commit.

class test_truncate10(wttest.WiredTigerTestCase):
    conn_config = 'statistics=(all)'
    session_config = 'isolation=snapshot'

    # Hook to run using remove instead of truncate for reference. This should not alter the
    # behavior... but may if things are broken. Disable the reference version by default as it's
    # only useful when investigating behavior changes. This list is first in the make_scenarios
    # call so the additional cases don't change the scenario numbering.
    trunc_values = [
        ('truncate', dict(trunc_with_remove=False)),
        #('remove', dict(trunc_with_remove=True)),
    ]

    format_values = [
        ('column', dict(key_format='r', value_format='S', extraconfig='')),
        ('column_fix', dict(key_format='r', value_format='8t',
            extraconfig=',allocation_size=512,leaf_page_max=512')),
        ('integer_row', dict(key_format='i', value_format='S', extraconfig='')),
    ]
    # Try various stable times.
    stable_values = [
        ('10', dict(stable_timestamp=10)),
        ('20', dict(stable_timestamp=20)),
        ('25', dict(stable_timestamp=25)),
        ('30', dict(stable_timestamp=30)),
    ]
    # Try both with and without an intermediate checkpoint.
    checkpoint_values = [
        ('checkpoint', dict(do_checkpoint=True)),
        ('no-checkpoint', dict(do_checkpoint=False)),
    ]

    scenarios = make_scenarios(trunc_values, format_values, stable_values, checkpoint_values)

    def truncate(self, uri, make_key, keynum1, keynum2):
        if self.trunc_with_remove:
            cursor = self.session.open_cursor(uri)
            err = 0
            for k in range(keynum1, keynum2 + 1):
                cursor.set_key(k)
                try:
                    err = cursor.remove()
                except WiredTigerError as e:
                    if wiredtiger_strerror(WT_ROLLBACK) in str(e):
                        err = WT_ROLLBACK
                    else:
                        raise e
                if err != 0:
                    break
            cursor.close()
        else:
            lo_cursor = self.session.open_cursor(uri)
            hi_cursor = self.session.open_cursor(uri)
            lo_cursor.set_key(make_key(keynum1))
            hi_cursor.set_key(make_key(keynum2))
            try:
                err = self.session.truncate(None, lo_cursor, hi_cursor, None)
            except WiredTigerError as e:
                if wiredtiger_strerror(WT_ROLLBACK) in str(e):
                    err = WT_ROLLBACK
                else:
                    raise e
            lo_cursor.close()
            hi_cursor.close()
        return err

    def check(self, uri, make_key, nrows, nzeros, value, ts):
        cursor = self.session.open_cursor(uri)
        self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts))
        seen = 0
        zseen = 0
        for k, v in cursor:
            if self.value_format == '8t' and v == 0:
                zseen += 1
            else:
                self.assertEqual(v, value)
                seen += 1
        self.assertEqual(seen, nrows)
        self.assertEqual(zseen, nzeros if self.value_format == '8t' else 0)
        self.session.rollback_transaction()
        cursor.close()

    def test_truncate10(self):
        nrows = 10000

        uri = "table:truncate10"
        ds = SimpleDataSet(
            self, uri, 0, key_format=self.key_format, value_format=self.value_format,
            config='log=(enabled=false)' + self.extraconfig)
        ds.populate()

        if self.value_format == '8t':
            value_a = 97
            value_b = 98
        else:
            value_a = "aaaaa" * 100
            value_b = "bbbbb" * 100

        # Pin oldest and stable timestamps to 1.
        self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1) +
            ',stable_timestamp=' + self.timestamp_str(1))

        # Write a bunch of data at time 10.
        cursor = self.session.open_cursor(ds.uri)
        self.session.begin_transaction()
        for i in range(1, nrows + 1):
            cursor[ds.key(i)] = value_a
        self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(10))

        # Mark it stable.
        self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(10))

        # Reopen the connection so nothing is in memory and we can fast-truncate.
        self.reopen_conn()

        # Truncate the data at time 25, but prepare at 20 and make durable 30.
        self.session.begin_transaction()
        err = self.truncate(ds.uri, ds.key, nrows // 4 + 1, nrows // 4 + nrows // 2)
        self.assertEqual(err, 0)
        self.session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(20))
        self.session.timestamp_transaction('commit_timestamp=' + self.timestamp_str(25))
        self.session.commit_transaction('durable_timestamp=' + self.timestamp_str(30))

        # Make sure we did at least one fast-delete. For columns, there's no fast-delete
        # support (yet) so assert we didn't.
        stat_cursor = self.session.open_cursor('statistics:', None, None)
        fastdelete_pages = stat_cursor[stat.conn.rec_page_delete_fast][2]
        if self.key_format == 'r':
            self.assertEqual(fastdelete_pages, 0)
        else:
            self.assertGreater(fastdelete_pages, 0)

        # Optionally advance stable.
        self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(self.stable_timestamp))

        # Optionally checkpoint.
        if self.do_checkpoint:
            self.session.checkpoint()

        # Validate the data.

        # At time 10 we should see all value_a.
        self.check(ds.uri, ds.key, nrows, 0, value_a, 10)

        # At time 20 we should still see all value_a.
        self.check(ds.uri, ds.key, nrows, 0, value_a, 20)

        # At time 25 we should still see half value_a, and for FLCS, half zeros.
        # (Note that reading between commit and durable can be problematic, but for
        # now at least it remains permitted.)
        self.check(ds.uri, ds.key, nrows // 2, nrows // 2, value_a, 25)

        # At time 30 we should also see half value_a, and for FLCS, half zeros.
        self.check(ds.uri, ds.key, nrows // 2, nrows // 2, value_a, 30)

        # Move the stable timestamp forward before exiting so we don't waste time rolling
        # back the changes during shutdown.
        self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(50))

if __name__ == '__main__':
    wttest.run()

#!/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 wttest
from wiredtiger import stat, WiredTigerError, wiredtiger_strerror, WT_ROLLBACK
from wtdataset import SimpleDataSet
from wtscenario import make_scenarios

# test_truncate10.py
#
# Check that nothing comes unstuck if we commit a truncate with durable > commit.

class test_truncate10(wttest.WiredTigerTestCase):
    conn_config = 'statistics=(all)'
    session_config = 'isolation=snapshot'

    # Hook to run using remove instead of truncate for reference. This should not alter the
    # behavior... but may if things are broken. Disable the reference version by default as it's
    # only useful when investigating behavior changes. This list is first in the make_scenarios
    # call so the additional cases don't change the scenario numbering.
    trunc_values = [
        ('truncate', dict(trunc_with_remove=False)),
        #('remove', dict(trunc_with_remove=True)),
    ]

    format_values = [
        ('column', dict(key_format='r', value_format='S', extraconfig='')),
        ('column_fix', dict(key_format='r', value_format='8t',
            extraconfig=',allocation_size=512,leaf_page_max=512')),
        ('integer_row', dict(key_format='i', value_format='S', extraconfig='')),
    ]
    # Try various stable times.
    stable_values = [
        ('10', dict(stable_timestamp=10)),
        ('20', dict(stable_timestamp=20)),
        ('25', dict(stable_timestamp=25)),
        ('30', dict(stable_timestamp=30)),
    ]
    # Try both with and without an intermediate checkpoint.
    checkpoint_values = [
        ('checkpoint', dict(do_checkpoint=True)),
        ('no-checkpoint', dict(do_checkpoint=False)),
    ]

    scenarios = make_scenarios(trunc_values, format_values, stable_values, checkpoint_values)

    def truncate(self, uri, make_key, keynum1, keynum2):
        if self.trunc_with_remove:
            cursor = self.session.open_cursor(uri)
            err = 0
            for k in range(keynum1, keynum2 + 1):
                cursor.set_key(k)
                try:
                    err = cursor.remove()
                except WiredTigerError as e:
                    if wiredtiger_strerror(WT_ROLLBACK) in str(e):
                        err = WT_ROLLBACK
                    else:
                        raise e
                if err != 0:
                    break
            cursor.close()
        else:
            lo_cursor = self.session.open_cursor(uri)
            hi_cursor = self.session.open_cursor(uri)
            lo_cursor.set_key(make_key(keynum1))
            hi_cursor.set_key(make_key(keynum2))
            try:
                err = self.session.truncate(None, lo_cursor, hi_cursor, None)
            except WiredTigerError as e:
                if wiredtiger_strerror(WT_ROLLBACK) in str(e):
                    err = WT_ROLLBACK
                else:
                    raise e
            lo_cursor.close()
            hi_cursor.close()
        return err

    def check(self, uri, make_key, nrows, nzeros, value, ts):
        cursor = self.session.open_cursor(uri)
        self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts))
        seen = 0
        zseen = 0
        for k, v in cursor:
            if self.value_format == '8t' and v == 0:
                zseen += 1
            else:
                self.assertEqual(v, value)
                seen += 1
        self.assertEqual(seen, nrows)
        self.assertEqual(zseen, nzeros if self.value_format == '8t' else 0)
        self.session.rollback_transaction()
        cursor.close()

    def test_truncate10(self):
        nrows = 10000

        uri = "table:truncate10"
        ds = SimpleDataSet(
            self, uri, 0, key_format=self.key_format, value_format=self.value_format,
            config='log=(enabled=false)' + self.extraconfig)
        ds.populate()

        if self.value_format == '8t':
            value_a = 97
            value_b = 98
        else:
            value_a = "aaaaa" * 100
            value_b = "bbbbb" * 100

        # Pin oldest and stable timestamps to 1.
        self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1) +
            ',stable_timestamp=' + self.timestamp_str(1))

        # Write a bunch of data at time 10.
        cursor = self.session.open_cursor(ds.uri)
        self.session.begin_transaction()
        for i in range(1, nrows + 1):
            cursor[ds.key(i)] = value_a
        self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(10))

        # Mark it stable.
        self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(10))

        # Reopen the connection so nothing is in memory and we can fast-truncate.
        self.reopen_conn()

        # Truncate the data at time 25, but prepare at 20 and make durable 30.
        self.session.begin_transaction()
        err = self.truncate(ds.uri, ds.key, nrows // 4 + 1, nrows // 4 + nrows // 2)
        self.assertEqual(err, 0)
        self.session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(20))
        self.session.timestamp_transaction('commit_timestamp=' + self.timestamp_str(25))
        self.session.commit_transaction('durable_timestamp=' + self.timestamp_str(30))

        # Make sure we did at least one fast-delete. For columns, there's no fast-delete
        # support (yet) so assert we didn't.
        stat_cursor = self.session.open_cursor('statistics:', None, None)
        fastdelete_pages = stat_cursor[stat.conn.rec_page_delete_fast][2]
        if self.key_format == 'r':
            self.assertEqual(fastdelete_pages, 0)
        else:
            self.assertGreater(fastdelete_pages, 0)

        # Optionally advance stable.
        self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(self.stable_timestamp))

        # Optionally checkpoint.
        if self.do_checkpoint:
            self.session.checkpoint()

        # Validate the data.

        # At time 10 we should see all value_a.
        self.check(ds.uri, ds.key, nrows, 0, value_a, 10)

        # At time 20 we should still see all value_a.
        self.check(ds.uri, ds.key, nrows, 0, value_a, 20)

        # At time 25 we should still see half value_a, and for FLCS, half zeros.
        # (Note that reading between commit and durable can be problematic, but for
        # now at least it remains permitted.)
        self.check(ds.uri, ds.key, nrows // 2, nrows // 2, value_a, 25)

        # At time 30 we should also see half value_a, and for FLCS, half zeros.
        self.check(ds.uri, ds.key, nrows // 2, nrows // 2, value_a, 30)

        # Move the stable timestamp forward before exiting so we don't waste time rolling
        # back the changes during shutdown.
        self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(50))

if __name__ == '__main__':
    wttest.run()

#!/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_truncate10.py # # Check that nothing comes unstuck if we commit a truncate with durable > commit. # Hook to run using remove instead of truncate for reference. This should not alter the # behavior... but may if things are broken. Disable the reference version by default as it's # only useful when investigating behavior changes. This list is first in the make_scenarios # call so the additional cases don't change the scenario numbering. #('remove', dict(trunc_with_remove=True)), # Try various stable times. # Try both with and without an intermediate checkpoint. # Pin oldest and stable timestamps to 1. # Write a bunch of data at time 10. # Mark it stable. # Reopen the connection so nothing is in memory and we can fast-truncate. # Truncate the data at time 25, but prepare at 20 and make durable 30. # Make sure we did at least one fast-delete. For columns, there's no fast-delete # support (yet) so assert we didn't. # Optionally advance stable. # Optionally checkpoint. # Validate the data. # At time 10 we should see all value_a. # At time 20 we should still see all value_a. # At time 25 we should still see half value_a, and for FLCS, half zeros. # (Note that reading between commit and durable can be problematic, but for # now at least it remains permitted.) # At time 30 we should also see half value_a, and for FLCS, half zeros. # Move the stable timestamp forward before exiting so we don't waste time rolling # back the changes during shutdown.