📄 dbd.pm
字号:
function correctly (see L</Statement attributes> below).We present a simplified implementation by using the I<drv_params>attribute from above: sub bind_param { my ($sth, $pNum, $val, $attr) = @_; my $type = (ref $attr) ? $attr->{TYPE} : $attr; if ($type) { my $dbh = $sth->{Database}; $val = $dbh->quote($sth, $type); } my $params = $sth->{drv_params}; $params->[$pNum-1] = $val; 1; } sub execute { my ($sth, @bind_values) = @_; # start of by finishing any previous execution if still active $sth->finish if $sth->FETCH('Active'); my $params = (@bind_values) ? \@bind_values : $sth->{drv_params}; my $numParam = $sth->FETCH('NUM_OF_PARAMS'); return $sth->set_err($DBI::stderr, "Wrong number of parameters") if @$params != $numParam; my $statement = $sth->{'Statement'}; for (my $i = 0; $i < $numParam; $i++) { $statement =~ s/?/$params->[$i]/; # XXX doesn't deal with quoting etc! } # Do anything ... we assume that an array ref of rows is # created and store it: $sth->{'drv_data'} = $data; $sth->{'drv_rows'} = @$data; # number of rows $sth->STORE('NUM_OF_FIELDS') = $numFields; $sth->{Active} = 1; @$data || '0E0'; }There are a number of things you should note here.We initialize the I<NUM_OF_FIELDS> and I<Active> attributes here,because they are essential for C<bind_columns()> to work.We use attribute C<$sth-E<gt>{Statement}> which we createdwithin C<prepare()>. The attribute C<$sth-E<gt>{Database}>, which isnothing else than the I<dbh>, was automatically created by B<DBI>.Finally, note that (as specified in the B<DBI> specification) we return thestring C<'0E0'> instead of the number 0, so that the result tests true butequal to zero. $sth->execute() or die $sth->errstr;=head4 The execute_array(), execute_for_fetch() and bind_param_array() methodsIn general, DBD's only need to implement C<execute_for_fetch()> andC<bind_param_array>. DBI's default C<execute_array()> will invoke theDBD's C<execute_for_fetch()> as needed.The following sequence describes the interaction betweenDBI C<execute_array> and a DBD's C<execute_for_fetch>:=over=item 1App calls C<$sth-E<gt>execute_array(\%attrs, @array_of_arrays)>=item 2If C<@array_of_arrays> was specified, DBI processes C<@array_of_arrays> by callingDBD's C<bind_param_array()>. Alternately, App may have directly calledC<bind_param_array()>=item 3DBD validates and binds each array=item 4DBI retrieves the validated param arrays from DBD's ParamArray attribute=item 5DBI calls DBD's C<execute_for_fetch($fetch_tuple_sub, \@tuple_status)>,where C<&$fetch_tuple_sub> is a closure to iterate over thereturned ParamArray values, and C<\@tuple_status> is an array to receivethe disposition status of each tuple.=item 6DBD iteratively calls C<&$fetch_tuple_sub> to retrieve parameter tuplesto be added to its bulk database operation/request.=item 7when DBD reaches the limit of tuples it can handle in a single databaseoperation/request, or the C<&$fetch_tuple_sub> indicates no moretuples by returning undef, the DBD executes the bulk operation, andreports the disposition of each tuple in \@tuple_status.=item 8DBD repeats steps 6 and 7 until all tuples are processed.=backE.g., here's the essence of L<DBD::Oracle>'s execute_for_fetch: while (1) { my @tuple_batch; for (my $i = 0; $i < $batch_size; $i++) { push @tuple_batch, [ @{$fetch_tuple_sub->() || last} ]; } last unless @tuple_batch; my $res = ora_execute_array($sth, \@tuple_batch, scalar(@tuple_batch), $tuple_batch_status); push @$tuple_status, @$tuple_batch_status; }Note that DBI's default execute_array()/execute_for_fetch() implementationrequires the use of positional (i.e., '?') placeholders. Driverswhich B<require> named placeholders must either emulate positionalplaceholders (e.g., see L<DBD::Oracle>), or must implement their ownexecute_array()/execute_for_fetch() methods to properly sequence boundparameter arrays.=head4 Fetching dataOnly one method needs to be written for fetching data, C<fetchrow_arrayref()>.The other methods, C<fetchrow_array()>, C<fetchall_arrayref()>, etc, as wellas the database handle's C<select*> methods are part of B<DBI>, and callC<fetchrow_arrayref()> as necessary. sub fetchrow_arrayref { my ($sth) = @_; my $data = $sth->{drv_data}; my $row = shift @$data; if (!$row) { $sth->STORE(Active => 0); # mark as no longer active return undef; } if ($sth->FETCH('ChopBlanks')) { map { $_ =~ s/\s+$//; } @$row; } return $sth->_set_fbav($row); } *fetch = \&fetchrow_arrayref; # required alias for fetchrow_arrayrefNote the use of the method C<_set_fbav()> -- this is required so thatC<bind_col()> and C<bind_columns()> work.If an error occurs which leaves the I<$sth> in a state where remaining rowscan't be fetched then I<Active> should be turned off before the method returns.The C<rows()> method for this driver can be implemented like this: sub rows { shift->{drv_rows} }because it knows in advance how many rows it has fetched.Alternatively you could delete that method and so fallbackto the B<DBI>'s own method which does the right thing basedon the number of calls to C<_set_fbav()>.=head4 The more_results methodIf your driver doesn't support multiple result sets, then don't even implement this method.Otherwise, this method needs to get the statement handle ready to fetch resultsfrom the next result set, if there is one. Typically you'd start with: $sth->finish;then you should delete all the attributes from the attribute cache that may nolonger be relevant for the new result set: delete $sth->{$_} for qw(NAME TYPE PRECISION SCALE ...);for drivers written in C use: hv_delete((HV*)SvRV(sth), "NAME", 4, G_DISCARD); hv_delete((HV*)SvRV(sth), "NULLABLE", 8, G_DISCARD); hv_delete((HV*)SvRV(sth), "NUM_OF_FIELDS", 13, G_DISCARD); hv_delete((HV*)SvRV(sth), "PRECISION", 9, G_DISCARD); hv_delete((HV*)SvRV(sth), "SCALE", 5, G_DISCARD); hv_delete((HV*)SvRV(sth), "TYPE", 4, G_DISCARD);Don't forget to also delete, or update, any driver-private attributes that maynot be correct for the next resultset.The NUM_OF_FIELDS attribute is a special case. It should be set using STORE: $sth->STORE(NUM_OF_FIELDS => 0); /* for DBI <= 1.53 */ $sth->STORE(NUM_OF_FIELDS => $new_value);for drivers written in C use this incantation: /* Adjust NUM_OF_FIELDS - which also adjusts the row buffer size */ DBIc_NUM_FIELDS(imp_sth) = 0; /* for DBI <= 1.53 */ DBIc_STATE(imp_xxh)->set_attr_k(sth, sv_2mortal(newSVpvn("NUM_OF_FIELDS",13)), 0, sv_2mortal(newSViv(mysql_num_fields(imp_sth->result))) );For DBI versions prior to 1.54 you'll also need to explicitly adjust thenumber of elements in the row buffer array (C<DBIc_FIELDS_AV(imp_sth)>)to match the new result set. Fill any new values with newSV(0) not &sv_undef.Alternatively you could free DBIc_FIELDS_AV(imp_sth) and set it to null,but that would mean bind_columns() woudn't work across result sets.=head4 Statement attributesThe main difference between I<dbh> and I<sth> attributes is, that youshould implement a lot of attributes here that are required bythe B<DBI>, such as I<NAME>, I<NULLABLE>, I<TYPE>, etc. SeeL<DBI/Statement Handle Attributes> for a complete list.Pay attention to attributes which are marked as read only, such asI<NUM_OF_PARAMS>. These attributes can only be set the first timea statement is executed. If a statement is prepared, then executedmultiple times, warnings may be generated.You can protect against these warnings, and prevent the recalculationof attributes which might be expensive to calculate (such as theI<NAME> and I<NAME_*> attributes): my $storedNumParams = $sth->FETCH('NUM_OF_PARAMS'); if (!defined $storedNumParams or $storedNumFields < 0) { $sth->STORE('NUM_OF_PARAMS') = $numParams; # Set other useful attributes that only need to be set once # for a statement, like $sth->{NAME} and $sth->{TYPE} }One particularly important attribute to set correctly (mentioned inL<DBI/ATTRIBUTES COMMON TO ALL HANDLES> is I<Active>. Many B<DBI> methods,including C<bind_columns()>, depend on this attribute.Besides that the C<STORE()> and C<FETCH()> methods are mainly the sameas above for I<dbh>'s.=head4 Other statement methodsA trivial C<finish()> method to discard stored data, reset any attributes(such as I<Active>) and do C<$sth-E<gt>SUPER::finish()>.If you've defined a C<parse_trace_flag()> method in B<::db> you'll also wantit in B<::st>, so just alias it in: *parse_trace_flag = \&DBD::foo:db::parse_trace_flag;And perhaps some other methods that are not part of the B<DBI>specification, in particular to make metadata available.Remember that they must have names that begin with your driversregistered prefix so they can be installed using C<install_method()>.If C<DESTROY()> is called on a statement handle that's still active(C<$sth-E<gt>{Active}> is true) then it should effectively call C<finish()>. sub DESTROY { my $sth = shift; $sth->finish if $sth->FETCH('Active'); }=head2 TestsThe test process should conform as closely as possibly to the Perlstandard test harness.In particular, most (all) of the tests should be run in the F<t> sub-directory,and should simply produce an C<ok> when run under C<make test>.For details on how this is done, see the Camel book and the section inChapter 7, "The Standard Perl Library" on L<Test::Harness>.The tests may need to adapt to the type of database which is being usedfor testing, and to the privileges of the user testing the driver. Forexample, the B<DBD::Informix> test code has to adapt in a number ofplaces to the type of database to which it is connected as differentInformix databases have different capabilities: some of the tests arefor databases without transaction logs; others are for databases with atransaction log; some versions of the server have support for blobs, orstored procedures, or user-defined data types, and others do not.When a complete file of tests must be skipped, you can provide a reasonin a pseudo-comment: if ($no_transactions_available) { print "1..0 # Skip: No transactions available\n"; exit 0; }Consider downloading the B<DBD::Informix> code and look at the code inF<DBD/Informix/TestHarness.pm> which is used throughout theB<DBD::Informix> tests in the F<t> sub-directory.=head1 CREATING A C/XS DRIVERCreating a new C/XS driver from scratch will always be a daunting task.You can and should greatly simplify your task by taking a goodreference driver implementation and modifying that to match thedatabase product for which you are writing a driver.The de facto reference driver has been the one for B<DBD::Oracle> writtenby Tim Bunce, who is also the author of the B<DBI> package. The B<DBD::Oracle>module is a good example of a driver implemented around a C-level API.Nowadays it it seems better to base on B<DBD::ODBC>, another drivermaintained by Tim and Jeff Urlwin, because it offers a lot of metadataand seems to become the guideline for the future development. (Also asB<DBD::Oracle> digs deeper into the Oracle 8 OCI interface it'll get evenmore hairy than it is now.)The B<DBD::Informix> driver is one driver implemented using embedded SQLinstead of a function-based API.B<DBD::Ingres> may also be worth a look.=head2 C/XS version of Driver.pmA lot of the code in the F<Driver.pm> file is very similar to the code for pure Perl modules- see above. However,there are also some subtle (and not so subtle) differences, including:=over 8=item *The variables I<$DBD::Driver::{dr|db|st}::imp_data_size> are not definedhere, but in the XS code, because they declare the size of certainC structures.=item *Some methods are typically moved to the XS code, in particularC<prepare()>, C<execute()>, C<disconnect()>, C<disconnect_all()> and theC<STORE()> and C<FETCH()> methods.=item *Other methods are still part of F<Driver.pm>, but have callbacks tothe XS code.=item *If the driver-specific parts of the I<imp_drh_t> structure need to beformally initialized (which does not seem to be a common requirement),then you need to add a call to an appropriate XS function in the drivermethod of C<DBD::Driver::driver()>, and you define the corresponding functionin F<Driver.xs>, and you define the C code in F<dbdimp.c> and the prototype inF<dbdimp.h>.For example, B<DBD::Informix> has such a requirement, and adds thefollowing call after the call to C<_new_drh()> in F<Informix.pm>: DBD::Informix::dr::driver_init($drh);and the following code in F<Informix.xs>: # Initialize the DBD::Informix driver data structure void driver_init(drh) SV *drh CODE:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -