Author: timbo
Date: Wed Feb 25 15:43:12 2004
New Revision: 159

Added:
   dbi/trunk/lib/DBD/DBM.pm
   dbi/trunk/lib/DBD/File.pm
   dbi/trunk/lib/DBI/SQL/
   dbi/trunk/lib/DBI/SQL/Nano.pm
   dbi/trunk/t/50dbm.t   (contents, props changed)
Modified:
   dbi/trunk/Changes
   dbi/trunk/MANIFEST
Log:
  Major additions that Jeff Zucker and I have been working on:

  Added DBI::SQL::Nano a 'smaller than micro' SQL parser
    with an SQL::Statement compatible API. If SQL::Statement
    is installed then DBI::SQL::Nano becomes an empty subclass
    of SQL::Statement, unless the DBI_SQL_NANO env var is true.
  Added DBD::File, modified to use DBI::SQL::Nano.
  Added DBD::DBM that provides an SQL interface to DBM files.

They need more docs, of course, but that's in the works.


Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes   (original)
+++ dbi/trunk/Changes   Wed Feb 25 15:43:12 2004
@@ -23,6 +23,15 @@
     trace flags into the corresponding trace flag bits:
     $h->{TraceLevel} = $h->trace_flags("foo,SQL,7");
 
+  Major additions that Jeff Zucker and I have been working on:
+
+  Added DBI::SQL::Nano a 'smaller than micro' SQL parser
+    with an SQL::Statement compatible API. If SQL::Statement
+    is installed then DBI::SQL::Nano becomes an empty subclass
+    of SQL::Statement, unless the DBI_SQL_NANO env var is true.
+  Added DBD::File, modified to use DBI::SQL::Nano.
+  Added DBD::DBM that provides an SQL interface to DBM files.
+
 =head1 CHANGES in DBI 1.41 (svn rev 130),    22nd February 2004
 
   Fixed execute_for_array() so tuple_status parameter is optional

Modified: dbi/trunk/MANIFEST
==============================================================================
--- dbi/trunk/MANIFEST  (original)
+++ dbi/trunk/MANIFEST  Wed Feb 25 15:43:12 2004
@@ -19,6 +19,8 @@
 lib/Bundle/DBI.pm              A bundle for automatic installation via CPAN.
 lib/DBD/ExampleP.pm            A very simple example Driver module
 lib/DBD/NullP.pm               An empty example Driver module
+lib/DBD/File.pm                        A driver base class for simple drivers
+lib/DBD/DBM.pm                 A driver for DBM files (uses DBD::File)
 lib/DBD/Proxy.pm               Proxy driver
 lib/DBD/Sponge.pm              A driver for fake cursors (precached data)
 lib/DBI/Const/GetInfo/ANSI.pm  GetInfo data based on ANSI standard
@@ -52,6 +54,7 @@
 t/40profile.t
 t/41prof_dump.t
 t/42prof_data.t
+t/50dbm.t
 t/60preparse.t
 t/80proxy.t
 test.pl                                A very simple test harness using ExampleP.pm

