📄 gofer.pm
字号:
} # set error/warn/info (after more_results as that'll clear err) DBD::Gofer::set_err_from_response($sth, $response); return $response->rv; } sub bind_param { my ($sth, $param, $value, $attr) = @_; $sth->{ParamValues}{$param} = $value; $sth->{ParamAttr}{$param} = $attr if defined $attr; # attr is sticky if not explicitly set return 1; } sub execute { my $sth = shift; $sth->bind_param($_, $_[$_-1]) for (1..@_); push @{ $sth->{go_method_calls} }, [ 'execute' ]; my $meta = { go_last_insert_id_args => $sth->{go_last_insert_id_args} }; return $sth->go_sth_method($meta); } sub more_results { my $sth = shift; $sth->finish; my $response = $sth->{go_response} or do { # e.g., we haven't sent a request yet (ie prepare then more_results) $sth->trace_msg(" No response object present", 3); return; }; my $resultset_list = $response->sth_resultsets or return $sth->set_err($DBI::stderr, "No sth_resultsets"); my $meta = shift @$resultset_list or return undef; # no more result sets #warn "more_results: ".Data::Dumper::Dumper($meta); # pull out the special non-atributes first my ($rowset, $err, $errstr, $state) = delete @{$meta}{qw(rowset err errstr state)}; # copy meta attributes into attribute cache my $NUM_OF_FIELDS = delete $meta->{NUM_OF_FIELDS}; $sth->STORE('NUM_OF_FIELDS', $NUM_OF_FIELDS); # XXX need to use STORE for some? $sth->{$_} = $meta->{$_} for keys %$meta; if (($NUM_OF_FIELDS||0) > 0) { $sth->{go_rows} = ($rowset) ? @$rowset : -1; $sth->{go_current_rowset} = $rowset; $sth->{go_current_rowset_err} = [ $err, $errstr, $state ] if defined $err; $sth->STORE(Active => 1) if $rowset; } return $sth; } sub go_clone_sth { my ($sth1) = @_; # clone an (un-fetched-from) sth - effectively undoes the initial more_results # not 100% so just for use in caching returned sth e.g. table_info my $sth2 = $sth1->{Database}->prepare($sth1->{Statement}, { go_skip_prepare_check => 1 }); $sth2->STORE($_, $sth1->{$_}) for qw(NUM_OF_FIELDS Active); my $sth2_inner = tied %$sth2; $sth2_inner->{$_} = $sth1->{$_} for qw(NUM_OF_PARAMS FetchHashKeyName); die "not fully implemented yet"; return $sth2; } sub fetchrow_arrayref { my ($sth) = @_; my $resultset = $sth->{go_current_rowset} || do { # should only happen if fetch called after execute failed my $rowset_err = $sth->{go_current_rowset_err} || [ 1, 'no result set (did execute fail)' ]; return $sth->set_err( @$rowset_err ); }; return $sth->_set_fbav(shift @$resultset) if @$resultset; $sth->finish; # no more data so finish return undef; } *fetch = \&fetchrow_arrayref; # alias sub fetchall_arrayref { my ($sth, $slice, $max_rows) = @_; my $resultset = $sth->{go_current_rowset} || do { # should only happen if fetch called after execute failed my $rowset_err = $sth->{go_current_rowset_err} || [ 1, 'no result set (did execute fail)' ]; return $sth->set_err( @$rowset_err ); }; my $mode = ref($slice) || 'ARRAY'; return $sth->SUPER::fetchall_arrayref($slice, $max_rows) if ref($slice) or defined $max_rows; $sth->finish; # no more data after this so finish return $resultset; } sub rows { return shift->{go_rows}; } sub STORE { my ($sth, $attrib, $value) = @_; return $sth->SUPER::STORE($attrib => $value) if $sth_local_store_attrib{$attrib} # handle locally # or it's a private_ (application) attribute or $attrib =~ /^private_/; # otherwise warn but do it anyway # this will probably need refining later my $msg = "Altering \$sth->{$attrib} won't affect proxied handle"; Carp::carp($msg) if $sth->FETCH('Warn'); # XXX could perhaps do # push @{ $sth->{go_method_calls} }, [ 'STORE', $attrib, $value ] # if not $sth->FETCH('Executed'); # but how to handle repeat executions? How to we know when an # attribute is being set to affect the current resultset or the # next execution? # Could just always use go_method_calls I guess. # do the store locally anyway, just in case $sth->SUPER::STORE($attrib => $value); return $sth->set_err($DBI::stderr, $msg); } # sub bind_param_array # we use DBI's default, which sets $sth->{ParamArrays}{$param} = $value # and calls bind_param($param, undef, $attr) if $attr. sub execute_array { my $sth = shift; my $attr = shift; $sth->bind_param_array($_, $_[$_-1]) for (1..@_); push @{ $sth->{go_method_calls} }, [ 'execute_array', $attr ]; return $sth->go_sth_method($attr); } *go_cache = \&DBD::Gofer::go_cache;}1;__END__=head1 NAMEDBD::Gofer - A stateless-proxy driver for communicating with a remote DBI=head1 SYNOPSIS use DBI; $original_dsn = "dbi:..."; # your original DBI Data Source Name $dbh = DBI->connect("dbi:Gofer:transport=$transport;...;dsn=$original_dsn", $user, $passwd, \%attributes); ... use $dbh as if it was connected to $original_dsn ...The C<transport=$transport> part specifies the name of the module to use totransport the requests to the remote DBI. If $transport doesn't contain anydouble colons then it's prefixed with C<DBD::Gofer::Transport::>.The C<dsn=$original_dsn> part I<must be the last element> of the DSN becauseeverything after C<dsn=> is assumed to be the DSN that the remote DBI shoulduse.The C<...> represents attributes that influence the operation of the Goferdriver or transport. These are described below or in the documentation of thetransport module being used.=head1 DESCRIPTIONDBD::Gofer is a DBI database driver that forwards requests to another DBIdriver, usually in a seperate process, often on a separate machine. It tries tobe as transparent as possible so it appears that you are using the remotedriver directly.DBD::Gofer is very similar to DBD::Proxy. The major difference is that withDBD::Gofer no state is maintained on the remote end. That means everyrequest contains all the information needed to create the required state. (So,for example, every request includes the DSN to connect to.) Each request can besent to any available server. The server executes the request and returns asingle response that includes all the data.This is very similar to the way http works as a stateless protocol for the web.Each request from your web browser can be handled by a different web server process.=head2 Use CasesThis may seem like pointless overhead but there are situations where this is avery good thing. Let's consider a specific case.Imagine using DBD::Gofer with an http transport. Your application callsconnect(), prepare("select * from table where foo=?"), bind_param(), and execute().At this point DBD::Gofer builds a request containing all the informationabout the method calls. It then uses the httpd transport to send that requestto an apache web server.This 'dbi execute' web server executes the request (using DBI::Gofer::Executeand related modules) and builds a response that contains all the rows of data,if the statement returned any, along with all the attributes that describe theresults, such as $sth->{NAME}. This response is sent back to DBD::Gofer whichunpacks it and presents it to the application as if it had executed thestatement itself.=head2 AdvantagesOkay, but you still don't see the point? Well let's consider what we've gained:=head3 Connection Pooling and ThrottlingThe 'dbi execute' web server leverages all the functionality of webinfrastructure in terms of load balancing, high-availability, firewalls, accessmanagement, proxying, caching.At its most basic level you get a configurable pool of persistent database connections.=head3 Simple ScalingGot thousands of processes all trying to connect to the database? You can useDBD::Gofer to connect them to your smaller pool of 'dbi execute' web servers instead.=head3 CachingClient-side caching is as simple as adding "C<cache=1>" to the DSN.This feature alone can be worth using DBD::Gofer for.=head3 Fewer Network Round-tripsDBD::Gofer sends as few requests as possible (dependent on the policy being used).=head3 Thin Clients / Unsupported PlatformsYou no longer need drivers for your database on every system. DBD::Gofer is pure perl.=head1 CONSTRAINTSThere are some natural constraints imposed by the DBD::Gofer 'stateless' approach.But not many:=head2 You can't change database handle attributes after connect()You can't change database handle attributes after you've connected.Use the connect() call to specify all the attribute settings you want.This is because it's critical that when a request is complete the databasehandle is left in the same state it was when first connected.An exception is made for attributes with names starting "C<private_>":They can be set after connect() but the change is only applied locally.=head2 You can't change statement handle attributes after prepare()You can't change statment handle attributes after prepare.An exception is made for attributes with names starting "C<private_>":They can be set after prepare() but the change is only applied locally.=head2 You can't use transactionsAutoCommit only. Transactions aren't supported.(In theory transactions could be supported when using a transport thatmaintains a connection, like C<stream> does. If you're interested in thisplease get in touch via dbi-dev@perl.org)=head2 You can't call driver-private sth methodsBut that's rarely needed anyway.=head1 GENERAL CAVEATSA few important things to keep in mind when using DBD::Gofer:=head2 Temporary tables, locks, and other per-connection persistent stateYou shouldn't expect any per-session state to persist between requests.This includes locks and temporary tables.Because the server-side may execute your requests via a differentdatabase connections, you can't rely on any per-connection persistent state,such as temporary tables, being available from one request to the next.This is an easy trap to fall into. A good way to check for this is to test yourcode with a Gofer policy package that sets the C<connect_method> policy to'connect' to force a new connection for each request. The C<pedantic> policy does this.=head2 Driver-private Database Handle AttributesSome driver-private dbh attributes may not be available if the driver has notimplemented the private_attribute_info() method (added in DBI 1.54).=head2 Driver-private Statement Handle AttributesDriver-private sth attributes can be set in the prepare() call. TODOSome driver-private dbh attributes may not be available if the driver has notimplemented the private_attribute_info() method (added in DBI 1.54).=head2 Multiple Resultsets
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -