AmberDB

 view release on metacpan or  search on metacpan

lib/AmberDB/Index.pm  view on Meta::CPAN


    return 0 unless defined $a && $a ne '' && defined $b && $b ne '';
    my $num = ( $a =~ /^-?[0-9.]+$/ && $b =~ /^-?[0-9.]+$/ );
    if    ( $op eq 'eq' || $op eq '==' ) { return $num ? $a == $b : $a eq $b }
    elsif ( $op eq 'ne' || $op eq '!=' ) { return $num ? $a != $b : $a ne $b }
    elsif ( $op eq '>'  )                { return $a >  $b }
    elsif ( $op eq '>=' )                { return $a >= $b }
    elsif ( $op eq '<'  )                { return $a <  $b }
    elsif ( $op eq '<=' )                { return $a <= $b }
    return 0;
}

# $adb->_resolve_field_value($table_info, \@record, $field_spec);
# ------------------------------------------------
sub _resolve_field_value {

    my ( $self, $table_info, $record, $field_spec ) = @_;

    return '' unless defined $field_spec && defined $record && ref($record) eq 'ARRAY';

    # 1. Relational RDBM or Nested Array: "2->14"
    if ( $field_spec =~ /^(\d+)->(\d+)$/ ) {
        my ( $b1, $b2 ) = ( $1, $2 );
        my $blk_info = $table_info->{blocks}->[$b1] if ref($table_info) eq 'HASH' && $table_info->{blocks};

        # RDBM relational resolution
        if ( $blk_info && $blk_info->{rdbm} ) {
            my $ref_table;
            if ( ref( $blk_info->{rdbm} ) eq 'HASH' ) {
                $ref_table = $blk_info->{rdbm}->{table};
            }
            elsif ( $blk_info->{rdbm} =~ /^([\w\-]+)[;:,]/ ) {
                $ref_table = $1;
            }

            my $ref_id = $record->[$b1];
            if ( $ref_table && defined $ref_id && $ref_id ne '' ) {
                my @ref_rec = $self->read_id( $ref_table, $ref_id );
                return $ref_rec[$b2] // '';
            }
            return '';
        }
        # Nested Array Reference
        elsif ( ref( $record->[$b1] ) eq 'ARRAY' ) {
            return $record->[$b1]->[$b2] // '';
        }
        # Tab/comma separated string
        else {
            my $raw = $record->[$b1] // '';
            my @parts = split /[\t,]/, $raw;
            return $parts[$b2] // '';
        }
    }

    # 2. Direct Field: 5
    return $record->[$field_spec] // '';
}

