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 )