Author: hmbrand
Date: Tue Apr 28 23:06:34 2009
New Revision: 12722

Modified:
   dbi/trunk/Changes
   dbi/trunk/lib/DBD/File.pm

Log:
Fixes to DBD::File (H.Merijn Brand)
  bind_param () now honors the attribute argument
  added f_ext attribute
  File::Spec is always required. (CORE since 5.00405)

Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes   (original)
+++ dbi/trunk/Changes   Tue Apr 28 23:06:34 2009
@@ -56,6 +56,10 @@
 
 =head2 Changes in DBI 1.608 (svn rXXX)
 
+  Fixes to DBD::File (H.Merijn Brand)
+    bind_param () now honors the attribute argument
+    added f_ext attribute
+    File::Spec is always required. (CORE since 5.00405)
   Fixed two small memory leaks when running in mod_perl
     one in DBI->connect and one in DBI::Gofer::Execute.
     Both due to "local $ENV{...};" leaking memory.

Modified: dbi/trunk/lib/DBD/File.pm
==============================================================================
--- dbi/trunk/lib/DBD/File.pm   (original)
+++ dbi/trunk/lib/DBD/File.pm   Tue Apr 28 23:06:34 2009
@@ -5,10 +5,11 @@
 #
 #  This module is currently maintained by
 #
-#      Jeff Zucker < jzucker AT cpan.org >
+#      H.Merijn Brand & Jens Rehsack
 #
 #  The original author is Jochen Wiedmann.
 #
+#  Copyright (C) 2009 by H.Merijn Brand & Jens Rehsack
 #  Copyright (C) 2004 by Jeff Zucker
 #  Copyright (C) 1998 by Jochen Wiedmann
 #
@@ -17,24 +18,28 @@
 #  You may distribute this module under the terms of either the GNU
 #  General Public License or the Artistic License, as specified in
 #  the Perl README file.
-#
-require 5.004;
+
+require 5.005;
+
 use strict;
 
 use DBI ();
 require DBI::SQL::Nano;
-my $haveFileSpec = eval { require File::Spec };
+require File::Spec;
 
 package DBD::File;
 
-use vars qw(@ISA $VERSION $drh $valid_attrs);
+use strict;
+
+use vars qw( @ISA $VERSION $drh $valid_attrs );
 
-$VERSION = '0.35';
+$VERSION = "0.36";
 
 $drh = undef;          # holds driver handle(s) once initialised
 
-sub driver ($;$) {
-    my($class, $attr) = @_;
+sub driver ($;$)
+{
+    my ($class, $attr) = @_;
 
     # Drivers typically use a singleton object for the $drh
     # We use a hash here to have one singleton per subclass.
@@ -43,607 +48,714 @@
     # An alternative would be not not cache the $drh here at all
     # and require that subclasses do that. Subclasses should do
     # their own caching, so caching here just provides extra safety.
-    return $drh->{$class} if $drh->{$class};
+    $drh->{$class} and return $drh->{$class};
 
-    DBI->setup_driver('DBD::File'); # only needed once but harmless to repeat
+    DBI->setup_driver ("DBD::File"); # only needed once but harmless to repeat
     $attr ||= {};
-    no strict qw(refs);
-    if (!$attr->{Attribution}) {
-       $attr->{Attribution} = "$class by Jeff Zucker"
-           if $class eq 'DBD::File';
-       $attr->{Attribution} ||= ${$class . '::ATTRIBUTION'}
-           || "oops the author of $class forgot to define this";
-    }
-    $attr->{Version} ||= ${$class . '::VERSION'};
-    ($attr->{Name} = $class) =~ s/^DBD\:\:// unless $attr->{Name};
+    {  no strict "refs";
+       unless ($attr->{Attribution}) {
+           $class eq "DBD::File" and $attr->{Attribution} = "$class by Jeff 
Zucker";
+           $attr->{Attribution} ||= ${$class . "::ATTRIBUTION"} ||
+               "oops the author of $class forgot to define this";
+           }
+       $attr->{Version} ||= ${$class . "::VERSION"};
+       $attr->{Name} or ($attr->{Name} = $class) =~ s/^DBD\:\://;
+       }
 
-    $drh->{$class} = DBI::_new_drh($class . "::dr", $attr);
-    $drh->{$class}->STORE(ShowErrorStatement => 1);
+    $drh->{$class} = DBI::_new_drh ($class . "::dr", $attr);
+    $drh->{$class}->STORE (ShowErrorStatement => 1);
     return $drh->{$class};
-}
+    } # driver
 
-sub CLONE {
+sub CLONE
+{
     undef $drh;
-}
+    } # CLONE
+
+sub file2table
+{
+    my ($data, $dir, $file, $file_is_tab) = @_;
+
+    $file eq "." || $file eq ".."      and return;
+
+    # Fully Qualified File Name
+    my $fqfn = File::Spec->catfile ($dir, $file);
+
+    my ($ext, $req) = ("", 0, 0);
+    if ($data->{f_ext}) {
+       ($ext, my $opt) = split m/\//, $data->{f_ext};
+       if ($ext) {
+           $opt =~ m/r/i and $req = 1;
+           }
+       }
 
-package DBD::File::dr; # ====== DRIVER ======
+    (my $tbl = $file) =~ s/$ext$//i;
+
+    $file_is_tab && $file !~ m/$ext$/i and $fqfn .= $ext;
+
+    $file = $fqfn;
+    if ($ext) {
+       if ($req) {
+           # File extension required
+           $file =~ s/$ext$//i         or  next;
+           }
+       else {
+           # File extension optional, skip if file with extension exists
+           grep m/$ext$/i, glob "$fqfn*"       and next;
+           $file =~ s/$ext$//i;
+           }
+       }
+
+    $data->{f_map}{$tbl} = $fqfn;
+    return $tbl;
+    } # file2table
+
+# ====== DRIVER 
================================================================
+
+package DBD::File::dr;
+
+use strict;
 
 $DBD::File::dr::imp_data_size = 0;
 
