📄 dbd.pm
字号:
Since DBI v1.06, if a I<dbd_db_login6> macro is defined (for a functionwith 6 arguments), it will be used instead with the attribute hashpassed as the sixth argument.People used to just pick Oracle's dbdimp.c and use the same names,structures and types.I strongly recommend against that.At first glance this saves time, but your implementation will be lessreadable.It was just hell when I had to separate DBI specific parts, Oraclespecific parts, mSQL specific parts and mysql specific parts inDBD::mysql's I<dbdimp.h> and I<dbdimp.c>.(DBD::mysql was a port of DBD::mSQL which was based on DBD::Oracle.)[Seconded, based on the experience taking DBD::Informix apart, eventhough the version inherited in 1996 was only based on DBD::Oracle.]This part of the driver is I<your exclusive part>.Rewrite it from scratch, so it will be clean and short: in other words,a better piece of code.(Of course keep an eye on other people's work.) struct imp_drh_st { dbih_drc_t com; /* MUST be first element in structure */ /* Insert your driver handle attributes here */ }; struct imp_dbh_st { dbih_dbc_t com; /* MUST be first element in structure */ /* Insert your database handle attributes here */ }; struct imp_sth_st { dbih_stc_t com; /* MUST be first element in structure */ /* Insert your statement handle attributes here */ }; /* Rename functions for avoiding name clashes; prototypes are */ /* in dbd_xst.h */ #define dbd_init drv_dr_init #define dbd_db_login6 drv_db_login #define dbd_db_do drv_db_do ... many more here ...These structures implement your private part of the handles.You I<have> to use the name I<imp_dbh_{dr|db|st}> and the first fieldI<must> be of type I<dbih_drc_t|_dbc_t|_stc_t> and I<must> be calledC<com>.You should never access these fields directly, except by using theI<DBIc_xxx> macros below.=head2 Implementation source dbdimp.cConventionally, I<dbdimp.c> is the main implementation file (butDBD::Informix calls the file dbdimp.ec).This section includes a short note on each function that is used in theDriver.xsi template and thus B<has> to be implemented.Of course, you will probably also need to implement other supportfunctions, which should usually be file static if the are placed inI<dbdimp.c>.If they are placed in other files, you need to list those files inMakefile.PL (and MANIFEST) to handle them correctly.It is wise to adhere to a namespace convention for your functions toavoid conflicts.For example, for a driver with prefix "drv", you might call externallyvisible functions "dbd_drv_xxxx".You should also avoid non-constant global variables as much as possibleto improve the support for threading.Since Perl 5.6 requires support for function prototypes (ANSI or ISO orStandard C), you should write your code using function prototypes too.Although technically DBI still supports Perl 5.005_03, which did notmandate prototype support from the C compiler, the only platform whereprototypes are a problem is on HP-UX with the bundled C compiler (whichis strictly K&R).The solution for that is to get a copy of the GNU Compiler Collection(GCC, aka the GNU C Compiler) for HP-UX.It is possible to use either the unmapped names such as C<dbd_init> orthe mapped names such as C<dbd_ix_dr_init> in the C<dbdimp.c> file.DBD::Informix uses the mapped names which makes it easier to identifywhere to look for linkage problems at runtime (which will report errorsusing the mapped names).Most other drivers, and in particular DBD::Oracle, use the unmappednames in the source code which makes it a little easier to compare codebetween drivers and eases discussions on the dbi-dev mailing list.The majority of the code fragments here will use the unmapped names.Ultimately, you should provide implementations for most fo the functionslisted in the I<dbd_xsh.h> header.The exceptions are optional functions (such as I<dbd_st_rows>) and thosefunctions with alternative signatures, such as I<dbd_db_login6> andI<dbd_db_login>.Then you should only implement one of the alternatives, and generallythe newer one of the alternatives.=head3 The dbd_init method #include "Driver.h" DBISTATE_DECLARE; void dbd_init(dbistate_t* dbistate) { DBISTATE_INIT; /* Initialize the DBI macros */ }The C<dbd_init> function will be called when your driver is firstloaded; the bootstrap command in DBD::Driver::dr::driver triggers this,and the call is generated in the BOOT section of Driver.xst.These statements are needed to allow your driver to use the DBI macros.They will include your private header file I<dbdimp.h> in turn.Note that DBISTATE_INIT requires the name of the argument to I<dbd_init>to be called I<dbistate>.=head3 The dbd_drv_error methodYou need a function to record errors so DBI can access them properly.You can call it whatever you like, but we'll call it C<dbd_drv_error>here.The argument list depends on your database software; different systemsprovide different ways to get at error information. static void dbd_drv_error(SV *h, int rc, const char *what) {Note that I<h> is a generic handle, may it be a driver handle, adatabase or a statement handle. D_imp_xxh(h);This macro will declare and initialize a variable I<imp_xxh> witha pointer to your private handle pointer. You may cast this toto I<imp_drh_t>, I<imp_dbh_t> or I<imp_sth_t>.To record the error correctly, equivalent to the set_err() method,use one of the DBIh_SET_ERR_CHAR(...) or DBIh_SET_ERR_SV(...) macros,which were added in DBI 1.41: DBIh_SET_ERR_SV(h, imp_xxh, err, errstr, state, method); DBIh_SET_ERR_CHAR(h, imp_xxh, err_c, err_i, errstr, state, method);For DBIh_SET_ERR_SV the err, errstr, state, and method parameters are SV*.For DBIh_SET_ERR_CHAR the err_c, errstr, state, method are char*.The err_i parameter is an IV that's used instead of err_c is err_c is Null.The method parameter can be ignored.The DBIh_SET_ERR_CHAR macro is usually the simplest to use when youjust have an integer error code and an error message string: DBIh_SET_ERR_CHAR(h, imp_xxh, Nullch, rc, what, Nullch, Nullch);As you can see, any parameters that aren't relevant to you can be Null.To make drivers compatible with DBI < 1.41 you should be using dbivport.has described in L</Driver.h> above.The (obsolete) macros such as DBIh_EVENT2 should be removed from drivers.The names I<dbis> and I<DBIS>, which were used in previous versions of this document,should be replaced with the C<DBIc_STATE(imp_xxh)> macro.The name DBILOGFP, which was also used in previous versions of this document, should bereplaced by DBIc_LOGPIO(imp_xxh).Your code should not call the C C<E<lt>stdio.hE<gt>> I/O functions; you should useC<PerlIO_printf>() as shown: if (DBIc_TRACE_LEVEL(imp_xxh) >= 2) PerlIO_printf(DBIc_LOGPIO(imp_xxh), "foobar %s: %s\n", foo, neatsvpv(errstr,0));That's the first time we see how tracing works within a DBIdriver. Make use of this as often as you can! But don't output anythingat a trace level less than 3. Levels 1 and 2 are reserved for the DBI.You can define up to 8 private trace flags using the top 8 bits ofDBIc_TRACE_FLAGS(imp), that is: 0xFF000000. See the parse_trace_flag() methodelsewhere in this document.=head3 The dbd_dr_data_sources methodThis method is optional; the support for it was added in DBI v1.33.As noted in the discussion of Driver.pm, if the data sources can bedetermined by pure Perl code, do it that way.If, as in DBD::Informix, the information is obtained by a C functioncall, then you need to define a function that matches the prototype: extern AV *dbd_dr_data_sources(SV *drh, imp_drh_t *imp_drh, SV *attrs);An outline implementation for DBD::Informix follows, assuming that thesqgetdbs() function call shown will return up to 100 databases names,with the pointers to each name in the array dbsname and the name stringsthemselves being stores in dbsarea.The actual DBD::Informix implementation has a number of extra lines ofcode, logs function entry and exit, reports the error from sqgetdbs(),and uses #define'd constatnts for the array sizes. AV *dbd_dr_data_sources(SV *drh, imp_drh_t *imp_drh, SV *attr) { int ndbs; int i; char *dbsname[100]; char dbsarea[10000]; AV *av = Nullav; if (sqgetdbs(&ndbs, dbsname, 100, dbsarea, sizeof(dbsarea)) == 0) { av = NewAV(); av_extend(av, (I32)ndbs); sv_2mortal((SV *)av); for (i = 0; i < ndbs; i++) av_store(av, i, newSVpvf("dbi:Informix:%s", dbsname[i])); } return(av); }=head3 The dbd_db_login6 method int dbd_db_login6(SV* dbh, imp_dbh_t* imp_dbh, char* dbname, char* user, char* auth, SV *attr);This function will really connect to the database.The argument I<dbh> is the database handle.I<imp_dbh> is the pointer to the handles private data, as is I<imp_xxx>in I<dbd_drv_error> above.The arguments I<dbname>, I<user>, I<auth> and I<attr> correspond to thearguments of the driver handle's I<connect> method.You will quite often use database specific attributes here, that arespecified in the DSN.I recommend you parse the DSN (using Perl) within the I<connect> methodand pass the segments of the DSN via the attributes parameter throughI<_login> to I<dbd_db_login6>.Here's how you fetch them; as an example we use I<hostname> attribute,which can be up to 12 characters long excluding null terminator: SV** svp; STRLEN len; char* hostname; if ( (svp = DBD_ATTRIB_GET_SVP(attr, "drv_hostname", 12)) && SvTRUE(*svp)) { hostname = SvPV(*svp, len); DBD__ATTRIB_DELETE(attr, "drv_hostname", 12); /* avoid later STORE */ } else { hostname = "localhost"; }Note that you can also obtain standard attributes such as AutoCommit andChopBlanks from the attributes parameter, using DBD_ATTRIB_GET_IV forinteger attributes.If, for example, your database does not support transactions butAutoCommit is set off (requesting transaction support), then you canemulate a 'failure to connect'.Now you should really connect to the database.In general, if the connection fails, it is best to ensure that allallocated resources are released so that the handle does not need to bedestroyed separately.If you are successful (and possibly even if you fail but you haveallocated some resources), you should use the following macros: DBIc_IMPSET_on(imp_dbh);This indicates that the driver (implementor) has allocated resources inthe imp_dbh structure and that the implementors private dbd_db_destroyfunction should be called when the handle is destroyed. DBIc_ACTIVE_on(imp_dbh);This indicates that the handle has an active connection to the serverand that the dbd_db_disconnect function should be called before thehandle is destroyed.Note that if you do need to fail, you should report errors via the drhor imp_drh rather than via dbh or imp_dbh because imp_dbh will bedestroyed by the failure, so errors recorded in that handle will not bevisible to DBI, and hence not the user either.Note to that the function is passed dbh and imp_dbh, and there is amacro D_imp_drh_from_dbh which can recover the imp_drh from the imp_dbh,but there is no DBI macro to provide you with the drh given either theimp_dbh or the dbh or the imp_drh (and there's no way to recover the dbhgiven just the imp_dbh).This suggests that despite the notes about dbd_drv_error above taking anSV *, it may be better to have two error routines, one taking imp_dbhand one taking imp_drh instead.With care, you can factor most of the formatting code out so that theseare small routines calling onto a common error formatter.See the code in DBD::Informix 1.05.00 for more information.The I<dbd_db_login6> function should return TRUE for success, FALSE otherwise.Drivers implemented long ago may define the five-argument functionI<dbd_db_login> instead of I<dbd_db_login6>.The missing argument is the attributes.There are ways to work around the missing attributes, but they areungainly; it is much better to use the 6-argument form.=head3 The dbd_db_commit and dbd_db_rollback methods int dbd_db_commit(SV *dbh, imp_dbh_t *imp_dbh); int dbd_db_rollback(SV* dbh, imp_dbh_t* imp_dbh);These are used for commit and rollback. They should return TRUE forsuccess, FALSE for error.The arguments I<dbh> and I<imp_dbh> are the same as for I<dbd_db_login6>above; I will omit describing them in what follows, as they appearalways.These functions should return TRUE for success, FALSE otherwise.=head3 The dbd_db_disconnect methodThis is your private part of the I<disconnect> method. Any dbh withthe I<ACTIVE> flag on must be disconnected. (Note that you have to setit in I<dbd_db_connect> above.) int dbd_db_disconnect(SV* dbh, imp_dbh_t* imp_dbh);The database handle will return TRUE for success, FALSE otherwise.In any case it should do a: DBIc_ACTIVE_off(imp_dbh);before returning so DBI knows that I<dbd_db_disconnect> was executed.Note that there's nothing to stop a dbh being I<disconnected> while itstill have active children.If your database API reacts badly to trying to use an sth in thissituation then you'll need to add code like this to all sth methods: if (!DBIc_ACTIVE(DBIc_PARENT_COM(imp_sth))) return 0;Alternatively, you can add code to your driver to keep explicit track ofthe statement handles that exist for each database handle and arrange todestroy those handles before disconnecting from the database.There is code to do this in DBD::Informix.Similar comments apply to the driver handle keeping track of all thedatabase handles.Note that the code which destroys the subordinate handles should onlyrelease the associated database resources and mark the handles inactive;it does not attempt to free the actual handle structures.This function should return TRUE for success, FALSE otherwise, butit is not clear what anything can do about a failure.=head3 The dbd_db_discon_all method int dbd_discon_all (SV *drh, imp_drh_t *imp_drh);This function may be called at shutdown time. It should makebest-efforts to disconnect all database handles - if possible. Somedatabases don't support that, in which case you can do nothingbut return 'success'.This function should return TRUE for success, FALSE otherwise, butit is not clear what
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -