AmberDB

 view release on metacpan or  search on metacpan

lib/AmberDB.pm  view on Meta::CPAN

    $self->match_add( $idx_path, $table_info, \@batch );
    $self->sort_add( $idx_path, $table_info, \@batch );

    # 2. Tiered stream (A: Aktif, B: Pasif/Junk)
    if ( $table_info->{use_junk} ) {
        my ( @active_batch, @junk_batch, @active_rids, @junk_rids );
        for my $rec (@batch) {
            if ( $self->junk_rules( $table_info, @$rec ) ) {
                push @junk_batch, $rec;
                push @junk_rids, $rec->[0];
            }
            else {
                push @active_batch, $rec;
                push @active_rids, $rec->[0];
            }
        }

        if (@active_batch) {
            $self->records_add( $idx_path, $table_info, $tableid, \@active_rids, 'A' );
            $self->search_add( $idx_path, $table_info, $tableid, \@active_batch, 'A' );
            $self->match_add( $idx_path, $table_info, \@active_batch, 'A' );
            $self->sort_add( $idx_path, $table_info, \@active_batch, 'A' ) if $table_info->{sort_block};
            $self->facet_add( $idx_path, $table_info, \@active_batch ) if $table_info->{use_facet};
        }

        if (@junk_batch) {
            $self->records_add( $idx_path, $table_info, $tableid, \@junk_rids, 'B' );
            $self->search_add( $idx_path, $table_info, $tableid, \@junk_batch, 'B' );
            $self->match_add( $idx_path, $table_info, \@junk_batch, 'B' );
            $self->sort_add( $idx_path, $table_info, \@junk_batch, 'B' ) if $table_info->{sort_block};
        }
    }
    elsif ( $table_info->{use_facet} ) {
        $self->facet_add( $idx_path, $table_info, \@batch );
    }

    if ($has_unique) {
        $self->unique_add( $idx_path, $table_info, \@batch );
    }

    $self->slug_add( $idx_path, $table_info, $tableid, \@batch );
    $self->suggest_add( $idx_path, $table_info, $tableid, \@batch );

    unless ($is_async_write) {
        if ( $table_info->{log_owner} ) {
            foreach my $rec (@batch) {
                $self->auth_write( $tableid, $table_path, "add", $rec->[0] );
            }
        }

        unless ( $self->config('no_backup') || $table_info->{no_backup} ) {
            $self->recs_back( "add", $tableid, @batch );
        }
    }

    return \%statu;
}