-sub connect ($$;$$$) {
-    my($drh, $dbname, $user, $auth, $attr)= @_;
+sub connect ($$;$$$)
+{
+    my ($drh, $dbname, $user, $auth, $attr)= @_;
 
     # create a 'blank' dbh
-    my $this = DBI::_new_dbh($drh, {
-       'Name' => $dbname,
-       'USER' => $user,
-       'CURRENT_USER' => $user,
-    });
+    my $this = DBI::_new_dbh ($drh, {
+       Name            => $dbname,
+       USER            => $user,
+       CURRENT_USER    => $user,
+       });
 
     if ($this) {
-       my($var, $val);
-       $this->{f_dir} = $haveFileSpec ? File::Spec->curdir() : '.';
-       while (length($dbname)) {
+       my ($var, $val);
+       $this->{f_dir} = File::Spec->curdir ();
+       $this->{f_ext} = "";
+       $this->{f_map} = {};
+       while (length $dbname) {
            if ($dbname =~ s/^((?:[^\\;]|\\.)*?);//s) {
-               $var = $1;
-           } else {
-               $var = $dbname;
-               $dbname = '';
-           }
-           if ($var =~ /^(.+?)=(.*)/s) {
+               $var    = $1;
+               }
+           else {
+               $var    = $dbname;
+               $dbname = "";
+               }
+           if ($var =~ m/^(.+?)=(.*)/s) {
                $var = $1;
                ($val = $2) =~ s/\\(.)/$1/g;
                $this->{$var} = $val;
+               }
            }
-       }
         $this->{f_valid_attrs} = {
-            f_version    => 1  # DBD::File version
-          , f_dir        => 1  # base directory
-          , f_tables     => 1  # base directory
-        };
+           f_version   => 1,  # DBD::File version
+           f_dir       => 1,  # base directory
+           f_ext       => "", # file extension
+           f_tables    => 1,  # base directory
+           };
         $this->{sql_valid_attrs} = {
-            sql_handler           => 1  # Nano or S:S
-          , sql_nano_version      => 1  # Nano version
-          , sql_statement_version => 1  # S:S version
-        };
-    }
-    $this->STORE('Active',1);
-    return set_versions($this);
-}
+           sql_handler           => 1, # Nano or S:S
+           sql_nano_version      => 1, # Nano version
+           sql_statement_version => 1, # S:S version
+           };
+       }
+    $this->STORE ("Active", 1);
+    return set_versions ($this);
+    } # connect
 
-sub set_versions {
+sub set_versions
+{
     my $this = shift;
     $this->{f_version} = $DBD::File::VERSION;
     for (qw( nano_version statement_version)) {
-        $this->{'sql_'.$_} = $DBI::SQL::Nano::versions->{$_}||'';
-    }
-    $this->{sql_handler} = ($this->{sql_statement_version})
-                         ? 'SQL::Statement'
-                        : 'DBI::SQL::Nano';
+       $this->{"sql_$_"} = $DBI::SQL::Nano::versions->{$_} || "";
+       }
+    $this->{sql_handler} = $this->{sql_statement_version}
+       ? "SQL::Statement"
+       : "DBI::SQL::Nano";
     return $this;
-}
+    } # set_versions
 
