Re: Refactoring DBD::File in preparation for ReadOnly and streams ...

Jens Rehsack <[email protected]>
Newsgroups gmane.comp.lang.perl.modules.dbi.sybase.devel
Message-ID <[email protected]>
On 22.09.2012 12:23, H.Merijn Brand wrote:
> On Wed, 19 Sep 2012 17:21:32 +0200, Jens Rehsack <[email protected]>
> wrote:
> 
>> Hi Merijn,
>>
>> while hacking around in DBD::File and DBI::DBD::SqlEngine I stumbled
>> over a major design fault made in the past:
> 
> Some - backward compatible - thoughts:
> 
> Replace all dir-related parts in DBD::File with callbacks
> 
> Make streaming interfaces able to override dir-related parts
> 
> Backward compatible AND extendable

First shot attached as committed (svn revert for the win ^^).
Needs some additional tests for streams as well as pod updates.

>> sub DBD::File::Table::get_table_meta () ... evaluates
>> $dbh->{f_meta}{$table}{initialized} and does something magic else. This
>> magic is fully DBD::File addicted (heavily relies on file2table) and it
>> should be broken into separate pieces to differ between initialisation
>> done for DBI::DBD::SqlEngine and DBD::File and DBD::DBM ...
>>
>> I'd like to discuss it tomorrow in IRC (but I read my e-Mail if you have
>> comments at the evening).
>>
>> If anyone else has ideas - please feel free to speak (but primary
>> restriction is backward compatibility to avoid breakage of dependent DBD's).
>>
>> Best regards,
>> Jens

Best regards,
Jens
patch-data_source (text/plain, 23.3 KB)
Index: lib/DBI/DBD/SqlEngine.pm
===================================================================
--- lib/DBI/DBD/SqlEngine.pm	(Revision 15395)
+++ lib/DBI/DBD/SqlEngine.pm	(Arbeitskopie)
@@ -206,6 +206,24 @@
     return $dbh;
 }    # connect
 
+sub data_sources ($;$)
+{
+    my ( $drh, $attr ) = @_;
+
+    my $tbl_src;
+    $attr
+      and defined $attr->{sql_table_source}
+      and $attr->{sql_table_source}->isa('DBI::DBD::SqlEngine::TableSource')
+      and $tbl_src = $attr->{sql_table_source};
+
+    !defined($tbl_src)
+      and $drh->can('default_table_source')
+      and $tbl_src = $drh->default_table_source();
+    defined($tbl_src) or return;
+
+    $tbl_src->data_sources( $drh, $attr );
+}    # data_sources
+
 sub disconnect_all
 {
 }    # disconnect_all
@@ -243,6 +261,15 @@
     ( $_[0]->FETCH("Active") ) ? 1 : 0;
 }    # ping
 
+sub data_sources
+{
+    my ( $dbh, $attr, @other ) = @_;
+    my $drh = $dbh->{Driver};    # XXX proxy issues?
+    ref($attr) eq 'HASH' or $attr = {};
+    defined( $attr->{sql_table_source} ) or $attr->{sql_table_source} = $dbh->{sql_table_source};
+    return $drh->data_sources( $attr, @other );
+}
+
 sub prepare ($$;@)
 {
     my ( $dbh, $statement, @attribs ) = @_;
@@ -834,12 +861,23 @@
 
     if ( $dbh->{sql_handler} eq "SQL::Statement" and $dbh->{sql_ram_tables} )
     {
+        # XXX map +[ undef, undef, $_, "TABLE", "TEMP" ], keys %{...}
         foreach my $table ( keys %{ $dbh->{sql_ram_tables} } )
         {
             push @tables, [ undef, undef, $table, "TABLE", "TEMP" ];
         }
     }
 
+    my $tbl_src;
+    defined $dbh->{sql_table_source}
+      and $dbh->{sql_table_source}->isa('DBI::DBD::SqlEngine::TableSource')
+      and $tbl_src = $dbh->{sql_table_source};
+
+    !defined($tbl_src)
+      and $dbh->{Driver}->{ImplementorClass}->can('default_table_source')
+      and $tbl_src = $dbh->{Driver}->{ImplementorClass}->default_table_source();
+    defined($tbl_src) and push( @tables, $tbl_src->avail_tables($dbh) );
+
     return @tables;
 }    # get_avail_tables
 