# Replace the DB record with new data.
# ------------------------------------------------
sub update_id {

    my ( $self, $tableid, $rid, @record ) = @_;

    # Perform the checks.
    $tableid or return;

    if ( ref($rid) eq 'HASH' && !@record ) {
        if ( $self->schema_matches_hash( $tableid, $rid ) ) {
            my $h = { %$rid };
            $rid = $h->{id} // $h->{ID};
            @record = ($h);
        }
        else {
            my $h = { %$rid };
            $rid = delete $h->{id} // delete $h->{ID};
            @record = ($h);
        }
    }

    $rid = $self->id_check( $tableid, $rid );
    $rid or return;

    # Deflate ONLY if table has a schema AND hash matches schema
    if ( @record && ref($record[0]) eq 'HASH' ) {
        if ( $self->schema_matches_hash( $tableid, $record[0] ) ) {
            my $h = { %{ $record[0] } };
            $h->{id} //= $rid;
            my $def = $self->deflate( $tableid, $h );
            if ( ref($def) eq 'ARRAY' ) {
                @record = ( scalar(@$def) > 1 ) ? @{$def}[ 1 .. $#$def ] : ();
            }
        }
    }

    my $table_info = $self->table_info($tableid);
    (undef, @record) = $self->repeat_fields( $table_info, $rid, @record );

    my $table_path = $self->table_path($tableid);
    my $file_path  = "$table_path.$self->{db_ext}";

    # Write authority cancelled.
    $self->config('no_write')
      and do { $self->transact_error( $file_path, "No authority to write to the file" ); return; };

    # Transaction journal & record locking (Lock BEFORE reading/modifying - Strict 2PL)
    my $is_txn = ( $self->{_txn} && $self->{_txn}->{active} ) ? 1 : 0;
    $self->flock_open( $tableid, "write", $rid );
    if ($is_txn) {
        $self->{_txn}->{locks}->{"${tableid}_${rid}"} = 1;
    }

    my $use_ramdisk = $table_info ? ( $table_info->{use_ramdisk} // $table_info->{use_cache} // 0 ) : 0;
    $use_ramdisk = $self->_normalize_ramdisk_tier($use_ramdisk);
    my $ramdisk_path;
    if ($use_ramdisk) {
        $self->ramdisk_ensure($tableid);
        $ramdisk_path = $self->ramdisk_path($tableid);
    }

    my $is_async_write = ( $use_ramdisk == 4 && !$is_txn );

lib/AmberDB.pm  view on Meta::CPAN

    if ($is_async_write) {
        $self->ramdisk_mark_dirty( $file_path, $rid, 2 );
    }
    elsif ( $use_ramdisk == 4 && $is_txn ) {
        $self->ramdisk_unmark_dirty( $file_path, $rid );
    }

    $self->table_close($file_path) unless ( $is_async_write || $use_ramdisk == 3 );
    $self->table_close($ram_file) if $ram_file;
    unless ($is_txn) { $self->flock_close( $tableid, $rid ); }

    # Cache invalidate
    $self->clear_cache($tableid, $rid);

    my @new_rec = ( $rid, @record );

    # text backup record.
    unless ( $is_async_write || $use_ramdisk == 3 ) {
        $self->recs_back( "edit", $tableid, \@new_rec )
          or cluck "[DB_TIE] Backup error (edit). $tableid\n";
    }

    if ( $self->config('use_simple') || ( $table_info && $table_info->{use_simple} ) ) {
        $self->_invalidate_child_ramdisk_tables($tableid);
        return $rid;
    }

    my @old_rec = $old_record ? ( $rid, $self->db_decode($old_record) ) : ($rid);

    # Index update (search, match, facet, sort)
    my @pairs = ( [ $rid, \@old_rec, \@new_rec ] );
    my $idx_path = ( $use_ramdisk && $ramdisk_path ) ? $ramdisk_path : $table_path;

    # 1. Base stream unconditional index updates
    $self->search_modify( $idx_path, $table_info, $tableid, \@pairs );
    $self->match_modify( $idx_path, $table_info, \@pairs );

    # 2. Tiered stream updates
    if ( $table_info->{use_junk} ) {
        $self->junk_transition( $idx_path, $table_info, $tableid, \@pairs );
    }
    elsif ( $table_info->{use_facet} ) {
        $self->facet_modify( $idx_path, $table_info, \@pairs );
    }
    $self->sort_modify( $idx_path, $table_info, \@pairs );
    $self->unique_modify( $idx_path, $table_info, \@pairs );

    $self->slug_modify( $idx_path, $table_info, $tableid, \@pairs );
    $self->suggest_add( $idx_path, $table_info, $tableid, [ \@new_rec ] );

    # Authorization
    $self->auth_write( $tableid, $table_path, "edit", $rid ) unless ( $is_async_write || $use_ramdisk == 3 );

    $self->_invalidate_child_ramdisk_tables($tableid);
    return 1;
}

# List record modify.
# Note: Bulk operations (insert_list, modify_list, delete_list) do NOT use transactions (_txn_log).
# ------------------------------------------------
sub update_list {

    my ( $self, $tableid, @records ) = @_;

    local $self->{_no_txn} = 1;

    $tableid        or return {};
    scalar @records or return {};

    # Deflate ONLY if table has a schema and records match schema
    my $should_deflate = 0;
    if ( $self->has_schema($tableid) ) {
        my $first_rec = ( @records == 1 && ref($records[0]) eq 'ARRAY' && @{$records[0]} && ref($records[0]->[0]) eq 'HASH' )
          ? $records[0]->[0]
          : ( ref($records[0]) eq 'HASH' ? $records[0] : ( ( @records > 1 && ref($records[1]) eq 'HASH' ) ? $records[1] : undef ) );
        if ( $first_rec && $self->schema_matches_hash( $tableid, $first_rec ) ) {
            $should_deflate = 1;
        }
    }

    if ($should_deflate) {
        if ( @records == 1 && ref($records[0]) eq 'ARRAY' && @{$records[0]} && ref($records[0]->[0]) eq 'HASH' ) {
            @records = $self->deflate( $tableid, @{ $records[0] } );
        }
        elsif ( @records == 1 && ref($records[0]) eq 'HASH' ) {
            @records = $self->deflate( $tableid, $records[0] );
        }
        elsif ( grep { ref($_) eq 'HASH' } @records ) {
            @records = $self->deflate( $tableid, @records );
        }
    }

    # Write authority cancelled.
    $self->config('no_write')
      and do { cluck "[DB_TIE] No authority to write to the file.\n"; return; };

    my $table_info = $self->table_info($tableid);
    my $is_simple  = $self->config('use_simple') || ( $table_info && $table_info->{use_simple} );

    my $table_path = $self->table_path($tableid);
    my $file_path  = "$table_path.$self->{db_ext}";

    my $use_ramdisk = $table_info ? ( $table_info->{use_ramdisk} // $table_info->{use_cache} // 0 ) : 0;
    $use_ramdisk = $self->_normalize_ramdisk_tier($use_ramdisk);
    my $ramdisk_path;
    if ($use_ramdisk) {
        $self->ramdisk_ensure($tableid);
        $ramdisk_path = $self->ramdisk_path($tableid);
    }

    my $is_txn         = ( $self->{_txn} && $self->{_txn}->{active} ) ? 1 : 0;
    my $is_async_write = ( $use_ramdisk == 4 && !$is_txn );
    my $ram_file       = $ramdisk_path ? "$ramdisk_path.$self->{db_ext}" : undef;
    my $target_file    = ( $is_async_write && $ram_file ) ? $ram_file : $file_path;

    my $has_unique = ( $table_info->{valid} && grep { /unique/i } values %{ $table_info->{valid} } ) ? 1 : 0;
    my $has_repeat = ( $table_info->{repeat} && %{ $table_info->{repeat} } ) ? 1 : 0;

    my ( @valid_inputs, @input_rids );
    foreach my $record (@records) {
        my ( $rid, @data ) = @$record;

lib/AmberDB.pm  view on Meta::CPAN

            foreach my $rec (@records_to_put) {
                $self->ramdisk_mark_dirty( $file_path, $rec->[0], 2 );
            }
        }
        elsif ( $use_ramdisk == 4 && $is_txn ) {
            foreach my $rec (@records_to_put) {
                $self->ramdisk_unmark_dirty( $file_path, $rec->[0] );
            }
        }
    }
    $self->table_close($file_path);
    $self->table_close($ram_file) if $ram_file;

    return \%statu unless @pairs;
    return \%statu if $is_simple;

    my $idx_path = ( $use_ramdisk && $ramdisk_path ) ? $ramdisk_path : $table_path;

    # Phase 2: bulk index updates
    # 1. Base stream unconditional index updates
    $self->search_modify( $idx_path, $table_info, $tableid, \@pairs );
    $self->match_modify( $idx_path, $table_info, \@pairs );

    # 2. Tiered stream updates
    if ( $table_info->{use_junk} ) {
        $self->junk_transition( $idx_path, $table_info, $tableid, \@pairs );
    }
    elsif ( $table_info->{use_facet} ) {
        $self->facet_modify( $idx_path, $table_info, \@pairs );
    }
    $self->sort_modify( $idx_path, $table_info, \@pairs );
    $self->unique_modify( $idx_path, $table_info, \@pairs );

    $self->slug_modify( $idx_path, $table_info, $tableid, \@pairs );

    if ( $table_info->{log_owner} && !$is_async_write ) {
        foreach my $pair (@pairs) {
            $self->auth_write( $tableid, $table_path, "edit", $pair->[0] );
        }
    }

    unless ( $is_async_write || $self->config('no_backup') || $table_info->{no_backup} ) {
        $self->recs_back( "edit", $tableid, map { $_->[2] } @pairs );
    }

    return \%statu;
}

# Standard SQL/CRUD aliases
sub modify_id   { shift->update_id(@_) }
sub modify_list { shift->update_list(@_) }

# Updates a field value in an existing record.
# Supports updating fixed schema blocks:
#   $adb->update_field($table, $rid, "price", 1750)
#   $adb->update_field($table, $rid, block => "price", 1750)
# Supports updating repeating child items by 'id' or 'pos':
#   $adb->update_field($table, $rid, id => 202, $new_item)
#   $adb->update_field($table, $rid, pos => 0, $new_item)
# ------------------------------------------------
sub update_field {
    my $self    = shift;
    my $tableid = shift;
    my $rid     = shift;

    $tableid or return;
    $rid = $self->id_check( $tableid, $rid );
    $rid or return;

    my ( $target_spec, $new_val );
    if ( @_ == 3 && !ref($_[0]) ) {
        my ( $k, $v, $val ) = @_;
        $target_spec = { $k => $v };
        $new_val     = $val;
    }
    elsif ( @_ == 2 ) {
        ( $target_spec, $new_val ) = @_;
        if ( !ref($target_spec) ) {
            $target_spec = { block => $target_spec };
        }
    }
    else {
        return;
    }

    my $table_info = $self->table_info($tableid);
    my $rep_start  = $table_info->{repeat_start};
    if ( !defined $rep_start && $table_info->{blocks} ) {
        for my $i ( 0 .. $#{ $table_info->{blocks} } ) {
            my $b = $table_info->{blocks}->[$i];
            if ( ref($b) eq 'HASH' && defined $b->{type} && lc( $b->{type} ) eq 'repeat' ) {
                $rep_start = $i;
                last;
            }
        }
    }

    my $table_path = $self->table_path($tableid);
    my $file_path  = "$table_path.$self->{db_ext}";

    $self->config('no_write')
      and do { $self->transact_error( $file_path, "No authority to write to the file" ); return; };

    my $is_txn = ( $self->{_txn} && $self->{_txn}->{active} ) ? 1 : 0;
    $self->flock_open( $tableid, "write", $rid );
    if ($is_txn) {
        $self->{_txn}->{locks}->{"${tableid}_${rid}"} = 1;
    }

    $self->table_write($file_path)
      or do {
          unless ($is_txn) { $self->flock_close( $tableid, $rid ); }
          $self->transact_error( $file_path, "$file_path can't open" );
          return;
      };

    my $old_record = $self->recs_get( $file_path, $rid )->{$rid};
    if ( !$table_info->{force} && !$old_record ) {
        $self->table_close($file_path);
        unless ($is_txn) { $self->flock_close( $tableid, $rid ); }
        $self->transact_error( $file_path, "Record not exist: $rid" );



( run in 1.478 second using v1.01-cache-2.11-cpan-145b269f2f3 )