AmberDB

 view release on metacpan or  search on metacpan

xt/amberdb_ramdisk.t  view on Meta::CPAN

            database => 'plain_db',
            path     => { dbase_dir => $t_root, ramdisk_dir => $plain_ram }
        );
        is( $plain_db->config('ramdisk_mounted'), 0, 'Plain local dir config ramdisk_mounted is 0' );
        my $plain_info = $plain_db->ramdisk_setup();
        is( $plain_info->{is_mounted}, 0, 'Plain local directory is correctly identified as unmounted (0)' );
        is( $plain_info->{mount_desc}, 'Local Storage (No RAM-disk active)', 'mount_desc shows local storage' );
    }

    # Test direct RAM-disk path diagnostics
    {
        local $ENV{AMBERDB_TEST_RAMDISK} = 0;
        my $direct_db = AmberDB->new(
            database => 'test',
            path     => { dbase_dir => $t_root, ramdisk_dir => ($^O eq 'MSWin32' || $^O eq 'msys' || $^O eq 'cygwin') ? 'R:/amberdb_test' : '/dev/shm/amberdb_test' }
        );
        my $d_info = $direct_db->ramdisk_setup();
        ok( defined $d_info->{is_mounted}, 'Direct RAM path diagnostics evaluated' );
    }
};

subtest 'Global use_ramdisk => 1 and 2 configuration' => sub {
    plan tests => 13;

    my $g_tmp = tempdir( CLEANUP => 1 );
    my $g_mount = tempdir( CLEANUP => 1 );
    my $g_ram = File::Spec->catdir( $g_mount, 'amberdb_catalog' );
    make_path($g_ram);

    # 1. First, create database schema and records on permanent disk without RAM-disk
    my $schema_dir = File::Spec->catdir( $g_tmp, 'schema' );
    make_path($schema_dir);

    # Write schema files
    {
        open my $fh1, '>', File::Spec->catfile( $schema_dir, 'catalog_books.table' );
        print $fh1 "{ record_index => 1, search_block => [1] };\n";
        close $fh1;

        open my $fh2, '>', File::Spec->catfile( $schema_dir, 'catalog_authors.table' );
        print $fh2 "{ record_index => 1 };\n";
        close $fh2;

        open my $fh3, '>', File::Spec->catfile( $schema_dir, 'catalog_excluded.table' );
        print $fh3 "{ record_index => 1, use_ramdisk => 0 };\n";
        close $fh3;
    }

    {
        local $ENV{AMBERDB_TEST_RAMDISK} = 0;
        my $init_db = AmberDB->new(
            database => 'catalog',
            path     => { dbase_dir => $g_tmp }
        );

        $init_db->insert_id( 'catalog_books', 1, 'Book One' );
        $init_db->insert_id( 'catalog_authors', 10, 'Author Ten' );
        $init_db->insert_id( 'catalog_excluded', 99, 'Excluded Data' );
    }

    # 2. Open with global cfg => { use_ramdisk => 1 }
    my $adb_g1 = AmberDB->new(
        database => 'catalog',
        cfg  => { use_ramdisk => 1 },
        path => { dbase_dir => $g_tmp, ramdisk_dir => $g_ram }
    );

    # Access catalog_books via table_path
    my $b_path = $adb_g1->table_path('catalog_books');
    my $b_ram  = $adb_g1->ramdisk_path('catalog_books');

    ok( -e "$b_ram.inx", 'Global use_ramdisk => 1 preloads .inx to RAM-disk on table_path' );
    ok( !-e "$b_ram.db", 'Global use_ramdisk => 1 does NOT copy .db (data remains on disk)' );
    is( ($adb_g1->read_id('catalog_books', 1))[1], 'Book One', 'read_id works seamlessly' );

    # Access catalog_authors via table_info
    my $a_info = $adb_g1->table_info('catalog_authors');
    my $a_ram  = $adb_g1->ramdisk_path('catalog_authors');
    is( $a_info->{use_ramdisk}, 1, 'Table inherits global use_ramdisk => 1' );
    ok( -e "$a_ram.inx", 'Preloads .inx on table_info call' );

    # Check excluded table with explicit use_ramdisk => 0
    my $e_info = $adb_g1->table_info('catalog_excluded');
    my $e_ram  = $adb_g1->ramdisk_path('catalog_excluded');
    is( $e_info->{use_ramdisk}, 0, 'Table preserves explicit use_ramdisk => 0 override' );
    ok( !-e "$e_ram.inx", 'Excluded table is NOT copied to RAM-disk' );

    # 3. Open with global cfg => { use_ramdisk => 2 } (Full Table Acceleration)
    my $adb_g2 = AmberDB->new(
        database => 'catalog',
        cfg  => { use_ramdisk => 2 },
        path => { dbase_dir => $g_tmp, ramdisk_dir => $g_ram }
    );

    my $b2_path = $adb_g2->table_path('catalog_books');
    ok( -e "$b_ram.db", 'Global use_ramdisk => 2 preloads .db to RAM-disk on table_path' );
    ok( -e "$b_ram.inx", 'Global use_ramdisk => 2 keeps .inx on RAM-disk' );
    is( ($adb_g2->read_id('catalog_books', 1))[1], 'Book One', 'read_id reads from RAM-disk in mode 2' );

    # Insert new record under global mode 2
    $adb_g2->insert_id( 'catalog_books', 2, 'Book Two' );
    is( ($adb_g2->read_id('catalog_books', 2))[1], 'Book Two', 'Newly inserted record readable' );
    ok( -e "$b_ram.db", 'RAM-disk .db persists with new record' );
    ok( -e "$b_path.db", 'Permanent disk .db dual-written synchronously' );
};