Added: dbi/trunk/lib/DBD/DBM.pm
==============================================================================
--- (empty file)
+++ dbi/trunk/lib/DBD/DBM.pm    Wed Feb 25 15:43:12 2004
@@ -0,0 +1,187 @@
+#######################################################################
+#
+#  DBD::DBM - a simple DBI driver for DBM files
+#
+#  Copyright (C) 2004 by Jeff Zucker < jzucker AT cpan.org >
+#
+#  All rights reserved.
+#
+#  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.005_03;
+use strict;
+
+package DBD::DBM;
+
+use DBD::File ();
+use vars qw($VERSION $ATTRIBUTION);
+use base qw( DBD::File );
+
+$VERSION = "0.01";
+$ATTRIBUTION = "DBD::DBM by Jeff Zucker";
+
+package DBD::DBM::dr;
+$DBD::DBM::dr::imp_data_size = 0;
[EMAIL PROTECTED]::DBM::dr::ISA = qw(DBD::File::dr);
+
+package DBD::DBM::db;
+$DBD::DBM::db::imp_data_size = 0;
[EMAIL PROTECTED]::DBM::db::ISA = qw(DBD::File::db);
+
+package DBD::DBM::st;
+$DBD::DBM::st::imp_data_size = 0;
[EMAIL PROTECTED]::DBM::st::ISA = qw(DBD::File::st);
+
+package DBD::DBM::Statement;
+use base qw( DBD::File::Statement );
+
+use Fcntl;
+#   AnyDBM_File defaults ISA to (NDBM_File DB_File GDBM_File SDBM_File ODBM_File)
+#   ideally we'd prefer DB_File but we don't want to mess with @ISA ourselves
+#   because the application using us may have already changed it
+use AnyDBM_File;
+
+sub open_table ($$$$$) {
+    # NEED TO ADD FILE LOCKING
+    my($self, $data, $table, $createMode, $lockMode) = @_;
+    my $dbh = $data->{Database};
+    my $file = $table || $self->{tables}->[0]->{name};
+    my $open_mode = O_RDONLY;
+       $open_mode = O_RDWR         if $lockMode;
+       $open_mode = O_RDWR|O_CREAT|O_TRUNC if $createMode;
+    my %h;
+    die "Cannot CREATE '$file', already exists!"
+        if $createMode and (-e "$file.pag" or -e "$file.dir");
+    my $dbm_type = $dbh->{dbm_type} || 'AnyDBM_File';
+    if ($dbm_type ne 'AnyDBM_File') {
+        require "$dbm_type.pm";
+        $dbh->STORE(dbm_type => $dbm_type);
+    }
+    eval { tie(%h, $dbm_type, $file, $open_mode, 0666) };
+    die "Cannot tie file '$file': $@" if $@;
+    my $tbl = {
+       file      => $file,
+        hash      => \%h,
+       col_nums  => {dkey=>0,dval=>1},
+       col_names => ['dkey','dval'],
+    };
+    my $class = ref($self);
+    $class =~ s/::Statement/::Table/;
+    bless($tbl, $class);
+    $tbl;
+}
+
+# DELETE is only needed for backward compat with old SQL::Statement
+# can remove when next SQL::Statement is released
+sub DELETE ($$$) {
+    my($self, $data, $params) = @_;
+    my $dbh   = $data->{Database};
+    my($table,$tname,@where_args);
+    if ($dbh->{Driver}->{statement_version}) {
+       my($eval,$all_cols) = $self->open_tables($data, 0, 1);
+       return undef unless $eval;
+       $eval->params($params);
+       $self->verify_columns($eval, $all_cols);
+       $table = $eval->table($self->tables(0)->name());
+       @where_args = ($eval,$self->tables(0)->name());
+    }
+    else {
+        $table = $self->open_tables($data, 0, 1);
+        @where_args = ($table);
+    }
+    my($affected) = 0;
+    while (my $array = $table->fetch_row($data)) {
+        if ($self->eval_where(@where_args,$array)) {
+            ++$affected;
+            $table->delete_one_row($data,$array);
+        }
+    }
+    return ($affected, 0);
+}
+
+package DBD::DBM::Table;
+use base qw( DBD::File::Table );
+
+sub drop ($$) {
+    my($self,$data) = @_;
+    untie %{$self->{hash}} if $self->{hash};
+    unlink $self->{file}.'.dir' if -f $self->{file}.'.dir';
+    unlink $self->{file}.'.pag' if -f $self->{file}.'.pag';
+    unlink $self->{file}.'.db' if -f $self->{file}.'.db'; # Berzerkeley
+    # put code to delete lockfile here
+    return 1;
+}
+sub fetch_row ($$$) {
+    my($self, $data, $row) = @_;
+    my @ary = each %{$self->{hash}};
+    return undef unless defined $ary[0];
+    return @ary if wantarray;
+    return [EMAIL PROTECTED];
+}
+sub push_row ($$$) {
+    my($self, $data, $row_aryref) = @_;
+    $self->{hash}->{$row_aryref->[0]}=$row_aryref->[1];
+    1;
+}
+# optimized for hash-lookup, fetches without looping
+sub fetch_one_row {
+    my($self,$key_only,$value) = @_;
+    return $self->{col_names}->[0] if $key_only;
+    return $self->{hash}->{$value};
+}
+# "delete_one_row" seems to work within the each loop through the hash
+# "update_one_row" does not
+sub delete_one_row {
+    my($self,$data,$aryref) = @_;
+    delete $self->{hash}->{$aryref->[0]};
+}
+sub DESTROY {
+    # code to release lock goes here
+}
+sub truncate {}
+sub seek {}
+sub push_names { 1; }
+1;
+__END__
+
+=head1 NAME
+
+DBD::DBM - simple DBI driver for DBM files
+
+=head1 SYNOPSIS
+
+    use DBI;
+    $dbh = DBI->connect("DBI:DBM:", undef, undef);
+    $dbh = DBI->connect("DBI:DBM:", undef, undef, { dbm_type => 'ODBM_File' });
+    $dbh = DBI->connect("DBI:DBM(dbm_type=ODBM_File):", undef, undef);
+
+=head1 DESCRIPTION
+
+See L<DBI(3)> for details on DBI, L<SQL::Statement(3)> for details on
+SQL::Statement and L<DBD::CSV(3)> or L<DBD::IniFile(3)> for example
+drivers.
+
+=head1 AUTHOR AND COPYRIGHT
+
+This module was written and maintained by
+
+      Jeff Zucker
+      <[EMAIL PROTECTED]>
+
+Copyright (C) 2004 by Jeff Zucker
+
+All rights reserved.
+
+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.
+
+=head1 SEE ALSO
+
+L<DBI(3)>
+
+=cut
+

Added: dbi/trunk/lib/DBD/File.pm
==============================================================================
--- (empty file)
+++ dbi/trunk/lib/DBD/File.pm   Wed Feb 25 15:43:12 2004
@@ -0,0 +1,755 @@
+# -*- perl -*-

+#

+#   DBD::File - A base class for implementing DBI drivers that

+#               act on plain files

+#

+#  This module is currently maintained by

+#

+#      Jeff Zucker < jeff AT cpan.org >

+#

+#  The original author is Jochen Wiedmann.

+#

+#  Copyright (C) 2004 by Jeff Zucker

+#  Copyright (C) 1998 by Jochen Wiedmann

+#

+#  All rights reserved.

+#

+#  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;

+use strict;

+

+

+require DynaLoader;

+require DBI::SQL::Nano;

+require DBI;

+my $haveFileSpec = eval { require File::Spec };

+

+package DBD::File;

+

+use vars qw(@ISA $VERSION $drh $err $errstr $sqlstate);

+

[EMAIL PROTECTED] = qw(DynaLoader);

+

+$VERSION = '0.23';      #

+

+$err = 0;              # holds error code   for DBI::err

+$errstr = "";          # holds error string for DBI::errstr

+$sqlstate = "";         # holds error state  for DBI::state

+$drh = undef;          # holds driver handle once initialised

+

+sub driver ($;$) {

+    my($class, $attr) = @_;

+    my $drh = eval '$' . $class . "::drh";

+    if (!$drh) {

+       if (!$attr) { $attr = {} };

+       if (!exists($attr->{Attribution})) {

+           $attr->{Attribution} = eval '$' . $class . '::ATTRIBUTION';

+           $attr->{Attribution} = "$class by Jeff Zucker"

+                                if $class eq 'DBD::File';

+           $attr->{Attribution} ||=

+                "oops the author of $class forgot to define this";

+       }

+       if (!exists($attr->{Version})) {

+           $attr->{Version} = eval '$' . $class . '::VERSION';

+        }

+        if (!exists($attr->{Err})) {

+           $attr->{Err} = eval '\$' . $class . '::err';

+        }

+        if (!exists($attr->{Errstr})) {

+           $attr->{Errstr} = eval '\$' . $class . '::errstr';

+        }

+        if (!exists($attr->{State})) {

+           $attr->{State} = eval '\$' . $class . '::state';

+        }

+        if (!exists($attr->{Name})) {

+           my $c = $class;

+           $c =~ s/^DBD\:\://;

+           $attr->{Name} = $c;

+        }

+        $attr->{file_version} = $DBD::File::VERSION;

+        for (qw( nano_version statement_version)) {

+            $attr->{$_} = $DBI::SQL::Nano::versions->{$_}||'';

+        }

+        $drh = DBI::_new_drh($class . "::dr", $attr);

+    }

+    $drh;

+}

