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 )