@@ -1269,6 +1307,48 @@
     return $_[0]->{sql_stmt}{NUM_OF_ROWS};
 }    # rows
 
+# ====== TableSource ===========================================================
+
+package DBI::DBD::SqlEngine::TableSource;
+
+use strict;
+use warnings;
+
+use Carp;
+
+sub data_sources ($;$)
+{
+    my ( $class, $drh, $attrs ) = @_;
+    croak( ( ref( $_[0] ) ? ref( $_[0] ) : $_[0] ) . " must implement data_sources" );
+}
+
+sub avail_tables
+{
+    my ( $self, $dbh ) = @_;
+    croak( ( ref( $_[0] ) ? ref( $_[0] ) : $_[0] ) . " must implement avail_tables" );
+}
+
+# ====== DataSource ============================================================
+
+package DBI::DBD::SqlEngine::DataSource;
+
+use strict;
+use warnings;
+
+use Carp;
+
+sub complete_table_name ($$;$)
+{
+    my ( $self, $meta, $table, $respect_case ) = @_;
+    croak( ( ref( $_[0] ) ? ref( $_[0] ) : $_[0] ) . " must implement complete_table_name" );
+}
+
+sub open_data ($)
+{
+    my ( $self, $meta, $attrs, $flags ) = @_;
+    croak( ( ref( $_[0] ) ? ref( $_[0] ) : $_[0] ) . " must implement open_data" );
+}
+
 # ====== SQL::STATEMENT ========================================================
 
 package DBI::DBD::SqlEngine::Statement;
@@ -1332,24 +1412,22 @@
       and $meta->{readonly} = $dbh->{ReadOnly};
     defined $meta->{sql_identifier_case}
       or $meta->{sql_identifier_case} = $dbh->{sql_identifier_case};
+
+    exists $meta->{sql_data_source} or $meta->{sql_data_source} = $dbh->{sql_data_source};
+
+    $meta;
 }
 
 sub init_table_meta
 {
-    my ( $self, $dbh, $meta, $table ) = @_;
+    my ( $self, $dbh, $meta, $table ) = @_ if(0);
 
     return;
 }    # init_table_meta
 
-our $respect_table_case;
-sub respect_case { $respect_table_case }
-
-our $bootstrap_table_meta_phase = 0;
-sub bootstrap_table_meta_phase { $bootstrap_table_meta_phase }
-
 sub get_table_meta ($$$;$)
 {
-    my ( $self, $dbh, $table, $respect_case ) = @_;
+    my ( $self, $dbh, $table, $respect_case, @other ) = @_;
     unless ( defined $respect_case )
     {
         $respect_case = 0;
@@ -1367,12 +1445,10 @@
 
     unless ( $meta->{initialized} )
     {
-        local $bootstrap_table_meta_phase = 1;
-        local $respect_table_case         = $respect_case;
+        $self->bootstrap_table_meta( $dbh, $meta, $table, @other );
+        $meta->{sql_data_source}->complete_table_name( $meta, $table, $respect_case, @other )
+          or return;
 
-        $self->bootstrap_table_meta( $dbh, $meta, $table );
-        return unless $meta->{table_name};
-
         if ( defined $meta->{table_name} and $table ne $meta->{table_name} )
         {
             $dbh->{sql_meta_map}{$table} = $meta->{table_name};
@@ -1384,13 +1460,9 @@
         if ( defined $dbh->{sql_meta}{$table} && defined $dbh->{sql_meta}{$table}{initialized} )
         {
             $meta = $dbh->{sql_meta}{$table};
-
-            unless ( $dbh->{sql_meta}{$table}{initialized} )
-            {
-                $bootstrap_table_meta_phase = 2;
-                $self->bootstrap_table_meta( $dbh, $meta, $table );
-                $meta->{table_name} or return;
-            }
+            $dbh->{sql_meta}{$table}{initialized}
+              or $meta->{sql_data_source}->complete_table_name( $dbh, $meta, $table, $respect_case, @other )
+              or return;
         }
 
         unless ( $dbh->{sql_meta}{$table}{initialized} )
Index: lib/DBD/File.pm
===================================================================
--- lib/DBD/File.pm	(Revision 15396)
+++ lib/DBD/File.pm	(Arbeitskopie)
@@ -97,35 +97,9 @@
     return $str;
     } # dsn_quote
 
-sub data_sources ($;$)
-{
-    my ($drh, $attr) = @_;
-    my $dir = $attr && exists $attr->{f_dir}
-	? $attr->{f_dir}
-	: File::Spec->curdir ();
-    defined $dir or return; # Stream-based databases do not have f_dir
-    my %attrs;
-    $attr and %attrs = %$attr;
-    delete $attrs{f_dir};
-    my $dsnextra = join ";", map { $_ . "=" . dsn_quote ($attrs{$_}) } keys %attrs;
-    my ($dirh) = Symbol::gensym ();
-    unless (opendir $dirh, $dir) {
-	$drh->set_err ($DBI::stderr, "Cannot open directory $dir: $!");
-	return;
-	}
+# XXX rewrite using TableConfig ...
+sub default_table_source { 'DBD::File::TableSource::FileSystem' }
 
-    my ($file, @dsns, %names, $driver);
-    $driver = $drh->{ImplementorClass} =~ m/^dbd\:\:([^\:]+)\:\:/i ? $1 : "File";
-
-    while (defined ($file = readdir ($dirh))) {
-	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=" . dsn_quote ($d) . ($dsnextra ? ";$dsnextra" : "");
-	}
-    return @dsns;
-    } # data_sources
-
 sub disconnect_all
 {
     } # disconnect_all
@@ -152,6 +126,14 @@
 @DBD::File::db::ISA           = qw(DBI::DBD::SqlEngine::db);
 $DBD::File::db::imp_data_size = 0;
 
+sub data_sources
+{
+    my ($dbh, $attr, @other) = @_;
+    ref($attr) eq 'HASH' or $attr = {};
+    exists($attr->{f_dir}) or $attr->{f_dir} = $dbh->{f_dir};
+    return $dbh->SUPER::data_sources($attr, @other);
+}
+
 sub set_versions
 {
     my $dbh = shift;
@@ -204,7 +186,7 @@
 	# f_map is deprecated (but might return)
 	$dbh->{f_dir}      = Cwd::abs_path (File::Spec->curdir ());
 
-	if(0) {
+	if(0) { # XXX remove block
 	# complete derived attributes, if required
 	(my $drv_class = $dbh->{ImplementorClass}) =~ s/::db$//;
 	my $drv_prefix = DBI->driver_prefix ($drv_class);
@@ -273,40 +255,6 @@
     return sprintf "%s using %s", $dbh->{f_version}, $dtype;
     } # get_f_versions
 
-sub get_avail_tables
-{
-    my $dbh = shift;
-
-    my @tables = $dbh->SUPER::get_avail_tables ();
-    my $dir    = $dbh->{f_dir};
-    defined $dir or return;	# Stream based db's cannot be queried for tables
-    my $dirh   = Symbol::gensym ();
-
-    unless (opendir $dirh, $dir) {
-	$dbh->set_err ($DBI::stderr, "Cannot open directory $dir: $!");
-	return @tables;
-	}
-
-    my $class = $dbh->FETCH ("ImplementorClass");
-    $class =~ s/::db$/::Table/;
-    my ($file, %names);
-    my $schema = exists $dbh->{f_schema}
-	? defined $dbh->{f_schema} && $dbh->{f_schema} ne ""
-	    ? $dbh->{f_schema} : undef
-	: eval { getpwuid ((stat $dir)[4]) }; # XXX Win32::pwent
-    my %seen;
-    while (defined ($file = readdir ($dirh))) {
-	my ($tbl, $meta) = $class->get_table_meta ($dbh, $file, 0, 0) or next; # XXX
-	# $tbl && $meta && -f $meta->{f_fqfn} or next;
-	$seen{defined $schema ? $schema : "\0"}{$tbl}++ or
-	    push @tables, [ undef, $schema, $tbl, "TABLE", "FILE" ];
-	}
-    closedir $dirh or
-	$dbh->set_err ($DBI::stderr, "Cannot close directory $dir: $!");
-
-    return @tables;
-    } # get_avail_tables
-
 # ====== STATEMENT =============================================================
 
 package DBD::File::st;
@@ -369,51 +317,179 @@
     return $sth->SUPER::FETCH ($attr);
     } # FETCH
 
-# ====== SQL::STATEMENT ========================================================
+# ====== TableSource ===========================================================
 
-package DBD::File::Statement;
+package DBD::File::TableSource::FileSystem;
 
 use strict;
 use warnings;
 
-@DBD::File::Statement::ISA = qw( DBI::DBD::SqlEngine::Statement );
+use IO::Dir;
 
-# ====== SQL::TABLE ============================================================
+@DBD::File::TableSource::FileSystem::ISA = 'DBI::DBD::SqlEngine::TableSource';
 
-package DBD::File::Table;
+sub data_sources
+{
+    my ($class, $drh, $attr) = @_;
+    my $dir = $attr && exists $attr->{f_dir}
+	? $attr->{f_dir}
+	: File::Spec->curdir ();
+    defined $dir or return; # Stream-based databases do not have f_dir
+    my %attrs;
+    $attr and %attrs = %$attr;
+    delete $attrs{f_dir};
+    my $dsn_quote = $drh->can("dsn_quote");
+    my $dsnextra = join ";", map { $_ . "=" . &{$dsn_quote} ($attrs{$_}) } keys %attrs;
+    my $dirh = IO::Dir->new($dir);
+    unless (defined $dirh) {
+	$drh->set_err ($DBI::stderr, "Cannot open directory $dir: $!");
+	return;
+	}
 
+    my ($file, @dsns, %names, $driver);
+    $driver = $drh->{ImplementorClass} =~ m/^dbd\:\:([^\:]+)\:\:/i ? $1 : "File";
+
+    while (defined ($file = $dirh->read())) {
+	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=" . dsn_quote ($d) . ($dsnextra ? ";$dsnextra" : "");
+	}
+    return @dsns;
+    } # data_sources
+
+sub avail_tables
+{
+    my ($self, $dbh) = @_;
+
+    my $dir    = $dbh->{f_dir};
+    defined $dir or return;	# Stream based db's cannot be queried for tables
+    my $dirh = IO::Dir->new($dir);
+
+    unless (defined $dirh) {
+	$dbh->set_err ($DBI::stderr, "Cannot open directory $dir: $!");
+	return;
+	}
+
+    my $class = $dbh->FETCH ("ImplementorClass");
+    $class =~ s/::db$/::Table/;
+    my ($file, %names);
+    my $schema = exists $dbh->{f_schema}
+	? defined $dbh->{f_schema} && $dbh->{f_schema} ne ""
+	    ? $dbh->{f_schema} : undef
+	: eval { getpwuid ((stat $dir)[4]) }; # XXX Win32::pwent
+    my %seen;
+    my @tables;
+    while (defined ($file = $dirh->read ())) {
+	my ($tbl, $meta) = $class->get_table_meta ($dbh, $file, 0, 0) or next; # XXX
+	# $tbl && $meta && -f $meta->{f_fqfn} or next;
+	$seen{defined $schema ? $schema : "\0"}{$tbl}++ or
+	    push @tables, [ undef, $schema, $tbl, "TABLE", "FILE" ];
+	}
+    $dirh->close() or
+	$dbh->set_err ($DBI::stderr, "Cannot close directory $dir: $!");
+
+    return @tables;
+    }
+
+# ====== DataSource ============================================================
+
+package DBD::File::DataSource::Stream;
+
 use strict;
 use warnings;
 
+@DBD::File::DataSource::Stream::ISA = 'DBI::DBD::SqlEngine::DataSource';
+
 use Carp;
-require IO::File;
-require File::Basename;
-require File::Spec;
-require Cwd;
 
-# 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 };
+sub complete_table_name
+{
+    my ($self, $meta, $file, $respect_case) = @_;
 
-@DBD::File::Table::ISA = qw( DBI::DBD::SqlEngine::Table );
+    my $tbl;
+    if (!$respect_case and $meta->{sql_identifier_case} == 1) { # XXX SQL_IC_UPPER
+        $tbl = uc $tbl;
+	}
+    elsif (!$respect_case and $meta->{sql_identifier_case} == 2) { # XXX SQL_IC_LOWER
+        $tbl = lc $tbl;
+	}
 
-# ====== FLYWEIGHT SUPPORT =====================================================
+    $meta->{f_fqfn} = undef;
+    $meta->{f_fqbn} = undef;
+    $meta->{f_fqln} = undef;
 
+    $meta->{table_name} = $tbl;
+
+    return $tbl;
+    } # complete_table_name
+
+sub apply_encoding
+{
+    my ($self, $meta, $fn) = @_;
+    defined($fn) or $fn = "file handle " . fileno($meta->{fh});
+    if (my $enc = $meta->{f_encoding}) {
+	binmode $meta->{fh}, ":encoding($enc)" or
+	    croak "Failed to set encoding layer '$enc' on $fn: $!";
+	}
+    else {
+	binmode $meta->{fh} or croak "Failed to set binary mode on $fn: $!";
+	}
+    } # apply_encoding
+
+sub open_data
+{
+    my ($self, $meta, $attrs, $flags) = @_;
+
+    $flags->{dropMode} and croak "Can't drop a table in stream";
+    my $fn = "file handle " . fileno($meta->{fh});
+
+    if ($flags->{createMode} || $flags->{lockMode}) {
+	$meta->{fh} = IO::Handle->new_from_fd( fileno($meta->{f_file} ), "w+" ) or
+	    croak "Cannot open $fn for writing: $! (" . ($!+0) . ")";
+	}
+    else {
+	$meta->{fh} = IO::Handle->new_from_fd( fileno($meta->{f_file} ), "r" ) or
+	    croak "Cannot open $fn for reading: $! (" . ($!+0) . ")";
+	}
+
+    $meta->{fh} = $meta->{f_file};
+    if ($meta->{fh}) {
+	if (my $enc = $meta->{f_encoding}) {
+	    binmode $meta->{fh}, ":encoding($enc)" or
+		croak "Failed to set encoding layer '$enc' on $fn: $!";
+	    }
+	else {
+	    binmode $meta->{fh} or croak "Failed to set binary mode on $fn: $!";
+	    }
+	} # have $meta->{$fh}
+    } # open_data
+
+package DBD::File::DataSource::File;
+
+use strict;
+use warnings;
+
+@DBD::File::DataSource::File::ISA = 'DBD::File::DataSource::Stream';
+
+use Carp;
+
 my $fn_any_ext_regex = qr/\.[^.]*/;
 
-# Flyweight support for table_info
-# The functions file2table, init_table_meta, default_table_meta and
-# get_table_meta are using $self arguments for polymorphism only. The
-# must not rely on an instantiated DBD::File::Table
-sub file2table
+# 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 };
+
+sub complete_table_name
 {
-    my ($self, $meta, $file, $file_is_table, $respect_case) = @_;
+    my ($self, $meta, $file, $respect_case, $file_is_table) = @_;
 
     $file eq "." || $file eq ".."	and return; # XXX would break a possible DBD::Dir
 
+    # XXX now called without proving f_fqfn first ...
     my ($ext, $req) = ("", 0);
     if ($meta->{f_ext}) {
-	($ext, my $opt) = split m/\//, $meta->{f_ext};
+	($ext, my $opt) = split m{/}, $meta->{f_ext};
 	if ($ext && $opt) {
 	    $opt =~ m/r/i and $req = 1;
 	    }
@@ -437,7 +513,7 @@
         $basename = uc $basename;
         $tbl = uc $tbl;
 	}
-    if( !$respect_case and $meta->{sql_identifier_case} == 2) { # XXX SQL_IC_LOWER
+    elsif (!$respect_case and $meta->{sql_identifier_case} == 2) { # XXX SQL_IC_LOWER
         $basename = lc $basename;
         $tbl = lc $tbl;
 	}
@@ -475,12 +551,15 @@
 		}
 	    }
 
-	opendir my $dh, $searchdir or croak "Can't open '$searchdir': $!";
-	my @f = sort { length $b <=> length $a } grep { &$cmpsub ($_) } readdir $dh;
+	my @f;
+	{
+	    my $dh = IO::Dir->new ($searchdir) or croak "Can't open '$searchdir': $!";
+	    @f = sort { length $b <=> length $a } grep { &$cmpsub ($_) } $dh->read();
+	    $dh->close() or croak "Can't close '$searchdir': $!";
+	    }
 	@f > 0 && @f <= 2 and $file = $f[0];
 	!$respect_case && $meta->{sql_identifier_case} == 4 and # XXX SQL_IC_MIXED
 	    ($tbl = $file) =~ s/$ext$//i;
-	closedir $dh or croak "Can't close '$searchdir': $!";
 
 	my $tmpfn = $file;
 	if ($ext && $req) {
@@ -501,60 +580,11 @@
     $meta->{table_name} = $tbl;
 
     return $tbl;
-    } # file2table
+    } # complete_table_name
 
-sub bootstrap_table_meta
-{
-    my ($self, $dbh, $meta, $table) = @_;
 
-    $self->SUPER::bootstrap_table_meta($dbh, $meta, $table);
-
-    exists  $meta->{f_dir}	or $meta->{f_dir}	= $dbh->{f_dir};
-    defined $meta->{f_ext}	or $meta->{f_ext}	= $dbh->{f_ext};
-    defined $meta->{f_encoding}	or $meta->{f_encoding}	= $dbh->{f_encoding};
-    exists  $meta->{f_lock}	or $meta->{f_lock}	= $dbh->{f_lock};
-    exists  $meta->{f_lockfile}	or $meta->{f_lockfile}	= $dbh->{f_lockfile};
-    defined $meta->{f_schema}	or $meta->{f_schema}	= $dbh->{f_schema};
-
-    if ($self->bootstrap_table_meta_phase == 2 or !defined $meta->{f_fqfn}) {
-	$self->file2table ($meta, $table, $self->file_is_table, $self->respect_case) or delete $meta->{table_name};
-	}
-
-    } # bootstrap_table_meta
-
-our $file_is_a_table;
-sub file_is_table { $file_is_a_table }
-
-sub get_table_meta ($$$$;$)
+sub open_data
 {
-    my ($self, $dbh, $table, $file_is_table, $respect_case) = @_;
-
-    local $file_is_a_table = $file_is_table;
-    my $meta = $self->SUPER::get_table_meta($dbh, $table, $respect_case);
-    $table = $meta->{table_name};
-    return unless $table;
-
-    return ($table, $meta);
-    } # get_table_meta
-
-my %reset_on_modify = (
-    f_file     => "f_fqfn",
-    f_dir      => "f_fqfn",
-    f_ext      => "f_fqfn",
-    f_lockfile => "f_fqfn", # forces new file2table call
-    );
-
-__PACKAGE__->register_reset_on_modify( \%reset_on_modify );
-
-my %compat_map = map { $_ => "f_$_" } qw( file ext lock lockfile );
-
-__PACKAGE__->register_compat_map( \%compat_map );
-
-
-# ====== FILE OPEN =============================================================
-
-sub open_file ($$$)
-{
     my ($self, $meta, $attrs, $flags) = @_;
 
     defined $meta->{f_fqfn} && $meta->{f_fqfn} ne "" or croak "No filename given";
@@ -574,19 +604,14 @@
 		}
 	    }
 
+	$meta->{fh} = $fh;
+
 	if ($fh) {
 	    $fh->seek (0, 0) or
 		croak "Error while seeking back: $!";
-	    if (my $enc = $meta->{f_encoding}) {
-		binmode $fh, ":encoding($enc)" or
-		    croak "Failed to set encoding layer '$enc' on $fn: $!";
-		}
-	    else {
-		binmode $fh or croak "Failed to set binary mode on $fn: $!";
-		}
+
+	    $self->apply_encoding($meta);
 	    }
-
-	$meta->{fh} = $fh;
 	}
     if ($meta->{f_fqln}) {
 	$fn = $meta->{f_fqln};
@@ -618,8 +643,131 @@
 	    }
 	# $lm = 0 is forced no locking at all
 	}