subtest 'Unmounted RAM-disk strips use_ramdisk from table_info and table_attr' => sub {
    plan tests => 6;

    my $u_tmp = tempdir( CLEANUP => 1 );
    my $schema_dir = File::Spec->catdir( $u_tmp, 'schema' );
    make_path($schema_dir);

    {
        open my $fh, '>', File::Spec->catfile( $schema_dir, 'books_rd.table' );
        print $fh "{ record_index => 1, use_ramdisk => 1 };\n";
        close $fh;
    }

    my $u_mount = File::Temp->newdir( CLEANUP => 1 );
    my $u_ram = File::Spec->catdir( $u_mount, 'amberdb_unmount_test' );
    make_path($u_ram);

    # Ensure unmounted state
    local $ENV{AMBERDB_TEST_RAMDISK} = 0;

    my $adb_unmounted = AmberDB->new(
        database => 'unmount_test',
        cfg      => { use_ramdisk => 1 },
        path     => { dbase_dir => $u_tmp, ramdisk_dir => $u_ram }
    );

    # 1. From schema file
    my $file_info = $adb_unmounted->table_info('books_rd');
    ok( !exists $file_info->{use_ramdisk}, 'use_ramdisk stripped from schema file when RAM-disk is unmounted' );

    # 2. From table_attr
    $adb_unmounted->table_attr( 'dynamic_tbl', use_ramdisk => 1 );
    my $attr_info = $adb_unmounted->table_info('dynamic_tbl');
    ok( !exists $attr_info->{use_ramdisk}, 'use_ramdisk stripped from table_info after table_attr when unmounted' );
    is( $adb_unmounted->table_attr( 'dynamic_tbl', 'use_ramdisk' ), undef, 'table_attr getter returns undef when unmounted' );

    # 3. From global config inheritance
    my $glob_info = $adb_unmounted->table_info('unconfigured_tbl');
    ok( !exists $glob_info->{use_ramdisk}, 'Global use_ramdisk is NOT inherited when unmounted' );

    # 4. Now simulate mounted RAM-disk
    local $ENV{AMBERDB_TEST_RAMDISK} = 1;
    my $adb_mounted = AmberDB->new(
        database => 'unmount_test',
        cfg      => { use_ramdisk => 1 },
        path     => { dbase_dir => $u_tmp, ramdisk_dir => $u_ram }
    );

    my $m_file_info = $adb_mounted->table_info('books_rd');
    is( $m_file_info->{use_ramdisk}, 1, 'use_ramdisk preserved when RAM-disk is mounted' );

    $adb_mounted->table_attr( 'dyn_m_tbl', use_ramdisk => 1 );
    is( $adb_mounted->table_attr( 'dyn_m_tbl', 'use_ramdisk' ), 1, 'table_attr preserves use_ramdisk when mounted' );
};

