The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
#%%%%% main ====================================================================
use DBI;
my $hDb = DBI->connect('dbi:Template:', '', '', 
        {RaiseError=>1, AutoCommit=> 1,
            tmpl_func_ => {
                connect => \&connect,
                prepare => \&prepare,
                execute => \&execute,
                fetch   => \&fetch,
                rows    => \&rows,
                name    => \&name,
                table_info    => \&table_info,
            },
        }
    ) or die "Can't connect $DBI::errstr";

print "-->>Data Source\n";
my @aDs = 
        DBI->data_sources('TemplateSS', 
            { tmplss_datasources  => \&datasources, });
print join("\n", @aDs), "\n";

print "-->>Table Info\n";
my $sth = $hDb->table_info('', '', '');
while(my $raD = $sth->fetchrow_arrayref()) {
    print join(':', map {$_||=''} @$raD), "\n";
}

print "--ALL--\n";
my $hSt = $hDb->prepare('SEL ANYFMT');
$hSt->execute();
while(my $raD = $hSt->fetchrow_arrayref()) {
    print join(':', @$raD), "\n";
}
print "---REC 2 and 1 ----\n";
$hSt = $hDb->prepare('SEL ANYFMT [2, ?]');
$hSt->execute(1);
while(my $raD = $hSt->fetchrow_arrayref()) {
    print join(':', @$raD), "\n";
}
$hSt = $hDb->prepare('INS ANYFMT [?, ?, ?, ?]');
$hSt->execute('Matui,Gojira', 'Tokyo', 'Right', 28);
$hDb->do(q/UPD ANYFMT [1, 'Nakama,Nori', 'Kyoto', 'First', 32]/);
$hDb->do(q/DEL ANYFMT [2, 1]/);

print "--ALL--\n";
$hSt = $hDb->prepare('SEL ANYFMT');
$hSt->execute();
while(my $raD = $hSt->fetchrow_arrayref()) {
    print join(':', @$raD), "\n";
}
$hDb->disconnect;