-sub data_sources ($;$) {
-    my($drh, $attr) = @_;
-    my($dir) = ($attr and exists($attr->{'f_dir'})) ?
-       $attr->{'f_dir'} : $haveFileSpec ? File::Spec->curdir() : '.';
-    my($dirh) = Symbol::gensym();
-    if (!opendir($dirh, $dir)) {
-        $drh->set_err($DBI::stderr, "Cannot open directory $dir: $!");
-       return undef;
-    }
-    my($file, @dsns, %names, $driver);
-    if ($drh->{'ImplementorClass'} =~ /^dbd\:\:([^\:]+)\:\:/i) {
+sub data_sources ($;$)
+{
+    my ($drh, $attr) = @_;
+    my $dir = $attr && exists $attr->{f_dir}
+       ? $attr->{f_dir}
+       : File::Spec->curdir ();
+    my ($dirh) = Symbol::gensym ();
+    unless (opendir ($dirh, $dir)) {
+       $drh->set_err ($DBI::stderr, "Cannot open directory $dir: $!");
+       return;
+       }
+
+    my ($file, @dsns, %names, $driver);
+    if ($drh->{ImplementorClass} =~ m/^dbd\:\:([^\:]+)\:\:/i) {
        $driver = $1;
-    } else {
-       $driver = 'File';
-    }
-    while (defined($file = readdir($dirh))) {
-        if ($^O eq 'VMS') {
-            # if on VMS then avoid warnings from catdir if you use a file
-            # (not a dir) as the file below
-            next if $file !~ /\.dir$/oi;
-        }
-       my $d = $haveFileSpec ?
-           File::Spec->catdir($dir, $file) : "$dir/$file";
-        # allow current dir ... it can be a data_source too
-       if ( $file ne ($haveFileSpec ? File::Spec->updir() : '..')
-           and  -d $d) {
-           push(@dsns, "DBI:$driver:f_dir=$d");
        }
-    }
+    else {
+       $driver = "File";
+       }
+
+    while (defined ($file = readdir ($dirh))) {
+       if ($^O eq "VMS") {
+           # if on VMS then avoid warnings from catdir if you use a file
+           # (not a dir) as the file below
+           $file !~ m/\.dir$/oi and next;
+           }
+       my $d = File::Spec->catdir ($dir, $file);
+       # allow current dir ... it can be a data_source too
+       $file ne File::Spec->updir () && -d $d and
+           push @dsns, "DBI:$driver:f_dir=$d";
+       }
     @dsns;
-}
+    } # data_sources
 
-sub disconnect_all {
-}
+sub disconnect_all
+{
+    } # disconnect_all
 
-sub DESTROY {
+sub DESTROY
+{
     undef;
-}
+    } # DESTROY
+
+# ====== DATABASE 
==============================================================
 
+package DBD::File::db;
 
-package DBD::File::db; # ====== DATABASE ======
+use strict;
+use Carp;
 
 $DBD::File::db::imp_data_size = 0;
 
-sub ping { return (shift->FETCH('Active')) ? 1 : 0 };
+sub ping
+{
+    return (shift->FETCH ("Active")) ? 1 : 0;
+    } # ping
 
-sub prepare ($$;@) {
-    my($dbh, $statement, @attribs)= @_;
+sub prepare ($$;@)
+{
+    my ($dbh, $statement, @attribs) = @_;
 
     # create a 'blank' sth
-    my $sth = DBI::_new_sth($dbh, {'Statement' => $statement});
+    my $sth = DBI::_new_sth ($dbh, {Statement => $statement});
 
     if ($sth) {
-       my $class = $sth->FETCH('ImplementorClass');
+       my $class = $sth->FETCH ("ImplementorClass");
        $class =~ s/::st$/::Statement/;
-       my($stmt);
+       my $stmt;
 
-        # if using SQL::Statement version > 1
-        # cache the parser object if the DBD supports parser caching
-        # SQL::Nano and older SQL::Statements don't support this
-
-       if ( $dbh->{sql_handler} eq 'SQL::Statement'
-             and $dbh->{sql_statement_version} > 1)
-           {
-            my $parser = $dbh->{csv_sql_parser_object};
-            $parser ||= eval { $dbh->func('csv_cache_sql_parser_object') };
-            if ($@) {
-               $stmt = eval { $class->new($statement) };
-           }
-            else {
-               $stmt = eval { $class->new($statement,$parser) };
-           }
-        }
-        else {
-           $stmt = eval { $class->new($statement) };
-       }
+       # if using SQL::Statement version > 1
+       # cache the parser object if the DBD supports parser caching
+       # SQL::Nano and older SQL::Statements don't support this
+
+       if ( $dbh->{sql_handler} eq "SQL::Statement" and
+            $dbh->{sql_statement_version} > 1) {
+           my $parser = $dbh->{csv_sql_parser_object};
+           $parser ||= eval { $dbh->func ("csv_cache_sql_parser_object") };
+           if ($@) {
+               $stmt = eval { $class->new ($statement) };
+               }
+           else {
+               $stmt = eval { $class->new ($statement, $parser) };
+               }
+           }
+       else {
+           $stmt = eval { $class->new ($statement) };
+           }
        if ($@) {
-           $dbh->set_err($DBI::stderr, $@);
+           $dbh->set_err ($DBI::stderr, $@);
            undef $sth;
-       } else {
-           $sth->STORE('f_stmt', $stmt);
-           $sth->STORE('f_params', []);
-           $sth->STORE('NUM_OF_PARAMS', scalar($stmt->params()));
+           }
+       else {
+           $sth->STORE ("f_stmt", $stmt);
+           $sth->STORE ("f_params", []);
+           $sth->STORE ("NUM_OF_PARAMS", scalar ($stmt->params ()));
+           }
        }
-    }
     $sth;
-}
-sub csv_cache_sql_parser_object {
-    my $dbh = shift;
+    } # prepare
+
+sub csv_cache_sql_parser_object
+{
+    my $dbh    = shift;
     my $parser = {
-            dialect    => 'CSV',
-            RaiseError => $dbh->FETCH('RaiseError'),
-            PrintError => $dbh->FETCH('PrintError'),
-        };
-    my $sql_flags  = $dbh->FETCH('sql_flags') || {};
-    %$parser = (%$parser,%$sql_flags);
-    $parser = SQL::Parser->new($parser->{dialect},$parser);
+       dialect    => "CSV",
+       RaiseError => $dbh->FETCH ("RaiseError"),
+       PrintError => $dbh->FETCH ("PrintError"),
+       };
+    my $sql_flags = $dbh->FETCH ("sql_flags") || {};
+    %$parser = (%$parser, %$sql_flags);
+    $parser = SQL::Parser->new ($parser->{dialect}, $parser);
     $dbh->{csv_sql_parser_object} = $parser;
     return $parser;
-}
-sub disconnect ($) {
-    shift->STORE('Active',0);
+    } # csv_cache_sql_parser_object
+
+sub disconnect ($)
+{
+    shift->STORE ("Active", 0);
     1;
-}
-sub FETCH ($$) {
+    } # disconnect
+
+sub FETCH ($$)
+{
     my ($dbh, $attrib) = @_;
-    if ($attrib eq 'AutoCommit') {
+    $attrib eq "AutoCommit" and
        return 1;
-    } elsif ($attrib eq (lc $attrib)) {
+
+    if ($attrib eq (lc $attrib)) {
        # Driver private attributes are lower cased
 
-        # Error-check for valid attributes
-        # not implemented yet, see STORE
-        #
-        return $dbh->{$attrib};
-    }
+       # Error-check for valid attributes
+       # not implemented yet, see STORE
+       #
+       return $dbh->{$attrib};
+       }
     # else pass up to DBI to handle
-    return $dbh->SUPER::FETCH($attrib);
-}
+    return $dbh->SUPER::FETCH ($attrib);
+    } # FETCH
 