subtest 'use_ramdisk => 3 (Volatile RAM-Disk Tier 3 & TTL)' => sub {
    my $tmp = File::Temp->newdir( CLEANUP => 1 );
    my $t3_mount = File::Temp->newdir( CLEANUP => 1 );
    my $t3_ram = File::Spec->catdir( $t3_mount, 'amberdb_session_db' );
    make_path($t3_ram);
    local $ENV{AMBERDB_TEST_RAMDISK} = 1;

    # 1. Global use_ramdisk => 3 rejection/fallback to 0
    my $adb_global = AmberDB->new(
        database => 'session_db',
        cfg      => { use_ramdisk => 3 },
        path     => { dbase_dir => $tmp, ramdisk_dir => $t3_ram }
    );
    is( $adb_global->config('use_ramdisk'), 0, 'Global use_ramdisk => 3 in constructor falls back to 0' );

    $adb_global->config( use_ramdisk => 3 );
    is( $adb_global->config('use_ramdisk'), 0, 'Global config(use_ramdisk => 3) falls back to 0' );

    my $inh_info = $adb_global->table_info('unspec_table');
    is( $inh_info->{use_ramdisk}, 0, 'Unspecified table inherits 0, not 3' );

    # 2. Per-table use_ramdisk => 3 configuration and schema stripping
    my $adb = AmberDB->new(
        database => 'session_db',
        path     => { dbase_dir => $tmp, ramdisk_dir => $t3_ram }
    );
    my $ramdisk_dir = $adb->ramdisk_dir();

    $adb->table_attr( 'session_store',
        use_ramdisk  => 3,
        search_block => [1],
        record_index => 1,
        match_block  => [2],
    );

    my $s_info = $adb->table_info('session_store');
    is( $s_info->{use_ramdisk}, 3, 'Table configured with use_ramdisk => 3' );
    is( $s_info->{use_simple}, 1, 'Simple mode enforced for Tier 3' );
    is( $s_info->{ramdisk_ttl}, 300, 'Default ramdisk_ttl is 300 seconds' );
    ok( !exists $s_info->{record_index}, 'record_index stripped in Tier 3' );
    ok( !exists $s_info->{search_block}, 'search_block stripped in Tier 3' );
    ok( !exists $s_info->{match_block}, 'match_block stripped in Tier 3' );

    # 3. Write data to Tier 3 volatile table
    ok( $adb->insert_id( 'session_store', 101, 'token_abc', 'user_123' ), 'Record 101 inserted' );
    ok( $adb->insert_id( 'session_store', 102, 'token_xyz', 'user_456' ), 'Record 102 inserted' );

    # Verify physical disk has ZERO files
    my $phys_db = File::Spec->catfile( $tmp, 'table', 'session_store.db' );
    ok( !-e $phys_db, 'Zero physical disk files: table/session_store.db does NOT exist' );
    ok( !-e File::Spec->catfile( $tmp, 'table', 'session_store.inx' ), 'Zero physical disk index files' );

    # Verify RAM-disk contains the .db file
    my $ram_db = File::Spec->catfile( $ramdisk_dir, 'table', 'session_store.db' );
    ok( -e $ram_db, 'RAM-disk contains session_store.db' );
    ok( !-e File::Spec->catfile( $ramdisk_dir, 'table', 'session_store.inx' ), 'RAM-disk has NO index files' );

    # 4. Read records from Tier 3
    my @rec101 = $adb->read_id( 'session_store', 101 );
    is( $rec101[0], 101, 'read_id correctly returns ID 101' );
    is( $rec101[1], 'token_abc', 'read_id correctly returns token_abc' );

    is( $adb->table_count('session_store'), 2, 'table_count is 2' );
    ok( $adb->exist_id( 'session_store', 101 ), 'exist_id is true for 101' );
    ok( !$adb->exist_id( 'session_store', 999 ), 'exist_id is false for nonexistent 999' );

    # 5. Modify record
    ok( $adb->modify_id( 'session_store', 101, 'token_abc_renewed', 'user_123' ), 'modify_id succeeded' );
    my @rec101_mod = $adb->read_id( 'session_store', 101 );
    is( $rec101_mod[1], 'token_abc_renewed', 'Modified token visible in read_id' );

    # 6. Delete record
    ok( $adb->delete_id( 'session_store', 102 ), 'Record 102 deleted' );
    ok( !$adb->exist_id( 'session_store', 102 ), 'Record 102 no longer exists' );
    is( $adb->table_count('session_store'), 1, 'table_count is now 1' );

    # 7. TTL Expiration Simulation
    $adb->table_attr( 'temp_cache', use_ramdisk => 3, ramdisk_ttl => 5 );
    ok( $adb->insert_id( 'temp_cache', 1, 'temp_val' ), 'temp_cache record inserted' );



( run in 0.670 second using v1.01-cache-2.11-cpan-4716db65a44 )