AmberDB

 view release on metacpan or  search on metacpan

bin/amberdb_cli.pl  view on Meta::CPAN

#!/usr/bin/perl

# bin/amberdb_cli.pl - AmberDB Command Line Interface & Management Console
# Direct file-based embedded execution, token-based session lifecycle, dynamic method reflection, and dash-tolerant argument parser.

use 5.016;
use strict;
use warnings;
use File::Basename qw(dirname basename);
use Cwd qw(abs_path getcwd);
use File::Spec;
use File::Path qw(make_path remove_tree);
use JSON::PP qw(encode_json decode_json);
use Data::Dumper;
use Time::HiRes qw(gettimeofday tv_interval);

our $t0 = [gettimeofday];

BEGIN {
    my $script_path = __FILE__;
    $script_path =~ s{[\\/]+}{/}g;
    my $bin_dir = $script_path =~ m{^(.*)/[^/]+$} ? $1 : '.';
    my $lib_dir = "$bin_dir/../lib";
    my $abs_lib = eval { abs_path($lib_dir) } // $lib_dir;
    unshift @INC, $abs_lib if -d $abs_lib;
    unshift @INC, $lib_dir if -d $lib_dir && $lib_dir ne $abs_lib;

    # Auto-detect local project lib directory if running from project root (e.g. ./lib)
    for my $cand ( './lib', '../lib' ) {
        my $abs_cand = eval { abs_path($cand) };
        unshift @INC, $abs_cand if defined $abs_cand && -d $abs_cand;
    }
}

use AmberDB;
use AmberDB::Tools;

$| = 1;

our $adb = AmberDB->new( path => { dbase_dir => 'dbstore' }, connect => { username => 'cli' }, config => { no_mkdir => 1 } );
our $tools = AmberDB::Tools->new($adb);

# ============================================================================
# ============================================================================
# SESSION REGISTRY (~/.amberdb/session/)
# Stores session files per active session under ~/.amberdb/session/sess_$token
# ============================================================================

my $user_home = $ENV{USERPROFILE} // $ENV{HOME} // '.';
$user_home =~ s{\\}{/}g;
our $AMBERDB_HOME = "$user_home/.amberdb";
our $CLI_SESS_DIR = "$AMBERDB_HOME/session";

sub setup_global_workspace {
    eval {
        make_path($AMBERDB_HOME) unless -d $AMBERDB_HOME;
        make_path($CLI_SESS_DIR) unless -d $CLI_SESS_DIR;
        my $cfg_dir = "$AMBERDB_HOME/config";
        make_path($cfg_dir) unless -d $cfg_dir;
    };
}

# Auto-provision global workspace in background on first run if missing
if ( !-d $AMBERDB_HOME ) {
    setup_global_workspace();
}

sub resolve_abs_path {
    my ($path) = @_;
    return '' unless defined $path && length $path;
    $path =~ s{\\}{/}g;
    my $abs = eval { abs_path($path) };
    if ( defined $abs && length $abs ) {
        $abs =~ s{\\}{/}g;
        return $abs;
    }
    my $cwd = eval { getcwd() } // '.';
    $cwd =~ s{\\}{/}g;
    my $rel = File::Spec->rel2abs( $path, $cwd );
    $rel =~ s{\\}{/}g;
    return $rel;
}

sub cli_session_file {
    my ($token) = @_;
    return '' unless $token;
    return "$CLI_SESS_DIR/sess_$token";
}

sub get_session_path {
    my ($token) = @_;
    $token //= '';

    # 1. If token is provided, check ~/.amberdb/session/sess_$token
    if ( $token ) {
        my $file = cli_session_file($token);
        if ( -f $file && open my $fh, '<', $file ) {
            local $/;
            my $content = <$fh>;
            close $fh;
            return '' unless $content;
            my $data = eval { decode_json($content) };
            if ( $data && ref $data eq 'HASH' && $data->{path} && $data->{path}->{dbase_dir} ) {
                return $data->{path}->{dbase_dir};
            }
            # Fallback if stored as plain path string
            $content =~ s/^\s+|\s+$//g if defined $content;
            return $content if length $content && -d $content;
        }
    }

    return undef;
}