-    } # open_file
+    }
 
+# ====== SQL::STATEMENT ========================================================
+
+package DBD::File::Statement;
+
+use strict;
+use warnings;
+
+@DBD::File::Statement::ISA = qw( DBI::DBD::SqlEngine::Statement );
+
+# ====== SQL::TABLE ============================================================
+
+package DBD::File::Table;
+
+use strict;
+use warnings;
+
+use Carp;
+require IO::File;
+require File::Basename;
+require File::Spec;
+require Cwd;
+require Scalar::Util;
+
+@DBD::File::Table::ISA = qw( DBI::DBD::SqlEngine::Table );
+
+# ====== UTILITIES ============================================================
+
+if ( eval { require Params::Util; } )
+{
+    Params::Util->import("_HANDLE");
+}
+else
+{
+    # taken but modified from Params::Util ...
+    *_HANDLE = sub {
+	# It has to be defined, of course
+	defined $_[0] or return;
+
+	# Normal globs are considered to be file handles
+	ref $_[0] eq 'GLOB' and return $_[0];
+
+	# Check for a normal tied filehandle
+	# Side Note: 5.5.4's tied() and can() doesn't like getting undef
+	tied($_[0]) and tied($_[0])->can('TIEHANDLE') and return $_[0];
+
+	# There are no other non-object handles that we support
+	Scalar::Util::blessed($_[0]) or return;
+
+	# Check for a common base classes for conventional IO::Handle object
+	$_[0]->isa('IO::Handle') and return $_[0];
+
+	# Check for tied file handles using Tie::Handle
+	$_[0]->isa('Tie::Handle') and return $_[0];
+
+	# IO::Scalar is not a proper seekable, but it is valid is a
+	# regular file handle
+	$_[0]->isa('IO::Scalar') and return $_[0];
+
+	# Yet another special case for IO::String, which refuses (for now
+	# anyway) to become a subclass of IO::Handle.
+	$_[0]->isa('IO::String') and return $_[0];
+
+	# This is not any sort of object we know about
+	return;
+    };
+}
+
+# ====== FLYWEIGHT SUPPORT =====================================================
+
+# Flyweight support for table_info
+# The functions file2table, init_table_meta, default_table_meta and
+# get_table_meta are using $self arguments for polymorphism only. The
+# must not rely on an instantiated DBD::File::Table
+sub file2table
+{
+    my ($self, $meta, $file, $file_is_table, $respect_case) = @_;
+
+    return $meta->{sql_data_source}->complete_table_name($meta, $file, $respect_case, $file_is_table);
+    } # file2table
+
+sub bootstrap_table_meta
+{
+    my ($self, $dbh, $meta, $table, @other) = @_;
+
+    $self->SUPER::bootstrap_table_meta($dbh, $meta, $table, @other);
+
+    exists  $meta->{f_dir}	or $meta->{f_dir}	= $dbh->{f_dir};
+    defined $meta->{f_ext}	or $meta->{f_ext}	= $dbh->{f_ext};
+    defined $meta->{f_encoding}	or $meta->{f_encoding}	= $dbh->{f_encoding};
+    exists  $meta->{f_lock}	or $meta->{f_lock}	= $dbh->{f_lock};
+    exists  $meta->{f_lockfile}	or $meta->{f_lockfile}	= $dbh->{f_lockfile};
+    defined $meta->{f_schema}	or $meta->{f_schema}	= $dbh->{f_schema};
+
+    defined ($meta->{sql_data_source}) or
+	$meta->{sql_data_source} = _HANDLE ($meta->{f_file})
+	                         ? 'DBD::File::DataSource::Stream'
+				 : 'DBD::File::DataSource::File';
+    } # bootstrap_table_meta
+
+sub get_table_meta ($$$$;$)
+{
+    my ($self, $dbh, $table, $file_is_table, $respect_case) = @_;
+
+    my $meta = $self->SUPER::get_table_meta($dbh, $table, $respect_case, $file_is_table);
+    $table = $meta->{table_name};
+    return unless $table;
+
+    return ($table, $meta);
+    } # get_table_meta
+
+my %reset_on_modify = (
+    f_file     => "f_fqfn",
+    f_dir      => "f_fqfn",
+    f_ext      => "f_fqfn",
+    f_lockfile => "f_fqfn", # forces new file2table call
+    );
+
+__PACKAGE__->register_reset_on_modify( \%reset_on_modify );
+
+my %compat_map = map { $_ => "f_$_" } qw( file ext lock lockfile );
+
+__PACKAGE__->register_compat_map( \%compat_map );
+
 # ====== SQL::Eval API =========================================================
 
 sub new
