# # General purpose file bashed key/value hash table system # based on SQLite3 and storing one hash table per database # # Copyright (c) 2010 Dirk Koopman G1TLH # use strict; use DXDebug; my $done_require = 0; my $avail = 0; sub avail { unless ($done_require) { $done_require = 1; eval {require DBI;}; if ($@) { dbg("SQLHash: no DBI available '$@'"); return 0; } eval {require DBD::SQLite;}; if ($@) { dbg("SQLHash: no DBD::SQLite available '$@'"); return 0; } import DBI; $avail = 1; } } sub file_exists { my $pkg = shift; my $fn = ref $pkg ? shift : $pkg; return -e $fn; } sub del_file { } sub new { my $pkg = shift; my $table = shift; my $dsnfn = $fn; if ($dsnfn =~ /\.sqlite$/) { $table =~ s/\.sqlite$//; } else { $dsnfn .= ".sqlite"; } my %flags = @_; my $blob = delete $flags{blob} ? 'blob' : 'text'; $flags{RaiseError} = 0 unless exists $flags{RaiseError}; my $exists = file_exists($dsnfn); my $dsn = "dbi:SQLite:dbname=$fn"; my $dbh = DBI->connect($dsn, "", "", \%flags); unless ($exists) { my $r = _sql_do($dbh, qq{create table $table (k text unique key not null, v $blob not null)}); dbg("SQLHash: created $table with data as $blob") if $r; } return bless {dbh => $dbh, table => $table}, $pkg; } sub get { my $self = shift; return _sql_get_single($self->{dbh}, qq{select v from $self->{table} where k = ?}, @_); } sub put { my $self = shift; _sql_do($self->{dbh}, qq{replace in $self->{table} (k,v) values(?,?)}, @_); return @r ? $r[0]->[0] : undef; } sub delete { my $self = shift; _sql_do($self->{dbh}, qq{delete from $self->{table} where k = ?}, @_); } sub keys { my $self = shift; return _sql_get_simple_array($self->{dbh}, qq{select k from $self->{table}}); } sub values { my $self = shift; return _sql_get_simple_array($self->{dbh}, qq{select v from $self->{table}}); } sub begin_work { $_[0]->{dbh}->begin_work; } sub commit { $_[0]->{dbh}->commit; } sub rollback { $_[0]->{dbh}->rollback; } sub _error { my $dbh = $shift; my $s = shift; dbg("SQL Error: '" . $dbh->errstr . "' on statement '$s', disconnecting"); } sub _sql_pre_exe { my $dbh = $shift; my $s = shift; dbg("sql => $s") if isdbg('sql'); my $sth = $dbh->prepare($s); _error($dbh, $s), $return 0 unless $sth; my $rv = $sth->execute(@_); _error($dbh, $s) unless $rv; return ($rv, $sth); } sub _sql_get_single { my $dbh = shift; my $s = shift; my $out; my ($rv, $sth) = _sql_pre_exe($dbh, $s); return $out unless $rv && $sth; my $ref = $sth->fetch; if ($sth->err) { dbg("SQL Error: '" . $sth->errstr . "' on statement '$s'") if $sth->err; } else { dbg("sql <= " . join(',', @$ref)) if isdbg('sql'); $out = $ref->[0]; } $sth->finish; return $out; } sub _sql_get_simple_array { my $dbh = shift; my $s = shift; my @out; my ($rv, $sth) = _sql_pre_exe($dbh, $s); return @out unless $rv && $sth; while (my $ref = $sth->fetch) { if ($sth->err) { dbg("SQL Error: '" . $sth->errstr . "' on statement '$s'") if $sth->err; last; } else { dbg("sql <= " . join(',', @$ref)) if isdbg('sql'); push @out, $ref->[0]; } } $sth->finish; return @out; } sub _sql_get { my $dbh = shift; my $s = shift; my @out; my ($rv, $sth) = _sql_pre_exe($dbh, $s); return @out unless $rv && $sth; while (my $ref = $sth->fetch) { if ($sth->err) { dbg("SQL Error: '" . $sth->errstr . "' on statement '$s'") if $sth->err; last; } else { dbg("sql <= " . join(',', @$ref)) if isdbg('sql'); push @out, [@$ref]; } } $sth->finish; return @out; } sub _sql_do { my $dbh = $shift; my $s = shift; dbg("sql => $s") if isdbg('sql'); my $rv = $dbh->do($s, @_); _error($dbh, $s) unless $rv; } 1;