+

+

+package DBD::File::dr; # ====== DRIVER ======

+

+$DBD::File::dr::imp_data_size = 0;

+

+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,

+    });

+

+    if ($this) {

+       my($var, $val);

+       $this->{f_dir} = $haveFileSpec ? File::Spec->curdir() : '.';

+       while (length($dbname)) {

+           if ($dbname =~ s/^((?:[^\\;]|\\.)*?);//s) {

+               $var = $1;

+           } else {

+               $var = $dbname;

+               $dbname = '';

+           }

+           if ($var =~ /^(.+?)=(.*)/s) {

+               $var = $1;

+               ($val = $2) =~ s/\\(.)/$1/g;

+               $this->{$var} = $val;

+           }

+       }

+    }

+    $this;

+}

+

+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)) {

+        DBI::set_err($drh, 1, "Cannot open directory $dir");

+       return undef;

+    }

+    my($file, @dsns, %names, $driver);

+    if ($drh->{'ImplementorClass'} =~ /^dbd\:\:([^\:]+)\:\:/i) {

+       $driver = $1;

+    } else {

+       $driver = 'File';

+    }

+    while (defined($file = readdir($dirh))) {

+       my $d = $haveFileSpec ?

+           File::Spec->catdir($dir, $file) : "$dir/$file";

+       if ($file ne ($haveFileSpec ? File::Spec->curdir() : '.')

+           and  $file ne ($haveFileSpec ? File::Spec->updir() : '..')

+           and  -d $d) {

+           push(@dsns, "DBI:$driver:f_dir=$d");

+       }

+    }

+    @dsns;

+}

+

+sub disconnect_all {

+}

+

+sub DESTROY {

+    undef;

+}

+

+

+package DBD::File::db; # ====== DATABASE ======

+

+$DBD::File::db::imp_data_size = 0;

+

+

+sub prepare ($$;@) {

+    my($dbh, $statement, @attribs)= @_;

+

+    # create a 'blank' dbh

+    my $sth = DBI::_new_sth($dbh, {'Statement' => $statement});

+

+    if ($sth) {

+       $@ = '';

+       my $class = $sth->FETCH('ImplementorClass');

+       $class =~ s/::st$/::Statement/;

+       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

+

+        my $sversion = $SQL::Statement::VERSION;

+       if ($sversion and $SQL::Statement::VERSION > 1) {

+            my $parser = $dbh->{csv_sql_parser_object};

+            eval { $parser ||= $dbh->func('csv_cache_sql_parser_object') };

+            if ($@) {

+                undef $@;

+               $stmt = eval { $class->new($statement) };

+           }

+            else {

+               $stmt = eval { $class->new($statement,$parser) };

+           }

+        }

+        else {

+           $stmt = eval { $class->new($statement) };

+       }

+       if ($@) {

+           DBI::set_err($dbh, 1, $@);

+           undef $sth;

+       } else {

+           $sth->STORE('f_stmt', $stmt);

+           $sth->STORE('f_params', []);

+           $sth->STORE('NUM_OF_PARAMS', scalar($stmt->params()));

+       }

+    }

+

+    $sth;

+}

+

+sub disconnect ($) {

+    1;

+}

+

+sub FETCH ($$) {

+    my ($dbh, $attrib) = @_;

+    if ($attrib eq 'AutoCommit') {

+       return 1;

+    } elsif ($attrib eq (lc $attrib)) {

+       # Driver private attributes are lower cased

+       return $dbh->{$attrib};

+    }

+    # else pass up to DBI to handle

+    return $dbh->DBD::_::db::FETCH($attrib);

+}

+

+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)) {

+       # Driver private attributes are lower cased

+       $dbh->{$attrib} = $value;

+       return 1;

+    }

+    return $dbh->DBD::_::db::STORE($attrib, $value);

+}

+

+sub DESTROY ($) {

+    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

+       ]

+     ]

+}

+

+

+{

+    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)) {

+           DBI::set_err($dbh, 1, "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)) {

+           DBI::set_err($dbh, 1, "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) {

+               DBI::set_err($dbh, 1, $DBI::errstr);

+               return undef;

+           }

+       }

+

+       # Temporary kludge: DBD::Sponge dies if @tables is empty. :-(

+       return undef if [EMAIL PROTECTED];

+

+       my $sth = $dbh2->prepare("TABLE_INFO", { 'rows' => [EMAIL PROTECTED],

+                                                'NAMES' => $names });

+       if (!$sth) {

+           DBI::set_err($dbh, 1, $dbh2->errstr());

+       }

+       $sth;

+    }

+}

+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]);

+    }

+    @tables;

+}

+

+sub quote ($$;$) {

+    my($self, $str, $type) = @_;

+    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::TINYINT())) {

+       return $str;

+    }

+    if (!defined($str)) { return "NULL" }

+    $str =~ s/\\/\\\\/sg;

+    $str =~ s/\0/\\0/sg;

+    $str =~ s/\'/\\\'/sg;

+    $str =~ s/\n/\\n/sg;

+    $str =~ s/\r/\\r/sg;

+    "'$str'";

+}

+

+sub commit ($) {

+    my($dbh) = shift;

+    if ($dbh->FETCH('Warn')) {

+       warn("Commit ineffective while AutoCommit is on", -1);

+    }

+    1;

+}

+

+sub rollback ($) {

+    my($dbh) = shift;

+    if ($dbh->FETCH('Warn')) {

+       warn("Rollback ineffective while AutoCommit is on", -1);

+    }

+    0;

+}

+

+

+package DBD::File::st; # ====== STATEMENT ======

+

+$DBD::File::st::imp_data_size = 0;

+

+sub bind_param ($$$;$) {

+    my($sth, $pNum, $val, $attr) = @_;

+    $sth->{f_params}->[$pNum-1] = $val;

+    1;

+}

+

+sub execute {

+    my $sth = shift;

+    my $params;

+    if (@_) {

+       $sth->{'f_params'} = ($params = [EMAIL PROTECTED]);

+    } else {

+       $params = $sth->{'f_params'};

+    }

+    my $stmt = $sth->{'f_stmt'};

+    my $result = eval { $stmt->execute($sth, $params); };

+    return $sth->set_err(1,$@) if $@;

+    if ($stmt->{'NUM_OF_FIELDS'}  &&  !$sth->FETCH('NUM_OF_FIELDS')) {

+       $sth->STORE('NUM_OF_FIELDS', $stmt->{'NUM_OF_FIELDS'});

+    }

+    return $result;

+}

