Blame lib/DBD/Gofer.pm

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