#%%%%% DRH(datasources) ========================================================
sub datasources($) {
    my ($drh) = @_;
#1. Open specified directry
    opendir(DIR, '.') or 
        return DBI::set_err($drh, 1, "Cannot open directory '.'");
    my @aDsns = grep { ($_ ne '.') and  ($_ ne '..') and  (-d $_) } readdir(DIR);
    closedir DIR;
    return ('', @aDsns);
}
#%%%%% DRH/DBH =================================================================
#>>>>> connect -----------------------------------------------------------------
sub connect($$) {
    my ($drh, $dbh) = @_;
    $dbh->{tmpl_data_pool_}={};
}
#%%%%% DBH =====================================================================
#>>>>> prepare, commit, rollback -----------------------------------------------
sub prepare($$$$) { 
    my($dbh, $sth, $sStmt, $rhAttr) = @_;
    return ($sStmt =~ tr/?//);  # bind_params
}
#>>>>> commit, rollback --------------------------------------------------------
#sub commit($)     {    my($dbh) = @_; }
#sub rollback($)   { my($dbh) = @_; }

#>>>>> table_info --------------------------------------------------------------
sub table_info($) {
    my($dbh) = @_;
    my @aTables;
#1. Open specified directry
    my $sDir = ($dbh->FETCH('tmpl_dir'));
    $sDir ||= '.';
    if (!opendir(DIR, $sDir)) {
        DBI::set_err($dbh, 1, "Cannot open directory $sDir");
        return undef;
    }
#2. Check and push it array
    my $sFile;
    while (defined($sFile = readdir(DIR))) {
#   while($sFile =glob("$sDir/*.sfm")) {
        next if($sFile !~/\.sfm$/i);
        my $sFullPath = "$sDir/$sFile";
        if (-f $sFullPath) {
            my $sF = $sFile;
            $sF =~ s/\.sfm$//;
            push(@aTables, [undef, undef, $sF, 'TABLE', 'Some Format']);
        }
    }
    return (\@aTables, 
            ['TABLE_QUALIFIER', 'TABLE_OWNER', 'TABLE_NAME', 
             'TABLE_TYPE', 'REMARKS']);
}
#%%%%% STH =====================================================================
#>>>>> execute, fetch, rows, name, finish --------------------------------------
sub execute($$){
    my($sth, $raParam) = @_;

#1.3 Replace Placeholder with parameters
    my $sStmt = $sth->FETCH('Statement');
    foreach my $sRep (@$raParam) {
        my $sQ = $sth->{Database}->quote($sRep);
        $sStmt =~ s/\?/$sQ/;
    }

#2. Parse command
    my ($sCmd, $sFile) = ($sStmt=~/(\S+)\s+(\S+)\s*/);
    return $sth->DBI::set_err(2, "No table specified") unless(defined $sFile);

    my $sRest = $';
    my $raData;
    if($sRest) {
        $raData = eval ($sRest);
        return $sth->DBI::set_err(2, "ERROR $sRest $@") if($@);
        return $sth->DBI::set_err(2, 
                qq/You should set array ref ($sRest)/) 
        if (ref($raData) ne 'ARRAY');
    }

#2.1 Get data from file
    my $rhData = $sth->{Database}->FETCH('tmpl_sf_data_pool');
    unless(exists $rhData->{$sFile}) {
        my $sDir = $sth->FETCH('tmpl_sf_dir') || '.';
       open(IN, "<$sDir/$sFile.sfm") ||
            return $sth->DBI::set_err(2, "Can't open table $sFile");
        my @aRows=();
        $rhData->{$sFile} = \@aRows;
        $sth->{Database}->STORE('tmpl_sf_data_pool', $rhData);
        while(my $sRec = <IN>) {
            chomp $sRec;
            if($sRec =~ /^(.{20})(.*)\t(.*)$/) {
                my ($sCol1, $sCol2_3, $sCol4) = ($1, $2, $3);
                $sCol1 =~ s/\s+$//;
                my ($sCol2, $sCol3) = split /,/, $sCol2_3;
                push @aRows, [$sCol1, $sCol2, $sCol3, $sCol4];
            }
        }
        close IN;
    }
#3. Execute command
    my $iRet = -1;
    if($sCmd eq 'INS') {
        if($raData) {
            push @{$rhData->{$sFile}},  $raData;
        }
        else {
            return $sth->DBI::set_err(2, 'You should set data for INSERT') ;
        }
    }
    elsif($sCmd eq 'SEL') {
        my @aRows = @{$rhData->{$sFile}};
        @aRows = @aRows[@$raData] if($raData);
        $sth->STORE('tmpl_sf_row_data', \@aRows);
        $iRet = scalar @aRows || '0E0';
    }
    elsif($sCmd eq 'UPD') {
        if($raData) {
            my $iPos = shift @$raData;
            @{$rhData->{$sFile}}->[$iPos] = $raData;
        }
        else {
            return $sth->DBI::set_err(2, 'You should set data for UPDATE');
        }
    }
    elsif($sCmd eq 'DEL') {
        if($raData) {
            splice @{$rhData->{$sFile}}, $raData->[0], $raData->[1];
        }
        else {
            $rhData->{$sFile} = [];
        }
    }
    else{
        return $sth->DBI::set_err(2, "$sCmd is not supported");
    }
#3.3 Set NUM_OF_FIELDS
    $sth->STORE('NUM_OF_FIELDS', 4);
}
sub fetch($)  {
    my($sth) = @_;
    my $raData = $sth->FETCH('tmpl_sf_row_data');
    return (undef, 1, 1) if (!$raData  ||  ref($raData) ne 'ARRAY');
    my $raDav = shift @$raData;
    return (defined $raDav)? 
            ($raDav, undef, undef) : (undef, 1, undef);
}
sub rows($)   {
    my($sth) = @_;
    return (defined $sth->FETCH('tmpl_sf_row_data'))? 
            scalar @{$sth->FETCH('tmpl_sf_row_data')} : -1;
}
sub name($)   {
    my($sth) = @_;
    return ['name', 'addr', 'birth', 'age'];
}
sub finish($) {
    my($sth) = @_;
    $sth->{tmpl_sf_row_data}=undef;
}