+

+sub fetch ($) {

+    my $sth = shift;

+    my $data = $sth->{f_stmt}->{data};

+    if (!$data  ||  ref($data) ne 'ARRAY') {

+       DBI::set_err($sth, 1,

+                    "Attempt to fetch row from a Non-SELECT statement");

+       return undef;

+    }

+    my $dav = shift @$data;

+    if (!$dav) {

+       return undef;

+    }

+    if ($sth->FETCH('ChopBlanks')) {

+       map { $_ =~ s/\s+$//; } @$dav;

+    }

+    $sth->_set_fbav($dav);

+}

+*fetchrow_arrayref = \&fetch;

+

+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);

+       }

+       return $names;

+    }

+    if ($attrib eq (lc $attrib)) {

+       # Private driver attributes are lower cased

+       return $sth->{$attrib};

+    }

+    # else pass up to DBI to handle

+    return $sth->DBD::_::st::FETCH($attrib);

+}

+

+sub STORE ($$$) {

+    my ($sth, $attrib, $value) = @_;

+    if ($attrib eq (lc $attrib)) {

+       # Private driver attributes are lower cased

+       $sth->{$attrib} = $value;

+       return 1;

+    }

+    return $sth->DBD::_::st::STORE($attrib, $value);

+}

+

+sub DESTROY ($) {

+    undef;

+}

+

+sub rows ($) { shift->{'f_stmt'}->{'NUM_OF_ROWS'} };

+

+sub finish ($) { 1; }

+

+

+package DBD::File::Statement;

+

+my $locking = $^O ne 'MacOS'  &&

+              ($^O ne 'MSWin32' || !Win32::IsWin95())  &&

+              $^O ne 'VMS';

+

[EMAIL PROTECTED]::File::Statement::ISA = qw(DBI::SQL::Nano::Statement);

+

+sub set_err {

+    my($handle,$errmsg,$state);

+    $handle->set_err($errmsg,$state);

+    die $errmsg;

+}

+

+my $open_table_re =

+    $haveFileSpec ?

+    sprintf('(?:%s|%s�%s)',

+           quotemeta(File::Spec->curdir()),

+           quotemeta(File::Spec->updir()),

+           quotemeta(File::Spec->rootdir()))

+    : '(?:\.?\.)?\/';

+sub open_table ($$$$$) {

+    my($self, $data, $table, $createMode, $lockMode) = @_;

+    my $file = $table;

+    if ($file !~ /^$open_table_re/o) {

+       $file = $haveFileSpec ?

+           File::Spec->catfile($data->{Database}->{'f_dir'}, $table)

+               : $data->{Database}->{'f_dir'} . "/$table";

+    }

+    my $fh;

+    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: $!";

+       }

+    }

+    binmode($fh);

+    if ($locking) {

+       if ($lockMode) {

+           if (!flock($fh, 2)) {

+               die " Cannot obtain exclusive lock on $file: $!";

+           }

+       } else {

+           if (!flock($fh, 1)) {

+               die "Cannot obtain shared lock on $file: $!";

+           }

+       }

+    }

+    my $columns = {};

+    my $array = [];

+    my $tbl = {

+       file => $file,

+       fh => $fh,

+       col_nums => $columns,

+       col_names => $array,

+       first_row_pos => $fh->tell()

+    };

+    my $class = ref($self);

+    $class =~ s/::Statement/::Table/;

+    bless($tbl, $class);

+    $tbl;

+}

+

+

+package DBD::File::Table;

+

[EMAIL PROTECTED]::File::Table::ISA = qw(DBI::SQL::Nano::Table);

+

+sub drop ($) {

+    my($self) = @_;

+    # We have to close the file before unlinking it: Some OS'es will

+    # refuse the unlink otherwise.

+    $self->{'fh'}->close();

+    unlink($self->{'file'});

+    return 1;

+}

+

+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 truncate ($$) {

+    my($self, $data) = @_;

+    if (!$self->{'fh'}->truncate($self->{'fh'}->tell())) {

+       die "Error while truncating " . $self->{'file'} . ": $!";

+    }

+    1;

+}

+

+1;

+

+

+__END__

+

+=head1 NAME

+

+DBD::File - Base class for writing DBI drivers for plain files

+

+=head1 SYNOPSIS

+

+    use DBI;

+    $dbh = DBI->connect("DBI:File:f_dir=/home/joe/csvdb")

+        or die "Cannot connect: " . $DBI::errstr;

+    $sth = $dbh->prepare("CREATE TABLE a (id INTEGER, name CHAR(10))")

+        or die "Cannot prepare: " . $dbh->errstr();

+    $sth->execute() or die "Cannot execute: " . $sth->errstr();

+    $sth->finish();

+    $dbh->disconnect();

+

+=head1 DESCRIPTION

+

+The DBD::File module is not a true DBI driver, but an abstract

+base class for deriving concrete DBI drivers from it. The implication is,

+that these drivers work with plain files, for example CSV files or

+INI files. The module is based on the SQL::Statement module, a simple

+SQL engine.

+

+See L<DBI(3)> for details on DBI, L<SQL::Statement(3)> for details on

+SQL::Statement and L<DBD::CSV(3)> or L<DBD::IniFile(3)> for example

+drivers.

+

+

+=head2 Metadata

+

+The following attributes are handled by DBI itself and not by DBD::File,

+thus they all work like expected:

+

+    Active

+    ActiveKids

+    CachedKids

+    CompatMode             (Not used)

+    InactiveDestroy

+    Kids

+    PrintError

+    RaiseError

+    Warn                   (Not used)

+

+The following DBI attributes are handled by DBD::File:

+

+=over 4

+

+=item AutoCommit

+

+Always on

+

+=item ChopBlanks

+

+Works

+

+=item NUM_OF_FIELDS

+

+Valid after C<$sth->execute>

+

+=item NUM_OF_PARAMS

+

+Valid after C<$sth->prepare>

+

