|
Packit |
d03604 |
{
|
|
Packit |
d03604 |
package DBD::Gofer;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
use strict;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
require DBI;
|
|
Packit |
d03604 |
require DBI::Gofer::Request;
|
|
Packit |
d03604 |
require DBI::Gofer::Response;
|
|
Packit |
d03604 |
require Carp;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
our $VERSION = "0.015327";
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# $Id: Gofer.pm 15326 2012-06-06 16:32:38Z Tim $
|
|
Packit |
d03604 |
#
|
|
Packit |
d03604 |
# Copyright (c) 2007, Tim Bunce, Ireland
|
|
Packit |
d03604 |
#
|
|
Packit |
d03604 |
# You may distribute under the terms of either the GNU General Public
|
|
Packit |
d03604 |
# License or the Artistic License, as specified in the Perl README file.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# attributes we'll allow local STORE
|
|
Packit |
d03604 |
our %xxh_local_store_attrib = map { $_=>1 } qw(
|
|
Packit |
d03604 |
Active
|
|
Packit |
d03604 |
CachedKids
|
|
Packit |
d03604 |
Callbacks
|
|
Packit |
d03604 |
DbTypeSubclass
|
|
Packit |
d03604 |
ErrCount Executed
|
|
Packit |
d03604 |
FetchHashKeyName
|
|
Packit |
d03604 |
HandleError HandleSetErr
|
|
Packit |
d03604 |
InactiveDestroy
|
|
Packit |
d03604 |
AutoInactiveDestroy
|
|
Packit |
d03604 |
PrintError PrintWarn
|
|
Packit |
d03604 |
Profile
|
|
Packit |
d03604 |
RaiseError
|
|
Packit |
d03604 |
RootClass
|
|
Packit |
d03604 |
ShowErrorStatement
|
|
Packit |
d03604 |
Taint TaintIn TaintOut
|
|
Packit |
d03604 |
TraceLevel
|
|
Packit |
d03604 |
Warn
|
|
Packit |
d03604 |
dbi_quote_identifier_cache
|
|
Packit |
d03604 |
dbi_connect_closure
|
|
Packit |
d03604 |
dbi_go_execute_unique
|
|
Packit |
d03604 |
);
|
|
Packit |
d03604 |
our %xxh_local_store_attrib_if_same_value = map { $_=>1 } qw(
|
|
Packit |
d03604 |
Username
|
|
Packit |
d03604 |
dbi_connect_method
|
|
Packit |
d03604 |
);
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
our $drh = undef; # holds driver handle once initialized
|
|
Packit |
d03604 |
our $methods_already_installed;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub driver{
|
|
Packit |
d03604 |
return $drh if $drh;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBI->setup_driver('DBD::Gofer');
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
unless ($methods_already_installed++) {
|
|
Packit |
d03604 |
my $opts = { O=> 0x0004 }; # IMA_KEEP_ERR
|
|
Packit |
d03604 |
DBD::Gofer::db->install_method('go_dbh_method', $opts);
|
|
Packit |
d03604 |
DBD::Gofer::st->install_method('go_sth_method', $opts);
|
|
Packit |
d03604 |
DBD::Gofer::st->install_method('go_clone_sth', $opts);
|
|
Packit |
d03604 |
DBD::Gofer::db->install_method('go_cache', $opts);
|
|
Packit |
d03604 |
DBD::Gofer::st->install_method('go_cache', $opts);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my($class, $attr) = @_;
|
|
Packit |
d03604 |
$class .= "::dr";
|
|
Packit |
d03604 |
($drh) = DBI::_new_drh($class, {
|
|
Packit |
d03604 |
'Name' => 'Gofer',
|
|
Packit |
d03604 |
'Version' => $VERSION,
|
|
Packit |
d03604 |
'Attribution' => 'DBD Gofer by Tim Bunce',
|
|
Packit |
d03604 |
});
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$drh;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub CLONE {
|
|
Packit |
d03604 |
undef $drh;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub go_cache {
|
|
Packit |
d03604 |
my $h = shift;
|
|
Packit |
d03604 |
$h->{go_cache} = shift if @_;
|
|
Packit |
d03604 |
# return handle's override go_cache, if it has one
|
|
Packit |
d03604 |
return $h->{go_cache} if defined $h->{go_cache};
|
|
Packit |
d03604 |
# or else the transports default go_cache
|
|
Packit |
d03604 |
return $h->{go_transport}->{go_cache};
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub set_err_from_response { # set error/warn/info and propagate warnings
|
|
Packit |
d03604 |
my $h = shift;
|
|
Packit |
d03604 |
my $response = shift;
|
|
Packit |
d03604 |
if (my $warnings = $response->warnings) {
|
|
Packit |
d03604 |
warn $_ for @$warnings;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
my ($err, $errstr, $state) = $response->err_errstr_state;
|
|
Packit |
d03604 |
# Only set_err() if there's an error else leave the current values
|
|
Packit |
d03604 |
# (The current values will normally be set undef by the DBI dispatcher
|
|
Packit |
d03604 |
# except for methods marked KEEPERR such as ping.)
|
|
Packit |
d03604 |
$h->set_err($err, $errstr, $state) if defined $err;
|
|
Packit |
d03604 |
return undef;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub install_methods_proxy {
|
|
Packit |
d03604 |
my ($installed_methods) = @_;
|
|
Packit |
d03604 |
while ( my ($full_method, $attr) = each %$installed_methods ) {
|
|
Packit |
d03604 |
# need to install both a DBI dispatch stub and a proxy stub
|
|
Packit |
d03604 |
# (the dispatch stub may be already here due to local driver use)
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBI->_install_method($full_method, "", $attr||{})
|
|
Packit |
d03604 |
unless defined &{$full_method};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# now install proxy stubs on the driver side
|
|
Packit |
d03604 |
$full_method =~ m/^DBI::(\w\w)::(\w+)$/
|
|
Packit |
d03604 |
or die "Invalid method name '$full_method' for install_method";
|
|
Packit |
d03604 |
my ($type, $method) = ($1, $2);
|
|
Packit |
d03604 |
my $driver_method = "DBD::Gofer::${type}::${method}";
|
|
Packit |
d03604 |
next if defined &{$driver_method};
|
|
Packit |
d03604 |
my $sub;
|
|
Packit |
d03604 |
if ($type eq 'db') {
|
|
Packit |
d03604 |
$sub = sub { return shift->go_dbh_method(undef, $method, @_) };
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
else {
|
|
Packit |
d03604 |
$sub = sub { shift->set_err($DBI::stderr, "Can't call \$${type}h->$method when using DBD::Gofer"); return; };
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
no strict 'refs';
|
|
Packit |
d03604 |
*$driver_method = $sub;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
{ package DBD::Gofer::dr; # ====== DRIVER ======
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$imp_data_size = 0;
|
|
Packit |
d03604 |
use strict;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub connect_cached {
|
|
Packit |
d03604 |
my ($drh, $dsn, $user, $auth, $attr)= @_;
|
|
Packit |
d03604 |
$attr ||= {};
|
|
Packit |
d03604 |
return $drh->SUPER::connect_cached($dsn, $user, $auth, {
|
|
Packit |
d03604 |
(%$attr),
|
|
Packit |
d03604 |
go_connect_method => $attr->{go_connect_method} || 'connect_cached',
|
|
Packit |
d03604 |
});
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub connect {
|
|
Packit |
d03604 |
my($drh, $dsn, $user, $auth, $attr)= @_;
|
|
Packit |
d03604 |
my $orig_dsn = $dsn;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# first remove dsn= and everything after it
|
|
Packit |
d03604 |
my $remote_dsn = ($dsn =~ s/;?\bdsn=(.*)$// && $1)
|
|
Packit |
d03604 |
or return $drh->set_err($DBI::stderr, "No dsn= argument in '$orig_dsn'");
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
if ($attr->{go_bypass}) { # don't use DBD::Gofer for this connection
|
|
Packit |
d03604 |
# useful for testing with DBI_AUTOPROXY, e.g., t/03handle.t
|
|
Packit |
d03604 |
return DBI->connect($remote_dsn, $user, $auth, $attr);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my %go_attr;
|
|
Packit |
d03604 |
# extract any go_ attributes from the connect() attr arg
|
|
Packit |
d03604 |
for my $k (grep { /^go_/ } keys %$attr) {
|
|
Packit |
d03604 |
$go_attr{$k} = delete $attr->{$k};
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
# then override those with any attributes embedded in our dsn (not remote_dsn)
|
|
Packit |
d03604 |
for my $kv (grep /=/, split /;/, $dsn, -1) {
|
|
Packit |
d03604 |
my ($k, $v) = split /=/, $kv, 2;
|
|
Packit |
d03604 |
$go_attr{ "go_$k" } = $v;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
if (not ref $go_attr{go_policy}) { # if not a policy object already
|
|
Packit |
d03604 |
my $policy_class = $go_attr{go_policy} || 'classic';
|
|
Packit |
d03604 |
$policy_class = "DBD::Gofer::Policy::$policy_class"
|
|
Packit |
d03604 |
unless $policy_class =~ /::/;
|
|
Packit |
d03604 |
_load_class($policy_class)
|
|
Packit |
d03604 |
or return $drh->set_err($DBI::stderr, "Can't load $policy_class: $@");
|
|
Packit |
d03604 |
# replace policy name in %go_attr with policy object
|
|
Packit |
d03604 |
$go_attr{go_policy} = eval { $policy_class->new(\%go_attr) }
|
|
Packit |
d03604 |
or return $drh->set_err($DBI::stderr, "Can't instanciate $policy_class: $@");
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
# policy object is left in $go_attr{go_policy} so transport can see it
|
|
Packit |
d03604 |
my $go_policy = $go_attr{go_policy};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
if ($go_attr{go_cache} and not ref $go_attr{go_cache}) { # if not a cache object already
|
|
Packit |
d03604 |
my $cache_class = $go_attr{go_cache};
|
|
Packit |
d03604 |
$cache_class = "DBI::Util::CacheMemory" if $cache_class eq '1';
|
|
Packit |
d03604 |
_load_class($cache_class)
|
|
Packit |
d03604 |
or return $drh->set_err($DBI::stderr, "Can't load $cache_class $@");
|
|
Packit |
d03604 |
$go_attr{go_cache} = eval { $cache_class->new() }
|
|
Packit |
d03604 |
or $drh->set_err(0, "Can't instanciate $cache_class: $@"); # warning
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# delete any other attributes that don't apply to transport
|
|
Packit |
d03604 |
my $go_connect_method = delete $go_attr{go_connect_method};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $transport_class = delete $go_attr{go_transport}
|
|
Packit |
d03604 |
or return $drh->set_err($DBI::stderr, "No transport= argument in '$orig_dsn'");
|
|
Packit |
d03604 |
$transport_class = "DBD::Gofer::Transport::$transport_class"
|
|
Packit |
d03604 |
unless $transport_class =~ /::/;
|
|
Packit |
d03604 |
_load_class($transport_class)
|
|
Packit |
d03604 |
or return $drh->set_err($DBI::stderr, "Can't load $transport_class: $@");
|
|
Packit |
d03604 |
my $go_transport = eval { $transport_class->new(\%go_attr) }
|
|
Packit |
d03604 |
or return $drh->set_err($DBI::stderr, "Can't instanciate $transport_class: $@");
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $request_class = "DBI::Gofer::Request";
|
|
Packit |
d03604 |
my $go_request = eval {
|
|
Packit |
d03604 |
my $go_attr = { %$attr };
|
|
Packit |
d03604 |
# XXX user/pass of fwd server vs db server ? also impact of autoproxy
|
|
Packit |
d03604 |
if ($user) {
|
|
Packit |
d03604 |
$go_attr->{Username} = $user;
|
|
Packit |
d03604 |
$go_attr->{Password} = $auth;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
# delete any attributes we can't serialize (or don't want to)
|
|
Packit |
d03604 |
delete @{$go_attr}{qw(Profile HandleError HandleSetErr Callbacks)};
|
|
Packit |
d03604 |
# delete any attributes that should only apply to the client-side
|
|
Packit |
d03604 |
delete @{$go_attr}{qw(RootClass DbTypeSubclass)};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$go_connect_method ||= $go_policy->connect_method($remote_dsn, $go_attr) || 'connect';
|
|
Packit |
d03604 |
$request_class->new({
|
|
Packit |
d03604 |
dbh_connect_call => [ $go_connect_method, $remote_dsn, $user, $auth, $go_attr ],
|
|
Packit |
d03604 |
})
|
|
Packit |
d03604 |
} or return $drh->set_err($DBI::stderr, "Can't instanciate $request_class: $@");
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my ($dbh, $dbh_inner) = DBI::_new_dbh($drh, {
|
|
Packit |
d03604 |
'Name' => $dsn,
|
|
Packit |
d03604 |
'USER' => $user,
|
|
Packit |
d03604 |
go_transport => $go_transport,
|
|
Packit |
d03604 |
go_request => $go_request,
|
|
Packit |
d03604 |
go_policy => $go_policy,
|
|
Packit |
d03604 |
});
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# mark as inactive temporarily for STORE. Active not set until connected() called.
|
|
Packit |
d03604 |
$dbh->STORE(Active => 0);
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# should we ping to check the connection
|
|
Packit |
d03604 |
# and fetch dbh attributes
|
|
Packit |
d03604 |
my $skip_connect_check = $go_policy->skip_connect_check($attr, $dbh);
|
|
Packit |
d03604 |
if (not $skip_connect_check) {
|
|
Packit |
d03604 |
if (not $dbh->go_dbh_method(undef, 'ping')) {
|
|
Packit |
d03604 |
return undef if $dbh->err; # error already recorded, typically
|
|
Packit |
d03604 |
return $dbh->set_err($DBI::stderr, "ping failed");
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
return $dbh;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub _load_class { # return true or false+$@
|
|
Packit |
d03604 |
my $class = shift;
|
|
Packit |
d03604 |
(my $pm = $class) =~ s{::}{/}g;
|
|
Packit |
d03604 |
$pm .= ".pm";
|
|
Packit |
d03604 |
return 1 if eval { require $pm };
|
|
Packit |
d03604 |
delete $INC{$pm}; # shouldn't be needed (perl bug?) and assigning undef isn't enough
|
|
Packit |
d03604 |
undef; # error in $@
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
{ package DBD::Gofer::db; # ====== DATABASE ======
|
|
Packit |
d03604 |
$imp_data_size = 0;
|
|
Packit |
d03604 |
use strict;
|
|
Packit |
d03604 |
use Carp qw(carp croak);
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my %dbh_local_store_attrib = %DBD::Gofer::xxh_local_store_attrib;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub connected {
|
|
Packit |
d03604 |
shift->STORE(Active => 1);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub go_dbh_method {
|
|
Packit |
d03604 |
my $dbh = shift;
|
|
Packit |
d03604 |
my $meta = shift;
|
|
Packit |
d03604 |
# @_ now contains ($method_name, @args)
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $request = $dbh->{go_request};
|
|
Packit |
d03604 |
$request->init_request([ wantarray, @_ ], $dbh);
|
|
Packit |
d03604 |
++$dbh->{go_request_count};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $go_policy = $dbh->{go_policy};
|
|
Packit |
d03604 |
my $dbh_attribute_update = $go_policy->dbh_attribute_update();
|
|
Packit |
d03604 |
$request->dbh_attributes( $go_policy->dbh_attribute_list() )
|
|
Packit |
d03604 |
if $dbh_attribute_update eq 'every'
|
|
Packit |
d03604 |
or $dbh->{go_request_count}==1;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$request->dbh_last_insert_id_args($meta->{go_last_insert_id_args})
|
|
Packit |
d03604 |
if $meta->{go_last_insert_id_args};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $transport = $dbh->{go_transport}
|
|
Packit |
d03604 |
or return $dbh->set_err($DBI::stderr, "Not connected (no transport)");
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
local $transport->{go_cache} = $dbh->{go_cache}
|
|
Packit |
d03604 |
if defined $dbh->{go_cache};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my ($response, $retransmit_sub) = $transport->transmit_request($request);
|
|
Packit |
d03604 |
$response ||= $transport->receive_response($request, $retransmit_sub);
|
|
Packit |
d03604 |
$dbh->{go_response} = $response
|
|
Packit |
d03604 |
or die "No response object returned by $transport";
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
die "response '$response' returned by $transport is not a response object"
|
|
Packit |
d03604 |
unless UNIVERSAL::isa($response,"DBI::Gofer::Response");
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
if (my $dbh_attributes = $response->dbh_attributes) {
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# XXX installed_methods piggybacks on dbh_attributes for now
|
|
Packit |
d03604 |
if (my $installed_methods = delete $dbh_attributes->{dbi_installed_methods}) {
|
|
Packit |
d03604 |
DBD::Gofer::install_methods_proxy($installed_methods)
|
|
Packit |
d03604 |
if $dbh->{go_request_count}==1;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# XXX we don't STORE here, we just stuff the value into the attribute cache
|
|
Packit |
d03604 |
$dbh->{$_} = $dbh_attributes->{$_}
|
|
Packit |
d03604 |
for keys %$dbh_attributes;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $rv = $response->rv;
|
|
Packit |
d03604 |
if (my $resultset_list = $response->sth_resultsets) {
|
|
Packit |
d03604 |
# dbh method call returned one or more resultsets
|
|
Packit |
d03604 |
# (was probably a metadata method like table_info)
|
|
Packit |
d03604 |
#
|
|
Packit |
d03604 |
# setup an sth but don't execute/forward it
|
|
Packit |
d03604 |
my $sth = $dbh->prepare(undef, { go_skip_prepare_check => 1 });
|
|
Packit |
d03604 |
# set the sth response to our dbh response
|
|
Packit |
d03604 |
(tied %$sth)->{go_response} = $response;
|
|
Packit |
d03604 |
# setup the sth with the results in our response
|
|
Packit |
d03604 |
$sth->more_results;
|
|
Packit |
d03604 |
# and return that new sth as if it came from original request
|
|
Packit |
d03604 |
$rv = [ $sth ];
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
elsif (!$rv) { # should only occur for major transport-level error
|
|
Packit |
d03604 |
#carp("no rv in response { @{[ %$response ]} }");
|
|
Packit |
d03604 |
$rv = [ ];
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBD::Gofer::set_err_from_response($dbh, $response);
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
return (wantarray) ? @$rv : $rv->[0];
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# Methods that should be forwarded but can be cached
|
|
Packit |
d03604 |
for my $method (qw(
|
|
Packit |
d03604 |
tables table_info column_info primary_key_info foreign_key_info statistics_info
|
|
Packit |
d03604 |
data_sources type_info_all get_info
|
|
Packit |
d03604 |
parse_trace_flags parse_trace_flag
|
|
Packit |
d03604 |
func
|
|
Packit |
d03604 |
)) {
|
|
Packit |
d03604 |
my $policy_name = "cache_$method";
|
|
Packit |
d03604 |
my $super_name = "SUPER::$method";
|
|
Packit |
d03604 |
my $sub = sub {
|
|
Packit |
d03604 |
my $dbh = shift;
|
|
Packit |
d03604 |
my $rv;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# if we know the remote side doesn't override the DBI's default method
|
|
Packit |
d03604 |
# then we might as well just call the DBI's default method on the client
|
|
Packit |
d03604 |
# (which may, in turn, call other methods that are forwarded, like get_info)
|
|
Packit |
d03604 |
if ($dbh->{dbi_default_methods}{$method} && $dbh->{go_policy}->skip_default_methods()) {
|
|
Packit |
d03604 |
$dbh->trace_msg(" !! $method: using local default as remote method is also default\n");
|
|
Packit |
d03604 |
return $dbh->$super_name(@_);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $cache;
|
|
Packit |
d03604 |
my $cache_key;
|
|
Packit |
d03604 |
if (my $cache_it = $dbh->{go_policy}->$policy_name(undef, $dbh, @_)) {
|
|
Packit |
d03604 |
$cache = $dbh->{go_meta_cache} ||= {}; # keep separate from go_cache
|
|
Packit |
d03604 |
$cache_key = sprintf "%s_wa%d(%s)", $policy_name, wantarray||0,
|
|
Packit |
d03604 |
join(",\t", map { # XXX basic but sufficient for now
|
|
Packit |
d03604 |
!ref($_) ? DBI::neat($_,1e6)
|
|
Packit |
d03604 |
: ref($_) eq 'ARRAY' ? DBI::neat_list($_,1e6,",\001")
|
|
Packit |
d03604 |
: ref($_) eq 'HASH' ? do { my @k = sort keys %$_; DBI::neat_list([@k,@{$_}{@k}],1e6,",\002") }
|
|
Packit |
d03604 |
: do { warn "unhandled argument type ($_)"; $_ }
|
|
Packit |
d03604 |
} @_);
|
|
Packit |
d03604 |
if ($rv = $cache->{$cache_key}) {
|
|
Packit |
d03604 |
$dbh->trace_msg("$method(@_) returning previously cached value ($cache_key)\n",4);
|
|
Packit |
d03604 |
my @cache_rv = @$rv;
|
|
Packit |
d03604 |
# if it's an sth we have to clone it
|
|
Packit |
d03604 |
$cache_rv[0] = $cache_rv[0]->go_clone_sth if UNIVERSAL::isa($cache_rv[0],'DBI::st');
|
|
Packit |
d03604 |
return (wantarray) ? @cache_rv : $cache_rv[0];
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$rv = [ (wantarray)
|
|
Packit |
d03604 |
? ($dbh->go_dbh_method(undef, $method, @_))
|
|
Packit |
d03604 |
: scalar $dbh->go_dbh_method(undef, $method, @_)
|
|
Packit |
d03604 |
];
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
if ($cache) {
|
|
Packit |
d03604 |
$dbh->trace_msg("$method(@_) caching return value ($cache_key)\n",4);
|
|
Packit |
d03604 |
my @cache_rv = @$rv;
|
|
Packit |
d03604 |
# if it's an sth we have to clone it
|
|
Packit |
d03604 |
#$cache_rv[0] = $cache_rv[0]->go_clone_sth
|
|
Packit |
d03604 |
# if UNIVERSAL::isa($cache_rv[0],'DBI::st');
|
|
Packit |
d03604 |
$cache->{$cache_key} = \@cache_rv
|
|
Packit |
d03604 |
unless UNIVERSAL::isa($cache_rv[0],'DBI::st'); # XXX cloning sth not yet done
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
return (wantarray) ? @$rv : $rv->[0];
|
|
Packit |
d03604 |
};
|
|
Packit |
d03604 |
no strict 'refs';
|
|
Packit |
d03604 |
*$method = $sub;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# Methods that can use the DBI defaults for some situations/drivers
|
|
Packit |
d03604 |
for my $method (qw(
|
|
Packit |
d03604 |
quote quote_identifier
|
|
Packit |
d03604 |
)) { # XXX keep DBD::Gofer::Policy::Base in sync
|
|
Packit |
d03604 |
my $policy_name = "locally_$method";
|
|
Packit |
d03604 |
my $super_name = "SUPER::$method";
|
|
Packit |
d03604 |
my $sub = sub {
|
|
Packit |
d03604 |
my $dbh = shift;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# if we know the remote side doesn't override the DBI's default method
|
|
Packit |
d03604 |
# then we might as well just call the DBI's default method on the client
|
|
Packit |
d03604 |
# (which may, in turn, call other methods that are forwarded, like get_info)
|
|
Packit |
d03604 |
if ($dbh->{dbi_default_methods}{$method} && $dbh->{go_policy}->skip_default_methods()) {
|
|
Packit |
d03604 |
$dbh->trace_msg(" !! $method: using local default as remote method is also default\n");
|
|
Packit |
d03604 |
return $dbh->$super_name(@_);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# false: use remote gofer
|
|
Packit |
d03604 |
# 1: use local DBI default method
|
|
Packit |
d03604 |
# code ref: use the code ref
|
|
Packit |
d03604 |
my $locally = $dbh->{go_policy}->$policy_name($dbh, @_);
|
|
Packit |
d03604 |
if ($locally) {
|
|
Packit |
d03604 |
return $locally->($dbh, @_) if ref $locally eq 'CODE';
|
|
Packit |
d03604 |
return $dbh->$super_name(@_);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
return $dbh->go_dbh_method(undef, $method, @_); # propagate context
|
|
Packit |
d03604 |
};
|
|
Packit |
d03604 |
no strict 'refs';
|
|
Packit |
d03604 |
*$method = $sub;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# Methods that should always fail
|
|
Packit |
d03604 |
for my $method (qw(
|
|
Packit |
d03604 |
begin_work commit rollback
|
|
Packit |
d03604 |
)) {
|
|
Packit |
d03604 |
no strict 'refs';
|
|
Packit |
d03604 |
*$method = sub { return shift->set_err($DBI::stderr, "$method not available with DBD::Gofer") }
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub do {
|
|
Packit |
d03604 |
my ($dbh, $sql, $attr, @args) = @_;
|
|
Packit |
d03604 |
delete $dbh->{Statement}; # avoid "Modification of non-creatable hash value attempted"
|
|
Packit |
d03604 |
$dbh->{Statement} = $sql; # for profiling and ShowErrorStatement
|
|
Packit |
d03604 |
my $meta = { go_last_insert_id_args => $attr->{go_last_insert_id_args} };
|
|
Packit |
d03604 |
return $dbh->go_dbh_method($meta, 'do', $sql, $attr, @args);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub ping {
|
|
Packit |
d03604 |
my $dbh = shift;
|
|
Packit |
d03604 |
return $dbh->set_err('', "can't ping while not connected") # info
|
|
Packit |
d03604 |
unless $dbh->SUPER::FETCH('Active');
|
|
Packit |
d03604 |
my $skip_ping = $dbh->{go_policy}->skip_ping();
|
|
Packit |
d03604 |
return ($skip_ping) ? 1 : $dbh->go_dbh_method(undef, 'ping', @_);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub last_insert_id {
|
|
Packit |
d03604 |
my $dbh = shift;
|
|
Packit |
d03604 |
my $response = $dbh->{go_response} or return undef;
|
|
Packit |
d03604 |
return $response->last_insert_id;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub FETCH {
|
|
Packit |
d03604 |
my ($dbh, $attrib) = @_;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# FETCH is effectively already cached because the DBI checks the
|
|
Packit |
d03604 |
# attribute cache in the handle before calling FETCH
|
|
Packit |
d03604 |
# and this FETCH copies the value into the attribute cache
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# forward driver-private attributes (except ours)
|
|
Packit |
d03604 |
if ($attrib =~ m/^[a-z]/ && $attrib !~ /^go_/) {
|
|
Packit |
d03604 |
my $value = $dbh->go_dbh_method(undef, 'FETCH', $attrib);
|
|
Packit |
d03604 |
$dbh->{$attrib} = $value; # XXX forces caching by DBI
|
|
Packit |
d03604 |
return $dbh->{$attrib} = $value;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# else pass up to DBI to handle
|
|
Packit |
d03604 |
return $dbh->SUPER::FETCH($attrib);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub STORE {
|
|
Packit |
d03604 |
my ($dbh, $attrib, $value) = @_;
|
|
Packit |
d03604 |
if ($attrib eq 'AutoCommit') {
|
|
Packit |
d03604 |
croak "Can't enable transactions when using DBD::Gofer" if !$value;
|
|
Packit |
d03604 |
return $dbh->SUPER::STORE($attrib => ($value) ? -901 : -900);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
return $dbh->SUPER::STORE($attrib => $value)
|
|
Packit |
d03604 |
# we handle this attribute locally
|
|
Packit |
d03604 |
if $dbh_local_store_attrib{$attrib}
|
|
Packit |
d03604 |
# or it's a private_ (application) attribute
|
|
Packit |
d03604 |
or $attrib =~ /^private_/
|
|
Packit |
d03604 |
# or not yet connected (ie being called by DBI->connect)
|
|
Packit |
d03604 |
or not $dbh->FETCH('Active');
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
return $dbh->SUPER::STORE($attrib => $value)
|
|
Packit |
d03604 |
if $DBD::Gofer::xxh_local_store_attrib_if_same_value{$attrib}
|
|
Packit |
d03604 |
&& do { # values are the same
|
|
Packit |
d03604 |
my $crnt = $dbh->FETCH($attrib);
|
|
Packit |
d03604 |
local $^W;
|
|
Packit |
d03604 |
(defined($value) ^ defined($crnt))
|
|
Packit |
d03604 |
? 0 # definedness differs
|
|
Packit |
d03604 |
: $value eq $crnt;
|
|
Packit |
d03604 |
};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# dbh attributes are set at connect-time - see connect()
|
|
Packit |
d03604 |
carp("Can't alter \$dbh->{$attrib} after handle created with DBD::Gofer") if $dbh->FETCH('Warn');
|
|
Packit |
d03604 |
return $dbh->set_err($DBI::stderr, "Can't alter \$dbh->{$attrib} after handle created with DBD::Gofer");
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub disconnect {
|
|
Packit |
d03604 |
my $dbh = shift;
|
|
Packit |
d03604 |
$dbh->{go_transport} = undef;
|
|
Packit |
d03604 |
$dbh->STORE(Active => 0);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub prepare {
|
|
Packit |
d03604 |
my ($dbh, $statement, $attr)= @_;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
return $dbh->set_err($DBI::stderr, "Can't prepare when disconnected")
|
|
Packit |
d03604 |
unless $dbh->FETCH('Active');
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$attr = { %$attr } if $attr; # copy so we can edit
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $policy = delete($attr->{go_policy}) || $dbh->{go_policy};
|
|
Packit |
d03604 |
my $lii_args = delete $attr->{go_last_insert_id_args};
|
|
Packit |
d03604 |
my $go_prepare = delete($attr->{go_prepare_method})
|
|
Packit |
d03604 |
|| $dbh->{go_prepare_method}
|
|
Packit |
d03604 |
|| $policy->prepare_method($dbh, $statement, $attr)
|
|
Packit |
d03604 |
|| 'prepare'; # e.g. for code not using placeholders
|
|
Packit |
d03604 |
my $go_cache = delete $attr->{go_cache};
|
|
Packit |
d03604 |
# set to undef if there are no attributes left for the actual prepare call
|
|
Packit |
d03604 |
$attr = undef if $attr and not %$attr;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my ($sth, $sth_inner) = DBI::_new_sth($dbh, {
|
|
Packit |
d03604 |
Statement => $statement,
|
|
Packit |
d03604 |
go_prepare_call => [ 0, $go_prepare, $statement, $attr ],
|
|
Packit |
d03604 |
# go_method_calls => [], # autovivs if needed
|
|
Packit |
d03604 |
go_request => $dbh->{go_request},
|
|
Packit |
d03604 |
go_transport => $dbh->{go_transport},
|
|
Packit |
d03604 |
go_policy => $policy,
|
|
Packit |
d03604 |
go_last_insert_id_args => $lii_args,
|
|
Packit |
d03604 |
go_cache => $go_cache,
|
|
Packit |
d03604 |
});
|
|
Packit |
d03604 |
$sth->STORE(Active => 0); # XXX needed? It should be the default
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $skip_prepare_check = $policy->skip_prepare_check($attr, $dbh, $statement, $attr, $sth);
|
|
Packit |
d03604 |
if (not $skip_prepare_check) {
|
|
Packit |
d03604 |
$sth->go_sth_method() or return undef;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
return $sth;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub prepare_cached {
|
|
Packit |
d03604 |
my ($dbh, $sql, $attr, $if_active)= @_;
|
|
Packit |
d03604 |
$attr ||= {};
|
|
Packit |
d03604 |
return $dbh->SUPER::prepare_cached($sql, {
|
|
Packit |
d03604 |
%$attr,
|
|
Packit |
d03604 |
go_prepare_method => $attr->{go_prepare_method} || 'prepare_cached',
|
|
Packit |
d03604 |
}, $if_active);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
*go_cache = \&DBD::Gofer::go_cache;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
{ package DBD::Gofer::st; # ====== STATEMENT ======
|
|
Packit |
d03604 |
$imp_data_size = 0;
|
|
Packit |
d03604 |
use strict;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my %sth_local_store_attrib = (%DBD::Gofer::xxh_local_store_attrib, NUM_OF_FIELDS => 1);
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub go_sth_method {
|
|
Packit |
d03604 |
my ($sth, $meta) = @_;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
if (my $ParamValues = $sth->{ParamValues}) {
|
|
Packit |
d03604 |
my $ParamAttr = $sth->{ParamAttr};
|
|
Packit |
d03604 |
# XXX the sort here is a hack to work around a DBD::Sybase bug
|
|
Packit |
d03604 |
# but only works properly for params 1..9
|
|
Packit |
d03604 |
# (reverse because of the unshift)
|
|
Packit |
d03604 |
my @params = reverse sort keys %$ParamValues;
|
|
Packit |
d03604 |
if (@params > 9 && ($sth->{Database}{go_dsn}||'') =~ /dbi:Sybase/) {
|
|
Packit |
d03604 |
# if more than 9 then we need to do a proper numeric sort
|
|
Packit |
d03604 |
# also warn to alert user of this issue
|
|
Packit |
d03604 |
warn "Sybase param binding order hack in use";
|
|
Packit |
d03604 |
@params = sort { $b <=> $a } @params;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
for my $p (@params) {
|
|
Packit |
d03604 |
# unshift to put binds before execute call
|
|
Packit |
d03604 |
unshift @{ $sth->{go_method_calls} },
|
|
Packit |
d03604 |
[ 'bind_param', $p, $ParamValues->{$p}, $ParamAttr->{$p} ];
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $dbh = $sth->{Database} or die "panic";
|
|
Packit |
d03604 |
++$dbh->{go_request_count};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $request = $sth->{go_request};
|
|
Packit |
d03604 |
$request->init_request($sth->{go_prepare_call}, $sth);
|
|
Packit |
d03604 |
$request->sth_method_calls(delete $sth->{go_method_calls})
|
|
Packit |
d03604 |
if $sth->{go_method_calls};
|
|
Packit |
d03604 |
$request->sth_result_attr({}); # (currently) also indicates this is an sth request
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$request->dbh_last_insert_id_args($meta->{go_last_insert_id_args})
|
|
Packit |
d03604 |
if $meta->{go_last_insert_id_args};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $go_policy = $sth->{go_policy};
|
|
Packit |
d03604 |
my $dbh_attribute_update = $go_policy->dbh_attribute_update();
|
|
Packit |
d03604 |
$request->dbh_attributes( $go_policy->dbh_attribute_list() )
|
|
Packit |
d03604 |
if $dbh_attribute_update eq 'every'
|
|
Packit |
d03604 |
or $dbh->{go_request_count}==1;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $transport = $sth->{go_transport}
|
|
Packit |
d03604 |
or return $sth->set_err($DBI::stderr, "Not connected (no transport)");
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
local $transport->{go_cache} = $sth->{go_cache}
|
|
Packit |
d03604 |
if defined $sth->{go_cache};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my ($response, $retransmit_sub) = $transport->transmit_request($request);
|
|
Packit |
d03604 |
$response ||= $transport->receive_response($request, $retransmit_sub);
|
|
Packit |
d03604 |
$sth->{go_response} = $response
|
|
Packit |
d03604 |
or die "No response object returned by $transport";
|
|
Packit |
d03604 |
$dbh->{go_response} = $response; # mainly for last_insert_id
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
if (my $dbh_attributes = $response->dbh_attributes) {
|
|
Packit |
d03604 |
# XXX we don't STORE here, we just stuff the value into the attribute cache
|
|
Packit |
d03604 |
$dbh->{$_} = $dbh_attributes->{$_}
|
|
Packit |
d03604 |
for keys %$dbh_attributes;
|
|
Packit |
d03604 |
# record the values returned, so we know that we have fetched
|
|
Packit |
d03604 |
# values are which we have fetched (see dbh->FETCH method)
|
|
Packit |
d03604 |
$dbh->{go_dbh_attributes_fetched} = $dbh_attributes;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $rv = $response->rv; # may be undef on error
|
|
Packit |
d03604 |
if ($response->sth_resultsets) {
|
|
Packit |
d03604 |
# setup first resultset - including sth attributes
|
|
Packit |
d03604 |
$sth->more_results;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
else {
|
|
Packit |
d03604 |
$sth->STORE(Active => 0);
|
|
Packit |
d03604 |
$sth->{go_rows} = $rv;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
# set error/warn/info (after more_results as that'll clear err)
|
|
Packit |
d03604 |
DBD::Gofer::set_err_from_response($sth, $response);
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
return $rv;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub bind_param {
|
|
Packit |
d03604 |
my ($sth, $param, $value, $attr) = @_;
|
|
Packit |
d03604 |
$sth->{ParamValues}{$param} = $value;
|
|
Packit |
d03604 |
$sth->{ParamAttr}{$param} = $attr
|
|
Packit |
d03604 |
if defined $attr; # attr is sticky if not explicitly set
|
|
Packit |
d03604 |
return 1;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub execute {
|
|
Packit |
d03604 |
my $sth = shift;
|
|
Packit |
d03604 |
$sth->bind_param($_, $_[$_-1]) for (1..@_);
|
|
Packit |
d03604 |
push @{ $sth->{go_method_calls} }, [ 'execute' ];
|
|
Packit |
d03604 |
my $meta = { go_last_insert_id_args => $sth->{go_last_insert_id_args} };
|
|
Packit |
d03604 |
return $sth->go_sth_method($meta);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub more_results {
|
|
Packit |
d03604 |
my $sth = shift;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$sth->finish;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $response = $sth->{go_response} or do {
|
|
Packit |
d03604 |
# e.g., we haven't sent a request yet (ie prepare then more_results)
|
|
Packit |
d03604 |
$sth->trace_msg(" No response object present", 3);
|
|
Packit |
d03604 |
return;
|
|
Packit |
d03604 |
};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $resultset_list = $response->sth_resultsets
|
|
Packit |
d03604 |
or return $sth->set_err($DBI::stderr, "No sth_resultsets");
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
my $meta = shift @$resultset_list
|
|
Packit |
d03604 |
or return undef; # no more result sets
|
|
Packit |
d03604 |
#warn "more_results: ".Data::Dumper::Dumper($meta);
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# pull out the special non-attributes first
|
|
Packit |
d03604 |
my ($rowset, $err, $errstr, $state)
|
|
Packit |
d03604 |
= delete @{$meta}{qw(rowset err errstr state)};
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# copy meta attributes into attribute cache
|
|
Packit |
d03604 |
my $NUM_OF_FIELDS = delete $meta->{NUM_OF_FIELDS};
|
|
Packit |
d03604 |
$sth->STORE('NUM_OF_FIELDS', $NUM_OF_FIELDS);
|
|
Packit |
d03604 |
# XXX need to use STORE for some?
|
|
Packit |
d03604 |
$sth->{$_} = $meta->{$_} for keys %$meta;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
if (($NUM_OF_FIELDS||0) > 0) {
|
|
Packit |
d03604 |
$sth->{go_rows} = ($rowset) ? @$rowset : -1;
|
|
Packit |
d03604 |
$sth->{go_current_rowset} = $rowset;
|
|
Packit |
d03604 |
$sth->{go_current_rowset_err} = [ $err, $errstr, $state ]
|
|
Packit |
d03604 |
if defined $err;
|
|
Packit |
d03604 |
$sth->STORE(Active => 1) if $rowset;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
return $sth;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub go_clone_sth {
|
|
Packit |
d03604 |
my ($sth1) = @_;
|
|
Packit |
d03604 |
# clone an (un-fetched-from) sth - effectively undoes the initial more_results
|
|
Packit |
d03604 |
# not 100% so just for use in caching returned sth e.g. table_info
|
|
Packit |
d03604 |
my $sth2 = $sth1->{Database}->prepare($sth1->{Statement}, { go_skip_prepare_check => 1 });
|
|
Packit |
d03604 |
$sth2->STORE($_, $sth1->{$_}) for qw(NUM_OF_FIELDS Active);
|
|
Packit |
d03604 |
my $sth2_inner = tied %$sth2;
|
|
Packit |
d03604 |
$sth2_inner->{$_} = $sth1->{$_} for qw(NUM_OF_PARAMS FetchHashKeyName);
|
|
Packit |
d03604 |
die "not fully implemented yet";
|
|
Packit |
d03604 |
return $sth2;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub fetchrow_arrayref {
|
|
Packit |
d03604 |
my ($sth) = @_;
|
|
Packit |
d03604 |
my $resultset = $sth->{go_current_rowset} || do {
|
|
Packit |
d03604 |
# should only happen if fetch called after execute failed
|
|
Packit |
d03604 |
my $rowset_err = $sth->{go_current_rowset_err}
|
|
Packit |
d03604 |
|| [ 1, 'no result set (did execute fail)' ];
|
|
Packit |
d03604 |
return $sth->set_err( @$rowset_err );
|
|
Packit |
d03604 |
};
|
|
Packit |
d03604 |
return $sth->_set_fbav(shift @$resultset) if @$resultset;
|
|
Packit |
d03604 |
$sth->finish; # no more data so finish
|
|
Packit |
d03604 |
return undef;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
*fetch = \&fetchrow_arrayref; # alias
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub fetchall_arrayref {
|
|
Packit |
d03604 |
my ($sth, $slice, $max_rows) = @_;
|
|
Packit |
d03604 |
my $resultset = $sth->{go_current_rowset} || do {
|
|
Packit |
d03604 |
# should only happen if fetch called after execute failed
|
|
Packit |
d03604 |
my $rowset_err = $sth->{go_current_rowset_err}
|
|
Packit |
d03604 |
|| [ 1, 'no result set (did execute fail)' ];
|
|
Packit |
d03604 |
return $sth->set_err( @$rowset_err );
|
|
Packit |
d03604 |
};
|
|
Packit |
d03604 |
my $mode = ref($slice) || 'ARRAY';
|
|
Packit |
d03604 |
return $sth->SUPER::fetchall_arrayref($slice, $max_rows)
|
|
Packit |
d03604 |
if ref($slice) or defined $max_rows;
|
|
Packit |
d03604 |
$sth->finish; # no more data after this so finish
|
|
Packit |
d03604 |
return $resultset;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub rows {
|
|
Packit |
d03604 |
return shift->{go_rows};
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub STORE {
|
|
Packit |
d03604 |
my ($sth, $attrib, $value) = @_;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
return $sth->SUPER::STORE($attrib => $value)
|
|
Packit |
d03604 |
if $sth_local_store_attrib{$attrib} # handle locally
|
|
Packit |
d03604 |
# or it's a private_ (application) attribute
|
|
Packit |
d03604 |
or $attrib =~ /^private_/;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# otherwise warn but do it anyway
|
|
Packit |
d03604 |
# this will probably need refining later
|
|
Packit |
d03604 |
my $msg = "Altering \$sth->{$attrib} won't affect proxied handle";
|
|
Packit |
d03604 |
Carp::carp($msg) if $sth->FETCH('Warn');
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# XXX could perhaps do
|
|
Packit |
d03604 |
# push @{ $sth->{go_method_calls} }, [ 'STORE', $attrib, $value ]
|
|
Packit |
d03604 |
# if not $sth->FETCH('Executed');
|
|
Packit |
d03604 |
# but how to handle repeat executions? How to we know when an
|
|
Packit |
d03604 |
# attribute is being set to affect the current resultset or the
|
|
Packit |
d03604 |
# next execution?
|
|
Packit |
d03604 |
# Could just always use go_method_calls I guess.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# do the store locally anyway, just in case
|
|
Packit |
d03604 |
$sth->SUPER::STORE($attrib => $value);
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
return $sth->set_err($DBI::stderr, $msg);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
# sub bind_param_array
|
|
Packit |
d03604 |
# we use DBI's default, which sets $sth->{ParamArrays}{$param} = $value
|
|
Packit |
d03604 |
# and calls bind_param($param, undef, $attr) if $attr.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
sub execute_array {
|
|
Packit |
d03604 |
my $sth = shift;
|
|
Packit |
d03604 |
my $attr = shift;
|
|
Packit |
d03604 |
$sth->bind_param_array($_, $_[$_-1]) for (1..@_);
|
|
Packit |
d03604 |
push @{ $sth->{go_method_calls} }, [ 'execute_array', $attr ];
|
|
Packit |
d03604 |
return $sth->go_sth_method($attr);
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
*go_cache = \&DBD::Gofer::go_cache;
|
|
Packit |
d03604 |
}
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
1;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
__END__
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 NAME
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBD::Gofer - A stateless-proxy driver for communicating with a remote DBI
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 SYNOPSIS
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
use DBI;
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$original_dsn = "dbi:..."; # your original DBI Data Source Name
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$dbh = DBI->connect("dbi:Gofer:transport=$transport;...;dsn=$original_dsn",
|
|
Packit |
d03604 |
$user, $passwd, \%attributes);
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
... use $dbh as if it was connected to $original_dsn ...
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The C<transport=$transport> part specifies the name of the module to use to
|
|
Packit |
d03604 |
transport the requests to the remote DBI. If $transport doesn't contain any
|
|
Packit |
d03604 |
double colons then it's prefixed with C<DBD::Gofer::Transport::>.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The C<dsn=$original_dsn> part I<must be the last element> of the DSN because
|
|
Packit |
d03604 |
everything after C<dsn=> is assumed to be the DSN that the remote DBI should
|
|
Packit |
d03604 |
use.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The C<...> represents attributes that influence the operation of the Gofer
|
|
Packit |
d03604 |
driver or transport. These are described below or in the documentation of the
|
|
Packit |
d03604 |
transport module being used.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=encoding ISO8859-1
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 DESCRIPTION
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBD::Gofer is a DBI database driver that forwards requests to another DBI
|
|
Packit |
d03604 |
driver, usually in a separate process, often on a separate machine. It tries to
|
|
Packit |
d03604 |
be as transparent as possible so it appears that you are using the remote
|
|
Packit |
d03604 |
driver directly.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBD::Gofer is very similar to DBD::Proxy. The major difference is that with
|
|
Packit |
d03604 |
DBD::Gofer no state is maintained on the remote end. That means every
|
|
Packit |
d03604 |
request contains all the information needed to create the required state. (So,
|
|
Packit |
d03604 |
for example, every request includes the DSN to connect to.) Each request can be
|
|
Packit |
d03604 |
sent to any available server. The server executes the request and returns a
|
|
Packit |
d03604 |
single response that includes all the data.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
This is very similar to the way http works as a stateless protocol for the web.
|
|
Packit |
d03604 |
Each request from your web browser can be handled by a different web server process.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Use Cases
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
This may seem like pointless overhead but there are situations where this is a
|
|
Packit |
d03604 |
very good thing. Let's consider a specific case.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Imagine using DBD::Gofer with an http transport. Your application calls
|
|
Packit |
d03604 |
connect(), prepare("select * from table where foo=?"), bind_param(), and execute().
|
|
Packit |
d03604 |
At this point DBD::Gofer builds a request containing all the information
|
|
Packit |
d03604 |
about the method calls. It then uses the httpd transport to send that request
|
|
Packit |
d03604 |
to an apache web server.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
This 'dbi execute' web server executes the request (using DBI::Gofer::Execute
|
|
Packit |
d03604 |
and related modules) and builds a response that contains all the rows of data,
|
|
Packit |
d03604 |
if the statement returned any, along with all the attributes that describe the
|
|
Packit |
d03604 |
results, such as $sth->{NAME}. This response is sent back to DBD::Gofer which
|
|
Packit |
d03604 |
unpacks it and presents it to the application as if it had executed the
|
|
Packit |
d03604 |
statement itself.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Advantages
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Okay, but you still don't see the point? Well let's consider what we've gained:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 Connection Pooling and Throttling
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The 'dbi execute' web server leverages all the functionality of web
|
|
Packit |
d03604 |
infrastructure in terms of load balancing, high-availability, firewalls, access
|
|
Packit |
d03604 |
management, proxying, caching.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
At its most basic level you get a configurable pool of persistent database connections.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 Simple Scaling
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Got thousands of processes all trying to connect to the database? You can use
|
|
Packit |
d03604 |
DBD::Gofer to connect them to your smaller pool of 'dbi execute' web servers instead.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 Caching
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Client-side caching is as simple as adding "C<cache=1>" to the DSN.
|
|
Packit |
d03604 |
This feature alone can be worth using DBD::Gofer for.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 Fewer Network Round-trips
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBD::Gofer sends as few requests as possible (dependent on the policy being used).
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 Thin Clients / Unsupported Platforms
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
You no longer need drivers for your database on every system. DBD::Gofer is pure perl.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 CONSTRAINTS
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
There are some natural constraints imposed by the DBD::Gofer 'stateless' approach.
|
|
Packit |
d03604 |
But not many:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 You can't change database handle attributes after connect()
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
You can't change database handle attributes after you've connected.
|
|
Packit |
d03604 |
Use the connect() call to specify all the attribute settings you want.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
This is because it's critical that when a request is complete the database
|
|
Packit |
d03604 |
handle is left in the same state it was when first connected.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
An exception is made for attributes with names starting "C<private_>":
|
|
Packit |
d03604 |
They can be set after connect() but the change is only applied locally.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 You can't change statement handle attributes after prepare()
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
You can't change statement handle attributes after prepare.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
An exception is made for attributes with names starting "C<private_>":
|
|
Packit |
d03604 |
They can be set after prepare() but the change is only applied locally.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 You can't use transactions
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
AutoCommit only. Transactions aren't supported.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
(In theory transactions could be supported when using a transport that
|
|
Packit |
d03604 |
maintains a connection, like C<stream> does. If you're interested in this
|
|
Packit |
d03604 |
please get in touch via dbi-dev@perl.org)
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 You can't call driver-private sth methods
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
But that's rarely needed anyway.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 GENERAL CAVEATS
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
A few important things to keep in mind when using DBD::Gofer:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Temporary tables, locks, and other per-connection persistent state
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
You shouldn't expect any per-session state to persist between requests.
|
|
Packit |
d03604 |
This includes locks and temporary tables.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Because the server-side may execute your requests via a different
|
|
Packit |
d03604 |
database connections, you can't rely on any per-connection persistent state,
|
|
Packit |
d03604 |
such as temporary tables, being available from one request to the next.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
This is an easy trap to fall into. A good way to check for this is to test your
|
|
Packit |
d03604 |
code with a Gofer policy package that sets the C<connect_method> policy to
|
|
Packit |
d03604 |
'connect' to force a new connection for each request. The C<pedantic> policy does this.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Driver-private Database Handle Attributes
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Some driver-private dbh attributes may not be available if the driver has not
|
|
Packit |
d03604 |
implemented the private_attribute_info() method (added in DBI 1.54).
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Driver-private Statement Handle Attributes
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Driver-private sth attributes can be set in the prepare() call. TODO
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Some driver-private sth attributes may not be available if the driver has not
|
|
Packit |
d03604 |
implemented the private_attribute_info() method (added in DBI 1.54).
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Multiple Resultsets
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Multiple resultsets are supported only if the driver supports the more_results() method
|
|
Packit |
d03604 |
(an exception is made for DBD::Sybase).
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Statement activity that also updates dbh attributes
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Some drivers may update one or more dbh attributes after performing activity on
|
|
Packit |
d03604 |
a child sth. For example, DBD::mysql provides $dbh->{mysql_insertid} in addition to
|
|
Packit |
d03604 |
$sth->{mysql_insertid}. Currently mysql_insertid is supported via a hack but a
|
|
Packit |
d03604 |
more general mechanism is needed for other drivers to use.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Methods that report an error always return undef
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
With DBD::Gofer, a method that sets an error always return an undef or empty list.
|
|
Packit |
d03604 |
That shouldn't be a problem in practice because the DBI doesn't define any
|
|
Packit |
d03604 |
methods that return meaningful values while also reporting an error.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Subclassing only applies to client-side
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The RootClass and DbTypeSubclass attributes are not passed to the Gofer server.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 CAVEATS FOR SPECIFIC METHODS
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 last_insert_id
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
To enable use of last_insert_id you need to indicate to DBD::Gofer that you'd
|
|
Packit |
d03604 |
like to use it. You do that my adding a C<go_last_insert_id_args> attribute to
|
|
Packit |
d03604 |
the do() or prepare() method calls. For example:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$dbh->do($sql, { go_last_insert_id_args => [...] });
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
or
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$sth = $dbh->prepare($sql, { go_last_insert_id_args => [...] });
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The array reference should contains the args that you want passed to the
|
|
Packit |
d03604 |
last_insert_id() method.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 execute_for_fetch
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The array methods bind_param_array() and execute_array() are supported.
|
|
Packit |
d03604 |
When execute_array() is called the data is serialized and executed in a single
|
|
Packit |
d03604 |
round-trip to the Gofer server. This makes it very fast, but requires enough
|
|
Packit |
d03604 |
memory to store all the serialized data.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The execute_for_fetch() method currently isn't optimised, it uses the DBI
|
|
Packit |
d03604 |
fallback behaviour of executing each tuple individually.
|
|
Packit |
d03604 |
(It could be implemented as a wrapper for execute_array() - patches welcome.)
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 TRANSPORTS
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBD::Gofer doesn't concern itself with transporting requests and responses to and fro.
|
|
Packit |
d03604 |
For that it uses special Gofer transport modules.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Gofer transport modules usually come in pairs: one for the 'client' DBD::Gofer
|
|
Packit |
d03604 |
driver to use and one for the remote 'server' end. They have very similar names:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBD::Gofer::Transport::<foo>
|
|
Packit |
d03604 |
DBI::Gofer::Transport::<foo>
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Sometimes the transports on the DBD and DBI sides may have different names. For
|
|
Packit |
d03604 |
example DBD::Gofer::Transport::http is typically used with DBI::Gofer::Transport::mod_perl
|
|
Packit |
d03604 |
(DBD::Gofer::Transport::http and DBI::Gofer::Transport::mod_perl modules are
|
|
Packit |
d03604 |
part of the GoferTransport-http distribution).
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Bundled Transports
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Several transport modules are provided with DBD::Gofer:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 null
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The null transport is the simplest of them all. It doesn't actually transport the request anywhere.
|
|
Packit |
d03604 |
It just serializes (freezes) the request into a string, then thaws it back into
|
|
Packit |
d03604 |
a data structure before passing it to DBI::Gofer::Execute to execute. The same
|
|
Packit |
d03604 |
freeze and thaw is applied to the results.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The null transport is the best way to test if your application will work with Gofer.
|
|
Packit |
d03604 |
Just set the DBI_AUTOPROXY environment variable to "C<dbi:Gofer:transport=null;policy=pedantic>"
|
|
Packit |
d03604 |
(see L</Using DBI_AUTOPROXY> below) and run your application, or ideally its test suite, as usual.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
It doesn't take any parameters.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 pipeone
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The pipeone transport launches a subprocess for each request. It passes in the
|
|
Packit |
d03604 |
request and reads the response.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The fact that a new subprocess is started for each request ensures that the
|
|
Packit |
d03604 |
server side is truly stateless. While this does make the transport I<very> slow,
|
|
Packit |
d03604 |
it is useful as a way to test that your application doesn't depend on
|
|
Packit |
d03604 |
per-connection state, such as temporary tables, persisting between requests.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
It's also useful both as a proof of concept and as a base class for the stream
|
|
Packit |
d03604 |
driver.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 stream
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The stream driver also launches a subprocess and writes requests and reads
|
|
Packit |
d03604 |
responses, like the pipeone transport. In this case, however, the subprocess
|
|
Packit |
d03604 |
is expected to handle more that one request. (Though it will be automatically
|
|
Packit |
d03604 |
restarted if it exits.)
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
This is the first transport that is truly useful because it can launch the
|
|
Packit |
d03604 |
subprocess on a remote machine using C<ssh>. This means you can now use DBD::Gofer
|
|
Packit |
d03604 |
to easily access any databases that's accessible from any system you can login to.
|
|
Packit |
d03604 |
You also get all the benefits of ssh, including encryption and optional compression.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
See L</Using DBI_AUTOPROXY> below for an example.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Other Transports
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Implementing a Gofer transport is I<very> simple, and more transports are very welcome.
|
|
Packit |
d03604 |
Just take a look at any existing transports that are similar to your needs.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 http
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
See the GoferTransport-http distribution on CPAN: http://search.cpan.org/dist/GoferTransport-http/
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 Gearman
|
|
Packit |
d03604 |
|
|
Packit Service |
10a460 |
I know Ask Bjørn Hansen has implemented a transport for the C<gearman> distributed
|
|
Packit |
d03604 |
job system, though it's not on CPAN at the time of writing this.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 CONNECTING
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Simply prefix your existing DSN with "C<dbi:Gofer:transport=$transport;dsn=>"
|
|
Packit |
d03604 |
where $transport is the name of the Gofer transport you want to use (see L</TRANSPORTS>).
|
|
Packit |
d03604 |
The C<transport> and C<dsn> attributes must be specified and the C<dsn> attributes must be last.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Other attributes can be specified in the DSN to configure DBD::Gofer and/or the
|
|
Packit |
d03604 |
Gofer transport module being used. The main attributes after C<transport>, are
|
|
Packit |
d03604 |
C<url> and C<policy>. These and other attributes are described below.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Using DBI_AUTOPROXY
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The simplest way to try out DBD::Gofer is to set the DBI_AUTOPROXY environment variable.
|
|
Packit |
d03604 |
In this case you don't include the C<dsn=> part. For example:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
export DBI_AUTOPROXY="dbi:Gofer:transport=null"
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
or, for a more useful example, try:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
export DBI_AUTOPROXY="dbi:Gofer:transport=stream;url=ssh:user@example.com"
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head2 Connection Attributes
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
These attributes can be specified in the DSN. They can also be passed in the
|
|
Packit |
d03604 |
\%attr parameter of the DBI connect method by adding a "C<go_>" prefix to the name.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 transport
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Specifies the Gofer transport class to use. Required. See L</TRANSPORTS> above.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
If the value does not include C<::> then "C<DBD::Gofer::Transport::>" is prefixed.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The transport object can be accessed via $h->{go_transport}.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 dsn
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Specifies the DSN for the remote side to connect to. Required, and must be last.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 url
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Used to tell the transport where to connect to. The exact form of the value depends on the transport used.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 policy
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Specifies the policy to use. See L</CONFIGURING BEHAVIOUR POLICY>.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
If the value does not include C<::> then "C<DBD::Gofer::Policy>" is prefixed.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The policy object can be accessed via $h->{go_policy}.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 timeout
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Specifies a timeout, in seconds, to use when waiting for responses from the server side.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 retry_limit
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Specifies the number of times a failed request will be retried. Default is 0.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 retry_hook
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Specifies a code reference to be called to decide if a failed request should be retried.
|
|
Packit |
d03604 |
The code reference is called like this:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$transport = $h->{go_transport};
|
|
Packit |
d03604 |
$retry = $transport->go_retry_hook->($request, $response, $transport);
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
If it returns true then the request will be retried, up to the C<retry_limit>.
|
|
Packit |
d03604 |
If it returns a false but defined value then the request will not be retried.
|
|
Packit |
d03604 |
If it returns undef then the default behaviour will be used, as if C<retry_hook>
|
|
Packit |
d03604 |
had not been specified.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The default behaviour is to retry requests where $request->is_idempotent is true,
|
|
Packit |
d03604 |
or the error message matches C</induced by DBI_GOFER_RANDOM/>.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head3 cache
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Specifies that client-side caching should be performed. The value is the name
|
|
Packit |
d03604 |
of a cache class to use.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Any class implementing get($key) and set($key, $value) methods can be used.
|
|
Packit |
d03604 |
That includes a great many powerful caching classes on CPAN, including the
|
|
Packit |
d03604 |
Cache and Cache::Cache distributions.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
You can use "C<cache=1>" is a shortcut for "C<cache=DBI::Util::CacheMemory>".
|
|
Packit |
d03604 |
See L<DBI::Util::CacheMemory> for a description of this simple fast default cache.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The cache object can be accessed via $h->go_cache. For example:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$dbh->go_cache->clear; # free up memory being used by the cache
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The cache keys are the frozen (serialized) requests, and the values are the
|
|
Packit |
d03604 |
frozen responses.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The default behaviour is to only use the cache for requests where
|
|
Packit |
d03604 |
$request->is_idempotent is true (i.e., the dbh has the ReadOnly attribute set
|
|
Packit |
d03604 |
or the SQL statement is obviously a SELECT without a FOR UPDATE clause.)
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
For even more control you can use the C<go_cache> attribute to pass in an
|
|
Packit |
d03604 |
instantiated cache object. Individual methods, including prepare(), can also
|
|
Packit |
d03604 |
specify alternative caches via the C<go_cache> attribute. For example, to
|
|
Packit |
d03604 |
specify no caching for a particular query, you could use
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
$sth = $dbh->prepare( $sql, { go_cache => 0 } );
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
This can be used to implement different caching policies for different statements.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
It's interesting to note that DBD::Gofer can be used to add client-side caching
|
|
Packit |
d03604 |
to any (gofer compatible) application, with no code changes and no need for a
|
|
Packit |
d03604 |
gofer server. Just set the DBI_AUTOPROXY environment variable like this:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBI_AUTOPROXY='dbi:Gofer:transport=null;cache=1'
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 CONFIGURING BEHAVIOUR POLICY
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
DBD::Gofer supports a 'policy' mechanism that allows you to fine-tune the number of round-trips to the Gofer server.
|
|
Packit |
d03604 |
The policies are grouped into classes (which may be subclassed) and referenced by the name of the class.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The L<DBD::Gofer::Policy::Base> class is the base class for all the policy
|
|
Packit |
d03604 |
packages and describes all the available policies.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Three policy packages are supplied with DBD::Gofer:
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
L<DBD::Gofer::Policy::pedantic> is most 'transparent' but slowest because it
|
|
Packit |
d03604 |
makes more round-trips to the Gofer server.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
L<DBD::Gofer::Policy::classic> is a reasonable compromise - it's the default policy.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
L<DBD::Gofer::Policy::rush> is fastest, but may require code changes in your applications.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Generally the default C<classic> policy is fine. When first testing an existing
|
|
Packit |
d03604 |
application with Gofer it is a good idea to start with the C<pedantic> policy
|
|
Packit |
d03604 |
first and then switch to C<classic> or a custom policy, for final testing.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 AUTHOR
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Tim Bunce, L<http://www.tim.bunce.name>
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 LICENCE AND COPYRIGHT
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
This module is free software; you can redistribute it and/or
|
|
Packit |
d03604 |
modify it under the same terms as Perl itself. See L<perlartistic>.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 ACKNOWLEDGEMENTS
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
The development of DBD::Gofer and related modules was sponsored by
|
|
Packit |
d03604 |
Shopzilla.com (L<http://Shopzilla.com>), where I currently work.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 SEE ALSO
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
L<DBI::Gofer::Request>, L<DBI::Gofer::Response>, L<DBI::Gofer::Execute>.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
L<DBI::Gofer::Transport::Base>, L<DBD::Gofer::Policy::Base>.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
L<DBI>
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 Caveats for specific drivers
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
This section aims to record issues to be aware of when using Gofer with specific drivers.
|
|
Packit |
d03604 |
It usually only documents issues that are not natural consequences of the limitations
|
|
Packit |
d03604 |
of the Gofer approach - as documented above.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=head1 TODO
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
This is just a random brain dump... (There's more in the source of the Changes file, not the pod)
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Document policy mechanism
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Add mechanism for transports to list config params and for Gofer to apply any that match (and warn if any left over?)
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Driver-private sth attributes - set via prepare() - change DBI spec
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
add hooks into transport base class for checking & updating a result set cache
|
|
Packit |
d03604 |
ie via a standard cache interface such as:
|
|
Packit |
d03604 |
http://search.cpan.org/~robm/Cache-FastMmap/FastMmap.pm
|
|
Packit |
d03604 |
http://search.cpan.org/~bradfitz/Cache-Memcached/lib/Cache/Memcached.pm
|
|
Packit |
d03604 |
http://search.cpan.org/~dclinton/Cache-Cache/
|
|
Packit |
d03604 |
http://search.cpan.org/~cleishman/Cache/
|
|
Packit |
d03604 |
Also caching instructions could be passed through the httpd transport layer
|
|
Packit |
d03604 |
in such a way that appropriate http cache headers are added to the results
|
|
Packit |
d03604 |
so that web caches (squid etc) could be used to implement the caching.
|
|
Packit |
d03604 |
(MUST require the use of GET rather than POST requests.)
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Rework handling of installed_methods to not piggyback on dbh_attributes?
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Perhaps support transactions for transports where it's possible (ie null and stream)?
|
|
Packit |
d03604 |
Would make stream transport (ie ssh) more useful to more people.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Make sth_result_attr more like dbh_attributes (using '*' etc)
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Add @val = FETCH_many(@names) to DBI in C and use in Gofer/Execute?
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
Implement _new_sth in C.
|
|
Packit |
d03604 |
|
|
Packit |
d03604 |
=cut
|