# my @vals = $adb->field_to_list($value, $mode, $table_path, $table_info, $blk);
# Converts ARRAY ref, comma/semicolon delimited string or single value to a normalized list.
# In 'write' mode, registers text strings into .unq with auto-incrementing lastid, or validates rdbm.
# In 'read' mode, resolves existing string IDs from .unq without creating new entries.
# ------------------------------------------------
sub field_to_list {

    my ( $self, $value, $mode, $table_path, $table_info, $blk ) = @_;

    return () unless defined $value && $value ne '';

    # 1. Normalize input using trim_space with flatten mode (1)
    my @raw;
    if ( ref $value eq 'ARRAY' ) {
        @raw = @{$value};
    }
    else {
        my $str = $self->trim_space( "$value", 1 );
        return () unless defined $str && $str ne '';

        if ( $str =~ /[,;]/ ) {
            @raw = split /[,;]/, $str;
        }
        else {
            @raw = ($str);
        }
    }

    # Clean and normalize each element with trim_space(..., 1)
    my @list;
    foreach my $item (@raw) {
        next unless defined $item;
        if ( ref $item eq 'ARRAY' ) {
            push @list, $self->field_to_list($item);
            next;
        }
        my $s = $self->trim_space( "$item", 1 );
        push @list, $s if defined $s && length($s);
    }

    return () unless @list;

    # If no mode or context provided, return the normalized elements
    return @list unless $mode && $table_path && defined $blk;

    # 2. Mode-specific processing ('write' or 'read')
    $mode = lc($mode);
    my ( $target_table, $target_blk ) = $self->rdbm_target( $table_info, $blk );

    if ($target_table) {
        my @ids;
        foreach my $item (@list) {
            if ( $item =~ /^\d+$/ ) {
                push @ids, $item;
            }
            elsif ( defined $target_blk ) {
                my $target_table_path = $self->table_path($target_table);
                my $target_unq        = "${target_table_path}_${target_blk}.unq";
                my $nid;
                if ( -e $target_unq || $self->{_db}->{$target_unq} ) {
                    ($nid) = $self->index_get( $target_unq, "s:$item", 'raw' );
                }
                if ( !defined $nid || $nid eq '' ) {

lib/AmberDB/Index.pm  view on Meta::CPAN

    return () unless ref($recs_ref) eq 'ARRAY' && @$recs_ref;
    return $self->db_sortid( $tableid, @$recs_ref ) unless $s_opt;

    my $norm = $self->normalize_sort_opt($s_opt);
    my $blk  = $norm->{blk} // 0;
    my $dir  = $norm->{dir} // 'desc';

    my $table_info = $tableid ? $self->table_info($tableid) : undef;
    my $type;
    if ( $table_info && exists $table_info->{sort_block} ) {
        foreach my $cfg ( @{ $table_info->{sort_block} } ) {
            if ( ref($cfg) eq 'HASH' && $cfg->{blk} == $blk ) {
                $type = $cfg->{type};
                last;
            }
        }
    }
    $type //= ( $blk == 0 ) ? ( $table_info->{id_type} // 'num' ) : 'auto';

    return $self->array_sort( $type, $dir, $blk, @$recs_ref );
}

1;



__END__

=head1 NAME

AmberDB::Index - Inverted search, exact field match, binary sort, and URL slug rewrite indexing engine

=head1 SYNOPSIS

  # Indexing methods are called directly on the AmberDB instance ($adb):

  # 1. URL Slug generation and reverse lookup (.slg)
  my $slug     = $adb->set_slug("catalog_product", $record_ref, 1);
  my $slug_map = $adb->get_slug("catalog_product", 0, 101, 102);

  # 2. Normalization of array or delimited values into clean lists and .unq IDs
  my @unq_ids  = $adb->field_to_list($raw_val, 'write', $table_path, $table_info, $blk);

  # 3. Monotonic sort key generation for fixed-width sorting (.srt)
  my $key      = $adb->normalize_sort_key("1250.50", "num");

=head1 DESCRIPTION

C<AmberDB::Index> manages flat-file inverted search indexes (C<.src>), exact field matching indexes (C<.fld>), primary key lists (C<.inx>), binary pre-sorted record indexes (C<.srt>), and bidirectional URL slug rewrite dictionaries (C<.slg>).

Facet forward indexing (C<.fac>) is handled by C<AmberDB::Index::Facet>, and dual-tier cold record indexing (C<.jinx>, C<.jfld>, C<.jsrc>) is managed by C<AmberDB::Index::Junk>.

B<Inheritance Note:> C<AmberDB> inherits from C<AmberDB::Index> via C<use parent>. All indexing methods can be called directly on C<$adb>.

=head1 METHODS

=head2 field_to_list($value, [$mode], [$table_path], [$table_info], [$blk])

Converts ARRAY references, comma/semicolon-delimited strings, or single scalars into a normalized list of trimmed values.
=over 4
=item * In C<'write'> mode: Registers text values into the per-block unique/dictionary index (C<_${blk}.unq>) with auto-incrementing numeric IDs (or validates foreign key IDs for RDBM fields).
=item * In C<'read'> mode: Resolves existing string IDs from C<_${blk}.unq> without creating new dictionary entries.
=back

  my @ids = $adb->field_to_list("Red, Blue, Green", 'write', $path, $info, 3);

=head2 normalize_sort_key($value, $type, [$length])

Normalizes an input value into a fixed-width byte key for fast monotonic sorting in binary C<.srt> files:
=over 4
=item * B<num / decimal>: Adds a C<1e12> (1,000,000,000,000) offset for signed float/integer monotonic sorting (C<%020.6f> format).
=item * B<string / ascii>: Converts to ASCII, removes punctuation, truncates to 8 bytes (or C<$length>), and pads with spaces.
=item * B<date>: Converts date expressions to 14-character C<YYYYMMDDHHMMSS> timestamps.
=back

  my $sort_key = $adb->normalize_sort_key("249.90", "num");

=head2 set_slug($table_id, $record, [$write_mode])

Generates a URL-friendly ASCII slug from designated schema title blocks (C<slug_block>) and registers bidirectional mapping in C<_0.slg> (Record ID -E<gt> Slug) and C<_1.slg> (Slug -E<gt> Record ID).

  my $slug = $adb->set_slug("catalog_product", \@record, 1);
  # => "kablosuz-bluetooth-kulaklik"

=head2 get_slug($table_id, [$type], @record_or_slug_ids)

Resolves URL slugs or reverse-maps slugs back to record IDs.
=over 4
=item * C<$type = 0>: Returns C<{ record_id =E<gt> slug }> (default, reads C<_0.slg>).
=item * C<$type = 1>: Returns C<{ slug =E<gt> record_id }> (reads C<_1.slg>).
=back

  my $slugs = $adb->get_slug("catalog_product", 0, 101, 102);
  # => { 101 => "kablosuz-kulaklik", 102 => "akilli-saat" }

=head2 rdbm_target($table_info, $blk)

Returns C<($target_table, $target_blk)> if the specified schema block is configured as a relational foreign key (RDBM), or empty list / undef otherwise.

  my ($target_table, $target_blk) = $adb->rdbm_target($schema, 2);

=head2 repeat_fields($table_info, @record)

Consolidates dynamic repeat columns (defined in C<$table_info-E<gt>{repeat_ids}> and C<repeat_start>) into a single comma-separated value.

=head2 Low-Level Index Maintenance Methods

These methods are called automatically by AmberDB during CRUD operations (C<insert_id>, C<modify_id>, C<delete_id>):

=over 4

=item * C<records_add / records_del> — Updates C<keys>, C<count>, and C<lastid> in primary C<.inx> files.

=item * C<match_add / match_modify / match_del> — Manages exact-match inverted index files (C<_${blk}.fld>).

=item * C<search_add / search_modify / search_del> — Manages full-text keyword search index files (C<_${blk}.src>).

=item * C<sort_add / sort_modify / sort_del> — Manages binary pre-sorted record index files (C<_${blk}.srt>).

=back



( run in 1.167 second using v1.01-cache-2.11-cpan-d01c6094234 )