+=item NAME

+

+Valid after C<$sth->execute>; undef for Non-Select statements.

+

+=item NULLABLE

+

+Not really working, always returns an array ref of one's, as DBD::CSV

+doesn't verify input data. Valid after C<$sth->execute>; undef for

+Non-Select statements.

+

+=back

+

+These attributes and methods are not supported:

+

+    bind_param_inout

+    CursorName

+    LongReadLen

+    LongTruncOk

+

+Additional to the DBI attributes, you can use the following dbh

+attribute:

+

+=over 4

+

+=item f_dir

+

+This attribute is used for setting the directory where CSV files are

+opened. Usually you set it in the dbh, it defaults to the current

+directory ("."). However, it is overwritable in the statement handles.

+

+=back

+

+

+=head2 Driver private methods

+

+=over 4

+

+=item data_sources

+

+The C<data_sources> method returns a list of subdirectories of the current

+directory in the form "DBI:CSV:f_dir=$dirname".

+

+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' );

+

+=item list_tables

+

+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');

+

+Note that the list includes all files contained in the directory, even

+those that have non-valid table names, from the view of SQL. See

+L<Creating and dropping tables> above.

+

+=back

+

+

+=head1 TODO

+

+=over 4

+

+=item Joins

+

+The current version of the module works with single table SELECT's

+only, although the basic design of the SQL::Statement module allows

+joins and the likes.

+

+=item Table name mapping

+

+Currently it is not possible to use files with names like C<names.csv>.

+Instead you have to use soft links or rename files. As an alternative

+one might use, for example a dbh attribute 'table_map'. It might be a

+hash ref, the keys being the table names and the values being the file

+names.

+

+=back

+

+

+=head1 KNOWN BUGS

+

+=over 8

+

+=item *

+

+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

+

+This module is currently maintained by

+

+      Jeff Zucker

+      <[EMAIL PROTECTED]>

+

+The original author is Jochen Wiedmann.

+

+Copyright (C) 2004 by Jeff Zucker

+Copyright (C) 1998 by Jochen Wiedmann

+

+All rights reserved.

+

+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.

+

+=head1 SEE ALSO

+

+L<DBI(3)>, L<Text::CSV_XS(3)>, L<SQL::Statement(3)>

+

+

+=cut


Added: dbi/trunk/lib/DBI/SQL/Nano.pm
==============================================================================
--- (empty file)
+++ dbi/trunk/lib/DBI/SQL/Nano.pm       Wed Feb 25 15:43:12 2004
@@ -0,0 +1,491 @@
+package DBI::SQL::Nano;

+

+use strict;

+use warnings;

+use vars qw( $VERSION $versions );

+

+BEGIN {

+    $VERSION = '0.01';

+    $versions->{nano_version} = $VERSION;

+    eval { require "SQL/Statement.pm" } unless $ENV{DBI_SQL_NANO};

+    if ($@ or $ENV{DBI_SQL_NANO}) {

+        @DBI::SQL::Nano::Statement::ISA = qw(DBI::SQL::Nano::Statement_);

+        @DBI::SQL::Nano::Table::ISA     = qw(DBI::SQL::Nano::Table_);

+    }

+    else {

+        @DBI::SQL::Nano::Statement::ISA = qw(SQL::Statement);

+        @DBI::SQL::Nano::Table::ISA     = qw(SQL::Eval::Table);

+        $versions->{statement_version}  = $SQL::Statement::VERSION;

+    }

+}

+

+package DBI::SQL::Nano::Statement_;

+use vars qw($numexp);

+# XXX change to DBI::looks_like_number?

+$numexp= qr/^([+-]?|\s+)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/;

+sub new {

+    my($class,$sql) = @_;

+    my $self  = {};

+    bless $self, $class;

+    return $self->prepare($sql);

+}

+

+#####################################################################

+# PREPARE

+#####################################################################

+sub prepare {

+    my($self,$sql) = @_;

+    for ($sql) {

+        /^\s*CREATE\s+TABLE\s+(.*?)\s*\((.+)\)\s*$/is

+            &&do{

+                $self->{command}      = 'CREATE';

+                $self->{table_name}   = $1;

+                $self->{column_names} = parse_comma_list($2) if $2;

+            };

+        /^\s*DROP\s+TABLE\s+(.*?)\s*$/is

+            &&do{

+                $self->{command}      = 'DROP';

+                $self->{table_name}   = $1;

+            };

+        /^\s*SELECT\s+(.*?)\s+FROM\s+(\w+)(\s+WHERE\s+(.*))?/is

+            &&do{

+                $self->{command}      = 'SELECT';

+                $self->{column_names} = parse_comma_list($1) if $1;

+                $self->{table_name}   = $2;

+                $self->{where_clause} = $self->parse_where_clause($4) if $4;

+            };

+        /^\s*INSERT\s+INTO\s+(\w+)\s*(\((.*?)\))?\s*VALUES\s*\((.+)\)/is

+            &&do{

+                $self->{command}      = 'INSERT';

+                $self->{table_name}   = $1;

+                $self->{column_names} = parse_comma_list($2) if $2;

+                $self->{values}       = $self->parse_values_list($4) if $4;

+            };

+        /DELETE\s+FROM\s+(\w+)(\s+WHERE\s+(.*))?/is

+            &&do{

+                $self->{command}      = 'DELETE';

+                $self->{table_name}   = $1;

+                $self->{where_clause} = $self->parse_where_clause($3) if $3;

+            };

+        /UPDATE\s+(\w+)\s+SET\s+(.+)WHERE\s+(.+)/is

+            &&do{

+                $self->{command}      = 'UPDATE';

+                $self->{table_name}   = $1;

+                $self->parse_set_clause($2) if $2;

+                $self->{where_clause} = $self->parse_where_clause($3) if $3;

+            };

+    }

+    return undef unless $self->{command} and $self->{table_name};

+    $self->{NAME} = $self->{column_names};

+    return $self;

+}

+sub parse_comma_list  {[map{clean_parse_str($_)} split(',',shift)]}

+sub clean_parse_str {

+    $_ = shift; s/\(//;s/\)//;s/^\s+//; s/\s+$//; s/^(\S+)\s*.*/$1/; $_;

+}

+sub parse_values_list {

+    my($self,$str) = @_;

+    [map{$self->parse_value(clean_parse_str($_))}split(',',$str)]

+}

+sub parse_set_clause {

+    my $self = shift;

+    my @cols = split /,/, shift;

+    my $set_clause;

+    for my $col(@cols) {

+        my($col_name,$value)= $col =~ /^\s*(.+?)\s*=\s*(.+?)\s*$/s;

+        push @{$self->{column_names}}, $col_name;

+        push @{$self->{values}}, $self->parse_value($value);

+    }

+}

+sub parse_value {

+    my($self,$str) = @_;

+    if ($str =~ /^\?$/) {

+        push @{$self->{params}},'?';

+        return { value=>'?'  ,type=> 'placeholder' };

+    }

+    return { value=>undef,type=> 'NULL'        } if $str =~ /^NULL$/i;

+    return { value=>$1   ,type=> 'string'      } if $str =~ /^'(.+)'$/s;

+    return { value=>$str ,type=> 'number'      } if $str =~ $numexp;

+    return { value=>$str ,type=> 'column'      };

+}

+sub parse_where_clause {

+    my($self,$str) = @_;

+    $str =~ s/\s+$//;

+    my($neg) = $str =~ s/^\s*(NOT)\s+//is;

+    my $opexp = '=|<>|<=|>=|<|>|LIKE|CLIKE|IS';

+    my($val1,$op,$val2) = $str =~ /(\S+?)\s*($opexp)\s*(\S+)/is;

+    #die "<$val1> <$op> <$val2>";

+    return {

+        arg1 => $self->parse_value($val1),

+        arg2 => $self->parse_value($val2),

+        op   => $op,

+        neg  => $neg,

+    }

+}

+#####################################################################

+# EXECUTE

+#####################################################################

+sub execute {

+    my($self, $data, $params) = @_;

+    my $num_placeholders = $self->params;

+    my $num_params       = scalar @$params || 0;

+    die "Number of params '$num_params' does not match "

+      . "number of placeholders '$num_placeholders'!\n"

+      unless $num_placeholders == $num_params;

+    if (scalar @$params) {

+        for my $i(0..$#{$self->{values}}) {

+            if ($self->{values}->[$i]->{type} eq 'placeholder') {

+                $self->{values}->[$i]->{value} = shift @$params;

+            }

+        }

+        if ($self->{where_clause}) {

+            if ($self->{where_clause}->{arg1}->{type} eq 'placeholder') {

+                $self->{where_clause}->{arg1}->{value} = shift @$params;

+            }

+            if ($self->{where_clause}->{arg2}->{type} eq 'placeholder') {

+                $self->{where_clause}->{arg2}->{value} = shift @$params;

+            }

+        }

+    }

+    my $command = $self->{command};

+    ( $self->{'NUM_OF_ROWS'},

+      $self->{'NUM_OF_FIELDS'},

+      $self->{'data'},

+    ) = $self->$command($data, $params);

+    $self->{'NUM_OF_ROWS'} || '0E0';

+}

+sub DROP ($$$) {

+    my($self, $data, $params) = @_;

+    my $tbl_obj = { file => $self->{table_name} };

+    my $class = ref($self);

+    $class =~ s/::Statement/::Table/;

+    bless($tbl_obj, $class);

+    $self->{tbl_obj} = $tbl_obj;

+    eval { $tbl_obj->drop($data); };

+    (-1, 0);

+}

+sub CREATE ($$$) {

+    my($self, $data, $params) = @_;

+     my $table = $self->open_tables($data, 1, 1);

+    $table->push_names($data, $self->{column_names});

+    (0, 0);

+}

+sub INSERT ($$$) {

+    my($self, $data, $params) = @_;

+     my $table = $self->open_tables($data, 0, 1);

+    $table->seek($data, 0, 2);

+    my($array) = [];

+    my($val, $col, $i);

+    $self->{column_names}=$table->{col_names} unless $self->{column_names};

+    my $cNum = scalar(@{$self->{column_names}}) if $self->{column_names};

+    my $param_num = 0;

+    if ($cNum) {

+        for ($i = 0;  $i < $cNum;  $i++) {

+            $col = $self->{column_names}->[$i];

+            $array->[$self->column_nums($table,$col)] = $self->row_values($i);

+        }

+    } else {

+        die "Bad col names in INSERT";

+    }

+    $table->push_row($data, $array);

+    (1, 0);

+}

+sub DELETE ($$$) {

+    my($self, $data, $params) = @_;

+    my $table = $self->open_tables($data, 0, 1);

+    my($affected) = 0;

+    my(@rows, $array);

+    if ( $table->can('delete_one_row') ) {

+        while (my $array = $table->fetch_row($data)) {

+            if ($self->eval_where($table,$array)) {

+                ++$affected;

+                $table->delete_one_row($data,$array);

+             }

+        }

+        return ($affected, 0);

+    }

+    while ($array = $table->fetch_row($data)) {

+        if ($self->eval_where($table,$array)) {

+            ++$affected;

+        } else {

+            push(@rows, $array);

+        }

+    }

+    $table->seek($data, 0, 0);

+    foreach $array (@rows) {

+        $table->push_row($data, $array);

+    }

+    $table->truncate($data);

+    return ($affected, 0);

+}

+sub SELECT ($$$) {

+    my($self, $data, $params) = @_;

+     my $table = $self->open_tables($data, 0, 0);

+    my $tname = $self->{table_name};

+    my($affected) = 0;

+    my(@rows, $array, $val, $col, $i);

+    while ($array = $table->fetch_row($data)) {

+        if ($self->eval_where($table,$array)) {

+           if ( $self->{fetched_from_key} ) {

+                push(@rows, [$self->{fetched_value}] );

+                return (scalar(@rows),scalar @{$self->{column_names}},[EMAIL 
PROTECTED]);

+           }

+            my $col_nums = $self->column_nums($table);

+            my %cols   = reverse %{ $col_nums };

+            my $rowhash;

+            for (sort keys %cols) {

+                $rowhash->{$cols{$_}} = $array->[$_];

+            }

+            my @newarray;

+            for ($i = 0;  $i < @{$self->{column_names}};  $i++) {

+               $col = $self->{column_names}->[$i];

+               push @newarray,$rowhash->{$col};

+            }

+            push(@rows, [EMAIL PROTECTED]);

+        }

+    }

+    (scalar(@rows), scalar @{$self->{column_names}}, [EMAIL PROTECTED]);

+}

+sub UPDATE ($$$) {

+    my($self, $data, $params) = @_;

+    my $table = $self->open_tables($data, 0, 1);

+    my($eval,$all_cols) = $self->open_tables($data, 0, 1);

+    return undef unless $eval;

+    my($affected) = 0;

+    my(@rows, $array, $val, $col, $i);

+    while ($array = $table->fetch_row($data)) {

+        if ($self->eval_where($table,$array)) {

+            my $col_nums = $self->column_nums($table);

+            my %cols   = reverse %{ $col_nums };

+            my $rowhash;

+            for (sort keys %cols) {

+                $rowhash->{$cols{$_}} = $array->[$_];

+            }

+            for ($i = 0;  $i < @{$self->{column_names}};  $i++) {

+               $col = $self->{column_names}->[$i];

+               $array->[$self->column_nums($table,$col)]=$self->row_values($i);

+            }

+            $affected++;

+            push(@rows, $array);

+       }

+        else {

+            push(@rows, $array);

+        }

+    }

+    $table->seek($data, 0, 0);

+    foreach my $array (@rows) {

+        $table->push_row($data, $array);

+    }

+    $table->truncate($data);

+    ($affected, 0);

+}

+sub column_nums {

+    my($self,$table,$stmt_col_name)[EMAIL PROTECTED];

+    my %dbd_nums = %{ $table->{col_nums} };

+    my @dbd_cols = @{ $table->{col_names} };

+    my %stmt_nums;

+    if ($stmt_col_name) {

+        while(my($k,$v)=each %dbd_nums) {

+            return $v if uc $k eq uc $stmt_col_name;

+        }

+        return undef;

+    }

+    for my $i(0 .. $#dbd_cols) {

+        for my $stmt_col(@{$self->{column_names}}) {

+            $stmt_nums{$stmt_col} = $i if uc $dbd_cols[$i] eq uc $stmt_col;

+        }

+    }

+    return \%stmt_nums;

+}

+sub eval_where {

+    my $self   = shift;

+    my $table  = shift;

+    my $rowary = shift;

+    my $where = $self->{"where_clause"} || return 1;

+    my $col_nums = $table->{"col_nums"} ;

+    my %cols   = reverse %{ $col_nums };

+    my $rowhash;

+    for (sort keys %cols) {

+        $rowhash->{uc $cols{$_}} = $rowary->[$_];

+    }

+    return $self->process_predicate ($where,$table,$rowhash);

+}

+sub process_predicate {

+    my($self,$pred,$table,$rowhash) = @_;

+    my $val1 = $pred->{arg1};

+    if ($val1->{type} eq 'column') {

+        $val1 = $rowhash->{ uc $val1->{value}};

+    }

+    else {

+        $val1 = $val1->{value};

+    }

+    my $val2 = $pred->{arg2};

+    if ($val2->{type}eq 'column') {

+        $val2 = $rowhash->{uc $val2->{value}};

+    }

+    else {

+        $val2 = $val2->{value};

+    }

+    my $op   = $pred->{op};

+    my $neg  = $pred->{neg};

+    my $match;

+    if ( $self->{command} eq 'SELECT'

+         and $op eq '=' and !$neg and $table->can('fetch_one_row')

+       ) {

+        my $key_col = $table->fetch_one_row(1,1);

+        if ($pred->{arg1}->{value} =~ /^$key_col$/i) {

+            $self->{fetched_from_key}=1;

+            $self->{fetched_value} = $table->fetch_one_row(

+                0,$pred->{arg2}->{value}

+            );

+            return 1;

+       }

+    }

+    $match = $self->is_matched($val1,$op,$val2) || 0;

+    if ($neg) { $match = $match ? 0 : 1; }

+    return $match;

+}

+sub is_matched {

+    my($self,$val1,$op,$val2)[EMAIL PROTECTED];

+    if ($op eq 'IS') {

+        return 1 if (!defined $val1 or $val1 eq '');

+        return 0;

+    }

+    #return $val1 == $val2;

+    #print "[$val1] [$op] [$val2]\n";

+    $val1 = '' unless defined $val1;

+    $val2 = '' unless defined $val2;

+    if ($op =~ /LIKE|CLIKE/i) {

+        $val2 = quotemeta($val2);

+        $val2 =~ s/\\%/.*/g;

+        $val2 =~ s/_/./g;

+    }

+    if ($op eq 'LIKE' )  { return $val1 =~ /^$val2$/s;  }

+    if ($op eq 'CLIKE' ) { return $val1 =~ /^$val2$/si; }

+    if ($val1 =~ /$numexp/ and $val2 =~ /$numexp/) {

+        if ($op eq '<'  ) { return $val1 <  $val2; }

+        if ($op eq '>'  ) { return $val1 >  $val2; }

+        if ($op eq '='  ) { return $val1 == $val2; }

+        if ($op eq '<>' ) { return $val1 != $val2; }

+        if ($op eq '<=' ) { return $val1 <= $val2; }

+        if ($op eq '>=' ) { return $val1 >= $val2; }

+    }

+    else {

+        if ($op eq '<'  ) { return $val1 lt $val2; }

+        if ($op eq '>'  ) { return $val1 gt $val2; }

+        if ($op eq '='  ) { return $val1 eq $val2; }

+        if ($op eq '<>' ) { return $val1 ne $val2; }

+        if ($op eq '<=' ) { return $val1 ge $val2; }

+        if ($op eq '>=' ) { return $val1 le $val2; }

+    }

+}

+sub params {

+    my $self = shift;

+    my $val_num = shift;

+    if (!$self->{"params"}) { return 0; }

+    if (defined $val_num) {

+        return $self->{"params"}->[$val_num];

+    }

+    if (wantarray) {

+        return @{$self->{"params"}};

+    }

+    else {

+        return scalar @{ $self->{"params"} };

+    }

+

+}

+sub open_tables {

+    my($self, $data, $createMode, $lockMode) = @_;

+    my $table_name = $self->{table_name};

+    my $table;

+    eval{$table = $self->open_table($data,$table_name,$createMode,$lockMode)};

+    die $@ if $@;

+    die "Couldn't open table '$table_name'!" unless $table;

+    if (!$self->{column_names} or $self->{column_names}->[0] eq '*') {

+        $self->{column_names} = $table->{col_names};

+    }

+    return $table;

+}

+sub row_values {

+    my $self = shift;

+    my $val_num = shift;

+    if (!$self->{"values"}) { return 0; }

+    if (defined $val_num) {

+        return $self->{"values"}->[$val_num]->{value};

+    }

+    if (wantarray) {

+        return map{$_->{"value"} } @{$self->{"values"}};

+    }

+    else {

+        return scalar @{ $self->{"values"} };

+    }

+}

+package DBI::SQL::Nano::Table_;

+sub new ($$) {

+    my($proto, $attr) = @_;

+    my($self) = { %$attr };

+    bless($self, (ref($proto) || $proto));

+    $self;

+}

+1;

+__END__

+

+=pod

+

+=head1 SUPPORTED SQL SYNTAX

+

+  statement ::=

+      DROP TABLE <table_name>

+    | CREATE TABLE <table_name> <col_def_list>

+    | INSERT INTO <table_name> <insert_col_list> VALUES <val_list>

+    | DELETE FROM <table_name> <where_clause>

+    | UPDATE <table_name> SET <set_clause> <where_clause>

+    | SELECT <select_col_list> FROM <table_name> <where_clause>

+  identifiers ::=

+    * table and column names should be valid SQL identifiers

+    * especially avoid using spaces and commas in identifiers

+    * note: there is no error checking for invalid names, some

+      will be accepted, others will cause parse failures

+  table_name ::=

+    * only one table (no multiple table operations)

+    * see identifier for valid table names

+  col_def_list ::=

+    * a parens delimited, comma-separated list of column names

+    * see identifier for valid column names

+    * column types and column constraints may be included but are ignored

+      e.g. these are all the same:

+        (id,phrase)

+        (id INT, phrase VARCHAR(40))

+        (id INT PRIMARY KEY, phrase VARCHAR(40) NOT NULL)

+  insert_col_list ::=

+    * a parens delimited, comma-separated list of column names

+    * as in standard SQL, this is optional

+  select_col_list ::=

+    * a comma-separated list of column names

+    * or an asterisk denoting all columns

+  val_list ::=

+    * a parens delimited, comma-separated list of values which can be:

+       * placeholders (an unquoted question mark)

+       * numbers (unquoted numbers)

+       * column names (unquoted strings)

+       * nulls (unquoted word NULL)

+       * strings (delimited with single quote marks);

+       * note: leading and trailing percent mark (%) and underscore (_)

+         can be used as wildcards in quoted strings for use with

+         the LIKE and CLIKE operators

+       * note: escaped single quote marks within strings are not

+         supported, neither are embedded commas, use placeholders instead

+  set_clause ::=

+    * a comma-separated list of column = value pairs

+    * see val_list for acceptable value formats

+  where_clause ::=

+    * a single "column/value <op> column/value" predicate, optionally

+      preceded by "NOT"

+    * note: multiple predicates combined with ORs or ANDs are not supported

+    * see val_list for acceptable value formats

+    * op may be one of:

+         < > >= <= = <> LIKE CLIKE IS

+    * CLIKE is a case insensitive LIKE

+

+=cut

+


Added: dbi/trunk/t/50dbm.t
==============================================================================
--- (empty file)
+++ dbi/trunk/t/50dbm.t Wed Feb 25 15:43:12 2004
@@ -0,0 +1,70 @@
+#!perl -w
+
+use strict;
+use Test::More;
+
+BEGIN {
+    $ENV{DBI_SQL_NANO}=1;    # 0=SQL::Statement 1=DBI::SQL::Nano
+    use lib './'             # to find the new DBD::File
+}
+use DBI;
+
+BEGIN { plan tests => 8 }
+
+$|=1;
+my $dbh = DBI->connect('dbi:DBM(RaiseError=0,PrintError=0):f_foo=bar');
+ok($dbh);
+
+#
+# dbm_type defaults to AnyDBM_File which uses the @INC to find
+# the DBM type. See AnyDBM_File docs for default order.
+#
+# the user may also specify a dbm_type in the connect() or afterwards
+# for example:
+#      $dbh->{dbm_type}='MLDBM';
+#      $dbh->{dbm_type}='SDBM_File';
+#
+# It should be possible to have multiple tables, each using different
+# DBMs in the same database handle.
+#
+
+printf "\n%s %s\n%s %s\n%s %s\n%s %s\n",
+         'DBD::DBM'       , $dbh->{Driver}->{Version}
+       , 'DBD::File'      , $dbh->{Driver}->{file_version}
+       , 'DBI::SQL::Nano' , $dbh->{Driver}->{nano_version}
+       , 'SQL::Statement' , $dbh->{Driver}->{statement_version}
+       ;
+
+for my $sql (split /;\s*\n+/,join '',<DATA>) {
+
+    my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+    $sth->execute;
+    die $sth->errstr if $sth->errstr and $sql !~ /DROP/;
+    next unless $sql =~ /SELECT/;
+
+    my $results='';
+    my $expected_results = {
+       1 => 'oranges',
+       2 => 'apples',
+       3 => '',        # NULL returned as undef, currently
+    };
+    # Note that we can't rely on the order here, it's not portable,
+    # different DBMs (or versions) will return different orders.
+    while (my ($key, $value) = $sth->fetchrow_array) {
+        ok exists $expected_results->{$key};
+       is $value, $expected_results->{$key};
+    }
+    is $DBI::rows, keys %$expected_results;
+}
+
+__DATA__
+DROP TABLE fruit;
+CREATE TABLE fruit (dKey INT, dVal VARCHAR(10));
+INSERT INTO  fruit VALUES (1,'oranges'   );
+INSERT INTO  fruit VALUES (2,'to_change' );
+INSERT INTO  fruit VALUES (3, NULL       );
+INSERT INTO  fruit VALUES (4,'to_delete' );
+DELETE FROM  fruit WHERE dKey=4;
+UPDATE fruit SET dVal='apples' WHERE dKey=2;
+SELECT * FROM fruit;
+DROP TABLE fruit;

Reply via email to