sub set_session_path {
    my ( $token, $db_path, $sess_data ) = @_;
    return unless defined $token && length $token;
    make_path($CLI_SESS_DIR) unless -d $CLI_SESS_DIR;

    my $abs_path = resolve_abs_path($db_path);

    my $file = cli_session_file($token);
    if ( open my $fh, '>', $file ) {
        if ($sess_data) {
            $sess_data->{path}->{dbase_dir} = $abs_path if $sess_data->{path};

bin/amberdb_cli.pl  view on Meta::CPAN

        my $offset = shift @pos_args;
        my $limit  = shift @pos_args;
        $method_args{offset} = parse_value($offset) if defined $offset;
        $method_args{limit}  = parse_value($limit)  if defined $limit;
    }
    elsif ( $mode =~ /,/ ) {
        $opt_action = 'read_list';
        $method_args{ids} = [ split /,/, $mode ];
    }
    elsif ( $mode =~ /^-?\d+$/ ) {
        if ( @pos_args && $pos_args[0] =~ /^-?\d+$/ ) {
            $opt_action = 'read_list';
            $method_args{ids} = [ parse_value($mode), map { parse_value($_) } @pos_args ];
            @pos_args = ();
        }
        else {
            $opt_action = 'read_id';
            $method_args{id} = parse_value($mode);
        }
    }
    else {
        $opt_action = 'read_id';
        $method_args{id} = $mode;
    }
}

# ============================================================================
# USAGE / HELP SCREEN
# ============================================================================

sub show_usage {
    my ($lang) = @_;
    $lang = lc( $lang // 'en' );

    if ( $lang eq 'tr' ) {
        print <<"USAGE_TR";
AmberDB CLI v$AmberDB::VERSION - Gömülü Veritabanı Konsolu ve Yönetim Aracı

Kullanım:
  amberdb [eylem|token] [tablo] [parametreler...]

Kullanım Biçimleri:

1. Doğrudan Yerel Kullanım (Varsayılan - Yerel ./dbstore):
  amberdb tables                              # Bulunulan dizindeki ./dbstore tablolarını listeler
  amberdb read products 10                    # ID ile tekil kayıt okuma
  amberdb read products 10 inflate=1 json     # Åžema geniÅŸletmeli JSON okuma
  amberdb search products "kulaklık" limit=10 # Tam metin ve fonetik arama
  amberdb insert users 0 data='{"name":"Ali"}'# Kayıt ekler (yoksa ./dbstore oluşturur)
  amberdb update users 10 data='{"role":"admin"}'
  amberdb delete users 10

2. İsimlendirilmiş Oturum (Session Management):
  amberdb connect <database>                  # ~/.amberdb/<database> havuzuna bağlanır
  amberdb connect <database>\@<path>           # Özel bir klasöre bağlanır (örn: mydb\@./dbstore)
  amberdb 1245 tables                         # 1245 nolu oturumun tablolarını listeler
  amberdb 1245 read products 10               # 1245 nolu oturumda okuma yapar
  amberdb 1245 disconnect                     # Oturumu sonlandırır

3. Altyapı ve Kurulum:
  amberdb setup                               # ~/.amberdb global çalışma alanını kurar
  amberdb setup [dir]                         # Özel bir dizinde veritabanı iskeleti kurar
  amberdb setup ramdisk [start|stop|status]   # RAM-disk sürücü yönetimi

Bakım ve Yönetim Eylemleri:
  amberdb update storage [--check] [--force]  # Dizin & ABR v5 veri biçimi migrasyonu
  amberdb update version [--check]            # MetaCPAN çekirdek sürüm kontrolü / güncelleme
  amberdb reindex products                    # İndeksleri sıfırdan oluştur
  amberdb check products                      # Fiziksel dosya bütünlük kontrolü
  amberdb vacuum products                     # BDB disk boşluklarını temizle
  amberdb migrate products                    # Şema güncellemesi
  amberdb export products file=yedek.csv      # CSV dışa aktarımı
  amberdb import products file=yeni.csv       # CSV içe aktarımı
  amberdb dump products file=yedek.tar.gz     # Veritabanı yedeği al
  amberdb restore file=yedek.tar.gz           # Yedekten geri yükle
  amberdb rename from=eski to=yeni            # Tablo adı değiştir
  amberdb drop temp_tbl force=1               # Tabloyu kalıcı olarak sil

Genel Seçenekler:
  --format=table|json|pretty|tsv|dumper       Çıktı biçimi (Varsayılan: table. Veya sonda: json, tsv, dumper)
  --db=/path/to/dbstore                       Oturumsuz doğrudan veritabanı yolu
  --token=TOKEN                               Aktif oturum anahtarı
  --time, time, time=1                        İşlem süresini en altta satır olarak yazar
  --dry-run                                   İşlemi uygulamadan simüle eder
  --force                                     Silme eylemleri için zorunlu onay
  --version, -v, version                      Sürüm bilgisini yazar

İngilizce yardım için: amberdb usage
USAGE_TR
        exit 0;
    }

    # Default: English
    print <<"USAGE_EN";
AmberDB CLI v$AmberDB::VERSION - Embedded Database Console & Management Tool

Usage:
  amberdb [action|token] [table] [parameters...]

Usage Modes:

1. Direct Local Execution (Default - Local ./dbstore):
  amberdb tables                              # List tables in current directory ./dbstore
  amberdb read products 10                    # Read single record by ID
  amberdb read products 10 inflate=1 json     # Read record with schema inflation as JSON
  amberdb search products "headphone" limit=10# Full-text and phonetic search
  amberdb insert users 0 data='{"name":"Ali"}'# Insert record (creates ./dbstore if missing)
  amberdb update users 10 data='{"role":"admin"}'
  amberdb delete users 10

2. Named Session (Session Management):
  amberdb connect <database>                  # Connect to ~/.amberdb/<database> pool
  amberdb connect <database>\@<path>           # Connect to custom folder (e.g. mydb\@./dbstore)
  amberdb 1245 tables                         # List tables for session 1245
  amberdb 1245 read products 10               # Read record in session 1245
  amberdb 1245 disconnect                     # Terminate active session

3. Infrastructure & Setup:
  amberdb setup                               # Provision ~/.amberdb global workspace
  amberdb setup [dir]                         # Initialize database skeleton in custom directory
  amberdb setup ramdisk [start|stop|status]   # RAM-disk drive management

Maintenance & Administrative Actions:
  amberdb update storage [--check] [--force]  # Directory & ABR v5 format migration
  amberdb update version [--check]            # MetaCPAN core version check / update
  amberdb reindex products                    # Rebuild all derived indexes
  amberdb check products                      # Physical file integrity check
  amberdb vacuum products                     # Compact and reclaim BDB storage space
  amberdb migrate products                    # Schema migration
  amberdb export products file=backup.csv     # Export table to CSV
  amberdb import products file=new.csv        # Import table from CSV
  amberdb dump products file=backup.tar.gz    # Create database backup archive
  amberdb restore file=backup.tar.gz          # Restore database from backup
  amberdb rename from=old to=new              # Rename table
  amberdb drop temp_tbl force=1               # Permanently drop table and indexes

General Options:
  --format=table|json|pretty|tsv|dumper       Output format (Default: table. Or trailing: json, tsv, dumper)
  --db=/path/to/dbstore                       Direct database directory without session
  --token=TOKEN                               Active session token
  --time, time, time=1                        Prints execution time at the bottom
  --dry-run                                   Simulates execution without writes
  --force                                     Required confirmation for destructive actions
  --version, -v, version                      Display version information

For Turkish help: amberdb usage.tr
USAGE_EN
    exit 0;
}

if ( $opt_help || ( defined $opt_action && $opt_action eq 'help' ) ) {
    my $lang = $opt_help_lang;
    if ( @pos_args && $pos_args[0] =~ /^(?:tr|en)$/i ) {
        $lang //= lc(shift @pos_args);
    }
    $lang //= 'en';
    $opt_help = 1;
    show_usage($lang);
}

if ( defined $opt_action && $opt_action =~ /^(?:version|--version|-v)$/i ) {
    if ( defined $opt_format && $opt_format eq 'json' ) {
        output_result( { version => $AmberDB::VERSION, engine => 'AmberDB', platform => $^O }, $opt_format );
    }
    else {
        print "AmberDB v$AmberDB::VERSION (CLI)\n";
    }
    exit 0;
}

# ============================================================================
# LIFECYCLE: CONNECT & DISCONNECT
# ============================================================================

if ( defined $opt_action && $opt_action eq 'connect' ) {
    my $conn_arg = $opt_database // shift @pos_args;
    my ( $conn_db, $custom_path );

    if ( defined $conn_arg ) {

bin/amberdb_cli.pl  view on Meta::CPAN

    if ( $@ || !$token ) {
        my $err = $@ || $adb->last_error() || "Authentication failed";
        $err =~ s/ at .* line \d+.*//s;
        die "[AMBERDB_ERROR] $err\n";
    }

    # Save session with canonical absolute path in ~/.amberdb/session/sess_$token
    my $sess_data = {
        token    => $token,
        database => $conn_db,
        username => $conn_user,
        path     => { dbase_dir => $target_data_dir },
        cfg      => $adb->config(),
    };
    set_session_path( $token, $target_data_dir, $sess_data );

    my $actual_file = cli_session_file($token);

    if ( defined $opt_format && $opt_format eq 'json' ) {
        print encode_json( {
            status       => 'connected',
            token        => $token,
            session_file => $actual_file,
            database     => $conn_db,
            username     => $conn_user,
            path         => { dbase_dir => $target_data_dir },
            cfg          => $adb->config(),
        } ), "\n";
    }
    else {
        print "[AMBERDB] Connected successfully.\n";
        print "Session Token : $token\n";
        print "Database      : $conn_db\n";
        print "Data Dir      : $target_data_dir\n";
        print "Session File  : $actual_file\n";
        print "\nUsage with Token:\n";
        print "  amberdb $token tables\n";
        print "  amberdb $token read <table_name> <id>\n";
        print "Or set environment variable in current shell:\n";
        print "  export AMBERDB_TOKEN=$token  (Windows CMD: set AMBERDB_TOKEN=$token)\n";
    }
    exit 0;
}

if ( defined $opt_action && $opt_action eq 'disconnect' ) {
    my $token = $active_token || resolve_active_token( $opt_token, 1 );
    unless ($token) {
        die "[AMBERDB_ERROR] No active session token found to disconnect.\n";
    }
    $adb->disconnect($token);
    delete_session($token);
    if ( defined $opt_format && $opt_format eq 'json' ) {
        print encode_json( { status => 'disconnected', token => $token } ), "\n";
    }
    else {
        print "[AMBERDB] Disconnected session '$token' successfully.\n";
    }
    exit 0;
}

# If in session and global configs/paths changed without action, update session and exit
if ( $session && ( $has_cfg_updates || $has_path_updates ) && !defined $opt_action ) {
    $session->{path} = $adb->path();
    $session->{cfg}  = $adb->config();
    $session->{updated_at} = time();
    save_session( $active_token, $session );

    if ( defined $opt_format && $opt_format eq 'json' ) {
        print encode_json( { status => 'updated', token => $active_token, path => $adb->path(), cfg => $adb->config() } ), "\n";
    }
    else {
        print "[AMBERDB] Session '$active_token' configuration updated successfully.\n";
        print "Path   : " . encode_json( $adb->path() ) . "\n";
        print "Config : " . encode_json( $adb->config() ) . "\n";
    }
    exit 0;
}

# ============================================================================
# SESSION COMMANDS: CONFIG & PATH
# ============================================================================

if ( defined $opt_action && $opt_action eq 'config' ) {
    unless ($session) {
        die "[AMBERDB_ERROR] 'config' requires an active session. Run 'connect' first (e.g. amberdb connect path-dbase_dir=...) or specify a token.\n";
    }
    if ( %method_args || $has_cfg_updates ) {
        for my $k ( keys %method_args ) {
            $adb->config( $k => $method_args{$k} );
        }
        $session->{cfg} = $adb->config();
        $session->{updated_at} = time();
        save_session( $active_token, $session );
        output_result( { status => 'ok', action => 'config', token => $active_token, cfg => $adb->config() }, $opt_format );
    }
    else {
        output_result( $adb->config() // {}, $opt_format );
    }
    exit 0;
}

if ( defined $opt_action && $opt_action eq 'path' ) {
    unless ($session) {
        die "[AMBERDB_ERROR] 'path' requires an active session. Run 'connect' first (e.g. amberdb connect path-dbase_dir=...) or specify a token.\n";
    }
    if ( %method_args || $has_path_updates ) {
        my $old_file = session_file($active_token);
        for my $k ( keys %method_args ) {
            my $val = $method_args{$k};
            if ( $k eq 'dbase_dir' || $k eq 'db' || $k eq 'dbase' ) {
                my $new_db_dir = eval { abs_path($val) } // $val;
                $adb->set_datadir($new_db_dir);
            }
            else {
                $adb->path( $k => $val );
            }
        }
        $session->{path} = $adb->path();
        $session->{updated_at} = time();

        my $new_file = session_file($active_token);

bin/amberdb_cli.pl  view on Meta::CPAN

}

# ============================================================================
# INFRASTRUCTURE SETUP & PROVISIONING (amberdb setup [dir])
# ============================================================================

if ( defined $opt_action && ( $opt_action eq 'setup' || $opt_action eq 'ramdisk' ) ) {
    my $first_arg = shift @pos_args;

    # Check if ramdisk subcommand: amberdb setup ramdisk ... or amberdb ramdisk ...
    if ( ( $opt_action eq 'ramdisk' ) || ( defined $first_arg && lc($first_arg) eq 'ramdisk' ) ) {
        my $subcmd = ( $opt_action eq 'ramdisk' ) ? ( $first_arg // 'status' ) : ( shift @pos_args // 'status' );
        $subcmd = lc($subcmd);
        $subcmd = 'start'  if $subcmd eq 'mount' || $subcmd eq '--start';
        $subcmd = 'stop'   if $subcmd eq 'unmount' || $subcmd eq '--stop';
        $subcmd = 'status' if $subcmd eq '--status';

        my $size  = delete $method_args{size}  // shift @pos_args // '512M';
        my $drive = delete $method_args{drive} // shift @pos_args // 'R:';

        my $is_win = ( $^O eq 'MSWin32' || $^O eq 'msys' || $^O eq 'cygwin' );

        if ($is_win) {
            my $script_bin = eval { abs_path(dirname(__FILE__)) } // dirname(__FILE__) // '.';
            my $bat_path = File::Spec->catfile( $script_bin, "setup_windows.bat" );
            if ( -f $bat_path ) {
                system( "cmd.exe", "/c", $bat_path, $subcmd, $size, $drive );
                exit $? >> 8;
            }
            else {
                if ( $subcmd eq 'start' ) {
                    system( "imdisk", "-a", "-s", $size, "-m", $drive, "-p", "/fs:ntfs /q /y /v:AmberDB" );
                }
                elsif ( $subcmd eq 'stop' ) {
                    system( "imdisk", "-D", "-m", $drive );
                }
                else {
                    system( "imdisk", "-l", "-m", $drive );
                }
                exit $? >> 8;
            }
        }
        else {
            if ( $subcmd eq 'status' ) {
                my $shm = -d "/dev/shm" ? "/dev/shm (Linux tmpfs available)" : "Not mounted";
                print "RAM-Disk Status: $shm\n";
                exit 0;
            }
            elsif ( $subcmd eq 'start' ) {
                eval { $adb->ramdisk_setup() };
                print "[RAM-DISK] Linux /dev/shm initialized.\n";
                exit 0;
            }
            elsif ( $subcmd eq 'stop' ) {
                print "[RAM-DISK] Stopped.\n";
                exit 0;
            }
        }
    }

    # If no argument is passed: setup global environment (~/.amberdb)
    if ( !defined $first_arg || !length $first_arg ) {
        setup_global_workspace();

        if ( defined $opt_format && $opt_format eq 'json' ) {
            output_result( {
                status       => 'ok',
                action       => 'setup',
                mode         => 'global',
                amberdb_home => $AMBERDB_HOME,
                session_dir  => $CLI_SESS_DIR,
            }, $opt_format );
            exit 0;
        }

        print "=================================================================\n";
        print " AmberDB Global Environment Setup & Initialization               \n";
        print "=================================================================\n";
        print "AmberDB Home     : $AMBERDB_HOME (~/.amberdb)\n";
        print "Session Registry : $CLI_SESS_DIR/\n";
        print "Platform         : $^O\n";
        print "-----------------------------------------------------------------\n";
        print "[SUCCESS] AmberDB global workspace initialized successfully!\n\n";
        print "Kullanım Biçimleri:\n";
        print "  1. Yerel Proje   : Proje klasörünüzde doğrudan çalıştırın (./dbstore kullanılır)\n";
        print "     amberdb insert users 0 data='{\"name\":\"Ahmet\"}'\n";
        print "     amberdb tables\n\n";
        print "  2. Merkezi Havuz : Belirtilen ada oturum açarak ~/.amberdb/<ad> kullanılır\n";
        print "     amberdb connect eticaretim\n";
        print "     amberdb 1245 tables\n\n";
        print "  3. Özel Klasör   : 'ad\@yol' formatı ile oturum açılır\n";
        print "     amberdb connect eticaretim\@./dbstore\n";
        print "=================================================================\n";
        exit 0;
    }

    # If custom directory passed (e.g. amberdb setup /var/data or amberdb setup my_dir)
    my $target_dir = resolve_abs_path($first_arg);
    my @subdirs = qw(table schema journal lock session config backup ramdisk);
    my @created;
    my @existing;
    make_path($target_dir) unless -d $target_dir;
    for my $sub (@subdirs) {
        my $dir_path = "$target_dir/$sub";
        if ( -d $dir_path ) {
            push @existing, $sub;
        }
        else {
            make_path($dir_path);
            push @created, $sub;
        }
    }
    my $core_conf = "$target_dir/config/core.conf";
    my $conf_created = 0;
    if ( !-f $core_conf && open my $fh, '>', $core_conf ) {
        print $fh "# AmberDB Core Configuration\n";
        print $fh "site_id        amberdb\n";
        print $fh "language       tr\n";
        close $fh;
        $conf_created = 1;
    }
    if ( defined $opt_format && $opt_format eq 'json' ) {
        output_result( {
            status        => 'ok',
            action        => 'setup',
            dbase_dir     => $target_dir,
            dirs_created  => \@created,
            dirs_existing => \@existing,
            config_init   => $conf_created ? 1 : 0,
        }, $opt_format );
        exit 0;
    }

    print "=================================================================\n";
    print " AmberDB Infrastructure Setup: $target_dir                       \n";
    print "=================================================================\n";
    for my $c (@created)  { print "  [+] Created  $c/\n"; }
    for my $e (@existing) { print "  [.] Exists   $e/\n"; }
    print "-----------------------------------------------------------------\n";
    print "[SUCCESS] Initialized successfully in $target_dir\n";
    print "=================================================================\n";
    exit 0;
}



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