-sub STORE ($$$) {
+sub STORE ($$$)
+{
     my ($dbh, $attrib, $value) = @_;
 
-    if ($attrib eq 'AutoCommit') {
-       return 1 if $value; # is already set
-       die("Can't disable AutoCommit");
-    } elsif ($attrib eq (lc $attrib)) {
+    if ($attrib eq "AutoCommit") {
+       $value and return 1;    # is already set
+       croak "Can't disable AutoCommit";
+       }
+
+    if ($attrib eq lc $attrib) {
        # Driver private attributes are lower cased
 
-  # I'm not implementing this yet becuase other drivers may be
-  # setting f_ and sql_ attrs I don't know about
-  # I'll investigate and publicize warnings to DBD authors
-  # then implement this
-  #
-        # return to implementor if not f_ or sql_
-        # not implemented yet
-        # my $class = $dbh->FETCH('ImplementorClass');
-        #
-        # if ( !$dbh->{f_valid_attrs}->{$attrib}
-        # and !$dbh->{sql_valid_attrs}->{$attrib}
-        # ) {
-       #    return $dbh->set_err( $DBI::stderr,"Invalid attribute '$attrib'");
-        # }
-        # else {
-       #    $dbh->{$attrib} = $value;
-       # }
-
-        if ($attrib eq 'f_dir') {
-           return $dbh->set_err( $DBI::stderr,"No such directory '$value'")
-                unless -d $value;
-       }
+       # I'm not implementing this yet becuase other drivers may be
+       # setting f_ and sql_ attrs I don't know about
+       # I'll investigate and publicize warnings to DBD authors
+       # then implement this
+
+       # return to implementor if not f_ or sql_
+       # not implemented yet
+       # my $class = $dbh->FETCH ("ImplementorClass");
+       #
+       # !$dbh->{f_valid_attrs}->{$attrib} && 
!$dbh->{sql_valid_attrs}->{$attrib} and
+       #    return $dbh->set_err ($DBI::stderr, "Invalid attribute '$attrib'");
+       #  $dbh->{$attrib} = $value;
+
+       if ($attrib eq "f_dir") {
+           -d $value or
+               return $dbh->set_err ($DBI::stderr, "No such directory 
'$value'")
+           }
+       if ($attrib eq "f_ext") {
+           $value eq "" || $value =~ m{^\.\w+(?:/[iIrR]*)?$}
+               or carp "'$value' doesn't look like a valid file extension 
attribute\n";
+           }
        $dbh->{$attrib} = $value;
        return 1;
-    }
-    return $dbh->SUPER::STORE($attrib, $value);
-}
+       }
+    return $dbh->SUPER::STORE ($attrib, $value);
+    } # STORE
 
-sub DESTROY ($) {
+sub DESTROY ($)
+{
     my $dbh = shift;
-    $dbh->disconnect if $dbh->SUPER::FETCH('Active');
-}
+    $dbh->SUPER::FETCH ("Active") and $dbh->disconnect ;
+    } # DESTROY
 
-sub type_info_all ($) {
-    [
-     {   TYPE_NAME         => 0,
-        DATA_TYPE         => 1,
-        PRECISION         => 2,
-        LITERAL_PREFIX    => 3,
-        LITERAL_SUFFIX    => 4,
-        CREATE_PARAMS     => 5,
-        NULLABLE          => 6,
-        CASE_SENSITIVE    => 7,
-        SEARCHABLE        => 8,
-        UNSIGNED_ATTRIBUTE=> 9,
-        MONEY             => 10,
-        AUTO_INCREMENT    => 11,
-        LOCAL_TYPE_NAME   => 12,
-        MINIMUM_SCALE     => 13,
-        MAXIMUM_SCALE     => 14,
-     },
-     [ 'VARCHAR', DBI::SQL_VARCHAR(),
-       undef, "'","'", undef,0, 1,1,0,0,0,undef,1,999999
-       ],
-     [ 'CHAR', DBI::SQL_CHAR(),
-       undef, "'","'", undef,0, 1,1,0,0,0,undef,1,999999
-       ],
-     [ 'INTEGER', DBI::SQL_INTEGER(),
-       undef,  "", "", undef,0, 0,1,0,0,0,undef,0,  0
-       ],
-     [ 'REAL', DBI::SQL_REAL(),
-       undef,  "", "", undef,0, 0,1,0,0,0,undef,0,  0
-       ],
-     [ 'BLOB', DBI::SQL_LONGVARBINARY(),
-       undef, "'","'", undef,0, 1,1,0,0,0,undef,1,999999
-       ],
-     [ 'BLOB', DBI::SQL_LONGVARBINARY(),
-       undef, "'","'", undef,0, 1,1,0,0,0,undef,1,999999
-       ],
-     [ 'TEXT', DBI::SQL_LONGVARCHAR(),
-       undef, "'","'", undef,0, 1,1,0,0,0,undef,1,999999
-       ]
-     ]
-}
-
-
-{
-    my $names = ['TABLE_QUALIFIER', 'TABLE_OWNER', 'TABLE_NAME',
-                 'TABLE_TYPE', 'REMARKS'];
-
-    sub table_info ($) {
-       my($dbh) = @_;
-       my($dir) = $dbh->{f_dir};
-       my($dirh) = Symbol::gensym();
-       if (!opendir($dirh, $dir)) {
-           $dbh->set_err($DBI::stderr, "Cannot open directory $dir: $!");
-           return undef;
-       }
-       my($file, @tables, %names);
-       while (defined($file = readdir($dirh))) {
-           if ($file ne '.'  &&  $file ne '..'  &&  -f "$dir/$file") {
-               my $user = eval { getpwuid((stat(_))[4]) };
-               push(@tables, [undef, $user, $file, "TABLE", undef]);
-           }
-       }
-       if (!closedir($dirh)) {
-           $dbh->set_err($DBI::stderr, "Cannot close directory $dir: $!");
-           return undef;
-       }
-
-       my $dbh2 = $dbh->{'csv_sponge_driver'};
-       if (!$dbh2) {
-           $dbh2 = $dbh->{'csv_sponge_driver'} = DBI->connect("DBI:Sponge:");
-           if (!$dbh2) {
-               $dbh->set_err($DBI::stderr, $DBI::errstr);
-               return undef;
+sub type_info_all ($)
+{
+    [ { TYPE_NAME          => 0,
+       DATA_TYPE          => 1,
+       PRECISION          => 2,
+       LITERAL_PREFIX     => 3,
+       LITERAL_SUFFIX     => 4,
+       CREATE_PARAMS      => 5,
+       NULLABLE           => 6,
+       CASE_SENSITIVE     => 7,
+       SEARCHABLE         => 8,
+       UNSIGNED_ATTRIBUTE => 9,
+       MONEY              => 10,
+       AUTO_INCREMENT     => 11,
+       LOCAL_TYPE_NAME    => 12,
+       MINIMUM_SCALE      => 13,
+       MAXIMUM_SCALE      => 14,
+       },
+      [ "VARCHAR",     DBI::SQL_VARCHAR (),
+       undef, "'", "'", undef, 0, 1, 1, 0, 0, 0, undef, 1, 999999,
+       ],
+      [ "CHAR",                DBI::SQL_CHAR (),
+       undef, "'", "'", undef, 0, 1, 1, 0, 0, 0, undef, 1, 999999,
+       ],
+      [ "INTEGER",     DBI::SQL_INTEGER (),
+       undef, "",  "",  undef, 0, 0, 1, 0, 0, 0, undef, 0, 0,
+       ],
+      [ "REAL",                DBI::SQL_REAL (),
+       undef, "",  "",  undef, 0, 0, 1, 0, 0, 0, undef, 0, 0,
+       ],
+      [ "BLOB",                DBI::SQL_LONGVARBINARY (),
+       undef, "'", "'", undef, 0, 1, 1, 0, 0, 0, undef, 1, 999999,
+       ],
+      [ "BLOB",                DBI::SQL_LONGVARBINARY (),
+       undef, "'", "'", undef, 0, 1, 1, 0, 0, 0, undef, 1, 999999,
+       ],
+      [ "TEXT",                DBI::SQL_LONGVARCHAR (),
+       undef, "'", "'", undef, 0, 1, 1, 0, 0, 0, undef, 1, 999999,
+       ]];
+    } # type_info_all
+
+{   my $names = [
+       qw( TABLE_QUALIFIER TABLE_OWNER TABLE_NAME TABLE_TYPE REMARKS )];
+
+    sub table_info ($)
+    {
+       my $dbh  = shift;
+       my $dir  = $dbh->{f_dir};
+       my $dirh = Symbol::gensym ();
+
+       unless (opendir $dirh, $dir) {
+           $dbh->set_err ($DBI::stderr, "Cannot open directory $dir: $!");
+           return;
+           }
+
+       my ($file, @tables, %names);
+       my $user = eval { getpwuid ((stat _)[4]) };
+       while (defined ($file = readdir ($dirh))) {
+           my $tbl = DBD::File::file2table ($dbh, $dir, $file, 0) or next;
+           push @tables, [ undef, $user, $tbl, "TABLE", undef ];
+           }
+       unless (closedir $dirh) {
+           $dbh->set_err ($DBI::stderr, "Cannot close directory $dir: $!");
+           return;
+           }
+
+       my $dbh2 = $dbh->{csv_sponge_driver};
+       unless ($dbh2) {
+           $dbh2 = $dbh->{csv_sponge_driver} = DBI->connect ("DBI:Sponge:");
+           unless ($dbh2) {
+               $dbh->set_err ($DBI::stderr, $DBI::errstr);
+               return;
+               }
            }
-       }
 
        # Temporary kludge: DBD::Sponge dies if @tables is empty. :-(
-       return undef if !...@tables;
+       @tables or return;
 
-       my $sth = $dbh2->prepare("TABLE_INFO", { 'rows' => \...@tables,
-                                                'NAMES' => $names });
-       if (!$sth) {
-           $dbh->set_err($DBI::stderr, $dbh2->errstr);
-       }
+       my $sth = $dbh2->prepare ("TABLE_INFO", {
+                                   rows  => \...@tables,
+                                   NAMES => $names,
+                                   });
+       $sth or $dbh->set_err ($DBI::stderr, $dbh2->errstr);
        $sth;
+       } # table_info
     }
-}
-sub list_tables ($) {
+
+sub list_tables ($)
+{
     my $dbh = shift;
-    my($sth, @tables);
-    if (!($sth = $dbh->table_info())) {
-       return ();
-    }
-    while (my $ref = $sth->fetchrow_arrayref()) {
-       push(@tables, $ref->[2]);
-    }
+    my ($sth, @tables);
+    $sth = $dbh->table_info () or return;
+    while (my $ref = $sth->fetchrow_arrayref ()) {
+       push @tables, $ref->[2];
+       }
     @tables;
-}
+    } # list_tables
+
+sub quote ($$;$)
+{
+    my ($self, $str, $type) = @_;
+    defined $str or return "NULL";
+    defined $type && (
+           $type == DBI::SQL_NUMERIC  ()
+        || $type == DBI::SQL_DECIMAL  ()
+        || $type == DBI::SQL_INTEGER  ()
+        || $type == DBI::SQL_SMALLINT ()
+        || $type == DBI::SQL_FLOAT    ()
+        || $type == DBI::SQL_REAL     ()
+        || $type == DBI::SQL_DOUBLE   ()
+        || $type == DBI::SQL_TINYINT  ())
+       and return $str;
 
-sub quote ($$;$) {
-    my($self, $str, $type) = @_;
-    if (!defined($str)) { return "NULL" }
-    if (defined($type)  &&
-       ($type == DBI::SQL_NUMERIC()   ||
-        $type == DBI::SQL_DECIMAL()   ||
-        $type == DBI::SQL_INTEGER()   ||
-        $type == DBI::SQL_SMALLINT()  ||
-        $type == DBI::SQL_FLOAT()     ||
-        $type == DBI::SQL_REAL()      ||
-        $type == DBI::SQL_DOUBLE()    ||
-        $type == DBI::SQL_TINYINT())) {
-       return $str;
-    }
     $str =~ s/\\/\\\\/sg;
     $str =~ s/\0/\\0/sg;
     $str =~ s/\'/\\\'/sg;
     $str =~ s/\n/\\n/sg;
     $str =~ s/\r/\\r/sg;
     "'$str'";
-}
+    } # quote
 
-sub commit ($) {
-    my($dbh) = shift;
-    if ($dbh->FETCH('Warn')) {
-       warn("Commit ineffective while AutoCommit is on", -1);
-    }
+sub commit ($)
+{
+    my $dbh = shift;
+    $dbh->FETCH ("Warn") and
+       carp "Commit ineffective while AutoCommit is on", -1;
     1;
-}
+    } # commit
 
-sub rollback ($) {
-    my($dbh) = shift;
-    if ($dbh->FETCH('Warn')) {
-       warn("Rollback ineffective while AutoCommit is on", -1);
-    }
+sub rollback ($)
+{
+    my $dbh = shift;
+    $dbh->FETCH ("Warn") and
+       carp "Rollback ineffective while AutoCommit is on", -1;
     0;
-}
+    } # rollback
+
+# ====== STATEMENT 
=============================================================
 
-package DBD::File::st; # ====== STATEMENT ======
+package DBD::File::st;
+
+use strict;
 
 $DBD::File::st::imp_data_size = 0;
 
-sub bind_param ($$$;$) {
-    my($sth, $pNum, $val, $attr) = @_;
-    $sth->{f_params}->[$pNum-1] = $val;
+sub bind_param ($$$;$)
+{
+    my ($sth, $pNum, $val, $attr) = @_;
+    if ($attr && defined $val) {
+       my $type = ref $attr eq "HASH" ? $attr->{TYPE} : $attr;
+       if (   $attr == DBI::SQL_BIGINT ()
+           || $attr == DBI::SQL_INTEGER ()
+           || $attr == DBI::SQL_SMALLINT ()
+           || $attr == DBI::SQL_TINYINT ()
+           ) {
+           $val += 0;
+           }
+       elsif ($attr == DBI::SQL_DECIMAL ()
+           || $attr == DBI::SQL_DOUBLE ()
+           || $attr == DBI::SQL_FLOAT ()
+           || $attr == DBI::SQL_NUMERIC ()
+           || $attr == DBI::SQL_REAL ()
+           ) {
+           $val += 0.;
+           }
+       else {
+           $val = "$val";
+           }
+       }
+    $sth->{f_params}[$pNum - 1] = $val;
     1;
-}
+    } # bind_param
 
-sub execute {
+sub execute
+{
     my $sth = shift;
-    my $params;
-    if (@_) {
-       $sth->{'f_params'} = ($params = [...@_]);
-    } else {
-       $params = $sth->{'f_params'};
-    }
+    my $params = @_ ? ($sth->{f_params} = [ @_ ]) : $sth->{f_params};
 
     $sth->finish;
-    my $stmt = $sth->{'f_stmt'};
-    my $result = eval { $stmt->execute($sth, $params); };
-    return $sth->set_err($DBI::stderr,$@) if $@;
-    if ($stmt->{'NUM_OF_FIELDS'}) { # is a SELECT statement
-       $sth->STORE(Active => 1);
-       $sth->STORE('NUM_OF_FIELDS', $stmt->{'NUM_OF_FIELDS'})
-        if !$sth->FETCH('NUM_OF_FIELDS');
-    }
+    my $stmt = $sth->{f_stmt};
+    my $result = eval { $stmt->execute ($sth, $params); };
+    $@ and return $sth->set_err ($DBI::stderr, $@);
+
+    if ($stmt->{NUM_OF_FIELDS}) {    # is a SELECT statement
+       $sth->STORE (Active => 1);
+       $sth->FETCH ("NUM_OF_FIELDS") or
+           $sth->STORE ("NUM_OF_FIELDS", $stmt->{NUM_OF_FIELDS})
+       }
     return $result;
-}
-sub finish {
+    } # execute
+
+sub finish
+{
     my $sth = shift;
-    $sth->SUPER::STORE(Active => 0);
+    $sth->SUPER::STORE (Active => 0);
     delete $sth->{f_stmt}->{data};
     return 1;
-}
-sub fetch ($) {
-    my $sth = shift;
+    } # finish
+
+sub fetch ($)
+{
+    my $sth  = shift;
     my $data = $sth->{f_stmt}->{data};
-    if (!$data  ||  ref($data) ne 'ARRAY') {
-       $sth->set_err($DBI::stderr, "Attempt to fetch row without a preceeding 
execute() call or from a non-SELECT statement");
-       return undef;
-    }
+    if (!$data || ref $data ne "ARRAY") {
+       $sth->set_err ($DBI::stderr,
+           "Attempt to fetch row without a preceeding execute () call or from 
a non-SELECT statement"
+           );
+       return
+       }
     my $dav = shift @$data;
-    if (!$dav) {
-        $sth->finish;
-       return undef;
-    }
-    if ($sth->FETCH('ChopBlanks')) {
-       map { $_ =~ s/\s+$// if $_; $_ } @$dav;
-    }
-    $sth->_set_fbav($dav);
-}
+    unless ($dav) {
+       $sth->finish;
+       return
+       }
+    if ($sth->FETCH ("ChopBlanks")) {
+       $_ && $_ =~ s/\s+$// for @$dav;
+       }
+    $sth->_set_fbav ($dav);
+    } # fetch
 *fetchrow_arrayref = \&fetch;
 
-sub FETCH ($$) {
+my %unsupported_attrib = map { $_ => 1 } qw( TYPE PRECISION );
+
+sub FETCH ($$)
+{
     my ($sth, $attrib) = @_;
-    return undef if ($attrib eq 'TYPE'); # Workaround for a bug in DBI 0.93
-    return $sth->FETCH('f_stmt')->{'NAME'} if ($attrib eq 'NAME');
-    if ($attrib eq 'NULLABLE') {
-       my($meta) = $sth->FETCH('f_stmt')->{'NAME'}; # Intentional !
-       if (!$meta) {
-           return undef;
-       }
-       my($names) = [];
-       my($col);
-       foreach $col (@$meta) {
-           push(@$names, 1);
+    exists $unsupported_attrib{$attrib}
+       and return;    # Workaround for a bug in DBI 0.93
+    $attrib eq "NAME" and
+       return $sth->FETCH ("f_stmt")->{NAME};
+    if ($attrib eq "NULLABLE") {
+       my ($meta) = $sth->FETCH ("f_stmt")->{NAME};    # Intentional !
+       $meta or return;
+       return [ (1) x @$meta ];
        }
-       return $names;
-    }
-    if ($attrib eq (lc $attrib)) {
+    if ($attrib eq lc $attrib) {
        # Private driver attributes are lower cased
        return $sth->{$attrib};
-    }
+       }
     # else pass up to DBI to handle
-    return $sth->SUPER::FETCH($attrib);
-}
+    return $sth->SUPER::FETCH ($attrib);
+    } # FETCH
 
-sub STORE ($$$) {
+sub STORE ($$$)
+{
     my ($sth, $attrib, $value) = @_;
-    if ($attrib eq (lc $attrib)) {
+    exists $unsupported_attrib{$attrib}
+       and return;    # Workaround for a bug in DBI 0.93
+    if ($attrib eq lc $attrib) {
        # Private driver attributes are lower cased
        $sth->{$attrib} = $value;
        return 1;
-    }
-    return $sth->SUPER::STORE($attrib, $value);
-}
+       }
+    return $sth->SUPER::STORE ($attrib, $value);
+    } # STORE
 
-sub DESTROY ($) {
+sub DESTROY ($)
+{
     my $sth = shift;
-    $sth->finish if $sth->SUPER::FETCH('Active');
-}
-
-sub rows ($) { shift->{'f_stmt'}->{'NUM_OF_ROWS'} };
+    $sth->SUPER::FETCH ("Active") and $sth->finish;
+    } # DESTROY
 
+sub rows ($)
+{
+    shift->{f_stmt}->{NUM_OF_ROWS};
+    } # rows
 
 package DBD::File::Statement;
 
-# We may have a working flock() built-in but that doesn't mean that locking
-# will work on NFS (flock() may hang hard)
+use strict;
+use Carp;
+
+# We may have a working flock () built-in but that doesn't mean that locking
+# will work on NFS (flock () may hang hard)
 my $locking = eval { flock STDOUT, 0; 1 };
 
-# Jochen's old check for flock()
+# Jochen's old check for flock ()
 #
-# my $locking = $^O ne 'MacOS'  &&
-#               ($^O ne 'MSWin32' || !Win32::IsWin95())  &&
-#               $^O ne 'VMS';
-
-...@dbd::File::Statement::ISA = qw(DBI::SQL::Nano::Statement);
-
-my $open_table_re =
-    $haveFileSpec ?
-    sprintf('(?:%s|%s|%s)',
-           quotemeta(File::Spec->curdir()),
-           quotemeta(File::Spec->updir()),
-           quotemeta(File::Spec->rootdir()))
-    : '(?:\.?\.)?\/';
-
-sub get_file_name($$$) {
-    my($self,$data,$table)=...@_;
-    $table =~ s/^\"//; # handle quoted identifiers
+# my $locking = $^O ne "MacOS"  &&
+#              ($^O ne "MSWin32" || !Win32::IsWin95 ())  &&
+#               $^O ne "VMS";
+
+...@dbd::File::Statement::ISA = qw( DBI::SQL::Nano::Statement );
+
+my $open_table_re = sprintf "(?:%s|%s|%s)",
+       quotemeta (File::Spec->curdir  ()),
+       quotemeta (File::Spec->updir   ()),
+       quotemeta (File::Spec->rootdir ());
+
+sub get_file_name ($$$)
+{
+    my ($self, $data, $table) = @_;
+    $table =~ s/^\"//;    # handle quoted identifiers
     $table =~ s/\"$//;
     my $file = $table;
-    if ( $file !~ /^$open_table_re/o
-     and $file !~ m!^[/\\]!   # root
-     and $file !~ m!^[a-z]\:! # drive letter
-    ) {
-       $file = $haveFileSpec ?
-           File::Spec->catfile($data->{Database}->{'f_dir'}, $table)
-               : $data->{Database}->{'f_dir'} . "/$table";
-    }
-    return($table,$file);
-}
+    if (    $file !~ m/^$open_table_re/o
+       and $file !~ m{^[/\\]}      # root
+       and $file !~ m{^[a-z]\:}    # drive letter
+       ) {
+       exists $data->{Database}{f_map}{$table} or
+           DBD::File::file2table ($data->{Database}, $data->{Database}{f_dir}, 
$file, 1);
+       $file = $data->{Database}{f_map}{$table} || undef;
+       }
+    return ($table, $file);
+    } # get_file_name
 
-sub open_table ($$$$$) {
-    my($self, $data, $table, $createMode, $lockMode) = @_;
+sub open_table ($$$$$)
+{
+    my ($self, $data, $table, $createMode, $lockMode) = @_;
     my $file;
-    ($table,$file) = $self->get_file_name($data,$table);
+    ($table, $file) = $self->get_file_name ($data, $table);
     require IO::File;
     my $fh;
-    my $safe_drop = 1 if $self->{ignore_missing_table};
+    my $safe_drop = $self->{ignore_missing_table} ? 1 : 0;
     if ($createMode) {
-       if (-f $file) {
-           die "Cannot create table $table: Already exists";
-       }
-       if (!($fh = IO::File->new($file, "a+"))) {
-           die "Cannot open $file for writing: $!";
-       }
-       if (!$fh->seek(0, 0)) {
-           die " Error while seeking back: $!";
-       }
-    } else {
-       if (!($fh = IO::File->new($file, ($lockMode ? "r+" : "r")))) {
-           die " Cannot open $file: $!" unless $safe_drop;
+       -f $file and
+           croak "Cannot create table $table: Already exists";
+       $fh = IO::File->new ($file, "a+") or
+           croak "Cannot open $file for writing: $!";
+       $fh->seek (0, 0) or
+           croak " Error while seeking back: $!";
+       }
+    else {
+       unless ($fh = IO::File->new ($file, ($lockMode ? "r+" : "r"))) {
+           $safe_drop or croak " Cannot open $file: $!";
+           }
        }
-    }
-    binmode($fh) if $fh;
+    $fh and binmode $fh;
     if ($locking and $fh) {
        if ($lockMode) {
-           if (!flock($fh, 2)) {
-               die " Cannot obtain exclusive lock on $file: $!";
+           flock $fh, 2 or
+               croak " Cannot obtain exclusive lock on $file: $!";
            }
-       } else {
-           if (!flock($fh, 1)) {
-               die "Cannot obtain shared lock on $file: $!";
+       else {
+           flock $fh, 1 or
+               croak "Cannot obtain shared lock on $file: $!";
            }
        }
-    }
     my $columns = {};
-    my $array = [];
-    my $pos = $fh->tell() if $fh;
-    my $tbl = {
-       file => $file,
-       fh => $fh,
-       col_nums => $columns,
-       col_names => $array,
+    my $array   = [];
+    my $pos     = $fh ? $fh->tell () : undef;
+    my $tbl     = {
+       file          => $file,
+       fh            => $fh,
+       col_nums      => $columns,
+       col_names     => $array,
        first_row_pos => $pos,
-    };
-    my $class = ref($self);
+       };
+    my $class = ref $self;
     $class =~ s/::Statement/::Table/;
-    bless($tbl, $class);
+    bless $tbl, $class;
     $tbl;
-}
-
+    } # open_table
 
 package DBD::File::Table;
 
+use strict;
+use Carp;
+
 @DBD::File::Table::ISA = qw(DBI::SQL::Nano::Table);
 
-sub drop ($) {
-    my($self) = @_;
+sub drop ($)
+{
+    my $self = shift;
     # We have to close the file before unlinking it: Some OS'es will
     # refuse the unlink otherwise.
-    $self->{'fh'}->close() if $self->{fh};
-    unlink($self->{'file'});
+    $self->{fh} and $self->{fh}->close ();
+    unlink $self->{file};
     return 1;
-}
+    } # drop
 
-sub seek ($$$$) {
-    my($self, $data, $pos, $whence) = @_;
-    if ($whence == 0  &&  $pos == 0) {
-       $pos = $self->{'first_row_pos'};
-    } elsif ($whence != 2  ||  $pos != 0) {
-       die "Illegal seek position: pos = $pos, whence = $whence";
-    }
-    if (!$self->{'fh'}->seek($pos, $whence)) {
-       die "Error while seeking in " . $self->{'file'} . ": $!";
-    }
-}
+sub seek ($$$$)
+{
+    my ($self, $data, $pos, $whence) = @_;
+    if ($whence == 0 && $pos == 0) {
+       $pos = $self->{first_row_pos};
+       }
+    elsif ($whence != 2 || $pos != 0) {
+       croak "Illegal seek position: pos = $pos, whence = $whence";
+       }
 
-sub truncate ($$) {
-    my($self, $data) = @_;
-    if (!$self->{'fh'}->truncate($self->{'fh'}->tell())) {
-       die "Error while truncating " . $self->{'file'} . ": $!";
-    }
+    $self->{fh}->seek ($pos, $whence) or
+       croak "Error while seeking in " . $self->{file} . ": $!";
+    } # seek
+
+sub truncate ($$)
+{
+    my ($self, $data) = @_;
+    $self->{fh}->truncate ($self->{fh}->tell ()) or
+       croak "Error while truncating " . $self->{file} . ": $!";
     1;
-}
+    } # truncate
 
 1;
 
-
 __END__
 
 =head1 NAME
@@ -735,8 +847,25 @@
 opened. Usually you set it in the dbh, it defaults to the current
 directory ("."). However, it is overwritable in the statement handles.
 
-=back
+=item f_ext
+
+This attribute is used for setting the file extension where (CSV) files are
+opened. There are several possibilities.
+
+    DBI:CSV:f_dir=data;f_ext=.csv
+
+In this case, DBD::File will open only C<table.csv> if both C<table.csv> and
+C<table> exist in the datadir. The table will still be named C<table>. If
+your datadir has files with extensions, and you do not pass this attribute,
+your table is named C<table.csv>, which is probably not what you wanted. The
+extension is always case-insensitive. The table names are not.
+
+    DBI:CSV:f_dir=data;f_ext=.csv/r
 
+In this case the extension is required, and all filenames that do not match
+are ignored.
+
+=back
 
 =head2 Driver private methods
 
@@ -749,16 +878,16 @@
 
 If you want to read the subdirectories of another directory, use
 
-    my($drh) = DBI->install_driver("CSV");
-    my(@list) = $drh->data_sources('f_dir' => '/usr/local/csv_data' );
+    my ($drh) = DBI->install_driver ("CSV");
+    my (@list) = $drh->data_sources (f_dir => "/usr/local/csv_data" );
 
 =item list_tables
 
-This method returns a list of file names inside $dbh->{'f_dir'}.
+This method returns a list of file names inside $dbh->{f_dir}.
 Example:
 
-    my($dbh) = DBI->connect("DBI:CSV:f_dir=/usr/local/csv_data");
-    my(@list) = $dbh->func('list_tables');
+    my ($dbh) = DBI->connect ("DBI:CSV:f_dir=/usr/local/csv_data");
+    my (@list) = $dbh->func ("list_tables");
 
 Note that the list includes all files contained in the directory, even
 those that have non-valid table names, from the view of SQL.
@@ -771,33 +900,36 @@
 
 =item *
 
-The module is using flock() internally. However, this function is not
-available on all platforms. Using flock() is disabled on MacOS and
+The module is using flock () internally. However, this function is not
+available on all platforms. Using flock () is disabled on MacOS and
 Windows 95: There's no locking at all (perhaps not so important on
 MacOS and Windows 95, as there's a single user anyways).
 
 =back
 
-
-=head1 AUTHOR AND COPYRIGHT
+=head1 AUTHOR 
 
 This module is currently maintained by
 
-Jeff Zucker < jzucker @ cpan.org >
+H.Merijn Brand < h.m.brand at xs4all.nl > and
+Jens Rehsack  < rehsack at googlemail.com >
 
 The original author is Jochen Wiedmann.
 
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009 by H.Merijn Brand & Jens Rehsack
 Copyright (C) 2004 by Jeff Zucker
 Copyright (C) 1998 by Jochen Wiedmann
 
 All rights reserved.
 
-You may freely distribute and/or modify this module under the terms of either 
the GNU General Public License (GPL) or the Artistic License, as specified in
-the Perl README file.
+You may freely distribute and/or modify this module under the terms of
+either the GNU General Public License (GPL) or the Artistic License, as
+specified in the Perl README file.
 
 =head1 SEE ALSO
 
-L<DBI>, L<Text::CSV_XS>, L<SQL::Statement>
-
+L<DBI>, L<Text::CSV>, L<Text::CSV_XS>, L<SQL::Statement>
 
 =cut

Reply via email to