@@ -630,7 +778,7 @@
     # because column name mapping is initialized in constructor ...
     my ($tblnm, $meta) = $className->get_table_meta ($data->{Database}, $attrs->{table}, 1) or
         croak "Cannot find appropriate file for table '$attrs->{table}'";
-    $className->open_file ($meta, $attrs, $flags);
+    $meta->{sql_data_source}->open_data ($meta, $attrs, $flags);
 
     return $className->SUPER::new ($data, $attrs, $flags);
     } # new
Index: lib/DBD/DBM.pm
===================================================================
--- lib/DBD/DBM.pm	(Revision 15390)
+++ lib/DBD/DBM.pm	(Arbeitskopie)
@@ -255,17 +255,6 @@
 
 my $dirfext = $^O eq 'VMS' ? '.sdbm_dir' : '.dir';
 
-sub file2table
-{
-    my ( $self, $meta, $file, $file_is_table, $quoted ) = @_;
-
-    my $tbl = $self->SUPER::file2table( $meta, $file, $file_is_table, $quoted ) or return;
-
-    $meta->{f_dontopen} = 1;
-
-    return $tbl;
-}
-
 my %reset_on_modify = (
                         dbm_type  => "dbm_tietype",
                         dbm_mldbm => "dbm_tietype",
@@ -321,6 +310,8 @@
 {
     my ( $self, $dbh, $meta, $table ) = @_;
 
+    $meta->{f_dontopen} = 1;
+
     unless ( defined( $meta->{dbm_tietype} ) )
     {
         my $tie_type = $meta->{dbm_type};
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.