AmberDB
view release on metacpan or search on metacpan
t/amberdb_transact.t view on Meta::CPAN
my @rec50 = $adb->read_id( 'test_table', 50 );
is( $rec50[1], 'Item 50 Base', 'Record 50 data restored' );
# 3. Start transaction, insert record 51, then trigger rollback
$adb->transact_start();
$adb->insert_id( 'test_table', 51, 'Item 51 Temp', 'Category Z', 200 );
$adb->transact_rollback();
my @rec51 = $adb->read_id( 'test_table', 51 );
is( scalar(@rec51), 0, 'Record 51 rolled back' );
$adb->table_read($aut_path);
my $res_aut = $adb->recs_get( $aut_path, 51 );
$adb->table_close($aut_path);
ok( !$res_aut || !$res_aut->{51}, 'Record 51 audit entry completely removed from .aut after rollback' );
};
# ---------------------------------------------------------------------------
subtest 'Index Error Does NOT Trigger Rollback' => sub {
plan tests => 3;
ok( $adb->transact_start(), 'Transaction started' );
# Manually register an index-classified error
$adb->transact_error( 'test_table.inx', 'Simulated non-critical index write failure' );
# Perform valid base insert with monotonic ID 60
$adb->insert_id( 'test_table', 60, 'Item 60', 'Category E', 600 );
my $res = $adb->transact_end();
is( $res->{status}, 'commit', 'Transaction committed despite index error' );
my @rec60 = $adb->read_id( 'test_table', 60 );
is( $rec60[1], 'Item 60', 'Base record 60 successfully persisted' );
};
# ---------------------------------------------------------------------------
subtest 'Orphan Transaction Recovery & Lock Protection' => sub {
plan tests => 5;
my $journal_dir = $adb->journal_dir();
# 1. Test dead orphan recovery (simulate process crash by abandoning transaction)
$adb->transact_start();
$adb->insert_id( 'test_table', 65, 'Item 65 Orphan', 'Category O', 650 );
my $orphan_file = $adb->{_txn}->{file};
close( delete $adb->{_txn}->{fh} );
delete $adb->{_txn};
$adb->close_all();
ok( -e $orphan_file, 'Orphan journal file created for test' );
# Call recover orphans
$adb->transact_recover();
ok( !-e $orphan_file, 'Orphan journal file removed after recovery' );
my @rec65 = $adb->read_id( 'test_table', 65 );
is( scalar(@rec65), 0, 'Orphaned insert was rolled back' );
# 2. Test active locked journal protection
my $locked_file = File::Spec->catfile( $journal_dir, 'txn_locked_test_888888' );
open my $lfh, '+>>', $locked_file or die "Cannot create locked test file: $!";
use Fcntl qw(:flock);
flock( $lfh, LOCK_EX ); # Actively lock file
$adb->transact_recover();
ok( -e $locked_file, 'Actively locked journal file is NOT removed by orphan recovery' );
flock( $lfh, LOCK_UN );
close $lfh;
unlink $locked_file;
ok( !-e $locked_file, 'Locked test file cleaned up' );
};
# ---------------------------------------------------------------------------
subtest 'Transaction Durability with txn_sync' => sub {
plan tests => 4;
my $sync_adb = AmberDB->new(
cfg => { language => 'tr', txn_sync => 1 },
path => { dbase_dir => $tmpdir }
);
ok( $sync_adb->transact_start(), 'Transaction started with txn_sync enabled' );
my $rid70 = $sync_adb->insert_id( 'test_table', 70, 'Item 70 Sync', 'Category S', 750 );
is( $rid70, 70, 'Inserted record 70 with sync enabled' );
my $res = $sync_adb->transact_end();
is( $res->{status}, 'commit', 'Transaction committed with fsync' );
my @rec70 = $sync_adb->read_id( 'test_table', 70 );
is( $rec70[1], 'Item 70 Sync', 'Synced record 70 read back successfully' );
};
# ---------------------------------------------------------------------------
subtest 'no_transact Tables Do NOT Trigger Rollback on Failure' => sub {
plan tests => 6;
# 1. Configure auxiliary table with no_transact => 1 via table_attr
$adb->table_attr( 'aux_log_table', no_transact => 1 );
is( $adb->table_attr( 'aux_log_table', 'no_transact' ), 1, 'aux_log_table configured with no_transact => 1' );
ok( $adb->transact_start(), 'Transaction started' );
# Primary operation: insert critical order in test_table
$adb->insert_id( 'test_table', 80, 'Critical Order 80', 'Sales', 1500 );
# Auxiliary operation: simulate an error on no_transact table (modify non-existent id)
$adb->modify_id( 'aux_log_table', 99999, 'Failed Auxiliary Update' );
# Transaction commit should succeed because aux_log_table has no_transact => 1
my $res = $adb->transact_end();
is( $res->{status}, 'commit', 'Transaction committed successfully despite error on no_transact table' );
my @rec80 = $adb->read_id( 'test_table', 80 );
is( $rec80[1], 'Critical Order 80', 'Primary critical record 80 persisted cleanly' );
# 2. Test rollback consistency: if primary fails, auxiliary operations are still rolled back
ok( $adb->transact_start(), 'Second transaction started' );
$adb->insert_id( 'aux_log_table', 101, 'Aux Event 101' );
$adb->modify_id( 'test_table', 99999, 'Non-existent critical error' ); # Triggers rollback
my $res2 = $adb->transact_end();
is( $res2->{status}, 'rollback', 'Transaction with primary failure rolled back' );
};
# ---------------------------------------------------------------------------
subtest 'Transaction Journal Base64 Payload Encoding & Complex Binary Rollback' => sub {
plan tests => 8;
# 1. Insert complex record before transaction
my $complex_payload = "Line1\nLine2\r\nTab\tSeparated\x1eRecordSep" . " â Türkçe ÅçöÄüİı";
( run in 1.134 second using v1.01-cache-2.11-cpan-800906f7e73 )