summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Changes2275
-rw-r--r--DBI.pm8323
-rw-r--r--DBI.xs5560
-rw-r--r--DBIXS.h573
-rw-r--r--Driver.xst778
-rw-r--r--Driver_xst.h122
-rw-r--r--MANIFEST121
-rw-r--r--META.json67
-rw-r--r--META.yml41
-rw-r--r--Makefile.PL397
-rw-r--r--Perl.xs54
-rw-r--r--README145
-rw-r--r--TODO_2005.txt579
-rw-r--r--TODO_gofer.txt56
-rw-r--r--dbd_xsh.h58
-rw-r--r--dbi_sql.h96
-rw-r--r--dbilogstrip.PL71
-rw-r--r--dbipport.h7258
-rw-r--r--dbiprof.PL287
-rw-r--r--dbiproxy.PL208
-rw-r--r--dbivport.h52
-rw-r--r--dbixs_rev.h4
-rw-r--r--dbixs_rev.pl51
-rw-r--r--ex/corogofer.pl32
-rw-r--r--ex/perl_dbi_nulls_test.pl176
-rw-r--r--ex/profile.pl25
-rw-r--r--lib/Bundle/DBI.pm51
-rw-r--r--lib/DBD/DBM.pm1461
-rw-r--r--lib/DBD/ExampleP.pm428
-rw-r--r--lib/DBD/File.pm1637
-rw-r--r--lib/DBD/File/Developers.pod556
-rw-r--r--lib/DBD/File/HowTo.pod270
-rw-r--r--lib/DBD/File/Roadmap.pod176
-rw-r--r--lib/DBD/Gofer.pm1292
-rw-r--r--lib/DBD/Gofer/Policy/Base.pm162
-rw-r--r--lib/DBD/Gofer/Policy/classic.pm79
-rw-r--r--lib/DBD/Gofer/Policy/pedantic.pm53
-rw-r--r--lib/DBD/Gofer/Policy/rush.pm90
-rw-r--r--lib/DBD/Gofer/Transport/Base.pm410
-rw-r--r--lib/DBD/Gofer/Transport/corostream.pm144
-rw-r--r--lib/DBD/Gofer/Transport/null.pm111
-rw-r--r--lib/DBD/Gofer/Transport/pipeone.pm253
-rw-r--r--lib/DBD/Gofer/Transport/stream.pm292
-rw-r--r--lib/DBD/NullP.pm166
-rw-r--r--lib/DBD/Proxy.pm997
-rw-r--r--lib/DBD/Sponge.pm305
-rw-r--r--lib/DBI/Const/GetInfo/ANSI.pm236
-rw-r--r--lib/DBI/Const/GetInfo/ODBC.pm1363
-rw-r--r--lib/DBI/Const/GetInfoReturn.pm105
-rw-r--r--lib/DBI/Const/GetInfoType.pm54
-rw-r--r--lib/DBI/DBD.pm3489
-rw-r--r--lib/DBI/DBD/Metadata.pm493
-rw-r--r--lib/DBI/DBD/SqlEngine.pm1232
-rw-r--r--lib/DBI/DBD/SqlEngine/Developers.pod422
-rw-r--r--lib/DBI/DBD/SqlEngine/HowTo.pod218
-rw-r--r--lib/DBI/FAQ.pm966
-rw-r--r--lib/DBI/Gofer/Execute.pm900
-rw-r--r--lib/DBI/Gofer/Request.pm200
-rw-r--r--lib/DBI/Gofer/Response.pm218
-rw-r--r--lib/DBI/Gofer/Serializer/Base.pm64
-rw-r--r--lib/DBI/Gofer/Serializer/DataDumper.pm53
-rw-r--r--lib/DBI/Gofer/Serializer/Storable.pm59
-rw-r--r--lib/DBI/Gofer/Transport/Base.pm176
-rw-r--r--lib/DBI/Gofer/Transport/pipeone.pm61
-rw-r--r--lib/DBI/Gofer/Transport/stream.pm76
-rw-r--r--lib/DBI/Profile.pm949
-rw-r--r--lib/DBI/ProfileData.pm737
-rw-r--r--lib/DBI/ProfileDumper.pm351
-rw-r--r--lib/DBI/ProfileDumper/Apache.pm219
-rw-r--r--lib/DBI/ProfileSubs.pm50
-rw-r--r--lib/DBI/ProxyServer.pm890
-rw-r--r--lib/DBI/PurePerl.pm1259
-rw-r--r--lib/DBI/SQL/Nano.pm1010
-rw-r--r--lib/DBI/Util/CacheMemory.pm117
-rw-r--r--lib/DBI/Util/_accessor.pm65
-rw-r--r--lib/DBI/W32ODBC.pm181
-rw-r--r--lib/Win32/DBIODBC.pm248
-rwxr-xr-xt/01basics.t336
-rwxr-xr-xt/02dbidrv.t254
-rw-r--r--t/03handle.t410
-rw-r--r--t/04mods.t59
-rw-r--r--t/05concathash.t190
-rw-r--r--t/06attrs.t311
-rw-r--r--t/07kids.t102
-rw-r--r--t/08keeperr.t291
-rw-r--r--t/09trace.t137
-rw-r--r--t/10examp.t579
-rw-r--r--t/11fetch.t124
-rw-r--r--t/12quote.t48
-rw-r--r--t/13taint.t133
-rw-r--r--t/14utf8.t76
-rw-r--r--t/15array.t254
-rw-r--r--t/16destroy.t147
-rw-r--r--t/19fhtrace.t306
-rw-r--r--t/20meta.t32
-rw-r--r--t/30subclass.t182
-rw-r--r--t/31methcache.t153
-rw-r--r--t/35thrclone.t81
-rw-r--r--t/40profile.t485
-rw-r--r--t/41prof_dump.t105
-rw-r--r--t/42prof_data.t150
-rw-r--r--t/43prof_env.t52
-rw-r--r--t/48dbi_dbd_sqlengine.t81
-rw-r--r--t/49dbd_file.t174
-rwxr-xr-xt/50dbm_simple.t264
-rw-r--r--t/51dbm_file.t130
-rw-r--r--t/52dbm_complex.t359
-rwxr-xr-xt/60preparse.t148
-rw-r--r--t/65transact.t35
-rw-r--r--t/70callbacks.t207
-rw-r--r--t/72childhandles.t149
-rw-r--r--t/80proxy.t473
-rw-r--r--t/85gofer.t264
-rw-r--r--t/86gofer_fail.t168
-rw-r--r--t/87gofer_cache.t108
-rw-r--r--t/90sql_type_cast.t148
-rw-r--r--t/lib.pl33
-rw-r--r--t/pod-coverage.t8
-rw-r--r--t/pod.t8
-rwxr-xr-xtest.pl201
-rw-r--r--typemap3
121 files changed, 60761 insertions, 0 deletions
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..2351905
--- /dev/null
+++ b/Changes
@@ -0,0 +1,2275 @@
+=head1 NAME
+
+DBI::Changes - List of significant changes to the DBI
+
+(As of $Date: 2012-06-06 17:37:26 +0100 (Wed, 06 Jun 2012) $ $Revision: 15327 $)
+
+=encoding ISO8859-1
+
+=cut
+
+=head2 Changes in DBI 1.622 (svn r15327) 6th June 2012
+
+ Fixed lack of =encoding in non-ASCII pod docs. RT#77588
+
+ Corrected typo in DBI::ProfileDumper thanks to Finn Hakansson.
+
+=head2 Changes in DBI 1.621 (svn r15315) 21st May 2012
+
+ Fixed segmentation fault when a thread is created from
+ within another thread RT#77137, thanks to Dave Mitchell.
+ Updated previous Changes to credit Booking.com for sponsoring
+ Dave Mitchell's recent DBI optimization work.
+
+=head2 Changes in DBI 1.620 (svn r15300) 25th April 2012
+
+ Modified column renaming in fetchall_arrayref, added in 1.619,
+ to work on column index numbers not names (an incompatible change).
+ Reworked the fetchall_arrayref documentation.
+ Hash slices in fetchall_arrayref now detect invalid column names.
+
+=head2 Changes in DBI 1.619 (svn r15294) 23rd April 2012
+
+ Fixed the connected method to stop showing the password in
+ trace file (Martin J. Evans).
+ Fixed _install_method to set CvFILE correctly
+ thanks to sprout RT#76296
+ Fixed SqlEngine "list_tables" thanks to David McMath
+ and Norbert Gruener. RT#67223 RT#69260
+
+ Optimized DBI method dispatch thanks to Dave Mitchell.
+ Optimized driver access to DBI internal state thanks to Dave Mitchell.
+ Optimized driver access to handle data thanks to Dave Mitchell.
+ Dave's work on these optimizations was sponsored by Booking.com.
+ Optimized fetchall_arrayref with hash slice thanks
+ to Dagfinn Ilmari Mannsåker. RT#76520
+ Allow renaming columns in fetchall_arrayref hash slices
+ thanks to Dagfinn Ilmari Mannsåker. RT#76572
+ Reserved snmp_ and tree_ for DBD::SNMP and DBD::TreeData
+
+=head2 Changes in DBI 1.618 (svn r15170) 25rd February 2012
+
+ Fixed compiler warnings in Driver_xst.h (Martin J. Evans)
+ Fixed compiler warning in DBI.xs (H.Merijn Brand)
+ Fixed Gofer tests failing on Windows RT74975 (Manoj Kumar)
+ Fixed my_ctx compile errors on Windows (Dave Mitchell)
+
+ Significantly optimized method dispatch via cache (Dave Mitchell)
+ Significantly optimized DBI internals for threads (Dave Mitchell)
+ Dave's work on these optimizations was sponsored by Booking.com.
+ Xsub to xsub calling optimization now enabled for threaded perls.
+ Corrected typo in example in docs (David Precious)
+ Added note that calling clone() without an arg may warn in future.
+ Minor changes to the install_method() docs in DBI::DBD.
+ Updated dbipport.h from Devel::PPPort 3.20
+
+=head2 Changes in DBI 1.617 (svn r15107) 30th January 2012
+
+ NOTE: The officially supported minimum perl version will change
+ from perl 5.8.1 (2003) to perl 5.8.3 (2004) in a future release.
+ (The last change, from perl 5.6 to 5.8.1, was announced
+ in July 2008 and implemented in DBI 1.611 in April 2010.)
+
+ Fixed ParamTypes example in the pod (Martin J. Evans)
+ Fixed the definition of ArrayTupleStatus and remove confusion over
+ rows affected in list context of execute_array (Martin J. Evans)
+ Fixed sql_type_cast example and typo in errors (Martin J. Evans)
+ Fixed Gofer error handling for keeperr methods like ping (Tim Bunce)
+ Fixed $dbh->clone({}) RT73250 (Tim Bunce)
+ Fixed is_nested_call logic error RT73118 (Reini Urban)
+
+ Enhanced performance for threaded perls (Dave Mitchell, Tim Bunce)
+ Dave's work on this optimization was sponsored by Booking.com.
+ Enhanced and standardized driver trace level mechanism (Tim Bunce)
+ Removed old code that was an inneffective attempt to detect
+ people doing DBI->{Attrib}.
+ Clear ParamValues on bind_param param count error RT66127 (Tim Bunce)
+ Changed DBI::ProxyServer to require DBI at compile-time RT62672 (Tim Bunce)
+
+ Added pod for default_user to DBI::DBD (Martin J. Evans)
+ Added CON, ENC and DBD trace flags and extended 09trace.t (Martin J. Evans)
+ Added TXN trace flags and applied CON and TXN to relevant methods (Tim Bunce)
+ Added some more fetchall_arrayref(..., $maxrows) tests (Tim Bunce)
+ Clarified docs for fetchall_arrayref called on an inactive handle.
+ Clarified docs for clone method (Tim Bunce)
+ Added note to DBI::Profile about async queries (Marcel Grünauer).
+ Reserved spatialite_ as a driver prefix for DBD::Spatialite
+ Reserved mo_ as a driver prefix for DBD::MO
+ Updated link to the SQL Reunion 95 docs, RT69577 (Ash Daminato)
+ Changed links for DBI recipes. RT73286 (Martin J. Evans)
+
+=head2 Changes in DBI 1.616 (svn r14616) 30th December 2010
+
+ Fixed spurious dbi_profile lines written to the log when
+ profiling is enabled and a trace flag, like SQL, is used.
+ Fixed to recognize SQL::Statement errors even if instantiated
+ with RaiseError=0 (Jens Rehsack)
+ Fixed RT#61513 by catching attribute assignment to tied table access
+ interface (Jens Rehsack)
+ Fixing some misbehavior of DBD::File when running within the Gofer
+ server.
+ Fixed compiler warnings RT#62640
+
+ Optimized connect() to remove redundant FETCH of \%attrib values.
+ Improved initialization phases in DBI::DBD::SqlEngine (Jens Rehsack)
+
+ Added DBD::Gofer::Transport::corostream. An experimental proof-of-concept
+ transport that enables asynchronous database calls with few code changes.
+ It enables asynchronous use of DBI frameworks like DBIx::Class.
+
+ Added additional notes on DBDs which avoid creating a statement in
+ the do() method and the effects on error handlers (Martin J. Evans)
+ Adding new attribute "sql_dialect" to DBI::DBD::SqlEngine to allow
+ users control used SQL dialect (ANSI, CSV or AnyData), defaults to
+ CSV (Jens Rehsack)
+ Add documentation for DBI::DBD::SqlEngine attributes (Jens Rehsack)
+ Documented dbd_st_execute return (Martin J. Evans)
+ Fixed typo in InactiveDestroy thanks to Emmanuel Rodriguez.
+
+=head2 Changes in DBI 1.615 (svn r14438) 21st September 2010
+
+ Fixed t/51dbm_file for file/directory names with whitespaces in them
+ RT#61445 (Jens Rehsack)
+ Fixed compiler warnings from ignored hv_store result (Martin J. Evans)
+ Fixed portability to VMS (Craig A. Berry)
+
+=head2 Changes in DBI 1.614 (svn r14408) 17th September 2010
+
+ Fixed bind_param () in DBI::DBD::SqlEngine (rt#61281)
+ Fixed internals to not refer to old perl symbols that
+ will no longer be visible in perl >5.13.3 (Andreas Koenig)
+ Many compiled drivers are likely to need updating.
+ Fixed issue in DBD::File when absolute filename is used as table name
+ (Jens Rehsack)
+ Croak manually when file after tie doesn't exists in DBD::DBM
+ when it have to exists (Jens Rehsack)
+ Fixed issue in DBD::File when users set individual file name for tables
+ via f_meta compatibility interface - reported by H.Merijn Brand while
+ working on RT#61168 (Jens Rehsack)
+
+ Changed 50dbm_simple to simplify and fix problems (Martin J. Evans)
+ Changed 50dbm_simple to skip aggregation tests when not using
+ SQL::Statement (Jens Rehsack)
+ Minor speed improvements in DBD::File (Jens Rehsack)
+
+ Added $h->{AutoInactiveDestroy} as simpler safer form of
+ $h->{InactiveDestroy} (David E. Wheeler)
+ Added ability for parallel testing "prove -j4 ..." (Jens Rehsack)
+ Added tests for delete in DBM (H.Merijn Brand)
+ Added test for absolute filename as table to 51dbm_file (Jens Rehsack)
+ Added two initialization phases to DBI::DBD::SqlEngine (Jens Rehsack)
+ Added improved developers documentation for DBI::DBD::SqlEngine
+ (Jens Rehsack)
+ Added guides how to write DBI drivers using DBI::DBD::SqlEngine
+ or DBD::File (Jens Rehsack)
+ Added register_compat_map() and table_meta_attr_changed() to
+ DBD::File::Table to support clean fix of RT#61168 (Jens Rehsack)
+
+=head2 Changes in DBI 1.613 (svn r14271) 22nd July 2010
+
+ Fixed Win32 prerequisite module from PathTools to File::Spec.
+
+ Changed attribute headings and fixed references in DBI pod (Martin J. Evans)
+ Corrected typos in DBI::FAQ and DBI::ProxyServer (Ansgar Burchardt)
+
+=head2 Changes in DBI 1.612 (svn r14254) 16th July 2010
+
+NOTE: This is a minor release for the DBI core but a major release for
+DBD::File and drivers that depend on it, like DBD::DBM and DBD::CSV.
+
+This is also the first release where the bulk of the development work
+has been done by other people. I'd like to thank (in no particular order)
+Jens Rehsack, Martin J. Evans, and H.Merijn Brand for all their contributions.
+
+ Fixed DBD::File's {ChopBlank} handling (it stripped \s instead of space
+ only as documented in DBI) (H.Merijn Brand)
+ Fixed DBD::DBM breakage with SQL::Statement (Jens Rehsack, fixes RT#56561)
+ Fixed DBD::File file handle leak (Jens Rehsack)
+ Fixed problems in 50dbm.t when running tests with multiple
+ dbms (Martin J. Evans)
+ Fixed DBD::DBM bugs found during tests (Jens Rehsack)
+ Fixed DBD::File doesn't find files without extensions under some
+ circumstances (Jens Rehsack, H.Merijn Brand, fixes RT#59038)
+
+ Changed Makefile.PL to modernize with CONFLICTS, recommended dependencies
+ and resources (Jens Rehsack)
+ Changed DBI::ProfileDumper to rename any existing profile file by
+ appending .prev, instead of overwriting it.
+ Changed DBI::ProfileDumper::Apache to work in more configurations
+ including vhosts using PerlOptions +Parent.
+ Add driver_prefix method to DBI (Jens Rehsack)
+
+ Added more tests to 50dbm_simple.t to prove optimizations in
+ DBI::SQL::Nano and SQL::Statement (Jens Rehsack)
+ Updated tests to cover optional installed SQL::Statement (Jens Rehsack)
+ Synchronize API between SQL::Statement and DBI::SQL::Nano (Jens Rehsack)
+ Merged some optimizations from SQL::Statement into DBI::SQL::Nano
+ (Jens Rehsack)
+ Added basic test for DBD::File (H.Merijn Brand, Jens Rehsack)
+ Extract dealing with Perl SQL engines from DBD::File into
+ DBI::DBD::SqlEngine for better subclassing of 3rd party non-db DBDs
+ (Jens Rehsack)
+
+ Updated and clarified documentation for finish method (Tim Bunce).
+ Changes to DBD::File for better English and hopefully better
+ explanation (Martin J. Evans)
+ Update documentation of DBD::DBM to cover current implementation,
+ tried to explain some things better and changes most examples to
+ preferred style of Merijn and myself (Jens Rehsack)
+ Added developer documentation (including a roadmap of future plans)
+ for DBD::File
+
+=head2 Changes in DBI 1.611 (svn r13935) 29th April 2010
+
+ NOTE: minimum perl version is now 5.8.1 (as announced in DBI 1.607)
+
+ Fixed selectcol_arrayref MaxRows attribute to count rows not values
+ thanks to Vernon Lyon.
+ Fixed DBI->trace(0, *STDERR); (H.Merijn Brand)
+ which tried to open a file named "*main::STDERR" in perl-5.10.x
+ Fixes in DBD::DBM for use under threads (Jens Rehsack)
+
+ Changed "Issuing rollback() due to DESTROY without explicit disconnect"
+ warning to not be issued if ReadOnly set for that dbh.
+
+ Added f_lock and f_encoding support to DBD::File (H.Merijn Brand)
+ Added ChildCallbacks => { ... } to Callbacks as a way to
+ specify Callbacks for child handles.
+ With tests added by David E. Wheeler.
+ Added DBI::sql_type_cast($value, $type, $flags) to cast a string value
+ to an SQL type. e.g. SQL_INTEGER effectively does $value += 0;
+ Has other options plus an internal interface for drivers.
+
+ Documentation changes:
+ Small fixes in the documentation of DBD::DBM (H.Merijn Brand)
+ Documented specification of type casting behaviour for bind_col()
+ based on DBI::sql_type_cast() and two new bind_col attributes
+ StrictlyTyped and DiscardString. Thanks to Martin Evans.
+ Document fetchrow_hashref() behaviour for functions,
+ aliases and duplicate names (H.Merijn Brand)
+ Updated DBI::Profile and DBD::File docs to fix pod nits
+ thanks to Frank Wiegand.
+ Corrected typos in Gopher documentation reported by Jan Krynicky.
+ Documented the Callbacks attribute thanks to David E. Wheeler.
+ Corrected the Timeout examples as per rt 50621 (Martin J. Evans).
+ Removed some internal broken links in the pod (Martin J. Evans)
+ Added Note to column_info for drivers which do not
+ support it (Martin J. Evans)
+ Updated dbipport.h to Devel::PPPort 3.19 (H.Merijn Brand)
+
+=head2 Changes in DBI 1.609 (svn r12816) 8th June 2009
+
+ Fixes to DBD::File (H.Merijn Brand)
+ added f_schema attribute
+ table names case sensitive when quoted, insensitive when unquoted
+ workaround a bug in SQL::Statement (temporary fix) related
+ to the "You passed x parameters where y required" error
+
+ Added ImplementorClass and Name info to the "Issuing rollback() due to
+ DESTROY without explicit disconnect" warning to identify the handle.
+ Applies to compiled drivers when they are recompiled.
+ Added DBI->visit_handles($coderef) method.
+ Added $h->visit_child_handles($coderef) method.
+ Added docs for column_info()'s COLUMN_DEF value.
+ Clarified docs on stickyness of data type via bind_param().
+ Clarified docs on stickyness of data type via bind_col().
+
+=head2 Changes in DBI 1.608 (svn r12742) 5th May 2009
+
+ Fixes to DBD::File (H.Merijn Brand)
+ bind_param () now honors the attribute argument
+ added f_ext attribute
+ File::Spec is always required. (CORE since 5.00405)
+ Fail and set errstr on parameter count mismatch in execute ()
+ Fixed two small memory leaks when running in mod_perl
+ one in DBI->connect and one in DBI::Gofer::Execute.
+ Both due to "local $ENV{...};" leaking memory.
+ Fixed DBD_ATTRIB_DELETE macro for driver authors
+ and updated DBI::DBD docs thanks to Martin J. Evans.
+ Fixed 64bit issues in trace messages thanks to Charles Jardine.
+ Fixed FETCH_many() method to work with drivers that incorrectly return
+ an empty list from $h->FETCH. Affected gofer.
+
+ Added 'sqlite_' as registered prefix for DBD::SQLite.
+ Corrected many typos in DBI docs thanks to Martin J. Evans.
+ Improved DBI::DBD docs thanks to H.Merijn Brand.
+
+=head2 Changes in DBI 1.607 (svn r11571) 22nd July 2008
+
+ NOTE: Perl 5.8.1 is now the minimum supported version.
+ If you need support for earlier versions send me a patch.
+
+ Fixed missing import of carp in DBI::Gofer::Execute.
+
+ Added note to docs about effect of execute(@empty_array).
+ Clarified docs for ReadOnly thanks to Martin Evans.
+
+=head2 Changes in DBI 1.605 (svn r11434) 16th June 2008
+
+ Fixed broken DBIS macro with threads on big-endian machines
+ with 64bit ints but 32bit pointers. Ticket #32309.
+ Fixed the selectall_arrayref, selectrow_arrayref, and selectrow_array
+ methods that get embedded into compiled drivers to use the
+ inner sth handle when passed a $sth instead of an sql string.
+ Drivers will need to be recompiled to pick up this change.
+ Fixed leak in neat() for some kinds of values thanks to Rudolf Lippan.
+ Fixed DBI::PurePerl neat() to behave more like XS neat().
+
+ Increased default $DBI::neat_maxlen from 400 to 1000.
+ Increased timeout on tests to accomodate very slow systems.
+ Changed behaviour of trace levels 1..4 to show less information
+ at lower levels.
+ Changed the format of the key used for $h->{CachedKids}
+ (which is undocumented so you shouldn't depend on it anyway)
+ Changed gofer error handling to avoid duplicate error text in errstr.
+ Clarified docs re ":N" style placeholders.
+ Improved gofer retry-on-error logic and refactored to aid subclassing.
+ Improved gofer trace output in assorted ways.
+
+ Removed the beeps "\a" from Makefile.PL warnings.
+ Removed check for PlRPC-modules from Makefile.PL
+
+ Added sorting of ParamValues reported by ShowErrorStatement
+ thanks to to Rudolf Lippan.
+ Added cache miss trace message to DBD::Gofer transport class.
+ Added $drh->dbixs_revision method.
+ Added explicit LICENSE specification (perl) to META.yaml
+
+=head2 Changes in DBI 1.604 (svn rev 10994) 24th March 2008
+
+ Fixed fetchall_arrayref with $max_rows argument broken in 1.603,
+ thanks to Greg Sabino Mullane.
+ Fixed a few harmless compiler warnings on cygwin.
+
+=head2 Changes in DBI 1.603
+
+ Fixed pure-perl fetchall_arrayref with $max_rows argument
+ to not error when fetching after all rows already fetched.
+ (Was fixed for compiled drivers back in DBI 1.31.)
+ Thanks to Mark Overmeer.
+ Fixed C sprintf formats and casts, fixing compiler warnings.
+
+ Changed dbi_profile() to accept a hash of profiles and apply to all.
+ Changed gofer stream transport to improve error reporting.
+ Changed gofer test timeout to avoid spurious failures on slow systems.
+
+ Added options to t/85gofer.t so it's more useful for manual testing.
+
+=head2 Changes in DBI 1.602 (svn rev 10706) 8th February 2008
+
+ Fixed potential coredump if stack reallocated while calling back
+ into perl from XS code. Thanks to John Gardiner Myers.
+ Fixed DBI::Util::CacheMemory->new to not clear the cache.
+ Fixed avg in DBI::Profile as_text() thanks to Abe Ingersoll.
+ Fixed DBD::DBM bug in push_names thanks to J M Davitt.
+ Fixed take_imp_data for some platforms thanks to Jeffrey Klein.
+ Fixed docs tie'ing CacheKids (ie LRU cache) thanks to Peter John Edwards.
+
+ Expanded DBI::DBD docs for driver authors thanks to Martin Evans.
+ Enhanced t/80proxy.t test script.
+ Enhanced t/85gofer.t test script thanks to Stig.
+ Enhanced t/10examp.t test script thanks to David Cantrell.
+ Documented $DBI::stderr as the default value of err for internal errors.
+
+ Gofer changes:
+ track_recent now also keeps track of N most recent errors.
+ The connect method is now also counted in stats.
+
+=head2 Changes in DBI 1.601 (svn rev 10103), 21st October 2007
+
+ Fixed t/05thrclone.t to work with Test::More >= 0.71
+ thanks to Jerry D. Hedden and Michael G Schwern.
+ Fixed DBI for VMS thanks to Peter (Stig) Edwards.
+
+ Added client-side caching to DBD::Gofer. Can use any cache with
+ get($k)/set($k,$v) methods, including all the Cache and Cache::Cache
+ distribution modules plus Cache::Memcached, Cache::FastMmap etc.
+ Works for all transports. Overridable per handle.
+
+ Added DBI::Util::CacheMemory for use with DBD::Gofer caching.
+ It's a very fast and small strict subset of Cache::Memory.
+
+=head2 Changes in DBI 1.59 (svn rev 9874), 23rd August 2007
+
+ Fixed DBI::ProfileData to unescape headers lines read from data file.
+ Fixed DBI::ProfileData to not clobber $_, thanks to Alexey Tourbin.
+ Fixed DBI::SQL::Nano to not clobber $_, thanks to Alexey Tourbin.
+ Fixed DBI::PurePerl to return undef for ChildHandles if weaken not available.
+ Fixed DBD::Proxy disconnect error thanks to Philip Dye.
+ Fixed DBD::Gofer::Transport::Base bug (typo) in timeout code.
+ Fixed DBD::Proxy rows method thanks to Philip Dye.
+ Fixed dbiprof compile errors, thanks to Alexey Tourbin.
+ Fixed t/03handle.t to skip some tests if ChildHandles not available.
+
+ Added check_response_sub to DBI::Gofer::Execute
+
+=head2 Changes in DBI 1.58 (svn rev 9678), 25th June 2007
+
+ Fixed code triggering fatal error in bleadperl, thanks to Steve Hay.
+ Fixed compiler warning thanks to Jerry D. Hedden.
+ Fixed t/40profile.t to use int(dbi_time()) for systems like Cygwin where
+ time() seems to be rounded not truncated from the high resolution time.
+ Removed dump_results() test from t/80proxy.t.
+
+=head2 Changes in DBI 1.57 (svn rev 9639), 13th June 2007
+
+ Note: this release includes a change to the DBI::hash() function which will
+ now produce different values than before *if* your perl was built with 64-bit
+ 'int' type (i.e. "perl -V:intsize" says intsize='8'). It's relatively rare
+ for perl to be configured that way, even on 64-bit systems.
+
+ Fixed XS versions of select*_*() methods to call execute()
+ fetch() etc., with inner handle instead of outer.
+ Fixed execute_for_fetch() to not cache errstr values
+ thanks to Bart Degryse.
+ Fixed unused var compiler warning thanks to JDHEDDEN.
+ Fixed t/86gofer_fail tests to be less likely to fail falsely.
+
+ Changed DBI::hash to return 'I32' type instead of 'int' so results are
+ portable/consistent regardless of size of the int type.
+ Corrected timeout example in docs thanks to Egmont Koblinger.
+ Changed t/01basic.t to warn instead of failing when it detects
+ a problem with Math::BigInt (some recent versions had problems).
+
+ Added support for !Time and !Time~N to DBI::Profile Path. See docs.
+ Added extra trace info to connect_cached thanks to Walery Studennikov.
+ Added non-random (deterministic) mode to DBI_GOFER_RANDOM mechanism.
+ Added DBIXS_REVISION macro that drivers can use.
+ Added more docs for private_attribute_info() method.
+
+ DBI::Profile changes:
+ dbi_profile() now returns ref to relevant leaf node.
+ Don't profile DESTROY during global destruction.
+ Added as_node_path_list() and as_text() methods.
+ DBI::ProfileDumper changes:
+ Don't write file if there's no profile data.
+ Uses full natural precision when saving data (was using %.6f)
+ Optimized flush_to_disk().
+ Locks the data file while writing.
+ Enabled filename to be a code ref for dynamic names.
+ DBI::ProfileDumper::Apache changes:
+ Added Quiet=>1 to avoid write to STDERR in flush_to_disk().
+ Added Dir=>... to specify a writable destination directory.
+ Enabled DBI_PROFILE_APACHE_LOG_DIR for mod_perl 1 as well as 2.
+ Added parent pid to default data file name.
+ DBI::ProfileData changes:
+ Added DeleteFiles option to rename & delete files once read.
+ Locks the data files while reading.
+ Added ability to sort by Path elements.
+ dbiprof changes:
+ Added --dumpnodes and --delete options.
+ Added/updated docs for both DBI::ProfileDumper && ::Apache.
+
+=head2 Changes in DBI 1.56 (svn rev 9660), 18th June 2007
+
+ Fixed printf arg warnings thanks to JDHEDDEN.
+ Fixed returning driver-private sth attributes via gofer.
+
+ Changed pod docs docs to use =head3 instead of =item
+ so now in html you get links to individual methods etc.
+ Changed default gofer retry_limit from 2 to 0.
+ Changed tests to workaround Math::BigInt broken versions.
+ Changed dbi_profile_merge() to dbi_profile_merge_nodes()
+ old name still works as an alias for the new one.
+ Removed old DBI internal sanity check that's no longer valid
+ causing "panic: DESTROY (dbih_clearcom)" when tracing enabled
+
+ Added DBI_GOFER_RANDOM env var that can be use to trigger random
+ failures and delays when executing gofer requests. Designed to help
+ test automatic retry on failures and timeout handling.
+ Added lots more docs to all the DBD::Gofer and DBI::Gofer classes.
+
+=head2 Changes in DBI 1.55 (svn rev 9504), 4th May 2007
+
+ Fixed set_err() so HandleSetErr hook is executed reliably, if set.
+ Fixed accuracy of profiling when perl configured to use long doubles.
+ Fixed 42prof_data.t on fast systems with poor timers thanks to Malcolm Nooning.
+ Fixed potential corruption in selectall_arrayref and selectrow_arrayref
+ for compiled drivers, thanks to Rob Davies.
+ Rebuild your compiled drivers after installing DBI.
+
+ Changed some handle creation code from perl to C code,
+ to reduce handle creation cost by ~20%.
+ Changed internal implementation of the CachedKids attribute
+ so it's a normal handle attribute (and initially undef).
+ Changed connect_cached and prepare_cached to avoid a FETCH method call,
+ and thereby reduced cost by ~5% and ~30% respectively.
+ Changed _set_fbav to not croak when given a wrongly sized array,
+ it now warns and adjusts the row buffer to match.
+ Changed some internals to improve performance with threaded perls.
+ Changed DBD::NullP to be slightly more useful for testing.
+ Changed File::Spec prerequisite to not require a minimum version.
+ Changed tests to work with other DBMs thanks to ZMAN.
+ Changed ex/perl_dbi_nulls_test.pl to be more descriptive.
+
+ Added more functionality to the (undocumented) Callback mechanism.
+ Callbacks can now elect to provide a value to be returned, in which case
+ the method won't be called. A callback for "*" is applied to all methods
+ that don't have their own callback.
+ Added $h->{ReadOnly} attribute.
+ Added support for DBI Profile Path to contain refs to scalars
+ which will be de-ref'd for each profile sample.
+ Added dbilogstrip utility to edit DBI logs for diff'ing (gets installed)
+ Added details for SQLite 3.3 to NULL handling docs thanks to Alex Teslik.
+ Added take_imp_data() to DBI::PurePerl.
+
+ Gofer related changes:
+ Fixed gofer pipeone & stream transports to avoid risk of hanging.
+ Improved error handling and tracing significantly.
+ Added way to generate random 1-in-N failures for methods.
+ Added automatic retry-on-error mechanism to gofer transport base class.
+ Added tests to show automatic retry mechanism works a treat!
+ Added go_retry_hook callback hook so apps can fine-tune retry behaviour.
+ Added header to request and response packets for sanity checking
+ and to enable version skew between client and server.
+ Added forced_single_resultset, max_cached_sth_per_dbh and max_cached_dbh_per_drh
+ to gofer executor config.
+ Driver-private methods installed with install_method are now proxied.
+ No longer does a round-trip to the server for methods it knows
+ have not been overridden by the remote driver.
+ Most significant aspects of gofer behaviour are controlled by policy mechanism.
+ Added policy-controlled caching of results for some methods, such as schema metadata.
+ The connect_cached and prepare_cached methods cache on client and server.
+ The bind_param_array and execute_array methods are now supported.
+ Worked around a DBD::Sybase bind_param bug (which is fixed in DBD::Sybase 1.07)
+ Added goferperf.pl utility (doesn't get installed).
+ Many other assorted Gofer related bug fixes, enhancements and docs.
+ The http and mod_perl transports have been remove to their own distribution.
+ Client and server will need upgrading together for this release.
+
+=head2 Changes in DBI 1.54 (svn rev 9157), 23rd February 2007
+
+ NOTE: This release includes the 'next big thing': DBD::Gofer.
+ Take a look!
+
+ WARNING: This version has some subtle changes in DBI internals.
+ It's possible, though doubtful, that some may affect your code.
+ I recommend some extra testing before using this release.
+ Or perhaps I'm just being over cautious...
+
+ Fixed type_info when called for multiple dbh thanks to Cosimo Streppone.
+ Fixed compile warnings in bleadperl on freebsd-6.1-release
+ and solaris 10g thanks to Philip M. Gollucci.
+ Fixed to compile for perl built with -DNO_MATHOMS thanks to Jerry D. Hedden.
+ Fixed to work for bleadperl (r29544) thanks to Nicholas Clark.
+ Users of Perl >= 5.9.5 will require DBI >= 1.54.
+ Fixed rare error when profiling access to $DBI::err etc tied variables.
+ Fixed DBI::ProfileDumper to not be affected by changes to $/ and $,
+ thanks to Michael Schwern.
+
+ Changed t/40profile.t to skip tests for perl < 5.8.0.
+ Changed setting trace file to no longer write "Trace file set" to new file.
+ Changed 'handle cleared whilst still active' warning for dbh
+ to only be given for dbh that have active sth or are not AutoCommit.
+ Changed take_imp_data to call finish on all Active child sth.
+ Changed DBI::PurePerl trace() method to be more consistent.
+ Changed set_err method to effectively not append to errstr if the new errstr
+ is the same as the current one.
+ Changed handle factory methods, like connect, prepare, and table_info,
+ to copy any error/warn/info state of the handle being returned
+ up into the handle the method was called on.
+ Changed row buffer handling to not alter NUM_OF_FIELDS if it's
+ inconsistent with number of elements in row buffer array.
+ Updated DBI::DBD docs re handling multiple result sets.
+ Updated DBI::DBD docs for driver authors thanks to Ammon Riley
+ and Dean Arnold.
+ Updated column_info docs to note that if a table doesn't exist
+ you get an sth for an empty result set and not an error.
+
+ Added new DBD::Gofer 'stateless proxy' driver and framework,
+ and the DBI test suite is now also executed via DBD::Gofer,
+ and DBD::Gofer+DBI::PurePerl, in addition to DBI::PurePerl.
+ Added ability for trace() to support filehandle argument,
+ including tracing into a string, thanks to Dean Arnold.
+ Added ability for drivers to implement func() method
+ so proxy drivers can proxy the func method itself.
+ Added SQL_BIGINT type code (resolved to the ODBC/JDBC value (-5))
+ Added $h->private_attribute_info method.
+
+=head2 Changes in DBI 1.53 (svn rev 7995), 31st October 2006
+
+ Fixed checks for weaken to work with early 5.8.x versions
+ Fixed DBD::Proxy handling of some methods, including commit and rollback.
+ Fixed t/40profile.t to be more insensitive to long double precision.
+ Fixed t/40profile.t to be insensitive to small negative shifts in time
+ thanks to Jamie McCarthy.
+ Fixed t/40profile.t to skip tests for perl < 5.8.0.
+ Fixed to work with current 'bleadperl' (~5.9.5) thanks to Steve Peters.
+ Users of Perl >= 5.9.5 will require DBI >= 1.53.
+ Fixed to be more robust against drivers not handling multiple result
+ sets properly, thanks to Gisle Aas.
+
+ Added array context support to execute_array and execute_for_fetch
+ methods which returns executed tuples and rows affected.
+ Added Tie::Cache::LRU example to docs thanks to Brandon Black.
+
+=head2 Changes in DBI 1.52 (svn rev 6840), 30th July 2006
+
+ Fixed memory leak (per handle) thanks to Nicholas Clark and Ephraim Dan.
+ Fixed memory leak (16 bytes per sth) thanks to Doru Theodor Petrescu.
+ Fixed execute_for_fetch/execute_array to RaiseError thanks to Martin J. Evans.
+ Fixed for perl 5.9.4. Users of Perl >= 5.9.4 will require DBI >= 1.52.
+
+ Updated DBD::File to 0.35 to match the latest release on CPAN.
+
+ Added $dbh->statistics_info specification thanks to Brandon Black.
+
+ Many changes and additions to profiling:
+ Profile Path can now uses sane strings instead of obscure numbers,
+ can refer to attributes, assorted magical values, and even code refs!
+ Parsing of non-numeric DBI_PROFILE env var values has changed.
+ Changed DBI::Profile docs extensively - many new features.
+ See DBI::Profile docs for more information.
+
+=head2 Changes in DBI 1.51 (svn rev 6475), 6th June 2006
+
+ Fixed $dbh->clone method 'signature' thanks to Jeffrey Klein.
+ Fixed default ping() method to return false if !$dbh->{Active}.
+ Fixed t/40profile.t to be insensitive to long double precision.
+ Fixed for perl 5.8.0's more limited weaken() function.
+ Fixed DBD::Proxy to not alter $@ in disconnect or AUTOLOADd methods.
+ Fixed bind_columns() to use return set_err(...) instead of die()
+ to report incorrect number of parameters, thanks to Ben Thul.
+ Fixed bind_col() to ignore undef as bind location, thanks to David Wheeler.
+ Fixed for perl 5.9.x for non-threaded builds thanks to Nicholas Clark.
+ Users of Perl >= 5.9.x will require DBI >= 1.51.
+ Fixed fetching of rows as hash refs to preserve utf8 on field names
+ from $sth->{NAME} thanks to Alexey Gaidukov.
+ Fixed build on Win32 (dbd_postamble) thanks to David Golden.
+
+ Improved performance for thread-enabled perls thanks to Gisle Aas.
+ Drivers can now use PERL_NO_GET_CONTEXT thanks to Gisle Aas.
+ Driver authors please read the notes in the DBI::DBD docs.
+ Changed DBI::Profile format to always include a percentage,
+ if not exiting then uses time between the first and last DBI call.
+ Changed DBI::ProfileData to be more forgiving of systems with
+ unstable clocks (where time may go backwards occasionally).
+ Clarified the 'Subclassing the DBI' docs.
+ Assorted minor changes to docs from comments on annocpan.org.
+ Changed Makefile.PL to avoid incompatible options for old gcc.
+
+ Added 'fetch array of hash refs' example to selectall_arrayref
+ docs thanks to Tom Schindl.
+ Added docs for $sth->{ParamArrays} thanks to Martin J. Evans.
+ Added reference to $DBI::neat_maxlen in TRACING section of docs.
+ Added ability for DBI::Profile Path to include attributes
+ and a summary of where the code was called from.
+
+=head2 Changes in DBI 1.50 (svn rev 2307), 13 December 2005
+
+ Fixed Makefile.PL options for gcc bug introduced in 1.49.
+ Fixed handle magic order to keep DBD::Oracle happy.
+ Fixed selectrow_array to return empty list on error.
+
+ Changed dbi_profile_merge() to be able to recurse and merge
+ sub-trees of profile data.
+
+ Added documentation for dbi_profile_merge(), including how to
+ measure the time spent inside the DBI for an http request.
+
+=head2 Changes in DBI 1.49 (svn rev 2287), 29th November 2005
+
+ Fixed assorted attribute handling bugs in DBD::Proxy.
+ Fixed croak() in DBD::NullP thanks to Sergey Skvortsov.
+ Fixed handling of take_imp_data() and dbi_imp_data attribute.
+ Fixed bugs in DBD::DBM thanks to Jeff Zucker.
+ Fixed bug in DBI::ProfileDumper thanks to Sam Tregar.
+ Fixed ping in DBD::Proxy thanks to George Campbell.
+ Fixed dangling ref in $sth after parent $dbh destroyed
+ with thanks to il@rol.ru for the bug report #13151
+ Fixed prerequisites to include Storable thanks to Michael Schwern.
+ Fixed take_imp_data to be more practical.
+
+ Change to require perl 5.6.1 (as advertised in 2003) not 5.6.0.
+ Changed internals to be more strictly coded thanks to Andy Lester.
+ Changed warning about multiple copies of Driver.xst found in @INC
+ to ignore duplicated directories thanks to Ed Avis.
+ Changed Driver.xst to enable drivers to define an dbd_st_prepare_sv
+ function where the statement parameter is an SV. That enables
+ compiled drivers to support SQL strings that are UTF-8.
+ Changed "use DBI" to only set $DBI::connect_via if not already set.
+ Changed docs to clarify pre-method clearing of err values.
+
+ Added ability for DBI::ProfileData to edit profile path on loading.
+ This enables aggregation of different SQL statements into the same
+ profile node - very handy when not using placeholders or when working
+ multiple separate tables for the same thing (ie logtable_2005_11_28)
+ Added $sth->{ParamTypes} specification thanks to Dean Arnold.
+ Added $h->{Callbacks} attribute to enable code hooks to be invoked
+ when certain methods are called. For example:
+ $dbh->{Callbacks}->{prepare} = sub { ... };
+ With thanks to David Wheeler for the kick start.
+ Added $h->{ChildHandles} (using weakrefs) thanks to Sam Tregar
+ I've recoded it in C so there's no significant performance impact.
+ Added $h->{Type} docs (returns 'dr', 'db', or 'st')
+ Adding trace message in DESTROY if InactiveDestroy enabled.
+ Added %drhs = DBI->installed_drivers();
+
+ Ported DBI::ProfileDumper::Apache to mod_perl2 RC5+
+ thanks to Philip M. Golluci
+
+=head2 Changes in DBI 1.48 (svn rev 928), 14th March 2005
+
+ Fixed DBI::DBD::Metadata generation of type_info_all thanks to Steffen Goeldner
+ (driver authors who have used it should rerun it).
+
+ Updated docs for NULL Value placeholders thanks to Brian Campbell.
+
+ Added multi-keyfield nested hash fetching to fetchall_hashref()
+ thanks to Zhuang (John) Li for polishing up my draft.
+ Added registered driver prefixes: amzn_ for DBD::Amazon and yaswi_ for DBD::Yaswi.
+
+
+=head2 Changes in DBI 1.47 (svn rev 854), 2nd February 2005
+
+ Fixed DBI::ProxyServer to not create pid files by default.
+ References: Ubuntu Security Notice USN-70-1, CAN-2005-0077
+ Thanks to Javier Fernández-Sanguino Peña from the
+ Debian Security Audit Project, and Jonathan Leffler.
+ Fixed some tests to work with older Test::More versions.
+ Fixed setting $DBI::err/errstr in DBI::PurePerl.
+ Fixed potential undef warning from connect_cached().
+ Fixed $DBI::lasth handling for DESTROY so lasth points to
+ parent even if DESTROY called other methods.
+ Fixed DBD::Proxy method calls to not alter $@.
+ Fixed DBD::File problem with encoding pragma thanks to Erik Rijkers.
+
+ Changed error handling so undef errstr doesn't cause warning.
+ Changed DBI::DBD docs to use =head3/=head4 pod thanks to
+ Jonathan Leffler. This may generate warnings for perl 5.6.
+ Changed DBI::PurePerl to set autoflush on trace filehandle.
+ Changed DBD::Proxy to treat Username as a local attribute
+ so recent DBI version can be used with old DBI::ProxyServer.
+ Changed driver handle caching in DBD::File.
+ Added $GetInfoType{SQL_DATABASE_NAME} thanks to Steffen Goeldner.
+
+ Updated docs to recommend some common DSN string attributes.
+ Updated connect_cached() docs with issues and suggestions.
+ Updated docs for NULL Value placeholders thanks to Brian Campbell.
+ Updated docs for primary_key_info and primary_keys.
+ Updated docs to clarify that the default fetchrow_hashref behaviour,
+ of returning a ref to a new hash for each row, will not change.
+ Updated err/errstr/state docs for DBD authors thanks to Steffen Goeldner.
+ Updated handle/attribute docs for DBD authors thanks to Steffen Goeldner.
+ Corrected and updated LongReadLen docs thanks to Bart Lateur.
+ Added DBD::JDBC as a registered driver.
+
+=head2 Changes in DBI 1.46 (svn rev 584), 16th November 2004
+
+ Fixed parsing bugs in DBI::SQL::Nano thanks to Jeff Zucker.
+ Fixed a couple of bad links in docs thanks to Graham Barr.
+ Fixed test.pl Win32 undef warning thanks to H.Merijn Brand & David Repko.
+ Fixed minor issues in DBI::DBD::Metadata thanks to Steffen Goeldner.
+ Fixed DBI::PurePerl neat() to use double quotes for utf8.
+
+ Changed execute_array() definition, and default implementation,
+ to not consider scalar values for execute tuple count. See docs.
+ Changed DBD::File to enable ShowErrorStatement by default,
+ which affects DBD::File subclasses such as DBD::CSV and DBD::DBM.
+ Changed use DBI qw(:utils) tag to include $neat_maxlen.
+ Updated Roadmap and ToDo.
+
+ Added data_string_diff() data_string_desc() and data_diff()
+ utility functions to help diagnose Unicode issues.
+ All can be imported via the use DBI qw(:utils) tag.
+
+=head2 Changes in DBI 1.45 (svn rev 480), 6th October 2004
+
+ Fixed DBI::DBD code for drivers broken in 1.44.
+ Fixed "Free to wrong pool"/"Attempt to free unreferenced scalar" in FETCH.
+
+=head2 Changes in DBI 1.44 (svn rev 478), 5th October 2004
+
+ Fixed build issues on VMS thanks to Jakob Snoer.
+ Fixed DBD::File finish() method to return 1 thanks to Jan Dubois.
+ Fixed rare core dump during global destruction thanks to Mark Jason Dominus.
+ Fixed risk of utf8 flag persisting from one row to the next.
+
+ Changed bind_param_array() so it doesn't require all bind arrays
+ to have the same number of elements.
+ Changed bind_param_array() to error if placeholder number <= 0.
+ Changed execute_array() definition, and default implementation,
+ to effectively NULL-pad shorter bind arrays.
+ Changed execute_array() to return "0E0" for 0 as per the docs.
+ Changed execute_for_fetch() definition, and default implementation,
+ to return "0E0" for 0 like execute() and execute_array().
+ Changed Test::More prerequisite to Test::Simple (which is also the name
+ of the distribution both are packaged in) to work around ppm behaviour.
+
+ Corrected docs to say that get/set of unknown attribute generates
+ a warning and is no longer fatal. Thanks to Vadim.
+ Corrected fetchall_arrayref() docs example thanks to Drew Broadley.
+
+ Added $h1->swap_inner_handle($h2) sponsored by BizRate.com
+
+
+=head2 Changes in DBI 1.43 (svn rev 377), 2nd July 2004
+
+ Fixed connect() and connect_cached() RaiseError/PrintError
+ which would sometimes show "(no error string)" as the error.
+ Fixed compiler warning thanks to Paul Marquess.
+ Fixed "trace level set to" trace message thanks to H.Merijn Brand.
+ Fixed DBD::DBM $dbh->{dbm_tables}->{...} to be keyed by the
+ table name not the file name thanks to Jeff Zucker.
+ Fixed last_insert_id(...) thanks to Rudy Lippan.
+ Fixed propagation of scalar/list context into proxied methods.
+ Fixed DBI::Profile::DESTROY to not alter $@.
+ Fixed DBI::ProfileDumper new() docs thanks to Michael Schwern.
+ Fixed _load_class to propagate $@ thanks to Drew Taylor.
+ Fixed compile warnings on Win32 thanks to Robert Baron.
+ Fixed problem building with recent versions of MakeMaker.
+ Fixed DBD::Sponge not to generate warning with threads.
+ Fixed DBI_AUTOPROXY to work more than once thanks to Steven Hirsch.
+
+ Changed TraceLevel 1 to not show recursive/nested calls.
+ Changed getting or setting an invalid attribute to no longer be
+ a fatal error but generate a warning instead.
+ Changed selectall_arrayref() to call finish() if
+ $attr->{MaxRows} is defined.
+ Changed all tests to use Test::More and enhanced the tests thanks
+ to Stevan Little and Andy Lester. See http://qa.perl.org/phalanx/
+ Changed Test::More minimum prerequisite version to 0.40 (2001).
+ Changed DBI::Profile header to include the date and time.
+
+ Added DBI->parse_dsn($dsn) method.
+ Added warning if build directory path contains white space.
+ Added docs for parse_trace_flags() and parse_trace_flag().
+ Removed "may change" warnings from the docs for table_info(),
+ primary_key_info(), and foreign_key_info() methods.
+
+=head2 Changes in DBI 1.42 (svn rev 222), 12th March 2004
+
+ Fixed $sth->{NUM_OF_FIELDS} of non-executed statement handle
+ to be undef as per the docs (it was 0).
+ Fixed t/41prof_dump.t to work with perl5.9.1.
+ Fixed DBD_ATTRIB_DELETE macro thanks to Marco Paskamp.
+ Fixed DBI::PurePerl looks_like_number() and $DBI::rows.
+ Fixed ref($h)->can("foo") to not croak.
+
+ Changed attributes (NAME, TYPE etc) of non-executed statement
+ handle to be undef instead of triggering an error.
+ Changed ShowErrorStatement to apply to more $dbh methods.
+ Changed DBI_TRACE env var so just does this at load time:
+ DBI->trace(split '=', $ENV{DBI_TRACE}, 2);
+ Improved "invalid number of parameters" error message.
+ Added DBI::common as base class for DBI::db, DBD::st etc.
+ Moved methods common to all handles into DBI::common.
+
+ Major tracing enhancement:
+
+ Added $h->parse_trace_flags("foo|SQL|7") to map a group of
+ trace flags into the corresponding trace flag bits.
+ Added automatic calling of parse_trace_flags() if
+ setting the trace level to a non-numeric value:
+ $h->{TraceLevel}="foo|SQL|7"; $h->trace("foo|SQL|7");
+ DBI->connect("dbi:Driver(TraceLevel=SQL|foo):...", ...);
+ Currently no trace flags have been defined.
+ Added to, and reworked, the trace documentation.
+ Added dbivport.h for driver authors to use.
+
+ Major driver additions that Jeff Zucker and I have been working on:
+
+ Added DBI::SQL::Nano a 'smaller than micro' SQL parser
+ with an SQL::Statement compatible API. If SQL::Statement
+ is installed then DBI::SQL::Nano becomes an empty subclass
+ of SQL::Statement, unless the DBI_SQL_NANO env var is true.
+ Added DBD::File, modified to use DBI::SQL::Nano.
+ Added DBD::DBM, an SQL interface to DBM files using DBD::File.
+
+ Documentation changes:
+
+ Corrected typos in docs thanks to Steffen Goeldner.
+ Corrected execute_for_fetch example thanks to Dean Arnold.
+
+=head2 Changes in DBI 1.41 (svn rev 130), 22nd February 2004
+
+ Fixed execute_for_array() so tuple_status parameter is optional
+ as per docs, thanks to Ed Avis.
+ Fixed execute_for_array() docs to say that it returns undef if
+ any of the execute() calls fail.
+ Fixed take_imp_data() test on m68k reported by Christian Hammers.
+ Fixed write_typeinfo_pm inconsistencies in DBI::DBD::Metadata
+ thanks to Andy Hassall.
+ Fixed $h->{TraceLevel} to not return DBI->trace trace level
+ which it used to if DBI->trace trace level was higher.
+
+ Changed set_err() to append to errstr, with a leading "\n" if it's
+ not empty, so that multiple error/warning messages are recorded.
+ Changed trace to limit elements dumped when an array reference is
+ returned from a method to the max(40, $DBI::neat_maxlen/10)
+ so that fetchall_arrayref(), for example, doesn't flood the trace.
+ Changed trace level to be a four bit integer (levels 0 thru 15)
+ and a set of topic flags (no topics have been assigned yet).
+ Changed column_info() to check argument count.
+ Extended bind_param() TYPE attribute specification to imply
+ standard formating of value, eg SQL_DATE implies 'YYYY-MM-DD'.
+
+ Added way for drivers to indicate 'success with info' or 'warning'
+ by setting err to "0" for warning and "" for information.
+ Both values are false and so don't trigger RaiseError etc.
+ Thanks to Steffen Goeldner for the original idea.
+ Added $h->{HandleSetErr} = sub { ... } to be called at the
+ point that an error, warn, or info state is recorded.
+ The code can alter the err, errstr, and state values
+ (e.g., to promote an error to a warning, or the reverse).
+ Added $h->{PrintWarn} attribute to enable printing of warnings
+ recorded by the driver. Defaults to same value as $^W (perl -w).
+ Added $h->{ErrCount} attribute, incremented whenever an error is
+ recorded by the driver via set_err().
+ Added $h->{Executed} attribute, set if do()/execute() called.
+ Added \%attr parameter to foreign_key_info() method.
+ Added ref count of inner handle to "DESTROY ignored for outer" msg.
+ Added Win32 build config checks to DBI::DBD thanks to Andy Hassall.
+ Added bind_col to Driver.xst so drivers can define their own.
+ Added TYPE attribute to bind_col and specified the expected
+ driver behaviour.
+
+ Major update to signal handling docs thanks to Lincoln Baxter.
+ Corrected dbiproxy usage doc thanks to Christian Hammers.
+ Corrected type_info_all index hash docs thanks to Steffen Goeldner.
+ Corrected type_info COLUMN_SIZE to chars not bytes thanks to Dean Arnold.
+ Corrected get_info() docs to include details of DBI::Const::GetInfoType.
+ Clarified that $sth->{PRECISION} is OCTET_LENGTH for char types.
+
+=head2 Changes in DBI 1.40, 7th January 2004
+
+ Fixed handling of CachedKids when DESTROYing threaded handles.
+ Fixed sql_user_name() in DBI::DBD::Metadata (used by write_getinfo_pm)
+ to use $dbh->{Username}. Driver authors please update your code.
+
+ Changed connect_cached() when running under Apache::DBI
+ to route calls to Apache::DBI::connect().
+
+ Added CLONE() to DBD::Sponge and DBD::ExampleP.
+ Added warning when starting a new thread about any loaded driver
+ which does not have a CLONE() function.
+ Added new prepare_cache($sql, \%attr, 3) option to manage Active handles.
+ Added SCALE and NULLABLE support to DBD::Sponge.
+ Added missing execute() in fetchall_hashref docs thanks to Iain Truskett.
+ Added a CONTRIBUTING section to the docs with notes on creating patches.
+
+=head2 Changes in DBI 1.39, 27th November 2003
+
+ Fixed STORE to not clear error during nested DBI call, again/better,
+ thanks to Tony Bowden for the report and helpful test case.
+ Fixed DBI dispatch to not try to use AUTOLOAD for driver methods unless
+ the method has been declared (as methods should be when using AUTOLOAD).
+ This fixes a problem when the Attribute::Handlers module is loaded.
+ Fixed cwd check code to use $Config{path_sep} thanks to Steve Hay.
+ Fixed unqualified croak() calls thanks to Steffen Goeldner.
+ Fixed DBD::ExampleP TYPE and PRECISION attributes thanks to Tom Lowery.
+ Fixed tracing of methods that only get traced at high trace levels.
+
+ The level 1 trace no longer includes nested method calls so it generally
+ just shows the methods the application explicitly calls.
+ Added line to trace log (level>=4) when err/errstr is cleared.
+ Updated docs for InactiveDestroy and point out where and when the
+ trace includes the process id.
+ Update DBI::DBD docs thanks to Steffen Goeldner.
+ Removed docs saying that the DBI->data_sources method could be
+ passed a $dbh. The $dbh->data_sources method should be used instead.
+ Added link to 'DBI recipes' thanks to Giuseppe Maxia:
+ http://gmax.oltrelinux.com/dbirecipes.html (note that this
+ is not an endorsement that the recipies are 'optimal')
+
+ Note: There is a bug in perl 5.8.2 when configured with threads
+ and debugging enabled (bug #24463) which causes a DBI test to fail.
+
+=head2 Changes in DBI 1.38, 21th August 2003
+
+ NOTE: The DBI now requires perl version 5.6.0 or later.
+ (As per notice in DBI 1.33 released 27th February 2003)
+
+ Fixed spurious t/03handles failure on 64bit perls reported by H.Merijn Brand.
+ Fixed spurious t/15array failure on some perl versions thanks to Ed Avis.
+ Fixed build using dmake on windows thanks to Steffen Goeldner.
+ Fixed build on using some shells thanks to Gurusamy Sarathy.
+ Fixed ParamValues to only be appended to ShowErrorStatement if not empty.
+ Fixed $dbh->{Statement} not being writable by drivers in some cases.
+ Fixed occasional undef warnings on connect failures thanks to Ed Avis.
+ Fixed small memory leak when using $sth->{NAME..._hash}.
+ Fixed 64bit warnings thanks to Marian Jancar.
+ Fixed DBD::Proxy::db::DESTROY to not alter $@ thanks to Keith Chapman.
+ Fixed Makefile.PL status from WriteMakefile() thanks to Leon Brocard.
+
+ Changed "Can't set ...->{Foo}: unrecognised attribute" from an error to a
+ warning when running with DBI::ProxyServer to simplify upgrades.
+ Changed execute_array() to no longer require ArrayTupleStatus attribute.
+ Changed DBI->available_drivers to not hide DBD::Sponge.
+ Updated/moved placeholder docs to a better place thanks to Johan Vromans.
+ Changed dbd_db_do4 api in Driver.xst to match dbd_st_execute (return int,
+ not bool), relevant only to driver authors.
+ Changed neat(), and thus trace(), so strings marked as utf8 are presented
+ in double quotes instead of single quotes and are not sanitized.
+
+ Added $dbh->data_sources method.
+ Added $dbh->last_insert_id method.
+ Added $sth->execute_for_fetch($fetch_tuple_sub, \@tuple_status) method.
+ Added DBI->installed_versions thanks to Jeff Zucker.
+ Added $DBI::Profile::ON_DESTROY_DUMP variable.
+ Added docs for DBD::Sponge thanks to Mark Stosberg.
+
+=head2 Changes in DBI 1.37, 15th May 2003
+
+ Fixed "Can't get dbh->{Statement}: unrecognised attribute" error in test
+ caused by change to perl internals in 5.8.0
+ Fixed to build with latest development perl (5.8.1@19525).
+ Fixed C code to use all ANSI declarations thanks to Steven Lembark.
+
+=head2 Changes in DBI 1.36, 11th May 2003
+
+ Fixed DBI->connect to carp instead of croak on 'old-style' usage.
+ Fixed connect(,,, { RootClass => $foo }) to not croak if module not found.
+ Fixed code generated by DBI::DBD::Metadata thanks to DARREN@cpan.org (#2270)
+ Fixed DBI::PurePerl to not reset $@ during method dispatch.
+ Fixed VMS build thanks to Michael Schwern.
+ Fixed Proxy disconnect thanks to Steven Hirsch.
+ Fixed error in DBI::DBD docs thanks to Andy Hassall.
+
+ Changed t/40profile.t to not require Time::HiRes.
+ Changed DBI::ProxyServer to load DBI only on first request, which
+ helps threaded server mode, thanks to Bob Showalter.
+ Changed execute_array() return value from row count to executed
+ tuple count, and now the ArrayTupleStatus attribute is mandatory.
+ NOTE: That is an API definition change that may affect your code.
+ Changed CompatMode attribute to also disable attribute 'quick FETCH'.
+ Changed attribute FETCH to be slightly faster thanks to Stas Bekman.
+
+ Added workaround for perl bug #17575 tied hash nested FETCH
+ thanks to Silvio Wanka.
+ Added Username and Password attributes to connect(..., \%attr) and so
+ also embedded in DSN like "dbi:Driver(Username=user,Password=pass):..."
+ Username and Password can't contain ")", ",", or "=" characters.
+ The predence is DSN first, then \%attr, then $user & $pass parameters,
+ and finally the DBI_USER & DBI_PASS environment variables.
+ The Username attribute is stored in the $dbh but the Password is not.
+ Added ProxyServer HOWTO configure restrictions docs thanks to Jochen Wiedmann.
+ Added MaxRows attribute to selectcol_arrayref prompted by Wojciech Pietron.
+ Added dump_handle as a method not just a DBI:: utility function.
+ Added on-demand by-row data feed into execute_array() using code ref,
+ or statement handle. For example, to insert from a select:
+ $insert_sth->execute_array( { ArrayTupleFetch => $select_sth, ... } )
+ Added warning to trace log when $h->{foo}=... is ignored due to
+ invalid prefix (e.g., not 'private_').
+
+=head2 Changes in DBI 1.35, 7th March 2003
+
+ Fixed memory leak in fetchrow_hashref introduced in DBI 1.33.
+ Fixed various DBD::Proxy errors introduced in DBI 1.33.
+ Fixed to ANSI C in dbd_dr_data_sources thanks to Jonathan Leffler.
+ Fixed $h->can($method_name) to return correct code ref.
+ Removed DBI::Format from distribution as it's now part of the
+ separate DBI::Shell distribution by Tom Lowery.
+ Updated DBI::DBD docs with a note about the CLONE method.
+ Updated DBI::DBD docs thanks to Jonathan Leffler.
+ Updated DBI::DBD::Metadata for perl 5.5.3 thanks to Jonathan Leffler.
+ Added note to install_method docs about setup_driver() method.
+
+=head2 Changes in DBI 1.34, 28th February 2003
+
+ Fixed DBI::DBD docs to refer to DBI::DBD::Metadata thanks to Jonathan Leffler.
+ Fixed dbi_time() compile using BorlandC on Windows thanks to Steffen Goeldner.
+ Fixed profile tests to do enough work to measure on Windows.
+ Fixed disconnect_all() to not be required by drivers.
+
+ Added $okay = $h->can($method_name) to check if a method exists.
+ Added DBD::*::*->install_method($method_name, \%attr) so driver private
+ methods can be 'installed' into the DBI dispatcher and no longer
+ need to be called using $h->func(..., $method_name).
+
+ Enhanced $dbh->clone() and documentation.
+ Enhanced docs to note that dbi_time(), and thus profiling, is limited
+ to only millisecond (seconds/1000) resolution on Windows.
+ Removed old DBI::Shell from distribution and added Tom Lowery's improved
+ version to the Bundle::DBI file.
+ Updated minimum version numbers for modules in Bundle::DBI.
+
+=head2 Changes in DBI 1.33, 27th February 2003
+
+ NOTE: Future versions of the DBI *will not* support perl 5.6.0 or earlier.
+ : Perl 5.6.1 will be the minimum supported version.
+
+ NOTE: The "old-style" connect: DBI->connect($database, $user, $pass, $driver);
+ : has been deprecated for several years and will now generate a warning.
+ : It will be removed in a later release. Please change any old connect() calls.
+
+ Added $dbh2 = $dbh1->clone to make a new connection to the database
+ that is identical to the original one. clone() can be called even after
+ the original handle has been disconnected. See the docs for more details.
+
+ Fixed merging of profile data to not sum DBIprof_FIRST_TIME values.
+ Fixed unescaping of newlines in DBI::ProfileData thanks to Sam Tregar.
+ Fixed Taint bug with fetchrow_hashref with help from Bradley Baetz.
+ Fixed $dbh->{Active} for DBD::Proxy, reported by Bob Showalter.
+ Fixed STORE to not clear error during nested DBI call,
+ thanks to Tony Bowden for the report and helpful test case.
+ Fixed DBI::PurePerl error clearing behaviour.
+ Fixed dbi_time() and thus DBI::Profile on Windows thanks to Smejkal Petr.
+ Fixed problem that meant ShowErrorStatement could show wrong statement,
+ thanks to Ron Savage for the report and test case.
+ Changed Apache::DBI hook to check for $ENV{MOD_PERL} instead of
+ $ENV{GATEWAY_INTERFACE} thanks to Ask Bjoern Hansen.
+ No longer tries to dup trace logfp when an interpreter is being cloned.
+ Database handles no longer inherit shared $h->err/errstr/state storage
+ from their drivers, so each $dbh has it's own $h->err etc. values
+ and is no longer affected by calls made on other dbh's.
+ Now when a dbh is destroyed it's err/errstr/state values are copied
+ up to the driver so checking $DBI::errstr still works as expected.
+
+ Build / portability fixes:
+ Fixed t/40profile.t to not use Time::HiRes.
+ Fixed t/06attrs.t to not be locale sensitive, reported by Christian Hammers.
+ Fixed sgi compiler warnings, reported by Paul Blake.
+ Fixed build using make -j4, reported by Jonathan Leffler.
+ Fixed build and tests under VMS thanks to Craig A. Berry.
+
+ Documentation changes:
+ Documented $high_resolution_time = dbi_time() function.
+ Documented that bind_col() can take an atribute hash.
+ Clarified documentation for ParamValues attribute hash keys.
+ Many good DBI documentation tweaks from Jonathan Leffler,
+ including a major update to the DBI::DBD driver author guide.
+ Clarified that execute() should itself call finish() if it's
+ called on a statement handle that's still active.
+ Clarified $sth->{ParamValues}. Driver authors please note.
+ Removed "NEW" markers on some methods and attributes and
+ added text to each giving the DBI version it was added in,
+ if it was added after DBI 1.21 (Feb 2002).
+
+ Changes of note for authors of all drivers:
+ Added SQL_DATA_TYPE, SQL_DATETIME_SUB, NUM_PREC_RADIX, and
+ INTERVAL_PRECISION fields to docs for type_info_all. There were
+ already in type_info(), but type_info_all() didn't specify the
+ index values. Please check and update your type_info_all() code.
+ Added DBI::DBD::Metadata module that auto-generates your drivers
+ get_info and type_info_all data and code, thanks mainly to
+ Jonathan Leffler and Steffen Goeldner. If you've not implemented
+ get_info and type_info_all methods and your database has an ODBC
+ driver available then this will do all the hard work for you!
+ Drivers should no longer pass Err, Errstr, or State to _new_drh
+ or _new_dbh functions.
+ Please check that you support the slightly modified behaviour of
+ $sth->{ParamValues}, e.g., always return hash with keys if possible.
+
+ Changes of note for authors of compiled drivers:
+ Added dbd_db_login6 & dbd_st_finish3 prototypes thanks to Jonathan Leffler.
+ All dbd_*_*() functions implemented by drivers must have a
+ corresponding #define dbd_*_* <driver_prefix>_*_* otherwise
+ the driver may not work with a future release of the DBI.
+
+ Changes of note for authors of drivers which use Driver.xst:
+ Some new method hooks have been added are are enabled by
+ defining corresponding macros:
+ $drh->data_sources() - dbd_dr_data_sources
+ $dbh->do() - dbd_db_do4
+ The following methods won't be compiled into the driver unless
+ the corresponding macro has been #defined:
+ $drh->disconnect_all() - dbd_discon_all
+
+
+=head2 Changes in DBI 1.32, 1st December 2002
+
+ Fixed to work with 5.005_03 thanks to Tatsuhiko Miyagawa (I've not tested it).
+ Reenabled taint tests (accidentally left disabled) spotted by Bradley Baetz.
+ Improved docs for FetchHashKeyName attribute thanks to Ian Barwick.
+ Fixed core dump if fetchrow_hashref given bad argument (name of attribute
+ with a value that wasn't an array reference), spotted by Ian Barwick.
+ Fixed some compiler warnings thanks to David Wheeler.
+ Updated Steven Hirsch's enhanced proxy work (seems I left out a bit).
+ Made t/40profile.t tests more reliable, reported by Randy, who is part of
+ the excellent CPAN testers team: http://testers.cpan.org/
+ (Please visit, see the valuable work they do and, ideally, join in!)
+
+=head2 Changes in DBI 1.31, 29th November 2002
+
+ The fetchall_arrayref method, when called with a $maxrows parameter,
+ no longer gives an error if called again after all rows have been
+ fetched. This simplifies application logic when fetching in batches.
+ Also added batch-fetch while() loop example to the docs.
+ The proxy now supports non-lazy (synchronous) prepare, positioned
+ updates (for selects containing 'for update'), PlRPC config set
+ via attributes, and accurate propagation of errors, all thanks
+ to Steven Hirsch (plus a minor fix from Sean McMurray and doc
+ tweaks from Michael A Chase).
+ The DBI_AUTOPROXY env var can now hold the full dsn of the proxy driver
+ plus attributes, like "dbi:Proxy(proxy_foo=>1):host=...".
+ Added TaintIn & TaintOut attributes to give finer control over
+ tainting thanks to Bradley Baetz.
+ The RootClass attribute no longer ignores failure to load a module,
+ but also doesn't try to load a module if the class already exists,
+ with thanks to James FitzGibbon.
+ HandleError attribute works for connect failures thanks to David Wheeler.
+ The connect() RaiseError/PrintError message now includes the username.
+ Changed "last handle unknown or destroyed" warning to be a trace message.
+ Removed undocumented $h->event() method.
+ Further enhancements to DBD::PurePerl accuracy.
+ The CursorName attribute now defaults to undef and not an error.
+
+ DBI::Profile changes:
+ New DBI::ProfileDumper, DBI::ProfileDumper::Apache, and
+ DBI::ProfileData modules (to manage the storage and processing
+ of profile data), plus dbiprof program for analyzing profile
+ data - with many thanks to Sam Tregar.
+ Added $DBI::err (etc) tied variable lookup time to profile.
+ Added time for DESTROY method into parent handles profile (used to be ignored).
+
+ Documentation changes:
+ Documented $dbh = $sth->{Database} attribute.
+ Documented $dbh->connected(...) post-connection call when subclassing.
+ Updated some minor doc issues thanks to H.Merijn Brand.
+ Updated Makefile.PL example in DBI::DBD thanks to KAWAI,Takanori.
+ Fixed execute_array() example thanks to Peter van Hardenberg.
+
+ Changes for driver authors, not required but strongly recommended:
+ Change DBIS to DBIc_DBISTATE(imp_xxh) [or imp_dbh, imp_sth etc]
+ Change DBILOGFP to DBIc_LOGPIO(imp_xxh) [or imp_dbh, imp_sth etc]
+ Any function from which all instances of DBIS and DBILOGFP are
+ removed can also have dPERLINTERP removed (a good thing).
+ All use of the DBIh_EVENT* macros should be removed.
+ Major update to DBI::DBD docs thanks largely to Jonathan Leffler.
+ Add these key values: 'Err' => \my $err, 'Errstr' => \my $errstr,
+ to the hash passed to DBI::_new_dbh() in your driver source code.
+ That will make each $dbh have it's own $h->err and $h->errstr
+ values separate from other $dbh belonging to the same driver.
+ If you have a ::db or ::st DESTROY methods that do nothing
+ you can now remove them - which speeds up handle destruction.
+
+
+=head2 Changes in DBI 1.30, 18th July 2002
+
+ Fixed problems with selectrow_array, selectrow_arrayref, and
+ selectall_arrayref introduced in DBI 1.29.
+ Fixed FETCHing a handle attribute to not clear $DBI::err etc (broken in 1.29).
+ Fixed core dump at trace level 9 or above.
+ Fixed compilation with perl 5.6.1 + ithreads (i.e. Windows).
+ Changed definition of behaviour of selectrow_array when called in a scalar
+ context to match fetchrow_array.
+ Corrected selectrow_arrayref docs which showed selectrow_array thanks to Paul DuBois.
+
+=head2 Changes in DBI 1.29, 15th July 2002
+
+ NOTE: This release changes the specified behaviour for the
+ : fetchrow_array method when called in a scalar context:
+ : The DBI spec used to say that it would return the FIRST field.
+ : Which field it returns (i.e., the first or the last) is now undefined.
+ : This does not affect statements that only select one column, which is
+ : usually the case when fetchrow_array is called in a scalar context.
+ : FYI, this change was triggered by discovering that the fetchrow_array
+ : implementation in Driver.xst (used by most compiled drivers)
+ : didn't match the DBI specification. Rather than change the code
+ : to match, and risk breaking existing applications, I've changed the
+ : specification (that part was always of dubious value anyway).
+
+ NOTE: Future versions of the DBI may not support for perl 5.5 much longer.
+ : If you are still using perl 5.005_03 you should be making plans to
+ : upgrade to at least perl 5.6.1, or 5.8.0. Perl 5.8.0 is due to be
+ : released in the next week or so. (Although it's a "point 0" release,
+ : it is the most throughly tested release ever.)
+
+ Added XS/C implementations of selectrow_array, selectrow_arrayref, and
+ selectall_arrayref to Driver.xst. See DBI 1.26 Changes for more info.
+ Removed support for the old (fatally flawed) "5005" threading model.
+ Added support for new perl 5.8 iThreads thanks to Gerald Richter.
+ (Threading support and safety should still be regarded as beta
+ quality until further notice. But it's much better than it was.)
+ Updated the "Threads and Thread Safety" section of the docs.
+ The trace output can be sent to STDOUT instead of STDERR by using
+ "STDOUT" as the name of the file, i.e., $h->trace(..., "STDOUT")
+ Added pointer to perlreftut, perldsc, perllol, and perlboot manuals
+ into the intro section of the docs, suggested by Brian McCain.
+ Fixed DBI::Const::GetInfo::* pod docs thanks to Zack Weinberg.
+ Some changes to how $dbh method calls are treated by DBI::Profile:
+ Meta-data methods now clear $dbh->{Statement} on entry.
+ Some $dbh methods are now profiled as if $dbh->{Statement} was empty
+ (because thet're unlikely to actually relate to its contents).
+ Updated dbiport.h to ppport.h from perl 5.8.0.
+ Tested with perl 5.5.3 (vanilla, Solaris), 5.6.1 (vanilla, Solaris), and
+ perl 5.8.0 (RC3@17527 with iThreads & Multiplicity on Solaris and FreeBSD).
+
+=head2 Changes in DBI 1.28, 14th June 2002
+
+ Added $sth->{ParamValues} to return a hash of the most recent
+ values bound to placeholders via bind_param() or execute().
+ Individual drivers need to be updated to support it.
+ Enhanced ShowErrorStatement to include ParamValues if available:
+ "DBD::foo::st execute failed: errstr [for statement ``...'' with params: 1='foo']"
+ Further enhancements to DBD::PurePerl accuracy.
+
+=head2 Changes in DBI 1.27, 13th June 2002
+
+ Fixed missing column in C implementation of fetchall_arrayref()
+ thanks to Philip Molter for the prompt reporting of the problem.
+
+=head2 Changes in DBI 1.26, 13th June 2002
+
+ Fixed t/40profile.t to work on Windows thanks to Smejkal Petr.
+ Fixed $h->{Profile} to return undef, not error, if not set.
+ Fixed DBI->available_drivers in scalar context thanks to Michael Schwern.
+
+ Added C implementations of selectrow_arrayref() and fetchall_arrayref()
+ in Driver.xst. All compiled drivers using Driver.xst will now be
+ faster making those calls. Most noticable with fetchall_arrayref for
+ many rows or selectrow_arrayref with a fast query. For example, using
+ DBD::mysql a selectrow_arrayref for a single row using a primary key
+ is ~20% faster, and fetchall_arrayref for 20000 rows is twice as fast!
+ Drivers just need to be recompiled and reinstalled to enable it.
+ The fetchall_arrayref speed up only applies if $slice parameter is not used.
+ Added $max_rows parameter to fetchall_arrayref() to optionally limit
+ the number of rows returned. Can now fetch batches of rows.
+ Added MaxRows attribute to selectall_arrayref()
+ which then passes it to fetchall_arrayref().
+ Changed selectrow_array to make use of selectrow_arrayref.
+ Trace level 1 now shows first two parameters of all methods
+ (used to only for that for some, like prepare,execute,do etc)
+ Trace indicator for recursive calls (first char on trace lines)
+ now starts at 1 not 2.
+
+ Documented that $h->func() does not trigger RaiseError etc
+ so applications must explicitly check for errors.
+ DBI::Profile with DBI_PROFILE now shows percentage time inside DBI.
+ HandleError docs updated to show that handler can edit error message.
+ HandleError subroutine interface is now regarded as stable.
+
+=head2 Changes in DBI 1.25, 5th June 2002
+
+ Fixed build problem on Windows and some compiler warnings.
+ Fixed $dbh->{Driver} and $sth->{Statement} for driver internals
+ These are 'inner' handles as per behaviour prior to DBI 1.16.
+ Further minor improvements to DBI::PurePerl accuracy.
+
+=head2 Changes in DBI 1.24, 4th June 2002
+
+ Fixed reference loop causing a handle/memory leak
+ that was introduced in DBI 1.16.
+ Fixed DBI::Format to work with 'filehandles' from IO::Scalar
+ and similar modules thanks to report by Jeff Boes.
+ Fixed $h->func for DBI::PurePerl thanks to Jeff Zucker.
+ Fixed $dbh->{Name} for DBI::PurePerl thanks to Dean Arnold.
+
+ Added DBI method call profiling and benchmarking.
+ This is a major new addition to the DBI.
+ See $h->{Profile} attribute and DBI::Profile module.
+ For a quick trial, set the DBI_PROFILE environment variable and
+ run your favourite DBI script. Try it with DBI_PROFILE set to 1,
+ then try 2, 4, 8, 10, and -10. Have fun!
+
+ Added execute_array() and bind_param_array() documentation
+ with thanks to Dean Arnold.
+ Added notes about the DBI having not yet been tested with iThreads
+ (testing and patches for SvLOCK etc welcome).
+ Removed undocumented Handlers attribute (replaced by HandleError).
+ Tested with 5.5.3 and 5.8.0 RC1.
+
+=head2 Changes in DBI 1.23, 25th May 2002
+
+ Greatly improved DBI::PurePerl in performance and accuracy.
+ Added more detail to DBI::PurePerl docs about what's not supported.
+ Fixed undef warnings from t/15array.t and DBD::Sponge.
+
+=head2 Changes in DBI 1.22, 22nd May 2002
+
+ Added execute_array() and bind_param_array() with special thanks
+ to Dean Arnold. Not yet documented. See t/15array.t for examples.
+ All drivers now automatically support these methods.
+ Added DBI::PurePerl, a transparent DBI emulation for pure-perl drivers
+ with special thanks to Jeff Zucker. Perldoc DBI::PurePerl for details.
+ Added DBI::Const::GetInfo* modules thanks to Steffen Goeldner.
+ Added write_getinfo_pm utility to DBI::DBD thanks to Steffen Goeldner.
+ Added $allow_active==2 mode for prepare_cached() thanks to Stephen Clouse.
+
+ Updated DBI::Format to Revision 11.4 thanks to Tom Lowery.
+ Use File::Spec in Makefile.PL (helps VMS etc) thanks to Craig Berry.
+ Extend $h->{Warn} to commit/rollback ineffective warning thanks to Jeff Baker.
+ Extended t/preparse.t and removed "use Devel::Peek" thanks to Scott Hildreth.
+ Only copy Changes to blib/lib/Changes.pm once thanks to Jonathan Leffler.
+ Updated internals for modern perls thanks to Jonathan Leffler and Jeff Urlwin.
+ Tested with perl 5.7.3 (just using default perl config).
+
+ Documentation changes:
+
+ Added 'Catalog Methods' section to docs thanks to Steffen Goeldner.
+ Updated README thanks to Michael Schwern.
+ Clarified that driver may choose not to start new transaction until
+ next use of $dbh after commit/rollback.
+ Clarified docs for finish method.
+ Clarified potentials problems with prepare_cached() thanks to Stephen Clouse.
+
+
+=head2 Changes in DBI 1.21, 7th February 2002
+
+ The minimum supported perl version is now 5.005_03.
+
+ Fixed DBD::Proxy support for AutoCommit thanks to Jochen Wiedmann.
+ Fixed DBI::ProxyServer bind_param(_inout) handing thanks to Oleg Mechtcheriakov.
+ Fixed DBI::ProxyServer fetch loop thanks to nobull@mail.com.
+ Fixed install_driver do-the-right-thing with $@ on error. It, and connect(),
+ will leave $@ empty on success and holding the error message on error.
+ Thanks to Jay Lawrence, Gavin Sherlock and others for the bug report.
+ Fixed fetchrow_hashref to assign columns to the hash left-to-right
+ so later fields with the same name overwrite earlier ones
+ as per DBI < 1.15, thanks to Kay Roepke.
+
+ Changed tables() to use quote_indentifier() if the driver returns a
+ true value for $dbh->get_info(29) # SQL_IDENTIFIER_QUOTE_CHAR
+ Changed ping() so it no longer triggers RaiseError/PrintError.
+ Changed connect() to not call $class->install_driver unless needed.
+ Changed DESTROY to catch fatal exceptions and append to $@.
+
+ Added ISO SQL/CLI & ODBCv3 data type definitions thanks to Steffen Goeldner.
+ Removed the definition of SQL_BIGINT data type constant as the value is
+ inconsistent between standards (ODBC=-5, SQL/CLI=25).
+ Added $dbh->column_info(...) thanks to Steffen Goeldner.
+ Added $dbh->foreign_key_info(...) thanks to Steffen Goeldner.
+ Added $dbh->quote_identifier(...) insipred by Simon Oliver.
+ Added $dbh->set_err(...) for DBD authors and DBI subclasses
+ (actually been there for a while, now expanded and documented).
+ Added $h->{HandleError} = sub { ... } addition and/or alternative
+ to RaiseError/PrintError. See the docs for more info.
+ Added $h->{TraceLevel} = N attribute to set/get trace level of handle
+ thus can set trace level via an (eg externally specified) DSN
+ using the embedded attribute syntax:
+ $dsn = 'dbi:DB2(PrintError=1,TraceLevel=2):dbname';
+ Plus, you can also now do: local($h->{TraceLevel}) = N;
+ (but that leaks a little memory in some versions of perl).
+ Added some call tree information to trace output if trace level >= 3
+ With thanks to Graham Barr for the stack walking code.
+ Added experimental undocumented $dbh->preparse(), see t/preparse.t
+ With thanks to Scott T. Hildreth for much of the work.
+ Added Fowler/Noll/Vo hash type as an option to DBI::hash().
+
+ Documentation changes:
+
+ Added DBI::Changes so now you can "perldoc DBI::Changes", yeah!
+ Added selectrow_arrayref & selectrow_hashref docs thanks to Doug Wilson.
+ Added 'Standards Reference Information' section to docs to gather
+ together all references to relevant on-line standards.
+ Added link to poop.sourceforge.net into the docs thanks to Dave Rolsky.
+ Added link to hyperlinked BNF for SQL92 thanks to Jeff Zucker.
+ Added 'Subclassing the DBI' docs thanks to Stephen Clouse, and
+ then changed some of them to reflect the new approach to subclassing.
+ Added stronger wording to description of $h->{private_*} attributes.
+ Added docs for DBI::hash.
+
+ Driver API changes:
+
+ Now a COPY of the DBI->connect() attributes is passed to the driver
+ connect() method, so it can process and delete any elements it wants.
+ Deleting elements reduces/avoids the explicit
+ $dbh->{$_} = $attr->{$_} foreach keys %$attr;
+ that DBI->connect does after the driver connect() method returns.
+
+
+=head2 Changes in DBI 1.20, 24th August 2001
+
+ WARNING: This release contains two changes that may affect your code.
+ : Any code using selectall_hashref(), which was added in March 2001, WILL
+ : need to be changed. Any code using fetchall_arrayref() with a non-empty
+ : hash slice parameter may, in a few rare cases, need to be changed.
+ : See the change list below for more information about the changes.
+ : See the DBI documentation for a description of current behaviour.
+
+ Fixed memory leak thanks to Toni Andjelkovic.
+ Changed fetchall_arrayref({ foo=>1, ...}) specification again (sorry):
+ The key names of the returned hashes is identical to the letter case of
+ the names in the parameter hash, regardless of the L</FetchHashKeyName>
+ attribute. The letter case is ignored for matching.
+ Changed fetchall_arrayref([...]) array slice syntax specification to
+ clarify that the numbers in the array slice are perl index numbers
+ (which start at 0) and not column numbers (which start at 1).
+ Added { Columns=>... } and { Slice =>... } attributes to selectall_arrayref()
+ which is passed to fetchall_arrayref() so it can fetch hashes now.
+ Added a { Columns => [...] } attribute to selectcol_arrayref() so that
+ the list it returns can be built from more than one column per row.
+ Why? Consider my %hash = @{$dbh->selectcol_arrayref($sql,{ Columns=>[1,2]})}
+ to return id-value pairs which can be used directly to build a hash.
+ Added $hash_ref = $sth->fetchall_hashref( $key_field )
+ which returns a ref to a hash with, typically, one element per row.
+ $key_field is the name of the field to get the key for each row from.
+ The value of the hash for each row is a hash returned by fetchrow_hashref.
+ Changed selectall_hashref to return a hash ref (from fetchall_hashref)
+ and not an array of hashes as it has since DBI 1.15 (end March 2001).
+ WARNING: THIS CHANGE WILL BREAK ANY CODE USING selectall_hashref()!
+ Sorry, but I think this is an important regularization of the API.
+ To get previous selectall_hashref() behaviour (an array of hash refs)
+ change $ary_ref = $dbh->selectall_hashref( $statement, undef, @bind);
+ to $ary_ref = $dbh->selectall_arrayref($statement, { Columns=>{} }, @bind);
+ Added NAME_lc_hash, NAME_uc_hash, NAME_hash statement handle attributes.
+ which return a ref to a hash of field_name => field_index (0..n-1) pairs.
+ Fixed select_hash() example thanks to Doug Wilson.
+ Removed (unbundled) DBD::ADO and DBD::Multiplex from the DBI distribution.
+ The latest versions of those modules are available from CPAN sites.
+ Added $dbh->begin_work. This method causes AutoCommit to be turned
+ off just until the next commit() or rollback().
+ Driver authors: if the DBIcf_BegunWork flag is set when your commit or
+ rollback method is called then please turn AutoCommit on and clear the
+ DBIcf_BegunWork flag. If you don't then the DBI will but it'll be much
+ less efficient and won't handle error conditions very cleanly.
+ Retested on perl 5.4.4, but the DBI won't support 5.4.x much longer.
+ Added text to SUPPORT section of the docs:
+ For direct DBI and DBD::Oracle support, enhancement, and related work
+ I am available for consultancy on standard commercial terms.
+ Added text to ACKNOWLEDGEMENTS section of the docs:
+ Much of the DBI and DBD::Oracle was developed while I was Technical
+ Director (CTO) of the Paul Ingram Group (www.ig.co.uk). So I'd
+ especially like to thank Paul for his generosity and vision in
+ supporting this work for many years.
+
+=head2 Changes in DBI 1.19, 20th July 2001
+
+ Made fetchall_arrayref({ foo=>1, ...}) be more strict to the specification
+ in relation to wanting hash slice keys to be lowercase names.
+ WARNING: If you've used fetchall_arrayref({...}) with a hash slice
+ that contains keys with uppercase letters then your code will break.
+ (As far as I recall the spec has always said don't do that.)
+ Fixed $sth->execute() to update $dbh->{Statement} to $sth->{Statement}.
+ Added row number to trace output for fetch method calls.
+ Trace level 1 no longer shows fetches with row>1 (to reduce output volume).
+ Added $h->{FetchHashKeyName} = 'NAME_lc' or 'NAME_uc' to alter
+ behaviour of fetchrow_hashref() method. See docs.
+ Added type_info quote caching to quote() method thanks to Dean Kopesky.
+ Makes using quote() with second data type param much much faster.
+ Added type_into_all() caching to type_info(), spotted by Dean Kopesky.
+ Added new API definition for table_info() and tables(),
+ driver authors please note!
+ Added primary_key_info() to DBI API thanks to Steffen Goeldner.
+ Added primary_key() to DBI API as simpler interface to primary_key_info().
+ Indent and other fixes for DBI::DBD doc thanks to H.Merijn Brand.
+ Added prepare_cached() insert_hash() example thanks to Doug Wilson.
+ Removed false docs for fetchall_hashref(), use fetchall_arrayref({}).
+
+=head2 Changes in DBI 1.18, 4th June 2001
+
+ Fixed that altering ShowErrorStatement also altered AutoCommit!
+ Thanks to Jeff Boes for spotting that clanger.
+ Fixed DBD::Proxy to handle commit() and rollback(). Long overdue, sorry.
+ Fixed incompatibility with perl 5.004 (but no one's using that right? :)
+ Fixed connect_cached and prepare_cached to not be affected by the order
+ of elements in the attribute hash. Spotted by Mitch Helle-Morrissey.
+ Fixed version number of DBI::Shell
+ reported by Stuhlpfarrer Gerhard and others.
+ Defined and documented table_info() attribute semantics (ODBC compatible)
+ thanks to Olga Voronova, who also implemented then in DBD::Oracle.
+ Updated Win32::DBIODBC (Win32::ODBC emulation) thanks to Roy Lee.
+
+=head2 Changes in DBI 1.16, 30th May 2001
+
+ Reimplemented fetchrow_hashref in C, now fetches about 25% faster!
+ Changed behaviour if both PrintError and RaiseError are enabled
+ to simply do both (in that order, obviously :)
+ Slight reduction in DBI handle creation overhead.
+ Fixed $dbh->{Driver} & $sth->{Database} to return 'outer' handles.
+ Fixed execute param count check to honour RaiseError spotted by Belinda Giardie.
+ Fixed build for perl5.6.1 with PERLIO thanks to H.Merijn Brand.
+ Fixed client sql restrictions in ProxyServer.pm thanks to Jochen Wiedmann.
+ Fixed batch mode command parsing in Shell thanks to Christian Lemburg.
+ Fixed typo in selectcol_arrayref docs thanks to Jonathan Leffler.
+ Fixed selectrow_hashref to be available to callers thanks to T.J.Mather.
+ Fixed core dump if statement handle didn't define Statement attribute.
+ Added bind_param_inout docs to DBI::DBD thanks to Jonathan Leffler.
+ Added note to data_sources() method docs that some drivers may
+ require a connected database handle to be supplied as an attribute.
+ Trace of install_driver method now shows path of driver file loaded.
+ Changed many '||' to 'or' in the docs thanks to H.Merijn Brand.
+ Updated DBD::ADO again (improvements in error handling) from Tom Lowery.
+ Updated Win32::DBIODBC (Win32::ODBC emulation) thanks to Roy Lee.
+ Updated email and web addresses in DBI::FAQ thanks to Michael A Chase.
+
+=head2 Changes in DBI 1.15, 28th March 2001
+
+ Added selectrow_arrayref
+ Added selectrow_hashref
+ Added selectall_hashref thanks to Leon Brocard.
+ Added DBI->connect(..., { dbi_connect_method => 'method' })
+ Added $dbh->{Statement} aliased to most recent child $sth->{Statement}.
+ Added $h->{ShowErrorStatement}=1 to cause the appending of the
+ relevant Statement text to the RaiseError/PrintError text.
+ Modified type_info to always return hash keys in uppercase and
+ to not require uppercase 'DATA_TYPE' key from type_info_all.
+ Thanks to Jennifer Tong and Rob Douglas.
+ Added \%attr param to tables() and table_info() methods.
+ Trace method uses warn() if it can't open the new file.
+ Trace shows source line and filename during global destruction.
+ Updated packages:
+ Updated Win32::DBIODBC (Win32::ODBC emulation) thanks to Roy Lee.
+ Updated DBD::ADO to much improved version 0.4 from Tom Lowery.
+ Updated DBD::Sponge to include $sth->{PRECISION} thanks to Tom Lowery.
+ Changed DBD::ExampleP to use lstat() instead of stat().
+ Documentation:
+ Documented $DBI::lasth (which has been there since day 1).
+ Documented SQL_* names.
+ Clarified and extended docs for $h->state thanks to Masaaki Hirose.
+ Clarified fetchall_arrayref({}) docs (thanks to, er, someone!).
+ Clarified type_info_all re lettercase and index values.
+ Updated DBI::FAQ to 0.38 thanks to Alligator Descartes.
+ Added cute bind_columns example thanks to H.Merijn Brand.
+ Extended docs on \%attr arg to data_sources method.
+ Makefile.PL
+ Removed obscure potential 'rm -rf /' (thanks to Ulrich Pfeifer).
+ Removed use of glob and find (thanks to Michael A. Chase).
+ Proxy:
+ Removed debug messages from DBD::Proxy AUTOLOAD thanks to Brian McCauley.
+ Added fix for problem using table_info thanks to Tom Lowery.
+ Added better determination of where to put the pid file, and...
+ Added KNOWN ISSUES section to DBD::Proxy docs thanks to Jochen Wiedmann.
+ Shell:
+ Updated DBI::Format to include DBI::Format::String thanks to Tom Lowery.
+ Added describe command thanks to Tom Lowery.
+ Added columnseparator option thanks to Tom Lowery (I think).
+ Added 'raw' format thanks to, er, someone, maybe Tom again.
+ Known issues:
+ Perl 5.005 and 5.006 both leak memory doing local($handle->{Foo}).
+ Perl 5.004 doesn't. The leak is not a DBI or driver bug.
+
+=head2 Changes in DBI 1.14, 14th June 2000
+
+ NOTE: This version is the one the DBI book is based on.
+ NOTE: This version requires at least Perl 5.004.
+ Perl 5.6 ithreads changes with thanks to Doug MacEachern.
+ Changed trace output to use PerlIO thanks to Paul Moore.
+ Fixed bug in RaiseError/PrintError handling.
+ (% chars in the error string could cause a core dump.)
+ Fixed Win32 PerlEx IIS concurrency bugs thanks to Murray Nesbitt.
+ Major documentation polishing thanks to Linda Mui at O'Reilly.
+ Password parameter now shown as **** in trace output.
+ Added two fields to type_info and type_info_all.
+ Added $dsn to PrintError/RaiseError message from DBI->connect().
+ Changed prepare_cached() croak to carp if sth still Active.
+ Added prepare_cached() example to the docs.
+ Added further DBD::ADO enhancements from Thomas Lowery.
+
+=head2 Changes in DBI 1.13, 11th July 1999
+
+ Fixed Win32 PerlEx IIS concurrency bugs thanks to Murray Nesbitt.
+ Fixed problems with DBD::ExampleP long_list test mode.
+ Added SQL_WCHAR SQL_WVARCHAR SQL_WLONGVARCHAR and SQL_BIT
+ to list of known and exportable SQL types.
+ Improved data fetch performance of DBD::ADO.
+ Added GetTypeInfo to DBD::ADO thanks to Thomas Lowery.
+ Actually documented connect_cached thanks to Michael Schwern.
+ Fixed user/key/cipher bug in ProxyServer thanks to Joshua Pincus.
+
+=head2 Changes in DBI 1.12, 29th June 1999
+
+ Fixed significant DBD::ADO bug (fetch skipped first row).
+ Fixed ProxyServer bug handling non-select statements.
+ Fixed VMS problem with t/examp.t thanks to Craig Berry.
+ Trace only shows calls to trace_msg and _set_fbav at high levels.
+ Modified t/examp.t to workaround Cygwin buffering bug.
+
+=head2 Changes in DBI 1.11, 17th June 1999
+
+ Fixed bind_columns argument checking to allow a single arg.
+ Fixed problems with internal default_user method.
+ Fixed broken DBD::ADO.
+ Made default $DBI::rows more robust for some obscure cases.
+
+=head2 Changes in DBI 1.10, 14th June 1999
+
+ Fixed trace_msg.al error when using Apache.
+ Fixed dbd_st_finish enhancement in Driver.xst (internals).
+ Enable drivers to define default username and password
+ and temporarily disabled warning added in 1.09.
+ Thread safety optimised for single thread case.
+
+=head2 Changes in DBI 1.09, 9th June 1999
+
+ Added optional minimum trace level parameter to trace_msg().
+ Added warning in Makefile.PL that DBI will require 5.004 soon.
+ Added $dbh->selectcol_arrayref($statement) method.
+ Fixed fetchall_arrayref hash-slice mode undef NAME problem.
+ Fixed problem with tainted parameter checking and t/examp.t.
+ Fixed problem with thread safety code, including 64 bit machines.
+ Thread safety now enabled by default for threaded perls.
+ Enhanced code for MULTIPLICITY/PERL_OBJECT from ActiveState.
+ Enhanced prepare_cached() method.
+ Minor changes to trace levels (less internal info at level 2).
+ Trace log now shows "!! ERROR..." before the "<- method" line.
+ DBI->connect() now warn's if user / password is undefined and
+ DBI_USER / DBI_PASS environment variables are not defined.
+ The t/proxy.t test now ignores any /etc/dbiproxy.conf file.
+ Added portability fixes for MacOS from Chris Nandor.
+ Updated mailing list address from fugue.com to isc.org.
+
+=head2 Changes in DBI 1.08, 12th May 1999
+
+ Much improved DBD::ADO driver thanks to Phlip Plumlee and others.
+ Connect now allows you to specify attribute settings within the DSN
+ E.g., "dbi:Driver(RaiseError=>1,Taint=>1,AutoCommit=>0):dbname"
+ The $h->{Taint} attribute now also enables taint checking of
+ arguments to almost all DBI methods.
+ Improved trace output in various ways.
+ Fixed bug where $sth->{NAME_xx} was undef in some situations.
+ Fixed code for MULTIPLICITY/PERL_OBJECT thanks to Alex Smishlajev.
+ Fixed and documented DBI->connect_cached.
+ Workaround for Cygwin32 build problem with help from Jong-Pork Park.
+ bind_columns no longer needs undef or hash ref as first parameter.
+
+=head2 Changes in DBI 1.07, 6th May 1999
+
+ Trace output now shows contents of array refs returned by DBI.
+ Changed names of some result columns from type_info, type_info_all,
+ tables and table_info to match ODBC 3.5 / ISO/IEC standards.
+ Many fixes for DBD::Proxy and ProxyServer.
+ Fixed error reporting in install_driver.
+ Major enhancement to DBI::W32ODBC from Patrick Hollins.
+ Added $h->{Taint} to taint fetched data if tainting (perl -T).
+ Added code for MULTIPLICITY/PERL_OBJECT contributed by ActiveState.
+ Added $sth->more_results (undocumented for now).
+
+=head2 Changes in DBI 1.06, 6th January 1999
+
+ Fixed Win32 Makefile.PL problem in 1.04 and 1.05.
+ Significant DBD::Proxy enhancements and fixes
+ including support for bind_param_inout (Jochen and I)
+ Added experimental DBI->connect_cached method.
+ Added $sth->{NAME_uc} and $sth->{NAME_lc} attributes.
+ Enhanced fetchrow_hashref to take an attribute name arg.
+
+=head2 Changes in DBI 1.05, 4th January 1999
+
+ Improved DBD::ADO connect (thanks to Phlip Plumlee).
+ Improved thread safety (thanks to Jochen Wiedmann).
+ [Quick release prompted by truncation of copies on CPAN]
+
+=head2 Changes in DBI 1.04, 3rd January 1999
+
+ Fixed error in Driver.xst. DBI build now tests Driver.xst.
+ Removed unused variable compiler warnings in Driver.xst.
+ DBI::DBD module now tested during DBI build.
+ Further clarification in the DBI::DBD driver writers manual.
+ Added optional name parameter to $sth->fetchrow_hashref.
+
+=head2 Changes in DBI 1.03, 1st January 1999
+
+ Now builds with Perl>=5.005_54 (PERL_POLLUTE in DBIXS.h)
+ DBI trace trims path from "at yourfile.pl line nnn".
+ Trace level 1 now shows statement passed to prepare.
+ Assorted improvements to the DBI manual.
+ Assorted improvements to the DBI::DBD driver writers manual.
+ Fixed $dbh->quote prototype to include optional $data_type.
+ Fixed $dbh->prepare_cached problems.
+ $dbh->selectrow_array behaves better in scalar context.
+ Added a (very) experimental DBD::ADO driver for Win32 ADO.
+ Added experimental thread support (perl Makefile.PL -thread).
+ Updated the DBI::FAQ - thanks to Alligator Descartes.
+ The following changes were implemented and/or packaged
+ by Jochen Wiedmann - thanks Jochen:
+ Added a Bundle for CPAN installation of DBI, the DBI proxy
+ server and prerequisites (lib/Bundle/DBI.pm).
+ DBI->available_drivers uses File::Spec, if available.
+ This makes it work on MacOS. (DBI.pm)
+ Modified type_info to work with read-only values returned
+ by type_info_all. (DBI.pm)
+ Added handling of magic values in $sth->execute,
+ $sth->bind_param and other methods (Driver.xst)
+ Added Perl's CORE directory to the linkers path on Win32,
+ required by recent versions of ActiveState Perl.
+ Fixed DBD::Sponge to work with empty result sets.
+ Complete rewrite of DBI::ProxyServer and DBD::Proxy.
+
+=head2 Changes in DBI 1.02, 2nd September 1998
+
+ Fixed DBI::Shell including @ARGV and /current.
+ Added basic DBI::Shell test.
+ Renamed DBI::Shell /display to /format.
+
+=head2 Changes in DBI 1.01, 2nd September 1998
+
+ Many enhancements to Shell (with many contributions from
+ Jochen Wiedmann, Tom Lowery and Adam Marks).
+ Assorted fixes to DBD::Proxy and DBI::ProxyServer.
+ Tidied up trace messages - trace(2) much cleaner now.
+ Added $dbh->{RowCacheSize} and $sth->{RowsInCache}.
+ Added experimental DBI::Format (mainly for DBI::Shell).
+ Fixed fetchall_arrayref($slice_hash).
+ DBI->connect now honours PrintError=1 if connect fails.
+ Assorted clarifications to the docs.
+
+=head2 Changes in DBI 1.00, 14th August 1998
+
+ The DBI is no longer 'alpha' software!
+ Added $dbh->tables and $dbh->table_info.
+ Documented \%attr arg to data_sources method.
+ Added $sth->{TYPE}, $sth->{PRECISION} and $sth->{SCALE}.
+ Added $sth->{Statement}.
+ DBI::Shell now uses neat_list to print results
+ It also escapes "'" chars and converts newlines to spaces.
+
+=head2 Changes in DBI 0.95, 10th August 1998
+
+ WARNING: THIS IS AN EXPERIMENTAL RELEASE!
+
+ Fixed 0.94 slip so it will build on pre-5.005 again.
+ Added DBI_AUTOPROXY environment variable.
+ Array ref returned from fetch/fetchrow_arrayref now readonly.
+ Improved connect error reporting by DBD::Proxy.
+ All trace/debug messages from DBI now go to trace file.
+
+=head2 Changes in DBI 0.94, 9th August 1998
+
+ WARNING: THIS IS AN EXPERIMENTAL RELEASE!
+
+ Added DBD::Shell and dbish interactive DBI shell. Try it!
+ Any database attribs can be set via DBI->connect(,,, \%attr).
+ Added _get_fbav and _set_fbav methods for Perl driver developers
+ (see ExampleP driver for perl usage). Drivers which don't use
+ one of these methods (either via XS or Perl) are not compliant.
+ DBI trace now shows adds " at yourfile.pl line nnn"!
+ PrintError and RaiseError now prepend driver and method name.
+ The available_drivers method no longer returns NullP or Sponge.
+ Added $dbh->{Name}.
+ Added $dbh->quote($value, $data_type).
+ Added more hints to install_driver failure message.
+ Added DBD::Proxy and DBI::ProxyServer (from Jochen Wiedmann).
+ Added $DBI::neat_maxlen to control truncation of trace output.
+ Added $dbh->selectall_arrayref and $dbh->selectrow_array methods.
+ Added $dbh->tables.
+ Added $dbh->type_info and $dbh->type_info_all.
+ Added $h->trace_msg($msg) to write to trace log.
+ Added @bool = DBI::looks_like_number(@ary).
+ Many assorted improvements to the DBI docs.
+
+=head2 Changes in DBI 0.93, 13th February 1998
+
+ Fixed DBI::DBD::dbd_postamble bug causing 'Driver.xsi not found' errors.
+ Changes to handling of 'magic' values in neatsvpv (used by trace).
+ execute (in Driver.xst) stops binding after first bind error.
+ This release requires drivers to be rebuilt.
+
+=head2 Changes in DBI 0.92, 3rd February 1998
+
+ Fixed per-handle memory leak (with many thanks to Irving Reid).
+ Added $dbh->prepare_cached() caching variant of $dbh->prepare.
+ Added some attributes:
+ $h->{Active} is the handle 'Active' (vague concept) (boolean)
+ $h->{Kids} e.g. number of sth's associated with a dbh
+ $h->{ActiveKids} number of the above which are 'Active'
+ $dbh->{CachedKids} ref to prepare_cached sth cache
+ Added support for general-purpose 'private_' attributes.
+ Added experimental support for subclassing the DBI: see t/subclass.t
+ Added SQL_ALL_TYPES to exported :sql_types.
+ Added dbd_dbi_dir() and dbd_dbi_arch_dir() to DBI::DBD module so that
+ DBD Makefile.PLs can work with the DBI installed in non-standard locations.
+ Fixed 'Undefined value' warning and &sv_no output from neatsvpv/trace.
+ Fixed small 'once per interpreter' leak.
+ Assorted minor documentation fixes.
+
+=head2 Changes in DBI 0.91, 10th December 1997
+
+ NOTE: This fix may break some existing scripts:
+ DBI->connect("dbi:...",$user,$pass) was not setting AutoCommit and PrintError!
+ DBI->connect(..., { ... }) no longer sets AutoCommit or PrintError twice.
+ DBI->connect(..., { RaiseError=>1 }) now croaks if connect fails.
+ Fixed $fh parameter of $sth->dump_results;
+ Added default statement DESTROY method which carps.
+ Added default driver DESTROY method to silence AUTOLOAD/__DIE__/CGI::Carp
+ Added more SQL_* types to %EXPORT_TAGS and @EXPORT_OK.
+ Assorted documentation updates (mainly clarifications).
+ Added workaround for perl's 'sticky lvalue' bug.
+ Added better warning for bind_col(umns) where fields==0.
+ Fixed to build okay with 5.004_54 with or without USE_THREADS.
+ Note that the DBI has not been tested for thread safety yet.
+
+=head2 Changes in DBI 0.90, 6th September 1997
+
+ Can once again be built with Perl 5.003.
+ The DBI class can be subclassed more easily now.
+ InactiveDestroy fixed for drivers using the *.xst template.
+ Slightly faster handle creation.
+ Changed prototype for dbd_*_*_attrib() to add extra param.
+ Note: 0.90, 0.89 and possibly some other recent versions have
+ a small memory leak. This will be fixed in the next release.
+
+=head2 Changes in DBI 0.89, 25th July 1997
+
+ Minor fix to neatsvpv (mainly used for debug trace) to workaround
+ bug in perl where SvPV removes IOK flag from an SV.
+ Minor updates to the docs.
+
+=head2 Changes in DBI 0.88, 22nd July 1997
+
+ Fixed build for perl5.003 and Win32 with Borland.
+ Fixed documentation formatting.
+ Fixed DBI_DSN ignored for old-style connect (with explicit driver).
+ Fixed AutoCommit in DBD::ExampleP
+ Fixed $h->trace.
+ The DBI can now export SQL type values: use DBI ':sql_types';
+ Modified Driver.xst and renamed DBDI.h to dbd_xsh.h
+
+=head2 Changes in DBI 0.87, 18th July 1997
+
+ Fixed minor type clashes.
+ Added more docs about placeholders and bind values.
+
+=head2 Changes in DBI 0.86, 16th July 1997
+
+ Fixed failed connect causing 'unblessed ref' and other errors.
+ Drivers must handle AutoCommit FETCH and STORE else DBI croaks.
+ Added $h->{LongReadLen} and $h->{LongTruncOk} attributes for BLOBS.
+ Added DBI_USER and DBI_PASS env vars. See connect docs for usage.
+ Added DBI->trace() to set global trace level (like per-handle $h->trace).
+ PERL_DBI_DEBUG env var renamed DBI_DEBUG (old name still works for now).
+ Updated docs, including commit, rollback, AutoCommit and Transactions sections.
+ Added bind_param method and execute(@bind_values) to docs.
+ Fixed fetchall_arrayref.
+
+ Since the DBIS structure has change the internal version numbers have also
+ changed (DBIXS_VERSION == 9 and DBISTATE_VERSION == 9) so drivers will have
+ to be recompiled. The test is also now more sensitive and the version
+ mismatch error message now more clear about what to do. Old drivers are
+ likely to core dump (this time) until recompiled for this DBI. In future
+ DBI/DBD version mismatch will always produce a clear error message.
+
+ Note that this DBI release contains and documents many new features
+ that won't appear in drivers for some time. Driver writers might like
+ to read perldoc DBI::DBD and comment on or apply the information given.
+
+=head2 Changes in DBI 0.85, 25th June 1997
+
+ NOTE: New-style connect now defaults to AutoCommit mode unless
+ { AutoCommit => 0 } specified in connect attributes. See the docs.
+ AutoCommit attribute now defined and tracked by DBI core.
+ Drivers should use/honour this and not implement their own.
+ Added pod doc changes from Andreas and Jonathan.
+ New DBI_DSN env var default for connect method. See docs.
+ Documented the func method.
+ Fixed "Usage: DBD::_::common::DESTROY" error.
+ Fixed bug which set some attributes true when there value was fetched.
+ Added new internal DBIc_set() macro for drivers to use.
+
+=head2 Changes in DBI 0.84, 20th June 1997
+
+ Added $h->{PrintError} attribute which, if set true, causes all errors to
+ trigger a warn().
+ New-style DBI->connect call now automatically sets PrintError=1 unless
+ { PrintError => 0 } specified in the connect attributes. See the docs.
+ The old-style connect with a separate driver parameter is deprecated.
+ Fixed fetchrow_hashref.
+ Renamed $h->debug to $h->trace() and added a trace filename arg.
+ Assorted other minor tidy-ups.
+
+=head2 Changes in DBI 0.83, 11th June 1997
+
+ Added driver specification syntax to DBI->connect data_source
+ parameter: DBI->connect('dbi:driver:...', $user, $passwd);
+ The DBI->data_sources method should return data_source
+ names with the appropriate 'dbi:driver:' prefix.
+ DBI->connect will warn if \%attr is true but not a hash ref.
+ Added the new fetchrow methods:
+ @row_ary = $sth->fetchrow_array;
+ $ary_ref = $sth->fetchrow_arrayref;
+ $hash_ref = $sth->fetchrow_hashref;
+ The old fetch and fetchrow methods still work.
+ Driver implementors should implement the new names for
+ fetchrow_array and fetchrow_arrayref ASAP (use the xs ALIAS:
+ directive to define aliases for fetch and fetchrow).
+ Fixed occasional problems with t/examp.t test.
+ Added automatic errstr reporting to the debug trace output.
+ Added the DBI FAQ from Alligator Descartes in module form for
+ easy reading via "perldoc DBI::FAQ". Needs reformatting.
+ Unknown driver specific attribute names no longer croak.
+ Fixed problem with internal neatsvpv macro.
+
+=head2 Changes in DBI 0.82, 23rd May 1997
+
+ Added $h->{RaiseError} attribute which, if set true, causes all errors to
+ trigger a die(). This makes it much easier to implement robust applications
+ in terms of higher level eval { ... } blocks and rollbacks.
+ Added DBI->data_sources($driver) method for implementation by drivers.
+ The quote method now returns the string NULL (without quotes) for undef.
+ Added VMS support thanks to Dan Sugalski.
+ Added a 'quick start guide' to the README.
+ Added neatsvpv function pointer to DBIS structure to make it available for
+ use by drivers. A macro defines neatsvpv(sv,len) as (DBIS->neatsvpv(sv,len)).
+ Old XS macro SV_YES_NO changes to standard boolSV.
+ Since the DBIS structure has change the internal version numbers have also
+ changed (DBIXS_VERSION == 8 and DBISTATE_VERSION == 8) so drivers will have
+ to be recompiled.
+
+=head2 Changes in DBI 0.81, 7th May 1997
+
+ Minor fix to let DBI build using less modern perls.
+ Fixed a suprious typo warning.
+
+=head2 Changes in DBI 0.80, 6th May 1997
+
+ Builds with no changes on NT using perl5.003_99 (with thanks to Jeffrey Urlwin).
+ Automatically supports Apache::DBI (with thanks to Edmund Mergl).
+ DBI scripts no longer need to be modified to make use of Apache::DBI.
+ Added a ping method and an experimental connect_test_perf method.
+ Added a fetchhash and fetch_all methods.
+ The func method no longer pre-clears err and errstr.
+ Added ChopBlanks attribute (currently defaults to off, that may change).
+ Support for the attribute needs to be implemented by individual drivers.
+ Reworked tests into standard t/*.t form.
+ Added more pod text. Fixed assorted bugs.
+
+
+=head2 Changes in DBI 0.79, 7th Apr 1997
+
+ Minor release. Tidied up pod text and added some more descriptions
+ (especially disconnect). Minor changes to DBI.xs to remove compiler
+ warnings.
+
+=head2 Changes in DBI 0.78, 28th Mar 1997
+
+ Greatly extended the pod documentation in DBI.pm, including the under
+ used bind_columns method. Use 'perldoc DBI' to read after installing.
+ Fixed $h->err. Fetching an attribute value no longer resets err.
+ Added $h->{InactiveDestroy}, see documentation for details.
+ Improved debugging of cached ('quick') attribute fetches.
+ errstr will return err code value if there is no string value.
+ Added DBI/W32ODBC to the distribution. This is a pure-perl experimental
+ DBI emulation layer for Win32::ODBC. Note that it's unsupported, your
+ mileage will vary, and bug reports without fixes will probably be ignored.
+
+=head2 Changes in DBI 0.77, 21st Feb 1997
+
+ Removed erroneous $h->errstate and $h->errmsg methods from DBI.pm.
+ Added $h->err, $h->errstr and $h->state default methods in DBI.xs.
+ Updated informal DBI API notes in DBI.pm. Updated README slightly.
+ DBIXS.h now correctly installed into INST_ARCHAUTODIR.
+ (DBD authors will need to edit their Makefile.PL's to use
+ -I$(INSTALLSITEARCH)/auto/DBI -I$(INSTALLSITEARCH)/DBI)
+
+
+=head2 Changes in DBI 0.76, 3rd Feb 1997
+
+ Fixed a compiler type warnings (pedantic IRIX again).
+
+=head2 Changes in DBI 0.75, 27th Jan 1997
+
+ Fix problem introduced by a change in Perl5.003_XX.
+ Updated README and DBI.pm docs.
+
+=head2 Changes in DBI 0.74, 14th Jan 1997
+
+ Dispatch now sets dbi_debug to the level of the current handle
+ (this makes tracing/debugging individual handles much easier).
+ The '>> DISPATCH' log line now only logged at debug >= 3 (was 2).
+ The $csr->NUM_OF_FIELDS attribute can be set if not >0 already.
+ You can log to a file using the env var PERL_DBI_DEBUG=/tmp/dbi.log.
+ Added a type cast needed by IRIX.
+ No longer sets perl_destruct_level unless debug set >= 4.
+ Make compatible with PerlIO and sfio.
+
+=head2 Changes in DBI 0.73, 10th Oct 1996
+
+ Fixed some compiler type warnings (IRIX).
+ Fixed DBI->internal->{DebugLog} = $filename.
+ Made debug log file unbuffered.
+ Added experimental bind_param_inout method to interface.
+ Usage: $dbh->bind_param_inout($param, \$value, $maxlen [, \%attribs ])
+ (only currently used by DBD::Oracle at this time.)
+
+=head2 Changes in DBI 0.72, 23 Sep 1996
+
+ Using an undefined value as a handle now gives a better
+ error message (mainly useful for emulators like Oraperl).
+ $dbh->do($sql, @params) now works for binding placeholders.
+
+=head2 Changes in DBI 0.71, 10 July 1996
+
+ Removed spurious abort() from invalid handle check.
+ Added quote method to DBI interface and added test.
+
+=head2 Changes in DBI 0.70, 16 June 1996
+
+ Added extra invalid handle check (dbih_getcom)
+ Fixed broken $dbh->quote method.
+ Added check for old GCC in Makefile.PL
+
+=head2 Changes in DBI 0.69
+
+ Fixed small memory leak.
+ Clarified the behaviour of DBI->connect.
+ $dbh->do now returns '0E0' instead of 'OK'.
+ Fixed "Can't read $DBI::errstr, lost last handle" problem.
+
+
+=head2 Changes in DBI 0.68, 2 Mar 1996
+
+ Changes to suit perl5.002 and site_lib directories.
+ Detects old versions ahead of new in @INC.
+
+
+=head2 Changes in DBI 0.67, 15 Feb 1996
+
+ Trivial change to test suite to fix a problem shown up by the
+ Perl5.002gamma release Test::Harness.
+
+
+=head2 Changes in DBI 0.66, 29 Jan 1996
+
+ Minor changes to bring the DBI into line with 5.002 mechanisms,
+ specifically the xs/pm VERSION checking mechanism.
+ No functionality changes. One no-last-handle bug fix (rare problem).
+ Requires 5.002 (beta2 or later).
+
+
+=head2 Changes in DBI 0.65, 23 Oct 1995
+
+ Added $DBI::state to hold SQL CLI / ODBC SQLSTATE value.
+ SQLSTATE "00000" (success) is returned as "" (false), all else is true.
+ If a driver does not explicitly initialise it (via $h->{State} or
+ DBIc_STATE(imp_xxh) then $DBI::state will automatically return "" if
+ $DBI::err is false otherwise "S1000" (general error).
+ As always, this is a new feature and liable to change.
+
+ The is *no longer* a default error handler!
+ You can add your own using push(@{$h->{Handlers}}, sub { ... })
+ but be aware that this interface may change (or go away).
+
+ The DBI now automatically clears $DBI::err, errstr and state before
+ calling most DBI methods. Previously error conditions would persist.
+ Added DBIh_CLEAR_ERROR(imp_xxh) macro.
+
+ DBI now EXPORT_OK's some utility functions, neat($value),
+ neat_list(@values) and dump_results($sth).
+
+ Slightly enhanced t/min.t minimal test script in an effort to help
+ narrow down the few stray core dumps that some porters still report.
+
+ Renamed readblob to blob_read (old name still works but warns).
+ Added default blob_copy_to_file method.
+
+ Added $sth = $dbh->tables method. This returns an $sth for a query
+ which has these columns: TABLE_CATALOGUE, TABLE_OWNER, TABLE_NAME,
+ TABLE_TYPE, REMARKS in that order. The TABLE_CATALOGUE column
+ should be ignored for now.
+
+
+=head2 Changes in DBI 0.64, 23 Oct 1995
+
+ Fixed 'disconnect invalidates 1 associated cursor(s)' problem.
+ Drivers using DBIc_ACTIVE_on/off() macros should not need any changes
+ other than to test for DBIc_ACTIVE_KIDS() instead of DBIc_KIDS().
+ Fixed possible core dump in dbih_clearcom during global destruction.
+
+
+=head2 Changes in DBI 0.63, 1 Sep 1995
+
+ Minor update. Fixed uninitialised memory bug in method
+ attribute handling and streamlined processing and debugging.
+ Revised usage definitions for bind_* methods and readblob.
+
+
+=head2 Changes in DBI 0.62, 26 Aug 1995
+
+ Added method redirection method $h->func(..., $method_name).
+ This is now the official way to call private driver methods
+ that are not part of the DBI standard. E.g.:
+ @ary = $sth->func('ora_types');
+ It can also be used to call existing methods. Has very low cost.
+
+ $sth->bind_col columns now start from 1 (not 0) to match SQL.
+ $sth->bind_columns now takes a leading attribute parameter (or undef),
+ e.g., $sth->bind_columns($attribs, \$col1 [, \$col2 , ...]);
+
+ Added handy DBD_ATTRIBS_CHECK macro to vet attribs in XS.
+ Added handy DBD_ATTRIB_GET_SVP, DBD_ATTRIB_GET_BOOL and
+ DBD_ATTRIB_GET_IV macros for handling attributes.
+
+ Fixed STORE for NUM_OF_FIELDS and NUM_OF_PARAMS.
+ Added FETCH for NUM_OF_FIELDS and NUM_OF_PARAMS.
+
+ Dispatch no longer bothers to call _untie().
+ Faster startup via install_method/_add_dispatch changes.
+
+
+=head2 Changes in DBI 0.61, 22 Aug 1995
+
+ Added $sth->bind_col($column, \$var [, \%attribs ]);
+
+ This method enables perl variable to be directly and automatically
+ updated when a row is fetched. It requires no driver support
+ (if the driver has been written to use DBIS->get_fbav).
+ Currently \%attribs is unused.
+
+ Added $sth->bind_columns(\$var [, \$var , ...]);
+
+ This method is a short-cut for bind_col which binds all the
+ columns of a query in one go (with no attributes). It also
+ requires no driver support.
+
+ Added $sth->bind_param($parameter, $var [, \%attribs ]);
+
+ This method enables attributes to be specified when values are
+ bound to placeholders. It also enables binding to occur away
+ from the execute method to improve execute efficiency.
+ The DBI does not provide a default implementation of this.
+ See the DBD::Oracle module for a detailed example.
+
+ The DBI now provides default implementations of both fetch and
+ fetchrow. Each is written in terms of the other. A driver is
+ expected to implement at least one of them.
+
+ More macro and assorted structure changes in DBDXS.h. Sorry!
+ The old dbihcom definitions have gone. All fields have macros.
+ The imp_xxh_t type is now used within the DBI as well as drivers.
+ Drivers must set DBIc_NUM_FIELDS(imp_sth) and DBIc_NUM_PARAMS(imp_sth).
+
+ test.pl includes a trivial test of bind_param and bind_columns.
+
+
+=head2 Changes in DBI 0.60, 17 Aug 1995
+
+ This release has significant code changes but much less
+ dramatic than the previous release. The new implementors data
+ handling mechanism has matured significantly (don't be put off
+ by all the struct typedefs in DBIXS.h, there's just to make it
+ easier for drivers while keeping things type-safe).
+
+ The DBI now includes two new methods:
+
+ do $dbh->do($statement)
+
+ This method prepares, executes and finishes a statement. It is
+ designed to be used for executing one-off non-select statements
+ where there is no benefit in reusing a prepared statement handle.
+
+ fetch $array_ref = $sth->fetch;
+
+ This method is the new 'lowest-level' row fetching method. The
+ previous @row = $sth->fetchrow method now defaults to calling
+ the fetch method and expanding the returned array reference.
+
+ The DBI now provides fallback attribute FETCH and STORE functions
+ which drivers should call if they don't recognise an attribute.
+
+ THIS RELEASE IS A GOOD STARTING POINT FOR DRIVER DEVELOPERS!
+ Study DBIXS.h from the DBI and Oracle.xs etc from DBD::Oracle.
+ There will be further changes in the interface but nothing
+ as dramatic as these last two releases! (I hope :-)
+
+
+=head2 Changes in DBI 0.59 15 Aug 1995
+
+ NOTE: THIS IS AN UNSTABLE RELEASE!
+
+ Major reworking of internal data management!
+ Performance improvements and memory leaks fixed.
+ Added a new NullP (empty) driver and a -m flag
+ to test.pl to help check for memory leaks.
+ Study DBD::Oracle version 0.21 for more details.
+ (Comparing parts of v0.21 with v0.20 may be useful.)
+
+
+=head2 Changes in DBI 0.58 21 June 1995
+
+ Added DBI->internal->{DebugLog} = $filename;
+ Reworked internal logging.
+ Added $VERSION.
+ Made disconnect_all a compulsary method for drivers.
+
+
+=head1 ANCIENT HISTORY
+
+12th Oct 1994: First public release of the DBI module.
+ (for Perl 5.000-beta-3h)
+
+19th Sep 1994: DBperl project renamed to DBI.
+
+29th Sep 1992: DBperl project started.
+
+=cut
diff --git a/DBI.pm b/DBI.pm
new file mode 100644
index 0000000..9b39b14
--- /dev/null
+++ b/DBI.pm
@@ -0,0 +1,8323 @@
+# $Id: DBI.pm 15327 2012-06-06 16:37:26Z timbo $
+# vim: ts=8:sw=4:et
+#
+# Copyright (c) 1994-2012 Tim Bunce Ireland
+#
+# See COPYRIGHT section in pod text below for usage and distribution rights.
+#
+
+package DBI;
+
+require 5.008_001;
+
+BEGIN {
+$VERSION = "1.622"; # ==> ALSO update the version in the pod text below!
+}
+
+=head1 NAME
+
+DBI - Database independent interface for Perl
+
+=head1 SYNOPSIS
+
+ use DBI;
+
+ @driver_names = DBI->available_drivers;
+ %drivers = DBI->installed_drivers;
+ @data_sources = DBI->data_sources($driver_name, \%attr);
+
+ $dbh = DBI->connect($data_source, $username, $auth, \%attr);
+
+ $rv = $dbh->do($statement);
+ $rv = $dbh->do($statement, \%attr);
+ $rv = $dbh->do($statement, \%attr, @bind_values);
+
+ $ary_ref = $dbh->selectall_arrayref($statement);
+ $hash_ref = $dbh->selectall_hashref($statement, $key_field);
+
+ $ary_ref = $dbh->selectcol_arrayref($statement);
+ $ary_ref = $dbh->selectcol_arrayref($statement, \%attr);
+
+ @row_ary = $dbh->selectrow_array($statement);
+ $ary_ref = $dbh->selectrow_arrayref($statement);
+ $hash_ref = $dbh->selectrow_hashref($statement);
+
+ $sth = $dbh->prepare($statement);
+ $sth = $dbh->prepare_cached($statement);
+
+ $rc = $sth->bind_param($p_num, $bind_value);
+ $rc = $sth->bind_param($p_num, $bind_value, $bind_type);
+ $rc = $sth->bind_param($p_num, $bind_value, \%attr);
+
+ $rv = $sth->execute;
+ $rv = $sth->execute(@bind_values);
+ $rv = $sth->execute_array(\%attr, ...);
+
+ $rc = $sth->bind_col($col_num, \$col_variable);
+ $rc = $sth->bind_columns(@list_of_refs_to_vars_to_bind);
+
+ @row_ary = $sth->fetchrow_array;
+ $ary_ref = $sth->fetchrow_arrayref;
+ $hash_ref = $sth->fetchrow_hashref;
+
+ $ary_ref = $sth->fetchall_arrayref;
+ $ary_ref = $sth->fetchall_arrayref( $slice, $max_rows );
+
+ $hash_ref = $sth->fetchall_hashref( $key_field );
+
+ $rv = $sth->rows;
+
+ $rc = $dbh->begin_work;
+ $rc = $dbh->commit;
+ $rc = $dbh->rollback;
+
+ $quoted_string = $dbh->quote($string);
+
+ $rc = $h->err;
+ $str = $h->errstr;
+ $rv = $h->state;
+
+ $rc = $dbh->disconnect;
+
+I<The synopsis above only lists the major methods and parameters.>
+
+
+=head2 GETTING HELP
+
+If you have questions about DBI, or DBD driver modules, you can get
+help from the I<dbi-users@perl.org> mailing list. You don't have to subscribe
+to the list in order to post, though I'd recommend it. You can get help on
+subscribing and using the list by emailing I<dbi-users-help@perl.org>.
+
+I don't recommend the DBI cpanforum (at http://www.cpanforum.com/dist/DBI)
+because relatively few people read it compared with dbi-users@perl.org.
+
+To help you make the best use of the dbi-users mailing list,
+and any other lists or forums you may use, I recommend that you read
+"Getting Answers" by Mike Ash: L<http://mikeash.com/getting_answers.html>.
+
+If you think you've found a bug then please also read
+"How to Report Bugs Effectively" by Simon Tatham:
+L<http://www.chiark.greenend.org.uk/~sgtatham/bugs.html>.
+
+The DBI home page at L<http://dbi.perl.org/> and the DBI FAQ
+at L<http://faq.dbi-support.com/> may be worth a visit.
+They include links to other resources, but are rather out-dated.
+
+Before asking any questions, reread this document, consult the
+archives and read the DBI FAQ. The archives are listed
+at the end of this document and on the DBI home page.
+
+You might also like to read the Advanced DBI Tutorial at
+L<http://www.slideshare.net/Tim.Bunce/dbi-advanced-tutorial-2007>
+
+This document often uses terms like I<references>, I<objects>,
+I<methods>. If you're not familiar with those terms then it would
+be a good idea to read at least the following perl manuals first:
+L<perlreftut>, L<perldsc>, L<perllol>, and L<perlboot>.
+
+Please note that Tim Bunce does not maintain the mailing lists or the
+web page (generous volunteers do that). So please don't send mail
+directly to him; he just doesn't have the time to answer questions
+personally. The I<dbi-users> mailing list has lots of experienced
+people who should be able to help you if you need it. If you do email
+Tim he is very likely to just forward it to the mailing list.
+
+=head2 NOTES
+
+This is the DBI specification that corresponds to DBI version 1.622
+(see L<DBI::Changes> for details).
+
+The DBI is evolving at a steady pace, so it's good to check that
+you have the latest copy.
+
+The significant user-visible changes in each release are documented
+in the L<DBI::Changes> module so you can read them by executing
+C<perldoc DBI::Changes>.
+
+Some DBI changes require changes in the drivers, but the drivers
+can take some time to catch up. Newer versions of the DBI have
+added features that may not yet be supported by the drivers you
+use. Talk to the authors of your drivers if you need a new feature
+that is not yet supported.
+
+Features added after DBI 1.21 (February 2002) are marked in the
+text with the version number of the DBI release they first appeared in.
+
+Extensions to the DBI API often use the C<DBIx::*> namespace.
+See L</Naming Conventions and Name Space>. DBI extension modules
+can be found at L<http://search.cpan.org/search?mode=module&query=DBIx>.
+And all modules related to the DBI can be found at
+L<http://search.cpan.org/search?query=DBI&mode=all>.
+
+=cut
+
+# The POD text continues at the end of the file.
+
+use Carp();
+use DynaLoader ();
+use Exporter ();
+
+BEGIN {
+@ISA = qw(Exporter DynaLoader);
+
+# Make some utility functions available if asked for
+@EXPORT = (); # we export nothing by default
+@EXPORT_OK = qw(%DBI %DBI_methods hash); # also populated by export_ok_tags:
+%EXPORT_TAGS = (
+ sql_types => [ qw(
+ SQL_GUID
+ SQL_WLONGVARCHAR
+ SQL_WVARCHAR
+ SQL_WCHAR
+ SQL_BIGINT
+ SQL_BIT
+ SQL_TINYINT
+ SQL_LONGVARBINARY
+ SQL_VARBINARY
+ SQL_BINARY
+ SQL_LONGVARCHAR
+ SQL_UNKNOWN_TYPE
+ SQL_ALL_TYPES
+ SQL_CHAR
+ SQL_NUMERIC
+ SQL_DECIMAL
+ SQL_INTEGER
+ SQL_SMALLINT
+ SQL_FLOAT
+ SQL_REAL
+ SQL_DOUBLE
+ SQL_DATETIME
+ SQL_DATE
+ SQL_INTERVAL
+ SQL_TIME
+ SQL_TIMESTAMP
+ SQL_VARCHAR
+ SQL_BOOLEAN
+ SQL_UDT
+ SQL_UDT_LOCATOR
+ SQL_ROW
+ SQL_REF
+ SQL_BLOB
+ SQL_BLOB_LOCATOR
+ SQL_CLOB
+ SQL_CLOB_LOCATOR
+ SQL_ARRAY
+ SQL_ARRAY_LOCATOR
+ SQL_MULTISET
+ SQL_MULTISET_LOCATOR
+ SQL_TYPE_DATE
+ SQL_TYPE_TIME
+ SQL_TYPE_TIMESTAMP
+ SQL_TYPE_TIME_WITH_TIMEZONE
+ SQL_TYPE_TIMESTAMP_WITH_TIMEZONE
+ SQL_INTERVAL_YEAR
+ SQL_INTERVAL_MONTH
+ SQL_INTERVAL_DAY
+ SQL_INTERVAL_HOUR
+ SQL_INTERVAL_MINUTE
+ SQL_INTERVAL_SECOND
+ SQL_INTERVAL_YEAR_TO_MONTH
+ SQL_INTERVAL_DAY_TO_HOUR
+ SQL_INTERVAL_DAY_TO_MINUTE
+ SQL_INTERVAL_DAY_TO_SECOND
+ SQL_INTERVAL_HOUR_TO_MINUTE
+ SQL_INTERVAL_HOUR_TO_SECOND
+ SQL_INTERVAL_MINUTE_TO_SECOND
+ DBIstcf_DISCARD_STRING
+ DBIstcf_STRICT
+ ) ],
+ sql_cursor_types => [ qw(
+ SQL_CURSOR_FORWARD_ONLY
+ SQL_CURSOR_KEYSET_DRIVEN
+ SQL_CURSOR_DYNAMIC
+ SQL_CURSOR_STATIC
+ SQL_CURSOR_TYPE_DEFAULT
+ ) ], # for ODBC cursor types
+ utils => [ qw(
+ neat neat_list $neat_maxlen dump_results looks_like_number
+ data_string_diff data_string_desc data_diff sql_type_cast
+ ) ],
+ profile => [ qw(
+ dbi_profile dbi_profile_merge dbi_profile_merge_nodes dbi_time
+ ) ], # notionally "in" DBI::Profile and normally imported from there
+);
+
+$DBI::dbi_debug = 0; # mixture of bit fields and int sub-fields
+$DBI::neat_maxlen = 1000;
+$DBI::stderr = 2_000_000_000; # a very round number below 2**31
+
+# If you get an error here like "Can't find loadable object ..."
+# then you haven't installed the DBI correctly. Read the README
+# then install it again.
+if ( $ENV{DBI_PUREPERL} ) {
+ eval { bootstrap DBI } if $ENV{DBI_PUREPERL} == 1;
+ require DBI::PurePerl if $@ or $ENV{DBI_PUREPERL} >= 2;
+ $DBI::PurePerl ||= 0; # just to silence "only used once" warnings
+}
+else {
+ bootstrap DBI;
+}
+
+$EXPORT_TAGS{preparse_flags} = [ grep { /^DBIpp_\w\w_/ } keys %{__PACKAGE__."::"} ];
+
+Exporter::export_ok_tags(keys %EXPORT_TAGS);
+
+}
+
+# Alias some handle methods to also be DBI class methods
+for (qw(trace_msg set_err parse_trace_flag parse_trace_flags)) {
+ no strict;
+ *$_ = \&{"DBD::_::common::$_"};
+}
+
+use strict;
+
+DBI->trace(split /=/, $ENV{DBI_TRACE}, 2) if $ENV{DBI_TRACE};
+
+$DBI::connect_via ||= "connect";
+
+# check if user wants a persistent database connection ( Apache + mod_perl )
+if ($INC{'Apache/DBI.pm'} && $ENV{MOD_PERL}) {
+ $DBI::connect_via = "Apache::DBI::connect";
+ DBI->trace_msg("DBI connect via $DBI::connect_via in $INC{'Apache/DBI.pm'}\n");
+}
+
+# check for weaken support, used by ChildHandles
+my $HAS_WEAKEN = eval {
+ require Scalar::Util;
+ # this will croak() if this Scalar::Util doesn't have a working weaken().
+ Scalar::Util::weaken( \my $test ); # same test as in t/72childhandles.t
+ 1;
+};
+
+%DBI::installed_drh = (); # maps driver names to installed driver handles
+sub installed_drivers { %DBI::installed_drh }
+%DBI::installed_methods = (); # XXX undocumented, may change
+sub installed_methods { %DBI::installed_methods }
+
+# Setup special DBI dynamic variables. See DBI::var::FETCH for details.
+# These are dynamically associated with the last handle used.
+tie $DBI::err, 'DBI::var', '*err'; # special case: referenced via IHA list
+tie $DBI::state, 'DBI::var', '"state'; # special case: referenced via IHA list
+tie $DBI::lasth, 'DBI::var', '!lasth'; # special case: return boolean
+tie $DBI::errstr, 'DBI::var', '&errstr'; # call &errstr in last used pkg
+tie $DBI::rows, 'DBI::var', '&rows'; # call &rows in last used pkg
+sub DBI::var::TIESCALAR{ my $var = $_[1]; bless \$var, 'DBI::var'; }
+sub DBI::var::STORE { Carp::croak("Can't modify \$DBI::${$_[0]} special variable") }
+
+# --- Driver Specific Prefix Registry ---
+
+my $dbd_prefix_registry = {
+ ad_ => { class => 'DBD::AnyData', },
+ ado_ => { class => 'DBD::ADO', },
+ amzn_ => { class => 'DBD::Amazon', },
+ best_ => { class => 'DBD::BestWins', },
+ csv_ => { class => 'DBD::CSV', },
+ db2_ => { class => 'DBD::DB2', },
+ dbi_ => { class => 'DBI', },
+ dbm_ => { class => 'DBD::DBM', },
+ df_ => { class => 'DBD::DF', },
+ f_ => { class => 'DBD::File', },
+ file_ => { class => 'DBD::TextFile', },
+ go_ => { class => 'DBD::Gofer', },
+ ib_ => { class => 'DBD::InterBase', },
+ ing_ => { class => 'DBD::Ingres', },
+ ix_ => { class => 'DBD::Informix', },
+ jdbc_ => { class => 'DBD::JDBC', },
+ mo_ => { class => 'DBD::MO', },
+ monetdb_ => { class => 'DBD::monetdb', },
+ msql_ => { class => 'DBD::mSQL', },
+ mvsftp_ => { class => 'DBD::MVS_FTPSQL', },
+ mysql_ => { class => 'DBD::mysql', },
+ mx_ => { class => 'DBD::Multiplex', },
+ nullp_ => { class => 'DBD::NullP', },
+ odbc_ => { class => 'DBD::ODBC', },
+ ora_ => { class => 'DBD::Oracle', },
+ pg_ => { class => 'DBD::Pg', },
+ pgpp_ => { class => 'DBD::PgPP', },
+ plb_ => { class => 'DBD::Plibdata', },
+ po_ => { class => 'DBD::PO', },
+ proxy_ => { class => 'DBD::Proxy', },
+ ram_ => { class => 'DBD::RAM', },
+ rdb_ => { class => 'DBD::RDB', },
+ sapdb_ => { class => 'DBD::SAP_DB', },
+ snmp_ => { class => 'DBD::SNMP', },
+ solid_ => { class => 'DBD::Solid', },
+ spatialite_ => { class => 'DBD::Spatialite', },
+ sponge_ => { class => 'DBD::Sponge', },
+ sql_ => { class => 'DBI::DBD::SqlEngine', },
+ sqlite_ => { class => 'DBD::SQLite', },
+ syb_ => { class => 'DBD::Sybase', },
+ sys_ => { class => 'DBD::Sys', },
+ tdat_ => { class => 'DBD::Teradata', },
+ tmpl_ => { class => 'DBD::Template', },
+ tmplss_ => { class => 'DBD::TemplateSS', },
+ tree_ => { class => 'DBD::TreeData', },
+ tuber_ => { class => 'DBD::Tuber', },
+ uni_ => { class => 'DBD::Unify', },
+ vt_ => { class => 'DBD::Vt', },
+ wmi_ => { class => 'DBD::WMI', },
+ x_ => { }, # for private use
+ xbase_ => { class => 'DBD::XBase', },
+ xl_ => { class => 'DBD::Excel', },
+ yaswi_ => { class => 'DBD::Yaswi', },
+};
+
+my %dbd_class_registry = map { $dbd_prefix_registry->{$_}->{class} => { prefix => $_ } }
+ grep { exists $dbd_prefix_registry->{$_}->{class} }
+ keys %{$dbd_prefix_registry};
+
+sub dump_dbd_registry {
+ require Data::Dumper;
+ local $Data::Dumper::Sortkeys=1;
+ local $Data::Dumper::Indent=1;
+ print Data::Dumper->Dump([$dbd_prefix_registry], [qw($dbd_prefix_registry)]);
+}
+
+# --- Dynamically create the DBI Standard Interface
+
+my $keeperr = { O=>0x0004 };
+
+%DBI::DBI_methods = ( # Define the DBI interface methods per class:
+
+ common => { # Interface methods common to all DBI handle classes
+ 'DESTROY' => { O=>0x004|0x10000 },
+ 'CLEAR' => $keeperr,
+ 'EXISTS' => $keeperr,
+ 'FETCH' => { O=>0x0404 },
+ 'FETCH_many' => { O=>0x0404 },
+ 'FIRSTKEY' => $keeperr,
+ 'NEXTKEY' => $keeperr,
+ 'STORE' => { O=>0x0418 | 0x4 },
+ _not_impl => undef,
+ can => { O=>0x0100 }, # special case, see dispatch
+ debug => { U =>[1,2,'[$debug_level]'], O=>0x0004 }, # old name for trace
+ dump_handle => { U =>[1,3,'[$message [, $level]]'], O=>0x0004 },
+ err => $keeperr,
+ errstr => $keeperr,
+ state => $keeperr,
+ func => { O=>0x0006 },
+ parse_trace_flag => { U =>[2,2,'$name'], O=>0x0404, T=>8 },
+ parse_trace_flags => { U =>[2,2,'$flags'], O=>0x0404, T=>8 },
+ private_data => { U =>[1,1], O=>0x0004 },
+ set_err => { U =>[3,6,'$err, $errmsg [, $state, $method, $rv]'], O=>0x0010 },
+ trace => { U =>[1,3,'[$trace_level, [$filename]]'], O=>0x0004 },
+ trace_msg => { U =>[2,3,'$message_text [, $min_level ]' ], O=>0x0004, T=>8 },
+ swap_inner_handle => { U =>[2,3,'$h [, $allow_reparent ]'] },
+ private_attribute_info => { },
+ visit_child_handles => { U => [2,3,'$coderef [, $info ]'], O=>0x0404, T=>4 },
+ },
+ dr => { # Database Driver Interface
+ 'connect' => { U =>[1,5,'[$db [,$user [,$passwd [,\%attr]]]]'], H=>3, O=>0x8000, T=>0x200 },
+ 'connect_cached'=>{U=>[1,5,'[$db [,$user [,$passwd [,\%attr]]]]'], H=>3, O=>0x8000, T=>0x200 },
+ 'disconnect_all'=>{ U =>[1,1], O=>0x0800, T=>0x200 },
+ data_sources => { U =>[1,2,'[\%attr]' ], O=>0x0800, T=>0x200 },
+ default_user => { U =>[3,4,'$user, $pass [, \%attr]' ], T=>0x200 },
+ dbixs_revision => $keeperr,
+ },
+ db => { # Database Session Class Interface
+ data_sources => { U =>[1,2,'[\%attr]' ], O=>0x0200 },
+ take_imp_data => { U =>[1,1], O=>0x10000 },
+ clone => { U =>[1,2,'[\%attr]'], T=>0x200 },
+ connected => { U =>[1,0], O => 0x0004, T=>0x200, H=>3 },
+ begin_work => { U =>[1,2,'[ \%attr ]'], O=>0x0400, T=>0x1000 },
+ commit => { U =>[1,1], O=>0x0480|0x0800, T=>0x1000 },
+ rollback => { U =>[1,1], O=>0x0480|0x0800, T=>0x1000 },
+ 'do' => { U =>[2,0,'$statement [, \%attr [, @bind_params ] ]'], O=>0x3200 },
+ last_insert_id => { U =>[5,6,'$catalog, $schema, $table_name, $field_name [, \%attr ]'], O=>0x2800 },
+ preparse => { }, # XXX
+ prepare => { U =>[2,3,'$statement [, \%attr]'], O=>0xA200 },
+ prepare_cached => { U =>[2,4,'$statement [, \%attr [, $if_active ] ]'], O=>0xA200 },
+ selectrow_array => { U =>[2,0,'$statement [, \%attr [, @bind_params ] ]'], O=>0x2000 },
+ selectrow_arrayref=>{U =>[2,0,'$statement [, \%attr [, @bind_params ] ]'], O=>0x2000 },
+ selectrow_hashref=>{ U =>[2,0,'$statement [, \%attr [, @bind_params ] ]'], O=>0x2000 },
+ selectall_arrayref=>{U =>[2,0,'$statement [, \%attr [, @bind_params ] ]'], O=>0x2000 },
+ selectall_hashref=>{ U =>[3,0,'$statement, $keyfield [, \%attr [, @bind_params ] ]'], O=>0x2000 },
+ selectcol_arrayref=>{U =>[2,0,'$statement [, \%attr [, @bind_params ] ]'], O=>0x2000 },
+ ping => { U =>[1,1], O=>0x0404 },
+ disconnect => { U =>[1,1], O=>0x0400|0x0800|0x10000, T=>0x200 },
+ quote => { U =>[2,3, '$string [, $data_type ]' ], O=>0x0430 },
+ quote_identifier=> { U =>[2,6, '$name [, ...] [, \%attr ]' ], O=>0x0430 },
+ rows => $keeperr,
+
+ tables => { U =>[1,6,'$catalog, $schema, $table, $type [, \%attr ]' ], O=>0x2200 },
+ table_info => { U =>[1,6,'$catalog, $schema, $table, $type [, \%attr ]' ], O=>0x2200|0x8800 },
+ column_info => { U =>[5,6,'$catalog, $schema, $table, $column [, \%attr ]'],O=>0x2200|0x8800 },
+ primary_key_info=> { U =>[4,5,'$catalog, $schema, $table [, \%attr ]' ], O=>0x2200|0x8800 },
+ primary_key => { U =>[4,5,'$catalog, $schema, $table [, \%attr ]' ], O=>0x2200 },
+ foreign_key_info=> { U =>[7,8,'$pk_catalog, $pk_schema, $pk_table, $fk_catalog, $fk_schema, $fk_table [, \%attr ]' ], O=>0x2200|0x8800 },
+ statistics_info => { U =>[6,7,'$catalog, $schema, $table, $unique_only, $quick, [, \%attr ]' ], O=>0x2200|0x8800 },
+ type_info_all => { U =>[1,1], O=>0x2200|0x0800 },
+ type_info => { U =>[1,2,'$data_type'], O=>0x2200 },
+ get_info => { U =>[2,2,'$info_type'], O=>0x2200|0x0800 },
+ },
+ st => { # Statement Class Interface
+ bind_col => { U =>[3,4,'$column, \\$var [, \%attr]'] },
+ bind_columns => { U =>[2,0,'\\$var1 [, \\$var2, ...]'] },
+ bind_param => { U =>[3,4,'$parameter, $var [, \%attr]'] },
+ bind_param_inout=> { U =>[4,5,'$parameter, \\$var, $maxlen, [, \%attr]'] },
+ execute => { U =>[1,0,'[@args]'], O=>0x1040 },
+
+ bind_param_array => { U =>[3,4,'$parameter, $var [, \%attr]'] },
+ bind_param_inout_array => { U =>[4,5,'$parameter, \\@var, $maxlen, [, \%attr]'] },
+ execute_array => { U =>[2,0,'\\%attribs [, @args]'], O=>0x1040|0x4000 },
+ execute_for_fetch => { U =>[2,3,'$fetch_sub [, $tuple_status]'], O=>0x1040|0x4000 },
+
+ fetch => undef, # alias for fetchrow_arrayref
+ fetchrow_arrayref => undef,
+ fetchrow_hashref => undef,
+ fetchrow_array => undef,
+ fetchrow => undef, # old alias for fetchrow_array
+
+ fetchall_arrayref => { U =>[1,3, '[ $slice [, $max_rows]]'] },
+ fetchall_hashref => { U =>[2,2,'$key_field'] },
+
+ blob_read => { U =>[4,5,'$field, $offset, $len [, \\$buf [, $bufoffset]]'] },
+ blob_copy_to_file => { U =>[3,3,'$field, $filename_or_handleref'] },
+ dump_results => { U =>[1,5,'$maxfieldlen, $linesep, $fieldsep, $filehandle'] },
+ more_results => { U =>[1,1] },
+ finish => { U =>[1,1] },
+ cancel => { U =>[1,1], O=>0x0800 },
+ rows => $keeperr,
+
+ _get_fbav => undef,
+ _set_fbav => { T=>6 },
+ },
+);
+
+while ( my ($class, $meths) = each %DBI::DBI_methods ) {
+ my $ima_trace = 0+($ENV{DBI_IMA_TRACE}||0);
+ while ( my ($method, $info) = each %$meths ) {
+ my $fullmeth = "DBI::${class}::$method";
+ if (($DBI::dbi_debug & 0xF) == 15) { # quick hack to list DBI methods
+ # and optionally filter by IMA flags
+ my $O = $info->{O}||0;
+ printf "0x%04x %-20s\n", $O, $fullmeth
+ unless $ima_trace && !($O & $ima_trace);
+ }
+ DBI->_install_method($fullmeth, 'DBI.pm', $info);
+ }
+}
+
+{
+ package DBI::common;
+ @DBI::dr::ISA = ('DBI::common');
+ @DBI::db::ISA = ('DBI::common');
+ @DBI::st::ISA = ('DBI::common');
+}
+
+# End of init code
+
+
+END {
+ return unless defined &DBI::trace_msg; # return unless bootstrap'd ok
+ local ($!,$?);
+ DBI->trace_msg(sprintf(" -- DBI::END (\$\@: %s, \$!: %s)\n", $@||'', $!||''), 2);
+ # Let drivers know why we are calling disconnect_all:
+ $DBI::PERL_ENDING = $DBI::PERL_ENDING = 1; # avoid typo warning
+ DBI->disconnect_all() if %DBI::installed_drh;
+}
+
+
+sub CLONE {
+ _clone_dbis() unless $DBI::PurePerl; # clone the DBIS structure
+ DBI->trace_msg("CLONE DBI for new thread\n");
+ while ( my ($driver, $drh) = each %DBI::installed_drh) {
+ no strict 'refs';
+ next if defined &{"DBD::${driver}::CLONE"};
+ warn("$driver has no driver CLONE() function so is unsafe threaded\n");
+ }
+ %DBI::installed_drh = (); # clear loaded drivers so they have a chance to reinitialize
+}
+
+sub parse_dsn {
+ my ($class, $dsn) = @_;
+ $dsn =~ s/^(dbi):(\w*?)(?:\((.*?)\))?://i or return;
+ my ($scheme, $driver, $attr, $attr_hash) = (lc($1), $2, $3);
+ $driver ||= $ENV{DBI_DRIVER} || '';
+ $attr_hash = { split /\s*=>?\s*|\s*,\s*/, $attr, -1 } if $attr;
+ return ($scheme, $driver, $attr, $attr_hash, $dsn);
+}
+
+sub visit_handles {
+ my ($class, $code, $outer_info) = @_;
+ $outer_info = {} if not defined $outer_info;
+ my %drh = DBI->installed_drivers;
+ for my $h (values %drh) {
+ my $child_info = $code->($h, $outer_info)
+ or next;
+ $h->visit_child_handles($code, $child_info);
+ }
+ return $outer_info;
+}
+
+
+# --- The DBI->connect Front Door methods
+
+sub connect_cached {
+ # For library code using connect_cached() with mod_perl
+ # we redirect those calls to Apache::DBI::connect() as well
+ my ($class, $dsn, $user, $pass, $attr) = @_;
+ my $dbi_connect_method = ($DBI::connect_via eq "Apache::DBI::connect")
+ ? 'Apache::DBI::connect' : 'connect_cached';
+ $attr = {
+ $attr ? %$attr : (), # clone, don't modify callers data
+ dbi_connect_method => $dbi_connect_method,
+ };
+ return $class->connect($dsn, $user, $pass, $attr);
+}
+
+sub connect {
+ my $class = shift;
+ my ($dsn, $user, $pass, $attr, $old_driver) = my @orig_args = @_;
+ my $driver;
+
+ if ($attr and !ref($attr)) { # switch $old_driver<->$attr if called in old style
+ Carp::carp("DBI->connect using 'old-style' syntax is deprecated and will be an error in future versions");
+ ($old_driver, $attr) = ($attr, $old_driver);
+ }
+
+ my $connect_meth = $attr->{dbi_connect_method};
+ $connect_meth ||= $DBI::connect_via; # fallback to default
+
+ $dsn ||= $ENV{DBI_DSN} || $ENV{DBI_DBNAME} || '' unless $old_driver;
+
+ if ($DBI::dbi_debug) {
+ local $^W = 0;
+ pop @_ if $connect_meth ne 'connect';
+ my @args = @_; $args[2] = '****'; # hide password
+ DBI->trace_msg(" -> $class->$connect_meth(".join(", ",@args).")\n");
+ }
+ Carp::croak('Usage: $class->connect([$dsn [,$user [,$passwd [,\%attr]]]])')
+ if (ref $old_driver or ($attr and not ref $attr) or ref $pass);
+
+ # extract dbi:driver prefix from $dsn into $1
+ $dsn =~ s/^dbi:(\w*?)(?:\((.*?)\))?://i
+ or '' =~ /()/; # ensure $1 etc are empty if match fails
+ my $driver_attrib_spec = $2 || '';
+
+ # Set $driver. Old style driver, if specified, overrides new dsn style.
+ $driver = $old_driver || $1 || $ENV{DBI_DRIVER}
+ or Carp::croak("Can't connect to data source '$dsn' "
+ ."because I can't work out what driver to use "
+ ."(it doesn't seem to contain a 'dbi:driver:' prefix "
+ ."and the DBI_DRIVER env var is not set)");
+
+ my $proxy;
+ if ($ENV{DBI_AUTOPROXY} && $driver ne 'Proxy' && $driver ne 'Sponge' && $driver ne 'Switch') {
+ my $dbi_autoproxy = $ENV{DBI_AUTOPROXY};
+ $proxy = 'Proxy';
+ if ($dbi_autoproxy =~ s/^dbi:(\w*?)(?:\((.*?)\))?://i) {
+ $proxy = $1;
+ $driver_attrib_spec = join ",",
+ ($driver_attrib_spec) ? $driver_attrib_spec : (),
+ ($2 ) ? $2 : ();
+ }
+ $dsn = "$dbi_autoproxy;dsn=dbi:$driver:$dsn";
+ $driver = $proxy;
+ DBI->trace_msg(" DBI_AUTOPROXY: dbi:$driver($driver_attrib_spec):$dsn\n");
+ }
+ # avoid recursion if proxy calls DBI->connect itself
+ local $ENV{DBI_AUTOPROXY} if $ENV{DBI_AUTOPROXY};
+
+ my %attributes; # take a copy we can delete from
+ if ($old_driver) {
+ %attributes = %$attr if $attr;
+ }
+ else { # new-style connect so new default semantics
+ %attributes = (
+ PrintError => 1,
+ AutoCommit => 1,
+ ref $attr ? %$attr : (),
+ # attributes in DSN take precedence over \%attr connect parameter
+ $driver_attrib_spec ? (split /\s*=>?\s*|\s*,\s*/, $driver_attrib_spec, -1) : (),
+ );
+ }
+ $attr = \%attributes; # now set $attr to refer to our local copy
+
+ my $drh = $DBI::installed_drh{$driver} || $class->install_driver($driver)
+ or die "panic: $class->install_driver($driver) failed";
+
+ # attributes in DSN take precedence over \%attr connect parameter
+ $user = $attr->{Username} if defined $attr->{Username};
+ $pass = $attr->{Password} if defined $attr->{Password};
+ delete $attr->{Password}; # always delete Password as closure stores it securely
+ if ( !(defined $user && defined $pass) ) {
+ ($user, $pass) = $drh->default_user($user, $pass, $attr);
+ }
+ $attr->{Username} = $user; # force the Username to be the actual one used
+
+ my $connect_closure = sub {
+ my ($old_dbh, $override_attr) = @_;
+
+ #use Data::Dumper;
+ #warn "connect_closure: ".Data::Dumper::Dumper([$attr,\%attributes, $override_attr]);
+
+ my $dbh;
+ unless ($dbh = $drh->$connect_meth($dsn, $user, $pass, $attr)) {
+ $user = '' if !defined $user;
+ $dsn = '' if !defined $dsn;
+ # $drh->errstr isn't safe here because $dbh->DESTROY may not have
+ # been called yet and so the dbh errstr would not have been copied
+ # up to the drh errstr. Certainly true for connect_cached!
+ my $errstr = $DBI::errstr;
+ # Getting '(no error string)' here is a symptom of a ref loop
+ $errstr = '(no error string)' if !defined $errstr;
+ my $msg = "$class connect('$dsn','$user',...) failed: $errstr";
+ DBI->trace_msg(" $msg\n");
+ # XXX HandleWarn
+ unless ($attr->{HandleError} && $attr->{HandleError}->($msg, $drh, $dbh)) {
+ Carp::croak($msg) if $attr->{RaiseError};
+ Carp::carp ($msg) if $attr->{PrintError};
+ }
+ $! = 0; # for the daft people who do DBI->connect(...) || die "$!";
+ return $dbh; # normally undef, but HandleError could change it
+ }
+
+ # merge any attribute overrides but don't change $attr itself (for closure)
+ my $apply = { ($override_attr) ? (%$attr, %$override_attr ) : %$attr };
+
+ # handle basic RootClass subclassing:
+ my $rebless_class = $apply->{RootClass} || ($class ne 'DBI' ? $class : '');
+ if ($rebless_class) {
+ no strict 'refs';
+ if ($apply->{RootClass}) { # explicit attribute (ie not static methd call class)
+ delete $apply->{RootClass};
+ DBI::_load_class($rebless_class, 0);
+ }
+ unless (@{"$rebless_class\::db::ISA"} && @{"$rebless_class\::st::ISA"}) {
+ Carp::carp("DBI subclasses '$rebless_class\::db' and ::st are not setup, RootClass ignored");
+ $rebless_class = undef;
+ $class = 'DBI';
+ }
+ else {
+ $dbh->{RootClass} = $rebless_class; # $dbh->STORE called via plain DBI::db
+ DBI::_set_isa([$rebless_class], 'DBI'); # sets up both '::db' and '::st'
+ DBI::_rebless($dbh, $rebless_class); # appends '::db'
+ }
+ }
+
+ if (%$apply) {
+
+ if ($apply->{DbTypeSubclass}) {
+ my $DbTypeSubclass = delete $apply->{DbTypeSubclass};
+ DBI::_rebless_dbtype_subclass($dbh, $rebless_class||$class, $DbTypeSubclass);
+ }
+ my $a;
+ foreach $a (qw(Profile RaiseError PrintError AutoCommit)) { # do these first
+ next unless exists $apply->{$a};
+ $dbh->{$a} = delete $apply->{$a};
+ }
+ while ( my ($a, $v) = each %$apply) {
+ eval { $dbh->{$a} = $v }; # assign in void context to avoid re-FETCH
+ warn $@ if $@;
+ }
+ }
+
+ # confirm to driver (ie if subclassed) that we've connected sucessfully
+ # and finished the attribute setup. pass in the original arguments
+ $dbh->connected(@orig_args); #if ref $dbh ne 'DBI::db' or $proxy;
+
+ DBI->trace_msg(" <- connect= $dbh\n") if $DBI::dbi_debug & 0xF;
+
+ return $dbh;
+ };
+
+ my $dbh = &$connect_closure(undef, undef);
+
+ $dbh->{dbi_connect_closure} = $connect_closure if $dbh;
+
+ return $dbh;
+}
+
+
+sub disconnect_all {
+ keys %DBI::installed_drh; # reset iterator
+ while ( my ($name, $drh) = each %DBI::installed_drh ) {
+ $drh->disconnect_all() if ref $drh;
+ }
+}
+
+
+sub disconnect { # a regular beginners bug
+ Carp::croak("DBI->disconnect is not a DBI method (read the DBI manual)");
+}
+
+
+sub install_driver { # croaks on failure
+ my $class = shift;
+ my($driver, $attr) = @_;
+ my $drh;
+
+ $driver ||= $ENV{DBI_DRIVER} || '';
+
+ # allow driver to be specified as a 'dbi:driver:' string
+ $driver = $1 if $driver =~ s/^DBI:(.*?)://i;
+
+ Carp::croak("usage: $class->install_driver(\$driver [, \%attr])")
+ unless ($driver and @_<=3);
+
+ # already installed
+ return $drh if $drh = $DBI::installed_drh{$driver};
+
+ $class->trace_msg(" -> $class->install_driver($driver"
+ .") for $^O perl=$] pid=$$ ruid=$< euid=$>\n")
+ if $DBI::dbi_debug & 0xF;
+
+ # --- load the code
+ my $driver_class = "DBD::$driver";
+ eval qq{package # hide from PAUSE
+ DBI::_firesafe; # just in case
+ require $driver_class; # load the driver
+ };
+ if ($@) {
+ my $err = $@;
+ my $advice = "";
+ if ($err =~ /Can't find loadable object/) {
+ $advice = "Perhaps DBD::$driver was statically linked into a new perl binary."
+ ."\nIn which case you need to use that new perl binary."
+ ."\nOr perhaps only the .pm file was installed but not the shared object file."
+ }
+ elsif ($err =~ /Can't locate.*?DBD\/$driver\.pm in \@INC/) {
+ my @drv = $class->available_drivers(1);
+ $advice = "Perhaps the DBD::$driver perl module hasn't been fully installed,\n"
+ ."or perhaps the capitalisation of '$driver' isn't right.\n"
+ ."Available drivers: ".join(", ", @drv).".";
+ }
+ elsif ($err =~ /Can't load .*? for module DBD::/) {
+ $advice = "Perhaps a required shared library or dll isn't installed where expected";
+ }
+ elsif ($err =~ /Can't locate .*? in \@INC/) {
+ $advice = "Perhaps a module that DBD::$driver requires hasn't been fully installed";
+ }
+ Carp::croak("install_driver($driver) failed: $err$advice\n");
+ }
+ if ($DBI::dbi_debug & 0xF) {
+ no strict 'refs';
+ (my $driver_file = $driver_class) =~ s/::/\//g;
+ my $dbd_ver = ${"$driver_class\::VERSION"} || "undef";
+ $class->trace_msg(" install_driver: $driver_class version $dbd_ver"
+ ." loaded from $INC{qq($driver_file.pm)}\n");
+ }
+
+ # --- do some behind-the-scenes checks and setups on the driver
+ $class->setup_driver($driver_class);
+
+ # --- run the driver function
+ $drh = eval { $driver_class->driver($attr || {}) };
+ unless ($drh && ref $drh && !$@) {
+ my $advice = "";
+ $@ ||= "$driver_class->driver didn't return a handle";
+ # catch people on case in-sensitive systems using the wrong case
+ $advice = "\nPerhaps the capitalisation of DBD '$driver' isn't right."
+ if $@ =~ /locate object method/;
+ Carp::croak("$driver_class initialisation failed: $@$advice");
+ }
+
+ $DBI::installed_drh{$driver} = $drh;
+ $class->trace_msg(" <- install_driver= $drh\n") if $DBI::dbi_debug & 0xF;
+ $drh;
+}
+
+*driver = \&install_driver; # currently an alias, may change
+
+
+sub setup_driver {
+ my ($class, $driver_class) = @_;
+ my $h_type;
+ foreach $h_type (qw(dr db st)){
+ my $h_class = $driver_class."::$h_type";
+ no strict 'refs';
+ push @{"${h_class}::ISA"}, "DBD::_::$h_type"
+ unless UNIVERSAL::isa($h_class, "DBD::_::$h_type");
+ # The _mem class stuff is (IIRC) a crufty hack for global destruction
+ # timing issues in early versions of perl5 and possibly no longer needed.
+ my $mem_class = "DBD::_mem::$h_type";
+ push @{"${h_class}_mem::ISA"}, $mem_class
+ unless UNIVERSAL::isa("${h_class}_mem", $mem_class)
+ or $DBI::PurePerl;
+ }
+}
+
+
+sub _rebless {
+ my $dbh = shift;
+ my ($outer, $inner) = DBI::_handles($dbh);
+ my $class = shift(@_).'::db';
+ bless $inner => $class;
+ bless $outer => $class; # outer last for return
+}
+
+
+sub _set_isa {
+ my ($classes, $topclass) = @_;
+ my $trace = DBI->trace_msg(" _set_isa([@$classes])\n");
+ foreach my $suffix ('::db','::st') {
+ my $previous = $topclass || 'DBI'; # trees are rooted here
+ foreach my $class (@$classes) {
+ my $base_class = $previous.$suffix;
+ my $sub_class = $class.$suffix;
+ my $sub_class_isa = "${sub_class}::ISA";
+ no strict 'refs';
+ if (@$sub_class_isa) {
+ DBI->trace_msg(" $sub_class_isa skipped (already set to @$sub_class_isa)\n")
+ if $trace;
+ }
+ else {
+ @$sub_class_isa = ($base_class) unless @$sub_class_isa;
+ DBI->trace_msg(" $sub_class_isa = $base_class\n")
+ if $trace;
+ }
+ $previous = $class;
+ }
+ }
+}
+
+
+sub _rebless_dbtype_subclass {
+ my ($dbh, $rootclass, $DbTypeSubclass) = @_;
+ # determine the db type names for class hierarchy
+ my @hierarchy = DBI::_dbtype_names($dbh, $DbTypeSubclass);
+ # add the rootclass prefix to each ('DBI::' or 'MyDBI::' etc)
+ $_ = $rootclass.'::'.$_ foreach (@hierarchy);
+ # load the modules from the 'top down'
+ DBI::_load_class($_, 1) foreach (reverse @hierarchy);
+ # setup class hierarchy if needed, does both '::db' and '::st'
+ DBI::_set_isa(\@hierarchy, $rootclass);
+ # finally bless the handle into the subclass
+ DBI::_rebless($dbh, $hierarchy[0]);
+}
+
+
+sub _dbtype_names { # list dbtypes for hierarchy, ie Informix=>ADO=>ODBC
+ my ($dbh, $DbTypeSubclass) = @_;
+
+ if ($DbTypeSubclass && $DbTypeSubclass ne '1' && ref $DbTypeSubclass ne 'CODE') {
+ # treat $DbTypeSubclass as a comma separated list of names
+ my @dbtypes = split /\s*,\s*/, $DbTypeSubclass;
+ $dbh->trace_msg(" DbTypeSubclass($DbTypeSubclass)=@dbtypes (explicit)\n");
+ return @dbtypes;
+ }
+
+ # XXX will call $dbh->get_info(17) (=SQL_DBMS_NAME) in future?
+
+ my $driver = $dbh->{Driver}->{Name};
+ if ( $driver eq 'Proxy' ) {
+ # XXX Looking into the internals of DBD::Proxy is questionable!
+ ($driver) = $dbh->{proxy_client}->{application} =~ /^DBI:(.+?):/i
+ or die "Can't determine driver name from proxy";
+ }
+
+ my @dbtypes = (ucfirst($driver));
+ if ($driver eq 'ODBC' || $driver eq 'ADO') {
+ # XXX will move these out and make extensible later:
+ my $_dbtype_name_regexp = 'Oracle'; # eg 'Oracle|Foo|Bar'
+ my %_dbtype_name_map = (
+ 'Microsoft SQL Server' => 'MSSQL',
+ 'SQL Server' => 'Sybase',
+ 'Adaptive Server Anywhere' => 'ASAny',
+ 'ADABAS D' => 'AdabasD',
+ );
+
+ my $name;
+ $name = $dbh->func(17, 'GetInfo') # SQL_DBMS_NAME
+ if $driver eq 'ODBC';
+ $name = $dbh->{ado_conn}->Properties->Item('DBMS Name')->Value
+ if $driver eq 'ADO';
+ die "Can't determine driver name! ($DBI::errstr)\n"
+ unless $name;
+
+ my $dbtype;
+ if ($_dbtype_name_map{$name}) {
+ $dbtype = $_dbtype_name_map{$name};
+ }
+ else {
+ if ($name =~ /($_dbtype_name_regexp)/) {
+ $dbtype = lc($1);
+ }
+ else { # generic mangling for other names:
+ $dbtype = lc($name);
+ }
+ $dbtype =~ s/\b(\w)/\U$1/g;
+ $dbtype =~ s/\W+/_/g;
+ }
+ # add ODBC 'behind' ADO
+ push @dbtypes, 'ODBC' if $driver eq 'ADO';
+ # add discovered dbtype in front of ADO/ODBC
+ unshift @dbtypes, $dbtype;
+ }
+ @dbtypes = &$DbTypeSubclass($dbh, \@dbtypes)
+ if (ref $DbTypeSubclass eq 'CODE');
+ $dbh->trace_msg(" DbTypeSubclass($DbTypeSubclass)=@dbtypes\n");
+ return @dbtypes;
+}
+
+sub _load_class {
+ my ($load_class, $missing_ok) = @_;
+ DBI->trace_msg(" _load_class($load_class, $missing_ok)\n", 2);
+ no strict 'refs';
+ return 1 if @{"$load_class\::ISA"}; # already loaded/exists
+ (my $module = $load_class) =~ s!::!/!g;
+ DBI->trace_msg(" _load_class require $module\n", 2);
+ eval { require "$module.pm"; };
+ return 1 unless $@;
+ return 0 if $missing_ok && $@ =~ /^Can't locate \Q$module.pm\E/;
+ die $@;
+}
+
+
+sub init_rootclass { # deprecated
+ return 1;
+}
+
+
+*internal = \&DBD::Switch::dr::driver;
+
+sub driver_prefix {
+ my ($class, $driver) = @_;
+ return $dbd_class_registry{$driver}->{prefix} if exists $dbd_class_registry{$driver};
+ return;
+}
+
+sub available_drivers {
+ my($quiet) = @_;
+ my(@drivers, $d, $f);
+ local(*DBI::DIR, $@);
+ my(%seen_dir, %seen_dbd);
+ my $haveFileSpec = eval { require File::Spec };
+ foreach $d (@INC){
+ chomp($d); # Perl 5 beta 3 bug in #!./perl -Ilib from Test::Harness
+ my $dbd_dir =
+ ($haveFileSpec ? File::Spec->catdir($d, 'DBD') : "$d/DBD");
+ next unless -d $dbd_dir;
+ next if $seen_dir{$d};
+ $seen_dir{$d} = 1;
+ # XXX we have a problem here with case insensitive file systems
+ # XXX since we can't tell what case must be used when loading.
+ opendir(DBI::DIR, $dbd_dir) || Carp::carp "opendir $dbd_dir: $!\n";
+ foreach $f (readdir(DBI::DIR)){
+ next unless $f =~ s/\.pm$//;
+ next if $f eq 'NullP';
+ if ($seen_dbd{$f}){
+ Carp::carp "DBD::$f in $d is hidden by DBD::$f in $seen_dbd{$f}\n"
+ unless $quiet;
+ } else {
+ push(@drivers, $f);
+ }
+ $seen_dbd{$f} = $d;
+ }
+ closedir(DBI::DIR);
+ }
+
+ # "return sort @drivers" will not DWIM in scalar context.
+ return wantarray ? sort @drivers : @drivers;
+}
+
+sub installed_versions {
+ my ($class, $quiet) = @_;
+ my %error;
+ my %version = ( DBI => $DBI::VERSION );
+ $version{"DBI::PurePerl"} = $DBI::PurePerl::VERSION
+ if $DBI::PurePerl;
+ for my $driver ($class->available_drivers($quiet)) {
+ next if $DBI::PurePerl && grep { -d "$_/auto/DBD/$driver" } @INC;
+ my $drh = eval {
+ local $SIG{__WARN__} = sub {};
+ $class->install_driver($driver);
+ };
+ ($error{"DBD::$driver"}=$@),next if $@;
+ no strict 'refs';
+ my $vers = ${"DBD::$driver" . '::VERSION'};
+ $version{"DBD::$driver"} = $vers || '?';
+ }
+ if (wantarray) {
+ return map { m/^DBD::(\w+)/ ? ($1) : () } sort keys %version;
+ }
+ if (!defined wantarray) { # void context
+ require Config; # add more detail
+ $version{OS} = "$^O\t($Config::Config{osvers})";
+ $version{Perl} = "$]\t($Config::Config{archname})";
+ $version{$_} = (($error{$_} =~ s/ \(\@INC.*//s),$error{$_})
+ for keys %error;
+ printf " %-16s: %s\n",$_,$version{$_}
+ for reverse sort keys %version;
+ }
+ return \%version;
+}
+
+
+sub data_sources {
+ my ($class, $driver, @other) = @_;
+ my $drh = $class->install_driver($driver);
+ my @ds = $drh->data_sources(@other);
+ return @ds;
+}
+
+
+sub neat_list {
+ my ($listref, $maxlen, $sep) = @_;
+ $maxlen = 0 unless defined $maxlen; # 0 == use internal default
+ $sep = ", " unless defined $sep;
+ join($sep, map { neat($_,$maxlen) } @$listref);
+}
+
+
+sub dump_results { # also aliased as a method in DBD::_::st
+ my ($sth, $maxlen, $lsep, $fsep, $fh) = @_;
+ return 0 unless $sth;
+ $maxlen ||= 35;
+ $lsep ||= "\n";
+ $fh ||= \*STDOUT;
+ my $rows = 0;
+ my $ref;
+ while($ref = $sth->fetch) {
+ print $fh $lsep if $rows++ and $lsep;
+ my $str = neat_list($ref,$maxlen,$fsep);
+ print $fh $str; # done on two lines to avoid 5.003 errors
+ }
+ print $fh "\n$rows rows".($DBI::err ? " ($DBI::err: $DBI::errstr)" : "")."\n";
+ $rows;
+}
+
+
+sub data_diff {
+ my ($a, $b, $logical) = @_;
+
+ my $diff = data_string_diff($a, $b);
+ return "" if $logical and !$diff;
+
+ my $a_desc = data_string_desc($a);
+ my $b_desc = data_string_desc($b);
+ return "" if !$diff and $a_desc eq $b_desc;
+
+ $diff ||= "Strings contain the same sequence of characters"
+ if length($a);
+ $diff .= "\n" if $diff;
+ return "a: $a_desc\nb: $b_desc\n$diff";
+}
+
+
+sub data_string_diff {
+ # Compares 'logical' characters, not bytes, so a latin1 string and an
+ # an equivalent Unicode string will compare as equal even though their
+ # byte encodings are different.
+ my ($a, $b) = @_;
+ unless (defined $a and defined $b) { # one undef
+ return ""
+ if !defined $a and !defined $b;
+ return "String a is undef, string b has ".length($b)." characters"
+ if !defined $a;
+ return "String b is undef, string a has ".length($a)." characters"
+ if !defined $b;
+ }
+
+ require utf8;
+ # hack to cater for perl 5.6
+ *utf8::is_utf8 = sub { (DBI::neat(shift)=~/^"/) } unless defined &utf8::is_utf8;
+
+ my @a_chars = (utf8::is_utf8($a)) ? unpack("U*", $a) : unpack("C*", $a);
+ my @b_chars = (utf8::is_utf8($b)) ? unpack("U*", $b) : unpack("C*", $b);
+ my $i = 0;
+ while (@a_chars && @b_chars) {
+ ++$i, shift(@a_chars), shift(@b_chars), next
+ if $a_chars[0] == $b_chars[0];# compare ordinal values
+ my @desc = map {
+ $_ > 255 ? # if wide character...
+ sprintf("\\x{%04X}", $_) : # \x{...}
+ chr($_) =~ /[[:cntrl:]]/ ? # else if control character ...
+ sprintf("\\x%02X", $_) : # \x..
+ chr($_) # else as themselves
+ } ($a_chars[0], $b_chars[0]);
+ # highlight probable double-encoding?
+ foreach my $c ( @desc ) {
+ next unless $c =~ m/\\x\{08(..)}/;
+ $c .= "='" .chr(hex($1)) ."'"
+ }
+ return sprintf "Strings differ at index $i: a[$i]=$desc[0], b[$i]=$desc[1]";
+ }
+ return "String a truncated after $i characters" if @b_chars;
+ return "String b truncated after $i characters" if @a_chars;
+ return "";
+}
+
+
+sub data_string_desc { # describe a data string
+ my ($a) = @_;
+ require bytes;
+ require utf8;
+
+ # hacks to cater for perl 5.6
+ *utf8::is_utf8 = sub { (DBI::neat(shift)=~/^"/) } unless defined &utf8::is_utf8;
+ *utf8::valid = sub { 1 } unless defined &utf8::valid;
+
+ # Give sufficient info to help diagnose at least these kinds of situations:
+ # - valid UTF8 byte sequence but UTF8 flag not set
+ # (might be ascii so also need to check for hibit to make it worthwhile)
+ # - UTF8 flag set but invalid UTF8 byte sequence
+ # could do better here, but this'll do for now
+ my $utf8 = sprintf "UTF8 %s%s",
+ utf8::is_utf8($a) ? "on" : "off",
+ utf8::valid($a||'') ? "" : " but INVALID encoding";
+ return "$utf8, undef" unless defined $a;
+ my $is_ascii = $a =~ m/^[\000-\177]*$/;
+ return sprintf "%s, %s, %d characters %d bytes",
+ $utf8, $is_ascii ? "ASCII" : "non-ASCII",
+ length($a), bytes::length($a);
+}
+
+
+sub connect_test_perf {
+ my($class, $dsn,$dbuser,$dbpass, $attr) = @_;
+ Carp::croak("connect_test_perf needs hash ref as fourth arg") unless ref $attr;
+ # these are non standard attributes just for this special method
+ my $loops ||= $attr->{dbi_loops} || 5;
+ my $par ||= $attr->{dbi_par} || 1; # parallelism
+ my $verb ||= $attr->{dbi_verb} || 1;
+ my $meth ||= $attr->{dbi_meth} || 'connect';
+ print "$dsn: testing $loops sets of $par connections:\n";
+ require "FileHandle.pm"; # don't let toke.c create empty FileHandle package
+ local $| = 1;
+ my $drh = $class->install_driver($dsn) or Carp::croak("Can't install $dsn driver\n");
+ # test the connection and warm up caches etc
+ $drh->connect($dsn,$dbuser,$dbpass) or Carp::croak("connect failed: $DBI::errstr");
+ my $t1 = dbi_time();
+ my $loop;
+ for $loop (1..$loops) {
+ my @cons;
+ print "Connecting... " if $verb;
+ for (1..$par) {
+ print "$_ ";
+ push @cons, ($drh->connect($dsn,$dbuser,$dbpass)
+ or Carp::croak("connect failed: $DBI::errstr\n"));
+ }
+ print "\nDisconnecting...\n" if $verb;
+ for (@cons) {
+ $_->disconnect or warn "disconnect failed: $DBI::errstr"
+ }
+ }
+ my $t2 = dbi_time();
+ my $td = $t2 - $t1;
+ printf "$meth %d and disconnect them, %d times: %.4fs / %d = %.4fs\n",
+ $par, $loops, $td, $loops*$par, $td/($loops*$par);
+ return $td;
+}
+
+
+# Help people doing DBI->errstr, might even document it one day
+# XXX probably best moved to cheaper XS code if this gets documented
+sub err { $DBI::err }
+sub errstr { $DBI::errstr }
+
+
+# --- Private Internal Function for Creating New DBI Handles
+
+# XXX move to PurePerl?
+*DBI::dr::TIEHASH = \&DBI::st::TIEHASH;
+*DBI::db::TIEHASH = \&DBI::st::TIEHASH;
+
+
+# These three special constructors are called by the drivers
+# The way they are called is likely to change.
+
+our $shared_profile;
+
+sub _new_drh { # called by DBD::<drivername>::driver()
+ my ($class, $initial_attr, $imp_data) = @_;
+ # Provide default storage for State,Err and Errstr.
+ # Note that these are shared by all child handles by default! XXX
+ # State must be undef to get automatic faking in DBI::var::FETCH
+ my ($h_state_store, $h_err_store, $h_errstr_store) = (undef, 0, '');
+ my $attr = {
+ # these attributes get copied down to child handles by default
+ 'State' => \$h_state_store, # Holder for DBI::state
+ 'Err' => \$h_err_store, # Holder for DBI::err
+ 'Errstr' => \$h_errstr_store, # Holder for DBI::errstr
+ 'TraceLevel' => 0,
+ FetchHashKeyName=> 'NAME',
+ %$initial_attr,
+ };
+ my ($h, $i) = _new_handle('DBI::dr', '', $attr, $imp_data, $class);
+
+ # XXX DBI_PROFILE unless DBI::PurePerl because for some reason
+ # it kills the t/zz_*_pp.t tests (they silently exit early)
+ if (($ENV{DBI_PROFILE} && !$DBI::PurePerl) || $shared_profile) {
+ # The profile object created here when the first driver is loaded
+ # is shared by all drivers so we end up with just one set of profile
+ # data and thus the 'total time in DBI' is really the true total.
+ if (!$shared_profile) { # first time
+ $h->{Profile} = $ENV{DBI_PROFILE}; # write string
+ $shared_profile = $h->{Profile}; # read and record object
+ }
+ else {
+ $h->{Profile} = $shared_profile;
+ }
+ }
+ return $h unless wantarray;
+ ($h, $i);
+}
+
+sub _new_dbh { # called by DBD::<drivername>::dr::connect()
+ my ($drh, $attr, $imp_data) = @_;
+ my $imp_class = $drh->{ImplementorClass}
+ or Carp::croak("DBI _new_dbh: $drh has no ImplementorClass");
+ substr($imp_class,-4,4) = '::db';
+ my $app_class = ref $drh;
+ substr($app_class,-4,4) = '::db';
+ $attr->{Err} ||= \my $err;
+ $attr->{Errstr} ||= \my $errstr;
+ $attr->{State} ||= \my $state;
+ _new_handle($app_class, $drh, $attr, $imp_data, $imp_class);
+}
+
+sub _new_sth { # called by DBD::<drivername>::db::prepare)
+ my ($dbh, $attr, $imp_data) = @_;
+ my $imp_class = $dbh->{ImplementorClass}
+ or Carp::croak("DBI _new_sth: $dbh has no ImplementorClass");
+ substr($imp_class,-4,4) = '::st';
+ my $app_class = ref $dbh;
+ substr($app_class,-4,4) = '::st';
+ _new_handle($app_class, $dbh, $attr, $imp_data, $imp_class);
+}
+
+
+# end of DBI package
+
+
+
+# --------------------------------------------------------------------
+# === The internal DBI Switch pseudo 'driver' class ===
+
+{ package # hide from PAUSE
+ DBD::Switch::dr;
+ DBI->setup_driver('DBD::Switch'); # sets up @ISA
+
+ $DBD::Switch::dr::imp_data_size = 0;
+ $DBD::Switch::dr::imp_data_size = 0; # avoid typo warning
+ my $drh;
+
+ sub driver {
+ return $drh if $drh; # a package global
+
+ my $inner;
+ ($drh, $inner) = DBI::_new_drh('DBD::Switch::dr', {
+ 'Name' => 'Switch',
+ 'Version' => $DBI::VERSION,
+ 'Attribution' => "DBI $DBI::VERSION by Tim Bunce",
+ });
+ Carp::croak("DBD::Switch init failed!") unless ($drh && $inner);
+ return $drh;
+ }
+ sub CLONE {
+ undef $drh;
+ }
+
+ sub FETCH {
+ my($drh, $key) = @_;
+ return DBI->trace if $key eq 'DebugDispatch';
+ return undef if $key eq 'DebugLog'; # not worth fetching, sorry
+ return $drh->DBD::_::dr::FETCH($key);
+ undef;
+ }
+ sub STORE {
+ my($drh, $key, $value) = @_;
+ if ($key eq 'DebugDispatch') {
+ DBI->trace($value);
+ } elsif ($key eq 'DebugLog') {
+ DBI->trace(-1, $value);
+ } else {
+ $drh->DBD::_::dr::STORE($key, $value);
+ }
+ }
+}
+
+
+# --------------------------------------------------------------------
+# === OPTIONAL MINIMAL BASE CLASSES FOR DBI SUBCLASSES ===
+
+# We only define default methods for harmless functions.
+# We don't, for example, define a DBD::_::st::prepare()
+
+{ package # hide from PAUSE
+ DBD::_::common; # ====== Common base class methods ======
+ use strict;
+
+ # methods common to all handle types:
+
+ sub _not_impl {
+ my ($h, $method) = @_;
+ $h->trace_msg("Driver does not implement the $method method.\n");
+ return; # empty list / undef
+ }
+
+ # generic TIEHASH default methods:
+ sub FIRSTKEY { }
+ sub NEXTKEY { }
+ sub EXISTS { defined($_[0]->FETCH($_[1])) } # XXX undef?
+ sub CLEAR { Carp::carp "Can't CLEAR $_[0] (DBI)" }
+
+ sub FETCH_many { # XXX should move to C one day
+ my $h = shift;
+ # scalar is needed to workaround drivers that return an empty list
+ # for some attributes
+ return map { scalar $h->FETCH($_) } @_;
+ }
+
+ *dump_handle = \&DBI::dump_handle;
+
+ sub install_method {
+ # special class method called directly by apps and/or drivers
+ # to install new methods into the DBI dispatcher
+ # DBD::Foo::db->install_method("foo_mumble", { usage => [...], options => '...' });
+ my ($class, $method, $attr) = @_;
+ Carp::croak("Class '$class' must begin with DBD:: and end with ::db or ::st")
+ unless $class =~ /^DBD::(\w+)::(dr|db|st)$/;
+ my ($driver, $subtype) = ($1, $2);
+ Carp::croak("invalid method name '$method'")
+ unless $method =~ m/^([a-z]+_)\w+$/;
+ my $prefix = $1;
+ my $reg_info = $dbd_prefix_registry->{$prefix};
+ Carp::carp("method name prefix '$prefix' is not associated with a registered driver") unless $reg_info;
+
+ my $full_method = "DBI::${subtype}::$method";
+ $DBI::installed_methods{$full_method} = $attr;
+
+ my (undef, $filename, $line) = caller;
+ # XXX reformat $attr as needed for _install_method
+ my %attr = %{$attr||{}}; # copy so we can edit
+ DBI->_install_method("DBI::${subtype}::$method", "$filename at line $line", \%attr);
+ }
+
+ sub parse_trace_flags {
+ my ($h, $spec) = @_;
+ my $level = 0;
+ my $flags = 0;
+ my @unknown;
+ for my $word (split /\s*[|&,]\s*/, $spec) {
+ if (DBI::looks_like_number($word) && $word <= 0xF && $word >= 0) {
+ $level = $word;
+ } elsif ($word eq 'ALL') {
+ $flags = 0x7FFFFFFF; # XXX last bit causes negative headaches
+ last;
+ } elsif (my $flag = $h->parse_trace_flag($word)) {
+ $flags |= $flag;
+ }
+ else {
+ push @unknown, $word;
+ }
+ }
+ if (@unknown && (ref $h ? $h->FETCH('Warn') : 1)) {
+ Carp::carp("$h->parse_trace_flags($spec) ignored unknown trace flags: ".
+ join(" ", map { DBI::neat($_) } @unknown));
+ }
+ $flags |= $level;
+ return $flags;
+ }
+
+ sub parse_trace_flag {
+ my ($h, $name) = @_;
+ # 0xddDDDDrL (driver, DBI, reserved, Level)
+ return 0x00000100 if $name eq 'SQL';
+ return 0x00000200 if $name eq 'CON';
+ return 0x00000400 if $name eq 'ENC';
+ return 0x00000800 if $name eq 'DBD';
+ return 0x00001000 if $name eq 'TXN';
+ return;
+ }
+
+ sub private_attribute_info {
+ return undef;
+ }
+
+ sub visit_child_handles {
+ my ($h, $code, $info) = @_;
+ $info = {} if not defined $info;
+ for my $ch (@{ $h->{ChildHandles} || []}) {
+ next unless $ch;
+ my $child_info = $code->($ch, $info)
+ or next;
+ $ch->visit_child_handles($code, $child_info);
+ }
+ return $info;
+ }
+}
+
+
+{ package # hide from PAUSE
+ DBD::_::dr; # ====== DRIVER ======
+ @DBD::_::dr::ISA = qw(DBD::_::common);
+ use strict;
+
+ sub default_user {
+ my ($drh, $user, $pass, $attr) = @_;
+ $user = $ENV{DBI_USER} unless defined $user;
+ $pass = $ENV{DBI_PASS} unless defined $pass;
+ return ($user, $pass);
+ }
+
+ sub connect { # normally overridden, but a handy default
+ my ($drh, $dsn, $user, $auth) = @_;
+ my ($this) = DBI::_new_dbh($drh, {
+ 'Name' => $dsn,
+ });
+ # XXX debatable as there's no "server side" here
+ # (and now many uses would trigger warnings on DESTROY)
+ # $this->STORE(Active => 1);
+ # so drivers should set it in their own connect
+ $this;
+ }
+
+
+ sub connect_cached {
+ my $drh = shift;
+ my ($dsn, $user, $auth, $attr) = @_;
+
+ my $cache = $drh->{CachedKids} ||= {};
+ my $key = do { local $^W;
+ join "!\001", $dsn, $user, $auth, DBI::_concat_hash_sorted($attr, "=\001", ",\001", 0, 0)
+ };
+ my $dbh = $cache->{$key};
+ $drh->trace_msg(sprintf(" connect_cached: key '$key', cached dbh $dbh\n", DBI::neat($key), DBI::neat($dbh)))
+ if (($DBI::dbi_debug & 0xF) >= 4);
+
+ my $cb = $attr->{Callbacks}; # take care not to autovivify
+ if ($dbh && $dbh->FETCH('Active') && eval { $dbh->ping }) {
+ # If the caller has provided a callback then call it
+ if ($cb and $cb = $cb->{"connect_cached.reused"}) {
+ local $_ = "connect_cached.reused";
+ $cb->($dbh, $dsn, $user, $auth, $attr);
+ }
+ return $dbh;
+ }
+
+ # If the caller has provided a callback then call it
+ if ($cb and $cb = $cb->{"connect_cached.new"}) {
+ local $_ = "connect_cached.new";
+ $cb->($dbh, $dsn, $user, $auth, $attr);
+ }
+
+ $dbh = $drh->connect(@_);
+ $cache->{$key} = $dbh; # replace prev entry, even if connect failed
+ return $dbh;
+ }
+
+}
+
+
+{ package # hide from PAUSE
+ DBD::_::db; # ====== DATABASE ======
+ @DBD::_::db::ISA = qw(DBD::_::common);
+ use strict;
+
+ sub clone {
+ my ($old_dbh, $attr) = @_;
+
+ my $closure = $old_dbh->{dbi_connect_closure}
+ or return $old_dbh->set_err($DBI::stderr, "Can't clone handle");
+
+ unless ($attr) { # XXX deprecated, caller should always pass a hash ref
+ # copy attributes visible in the attribute cache
+ keys %$old_dbh; # reset iterator
+ while ( my ($k, $v) = each %$old_dbh ) {
+ # ignore non-code refs, i.e., caches, handles, Err etc
+ next if ref $v && ref $v ne 'CODE'; # HandleError etc
+ $attr->{$k} = $v;
+ }
+ # explicitly set attributes which are unlikely to be in the
+ # attribute cache, i.e., boolean's and some others
+ $attr->{$_} = $old_dbh->FETCH($_) for (qw(
+ AutoCommit ChopBlanks InactiveDestroy AutoInactiveDestroy
+ LongTruncOk PrintError PrintWarn Profile RaiseError
+ ShowErrorStatement TaintIn TaintOut
+ ));
+ }
+
+ # use Data::Dumper; warn Dumper([$old_dbh, $attr]);
+ my $new_dbh = &$closure($old_dbh, $attr);
+ unless ($new_dbh) {
+ # need to copy err/errstr from driver back into $old_dbh
+ my $drh = $old_dbh->{Driver};
+ return $old_dbh->set_err($drh->err, $drh->errstr, $drh->state);
+ }
+ $new_dbh->{dbi_connect_closure} = $closure;
+ return $new_dbh;
+ }
+
+ sub quote_identifier {
+ my ($dbh, @id) = @_;
+ my $attr = (@id > 3 && ref($id[-1])) ? pop @id : undef;
+
+ my $info = $dbh->{dbi_quote_identifier_cache} ||= [
+ $dbh->get_info(29) || '"', # SQL_IDENTIFIER_QUOTE_CHAR
+ $dbh->get_info(41) || '.', # SQL_CATALOG_NAME_SEPARATOR
+ $dbh->get_info(114) || 1, # SQL_CATALOG_LOCATION
+ ];
+
+ my $quote = $info->[0];
+ foreach (@id) { # quote the elements
+ next unless defined;
+ s/$quote/$quote$quote/g; # escape embedded quotes
+ $_ = qq{$quote$_$quote};
+ }
+
+ # strip out catalog if present for special handling
+ my $catalog = (@id >= 3) ? shift @id : undef;
+
+ # join the dots, ignoring any null/undef elements (ie schema)
+ my $quoted_id = join '.', grep { defined } @id;
+
+ if ($catalog) { # add catalog correctly
+ $quoted_id = ($info->[2] == 2) # SQL_CL_END
+ ? $quoted_id . $info->[1] . $catalog
+ : $catalog . $info->[1] . $quoted_id;
+ }
+ return $quoted_id;
+ }
+
+ sub quote {
+ my ($dbh, $str, $data_type) = @_;
+
+ return "NULL" unless defined $str;
+ unless ($data_type) {
+ $str =~ s/'/''/g; # ISO SQL2
+ return "'$str'";
+ }
+
+ my $dbi_literal_quote_cache = $dbh->{'dbi_literal_quote_cache'} ||= [ {} , {} ];
+ my ($prefixes, $suffixes) = @$dbi_literal_quote_cache;
+
+ my $lp = $prefixes->{$data_type};
+ my $ls = $suffixes->{$data_type};
+
+ if ( ! defined $lp || ! defined $ls ) {
+ my $ti = $dbh->type_info($data_type);
+ $lp = $prefixes->{$data_type} = $ti ? $ti->{LITERAL_PREFIX} || "" : "'";
+ $ls = $suffixes->{$data_type} = $ti ? $ti->{LITERAL_SUFFIX} || "" : "'";
+ }
+ return $str unless $lp || $ls; # no quoting required
+
+ # XXX don't know what the standard says about escaping
+ # in the 'general case' (where $lp != "'").
+ # So we just do this and hope:
+ $str =~ s/$lp/$lp$lp/g
+ if $lp && $lp eq $ls && ($lp eq "'" || $lp eq '"');
+ return "$lp$str$ls";
+ }
+
+ sub rows { -1 } # here so $DBI::rows 'works' after using $dbh
+
+ sub do {
+ my($dbh, $statement, $attr, @params) = @_;
+ my $sth = $dbh->prepare($statement, $attr) or return undef;
+ $sth->execute(@params) or return undef;
+ my $rows = $sth->rows;
+ ($rows == 0) ? "0E0" : $rows;
+ }
+
+ sub _do_selectrow {
+ my ($method, $dbh, $stmt, $attr, @bind) = @_;
+ my $sth = ((ref $stmt) ? $stmt : $dbh->prepare($stmt, $attr))
+ or return;
+ $sth->execute(@bind)
+ or return;
+ my $row = $sth->$method()
+ and $sth->finish;
+ return $row;
+ }
+
+ sub selectrow_hashref { return _do_selectrow('fetchrow_hashref', @_); }
+
+ # XXX selectrow_array/ref also have C implementations in Driver.xst
+ sub selectrow_arrayref { return _do_selectrow('fetchrow_arrayref', @_); }
+ sub selectrow_array {
+ my $row = _do_selectrow('fetchrow_arrayref', @_) or return;
+ return $row->[0] unless wantarray;
+ return @$row;
+ }
+
+ # XXX selectall_arrayref also has C implementation in Driver.xst
+ # which fallsback to this if a slice is given
+ sub selectall_arrayref {
+ my ($dbh, $stmt, $attr, @bind) = @_;
+ my $sth = (ref $stmt) ? $stmt : $dbh->prepare($stmt, $attr)
+ or return;
+ $sth->execute(@bind) || return;
+ my $slice = $attr->{Slice}; # typically undef, else hash or array ref
+ if (!$slice and $slice=$attr->{Columns}) {
+ if (ref $slice eq 'ARRAY') { # map col idx to perl array idx
+ $slice = [ @{$attr->{Columns}} ]; # take a copy
+ for (@$slice) { $_-- }
+ }
+ }
+ my $rows = $sth->fetchall_arrayref($slice, my $MaxRows = $attr->{MaxRows});
+ $sth->finish if defined $MaxRows;
+ return $rows;
+ }
+
+ sub selectall_hashref {
+ my ($dbh, $stmt, $key_field, $attr, @bind) = @_;
+ my $sth = (ref $stmt) ? $stmt : $dbh->prepare($stmt, $attr);
+ return unless $sth;
+ $sth->execute(@bind) || return;
+ return $sth->fetchall_hashref($key_field);
+ }
+
+ sub selectcol_arrayref {
+ my ($dbh, $stmt, $attr, @bind) = @_;
+ my $sth = (ref $stmt) ? $stmt : $dbh->prepare($stmt, $attr);
+ return unless $sth;
+ $sth->execute(@bind) || return;
+ my @columns = ($attr->{Columns}) ? @{$attr->{Columns}} : (1);
+ my @values = (undef) x @columns;
+ my $idx = 0;
+ for (@columns) {
+ $sth->bind_col($_, \$values[$idx++]) || return;
+ }
+ my @col;
+ if (my $max = $attr->{MaxRows}) {
+ push @col, @values while 0 < $max-- && $sth->fetch;
+ }
+ else {
+ push @col, @values while $sth->fetch;
+ }
+ return \@col;
+ }
+
+ sub prepare_cached {
+ my ($dbh, $statement, $attr, $if_active) = @_;
+
+ # Needs support at dbh level to clear cache before complaining about
+ # active children. The XS template code does this. Drivers not using
+ # the template must handle clearing the cache themselves.
+ my $cache = $dbh->{CachedKids} ||= {};
+ my $key = do { local $^W;
+ join "!\001", $statement, DBI::_concat_hash_sorted($attr, "=\001", ",\001", 0, 0)
+ };
+ my $sth = $cache->{$key};
+
+ if ($sth) {
+ return $sth unless $sth->FETCH('Active');
+ Carp::carp("prepare_cached($statement) statement handle $sth still Active")
+ unless ($if_active ||= 0);
+ $sth->finish if $if_active <= 1;
+ return $sth if $if_active <= 2;
+ }
+
+ $sth = $dbh->prepare($statement, $attr);
+ $cache->{$key} = $sth if $sth;
+
+ return $sth;
+ }
+
+ sub ping {
+ my $dbh = shift;
+ $dbh->_not_impl('ping');
+ # "0 but true" is a special kind of true 0 that is used here so
+ # applications can check if the ping was a real ping or not
+ ($dbh->FETCH('Active')) ? "0 but true" : 0;
+ }
+
+ sub begin_work {
+ my $dbh = shift;
+ return $dbh->set_err($DBI::stderr, "Already in a transaction")
+ unless $dbh->FETCH('AutoCommit');
+ $dbh->STORE('AutoCommit', 0); # will croak if driver doesn't support it
+ $dbh->STORE('BegunWork', 1); # trigger post commit/rollback action
+ return 1;
+ }
+
+ sub primary_key {
+ my ($dbh, @args) = @_;
+ my $sth = $dbh->primary_key_info(@args) or return;
+ my ($row, @col);
+ push @col, $row->[3] while ($row = $sth->fetch);
+ Carp::croak("primary_key method not called in list context")
+ unless wantarray; # leave us some elbow room
+ return @col;
+ }
+
+ sub tables {
+ my ($dbh, @args) = @_;
+ my $sth = $dbh->table_info(@args[0,1,2,3,4]) or return;
+ my $tables = $sth->fetchall_arrayref or return;
+ my @tables;
+ if ($dbh->get_info(29)) { # SQL_IDENTIFIER_QUOTE_CHAR
+ @tables = map { $dbh->quote_identifier( @{$_}[0,1,2] ) } @$tables;
+ }
+ else { # temporary old style hack (yeach)
+ @tables = map {
+ my $name = $_->[2];
+ if ($_->[1]) {
+ my $schema = $_->[1];
+ # a sad hack (mostly for Informix I recall)
+ my $quote = ($schema eq uc($schema)) ? '' : '"';
+ $name = "$quote$schema$quote.$name"
+ }
+ $name;
+ } @$tables;
+ }
+ return @tables;
+ }
+
+ sub type_info { # this should be sufficient for all drivers
+ my ($dbh, $data_type) = @_;
+ my $idx_hash;
+ my $tia = $dbh->{dbi_type_info_row_cache};
+ if ($tia) {
+ $idx_hash = $dbh->{dbi_type_info_idx_cache};
+ }
+ else {
+ my $temp = $dbh->type_info_all;
+ return unless $temp && @$temp;
+ # we cache here because type_info_all may be expensive to call
+ # (and we take a copy so the following shift can't corrupt
+ # the data that may be returned by future calls to type_info_all)
+ $tia = $dbh->{dbi_type_info_row_cache} = [ @$temp ];
+ $idx_hash = $dbh->{dbi_type_info_idx_cache} = shift @$tia;
+ }
+
+ my $dt_idx = $idx_hash->{DATA_TYPE} || $idx_hash->{data_type};
+ Carp::croak("type_info_all returned non-standard DATA_TYPE index value ($dt_idx != 1)")
+ if $dt_idx && $dt_idx != 1;
+
+ # --- simple DATA_TYPE match filter
+ my @ti;
+ my @data_type_list = (ref $data_type) ? @$data_type : ($data_type);
+ foreach $data_type (@data_type_list) {
+ if (defined($data_type) && $data_type != DBI::SQL_ALL_TYPES()) {
+ push @ti, grep { $_->[$dt_idx] == $data_type } @$tia;
+ }
+ else { # SQL_ALL_TYPES
+ push @ti, @$tia;
+ }
+ last if @ti; # found at least one match
+ }
+
+ # --- format results into list of hash refs
+ my $idx_fields = keys %$idx_hash;
+ my @idx_names = map { uc($_) } keys %$idx_hash;
+ my @idx_values = values %$idx_hash;
+ Carp::croak "type_info_all result has $idx_fields keys but ".(@{$ti[0]})." fields"
+ if @ti && @{$ti[0]} != $idx_fields;
+ my @out = map {
+ my %h; @h{@idx_names} = @{$_}[ @idx_values ]; \%h;
+ } @ti;
+ return $out[0] unless wantarray;
+ return @out;
+ }
+
+ sub data_sources {
+ my ($dbh, @other) = @_;
+ my $drh = $dbh->{Driver}; # XXX proxy issues?
+ return $drh->data_sources(@other);
+ }
+
+}
+
+
+{ package # hide from PAUSE
+ DBD::_::st; # ====== STATEMENT ======
+ @DBD::_::st::ISA = qw(DBD::_::common);
+ use strict;
+
+ sub bind_param { Carp::croak("Can't bind_param, not implement by driver") }
+
+#
+# ********************************************************
+#
+# BEGIN ARRAY BINDING
+#
+# Array binding support for drivers which don't support
+# array binding, but have sufficient interfaces to fake it.
+# NOTE: mixing scalars and arrayrefs requires using bind_param_array
+# for *all* params...unless we modify bind_param for the default
+# case...
+#
+# 2002-Apr-10 D. Arnold
+
+ sub bind_param_array {
+ my $sth = shift;
+ my ($p_id, $value_array, $attr) = @_;
+
+ return $sth->set_err($DBI::stderr, "Value for parameter $p_id must be a scalar or an arrayref, not a ".ref($value_array))
+ if defined $value_array and ref $value_array and ref $value_array ne 'ARRAY';
+
+ return $sth->set_err($DBI::stderr, "Can't use named placeholder '$p_id' for non-driver supported bind_param_array")
+ unless DBI::looks_like_number($p_id); # because we rely on execute(@ary) here
+
+ return $sth->set_err($DBI::stderr, "Placeholder '$p_id' is out of range")
+ if $p_id <= 0; # can't easily/reliably test for too big
+
+ # get/create arrayref to hold params
+ my $hash_of_arrays = $sth->{ParamArrays} ||= { };
+
+ # If the bind has attribs then we rely on the driver conforming to
+ # the DBI spec in that a single bind_param() call with those attribs
+ # makes them 'sticky' and apply to all later execute(@values) calls.
+ # Since we only call bind_param() if we're given attribs then
+ # applications using drivers that don't support bind_param can still
+ # use bind_param_array() so long as they don't pass any attribs.
+
+ $$hash_of_arrays{$p_id} = $value_array;
+ return $sth->bind_param($p_id, undef, $attr)
+ if $attr;
+ 1;
+ }
+
+ sub bind_param_inout_array {
+ my $sth = shift;
+ # XXX not supported so we just call bind_param_array instead
+ # and then return an error
+ my ($p_num, $value_array, $attr) = @_;
+ $sth->bind_param_array($p_num, $value_array, $attr);
+ return $sth->set_err($DBI::stderr, "bind_param_inout_array not supported");
+ }
+
+ sub bind_columns {
+ my $sth = shift;
+ my $fields = $sth->FETCH('NUM_OF_FIELDS') || 0;
+ if ($fields <= 0 && !$sth->{Active}) {
+ return $sth->set_err($DBI::stderr, "Statement has no result columns to bind"
+ ." (perhaps you need to successfully call execute first)");
+ }
+ # Backwards compatibility for old-style call with attribute hash
+ # ref as first arg. Skip arg if undef or a hash ref.
+ my $attr;
+ $attr = shift if !defined $_[0] or ref($_[0]) eq 'HASH';
+
+ my $idx = 0;
+ $sth->bind_col(++$idx, shift, $attr) or return
+ while (@_ and $idx < $fields);
+
+ return $sth->set_err($DBI::stderr, "bind_columns called with ".($idx+@_)." values but $fields are needed")
+ if @_ or $idx != $fields;
+
+ return 1;
+ }
+
+ sub execute_array {
+ my $sth = shift;
+ my ($attr, @array_of_arrays) = @_;
+ my $NUM_OF_PARAMS = $sth->FETCH('NUM_OF_PARAMS'); # may be undef at this point
+
+ # get tuple status array or hash attribute
+ my $tuple_sts = $attr->{ArrayTupleStatus};
+ return $sth->set_err($DBI::stderr, "ArrayTupleStatus attribute must be an arrayref")
+ if $tuple_sts and ref $tuple_sts ne 'ARRAY';
+
+ # bind all supplied arrays
+ if (@array_of_arrays) {
+ $sth->{ParamArrays} = { }; # clear out old params
+ return $sth->set_err($DBI::stderr,
+ @array_of_arrays." bind values supplied but $NUM_OF_PARAMS expected")
+ if defined ($NUM_OF_PARAMS) && @array_of_arrays != $NUM_OF_PARAMS;
+ $sth->bind_param_array($_, $array_of_arrays[$_-1]) or return
+ foreach (1..@array_of_arrays);
+ }
+
+ my $fetch_tuple_sub;
+
+ if ($fetch_tuple_sub = $attr->{ArrayTupleFetch}) { # fetch on demand
+
+ return $sth->set_err($DBI::stderr,
+ "Can't use both ArrayTupleFetch and explicit bind values")
+ if @array_of_arrays; # previous bind_param_array calls will simply be ignored
+
+ if (UNIVERSAL::isa($fetch_tuple_sub,'DBI::st')) {
+ my $fetch_sth = $fetch_tuple_sub;
+ return $sth->set_err($DBI::stderr,
+ "ArrayTupleFetch sth is not Active, need to execute() it first")
+ unless $fetch_sth->{Active};
+ # check column count match to give more friendly message
+ my $NUM_OF_FIELDS = $fetch_sth->{NUM_OF_FIELDS};
+ return $sth->set_err($DBI::stderr,
+ "$NUM_OF_FIELDS columns from ArrayTupleFetch sth but $NUM_OF_PARAMS expected")
+ if defined($NUM_OF_FIELDS) && defined($NUM_OF_PARAMS)
+ && $NUM_OF_FIELDS != $NUM_OF_PARAMS;
+ $fetch_tuple_sub = sub { $fetch_sth->fetchrow_arrayref };
+ }
+ elsif (!UNIVERSAL::isa($fetch_tuple_sub,'CODE')) {
+ return $sth->set_err($DBI::stderr, "ArrayTupleFetch '$fetch_tuple_sub' is not a code ref or statement handle");
+ }
+
+ }
+ else {
+ my $NUM_OF_PARAMS_given = keys %{ $sth->{ParamArrays} || {} };
+ return $sth->set_err($DBI::stderr,
+ "$NUM_OF_PARAMS_given bind values supplied but $NUM_OF_PARAMS expected")
+ if defined($NUM_OF_PARAMS) && $NUM_OF_PARAMS != $NUM_OF_PARAMS_given;
+
+ # get the length of a bound array
+ my $maxlen;
+ my %hash_of_arrays = %{$sth->{ParamArrays}};
+ foreach (keys(%hash_of_arrays)) {
+ my $ary = $hash_of_arrays{$_};
+ next unless ref $ary eq 'ARRAY';
+ $maxlen = @$ary if !$maxlen || @$ary > $maxlen;
+ }
+ # if there are no arrays then execute scalars once
+ $maxlen = 1 unless defined $maxlen;
+ my @bind_ids = 1..keys(%hash_of_arrays);
+
+ my $tuple_idx = 0;
+ $fetch_tuple_sub = sub {
+ return if $tuple_idx >= $maxlen;
+ my @tuple = map {
+ my $a = $hash_of_arrays{$_};
+ ref($a) ? $a->[$tuple_idx] : $a
+ } @bind_ids;
+ ++$tuple_idx;
+ return \@tuple;
+ };
+ }
+ # pass thru the callers scalar or list context
+ return $sth->execute_for_fetch($fetch_tuple_sub, $tuple_sts);
+ }
+
+ sub execute_for_fetch {
+ my ($sth, $fetch_tuple_sub, $tuple_status) = @_;
+ # start with empty status array
+ ($tuple_status) ? @$tuple_status = () : $tuple_status = [];
+
+ my $rc_total = 0;
+ my $err_count;
+ while ( my $tuple = &$fetch_tuple_sub() ) {
+ if ( my $rc = $sth->execute(@$tuple) ) {
+ push @$tuple_status, $rc;
+ $rc_total = ($rc >= 0 && $rc_total >= 0) ? $rc_total + $rc : -1;
+ }
+ else {
+ $err_count++;
+ push @$tuple_status, [ $sth->err, $sth->errstr, $sth->state ];
+ # XXX drivers implementing execute_for_fetch could opt to "last;" here
+ # if they know the error code means no further executes will work.
+ }
+ }
+ my $tuples = @$tuple_status;
+ return $sth->set_err($DBI::stderr, "executing $tuples generated $err_count errors")
+ if $err_count;
+ $tuples ||= "0E0";
+ return $tuples unless wantarray;
+ return ($tuples, $rc_total);
+ }
+
+
+ sub fetchall_arrayref { # ALSO IN Driver.xst
+ my ($sth, $slice, $max_rows) = @_;
+
+ # when batch fetching with $max_rows were very likely to try to
+ # fetch the 'next batch' after the previous batch returned
+ # <=$max_rows. So don't treat that as an error.
+ return undef if $max_rows and not $sth->FETCH('Active');
+
+ my $mode = ref($slice) || 'ARRAY';
+ my @rows;
+
+ if ($mode eq 'ARRAY') {
+ my $row;
+ # we copy the array here because fetch (currently) always
+ # returns the same array ref. XXX
+ if ($slice && @$slice) {
+ $max_rows = -1 unless defined $max_rows;
+ push @rows, [ @{$row}[ @$slice] ]
+ while($max_rows-- and $row = $sth->fetch);
+ }
+ elsif (defined $max_rows) {
+ push @rows, [ @$row ]
+ while($max_rows-- and $row = $sth->fetch);
+ }
+ else {
+ push @rows, [ @$row ] while($row = $sth->fetch);
+ }
+ return \@rows
+ }
+
+ my %row;
+ if ($mode eq 'REF' && ref($$slice) eq 'HASH') { # \{ $idx => $name }
+ keys %$$slice; # reset the iterator
+ while ( my ($idx, $name) = each %$$slice ) {
+ $sth->bind_col($idx+1, \$row{$name});
+ }
+ }
+ elsif ($mode eq 'HASH') {
+ if (keys %$slice) {
+ keys %$slice; # reset the iterator
+ my $name2idx = $sth->FETCH('NAME_lc_hash');
+ while ( my ($name, $unused) = each %$slice ) {
+ my $idx = $name2idx->{lc $name};
+ return $sth->set_err($DBI::stderr, "Invalid column name '$name' for slice")
+ if not defined $idx;
+ $sth->bind_col($idx+1, \$row{$name});
+ }
+ }
+ else {
+ $sth->bind_columns( \( @row{ @{$sth->FETCH($sth->FETCH('FetchHashKeyName')) } } ) );
+ }
+ }
+ else {
+ return $sth->set_err($DBI::stderr, "fetchall_arrayref($mode) invalid");
+ }
+
+ if (not defined $max_rows) {
+ push @rows, { %row } while ($sth->fetch); # full speed ahead!
+ }
+ else {
+ push @rows, { %row } while ($max_rows-- and $sth->fetch);
+ }
+
+ return \@rows;
+ }
+
+ sub fetchall_hashref {
+ my ($sth, $key_field) = @_;
+
+ my $hash_key_name = $sth->{FetchHashKeyName} || 'NAME';
+ my $names_hash = $sth->FETCH("${hash_key_name}_hash");
+ my @key_fields = (ref $key_field) ? @$key_field : ($key_field);
+ my @key_indexes;
+ my $num_of_fields = $sth->FETCH('NUM_OF_FIELDS');
+ foreach (@key_fields) {
+ my $index = $names_hash->{$_}; # perl index not column
+ $index = $_ - 1 if !defined $index && DBI::looks_like_number($_) && $_>=1 && $_ <= $num_of_fields;
+ return $sth->set_err($DBI::stderr, "Field '$_' does not exist (not one of @{[keys %$names_hash]})")
+ unless defined $index;
+ push @key_indexes, $index;
+ }
+ my $rows = {};
+ my $NAME = $sth->FETCH($hash_key_name);
+ my @row = (undef) x $num_of_fields;
+ $sth->bind_columns(\(@row));
+ while ($sth->fetch) {
+ my $ref = $rows;
+ $ref = $ref->{$row[$_]} ||= {} for @key_indexes;
+ @{$ref}{@$NAME} = @row;
+ }
+ return $rows;
+ }
+
+ *dump_results = \&DBI::dump_results;
+
+ sub blob_copy_to_file { # returns length or undef on error
+ my($self, $field, $filename_or_handleref, $blocksize) = @_;
+ my $fh = $filename_or_handleref;
+ my($len, $buf) = (0, "");
+ $blocksize ||= 512; # not too ambitious
+ local(*FH);
+ unless(ref $fh) {
+ open(FH, ">$fh") || return undef;
+ $fh = \*FH;
+ }
+ while(defined($self->blob_read($field, $len, $blocksize, \$buf))) {
+ print $fh $buf;
+ $len += length $buf;
+ }
+ close(FH);
+ $len;
+ }
+
+ sub more_results {
+ shift->{syb_more_results}; # handy grandfathering
+ }
+
+}
+
+unless ($DBI::PurePerl) { # See install_driver
+ { @DBD::_mem::dr::ISA = qw(DBD::_mem::common); }
+ { @DBD::_mem::db::ISA = qw(DBD::_mem::common); }
+ { @DBD::_mem::st::ISA = qw(DBD::_mem::common); }
+ # DBD::_mem::common::DESTROY is implemented in DBI.xs
+}
+
+1;
+__END__
+
+=head1 DESCRIPTION
+
+The DBI is a database access module for the Perl programming language. It defines
+a set of methods, variables, and conventions that provide a consistent
+database interface, independent of the actual database being used.
+
+It is important to remember that the DBI is just an interface.
+The DBI is a layer
+of "glue" between an application and one or more database I<driver>
+modules. It is the driver modules which do most of the real work. The DBI
+provides a standard interface and framework for the drivers to operate
+within.
+
+
+=head2 Architecture of a DBI Application
+
+ |<- Scope of DBI ->|
+ .-. .--------------. .-------------.
+ .-------. | |---| XYZ Driver |---| XYZ Engine |
+ | Perl | | | `--------------' `-------------'
+ | script| |A| |D| .--------------. .-------------.
+ | using |--|P|--|B|---|Oracle Driver |---|Oracle Engine|
+ | DBI | |I| |I| `--------------' `-------------'
+ | API | | |...
+ |methods| | |... Other drivers
+ `-------' | |...
+ `-'
+
+The API, or Application Programming Interface, defines the
+call interface and variables for Perl scripts to use. The API
+is implemented by the Perl DBI extension.
+
+The DBI "dispatches" the method calls to the appropriate driver for
+actual execution. The DBI is also responsible for the dynamic loading
+of drivers, error checking and handling, providing default
+implementations for methods, and many other non-database specific duties.
+
+Each driver
+contains implementations of the DBI methods using the
+private interface functions of the corresponding database engine. Only authors
+of sophisticated/multi-database applications or generic library
+functions need be concerned with drivers.
+
+=head2 Notation and Conventions
+
+The following conventions are used in this document:
+
+ $dbh Database handle object
+ $sth Statement handle object
+ $drh Driver handle object (rarely seen or used in applications)
+ $h Any of the handle types above ($dbh, $sth, or $drh)
+ $rc General Return Code (boolean: true=ok, false=error)
+ $rv General Return Value (typically an integer)
+ @ary List of values returned from the database, typically a row of data
+ $rows Number of rows processed (if available, else -1)
+ $fh A filehandle
+ undef NULL values are represented by undefined values in Perl
+ \%attr Reference to a hash of attribute values passed to methods
+
+Note that Perl will automatically destroy database and statement handle objects
+if all references to them are deleted.
+
+
+=head2 Outline Usage
+
+To use DBI,
+first you need to load the DBI module:
+
+ use DBI;
+ use strict;
+
+(The C<use strict;> isn't required but is strongly recommended.)
+
+Then you need to L</connect> to your data source and get a I<handle> for that
+connection:
+
+ $dbh = DBI->connect($dsn, $user, $password,
+ { RaiseError => 1, AutoCommit => 0 });
+
+Since connecting can be expensive, you generally just connect at the
+start of your program and disconnect at the end.
+
+Explicitly defining the required C<AutoCommit> behaviour is strongly
+recommended and may become mandatory in a later version. This
+determines whether changes are automatically committed to the
+database when executed, or need to be explicitly committed later.
+
+The DBI allows an application to "prepare" statements for later
+execution. A prepared statement is identified by a statement handle
+held in a Perl variable.
+We'll call the Perl variable C<$sth> in our examples.
+
+The typical method call sequence for a C<SELECT> statement is:
+
+ prepare,
+ execute, fetch, fetch, ...
+ execute, fetch, fetch, ...
+ execute, fetch, fetch, ...
+
+for example:
+
+ $sth = $dbh->prepare("SELECT foo, bar FROM table WHERE baz=?");
+
+ $sth->execute( $baz );
+
+ while ( @row = $sth->fetchrow_array ) {
+ print "@row\n";
+ }
+
+The typical method call sequence for a I<non>-C<SELECT> statement is:
+
+ prepare,
+ execute,
+ execute,
+ execute.
+
+for example:
+
+ $sth = $dbh->prepare("INSERT INTO table(foo,bar,baz) VALUES (?,?,?)");
+
+ while(<CSV>) {
+ chomp;
+ my ($foo,$bar,$baz) = split /,/;
+ $sth->execute( $foo, $bar, $baz );
+ }
+
+The C<do()> method can be used for non repeated I<non>-C<SELECT> statement
+(or with drivers that don't support placeholders):
+
+ $rows_affected = $dbh->do("UPDATE your_table SET foo = foo + 1");
+
+To commit your changes to the database (when L</AutoCommit> is off):
+
+ $dbh->commit; # or call $dbh->rollback; to undo changes
+
+Finally, when you have finished working with the data source, you should
+L</disconnect> from it:
+
+ $dbh->disconnect;
+
+
+=head2 General Interface Rules & Caveats
+
+The DBI does not have a concept of a "current session". Every session
+has a handle object (i.e., a C<$dbh>) returned from the C<connect> method.
+That handle object is used to invoke database related methods.
+
+Most data is returned to the Perl script as strings. (Null values are
+returned as C<undef>.) This allows arbitrary precision numeric data to be
+handled without loss of accuracy. Beware that Perl may not preserve
+the same accuracy when the string is used as a number.
+
+Dates and times are returned as character strings in the current
+default format of the corresponding database engine. Time zone effects
+are database/driver dependent.
+
+Perl supports binary data in Perl strings, and the DBI will pass binary
+data to and from the driver without change. It is up to the driver
+implementors to decide how they wish to handle such binary data.
+
+Perl supports two kinds of strings: Unicode (utf8 internally) and non-Unicode
+(defaults to iso-8859-1 if forced to assume an encoding). Drivers should
+accept both kinds of strings and, if required, convert them to the character
+set of the database being used. Similarly, when fetching from the database
+character data that isn't iso-8859-1 the driver should convert it into utf8.
+
+Multiple SQL statements may not be combined in a single statement
+handle (C<$sth>), although some databases and drivers do support this
+(notably Sybase and SQL Server).
+
+Non-sequential record reads are not supported in this version of the DBI.
+In other words, records can only be fetched in the order that the
+database returned them, and once fetched they are forgotten.
+
+Positioned updates and deletes are not directly supported by the DBI.
+See the description of the C<CursorName> attribute for an alternative.
+
+Individual driver implementors are free to provide any private
+functions and/or handle attributes that they feel are useful.
+Private driver functions can be invoked using the DBI C<func()> method.
+Private driver attributes are accessed just like standard attributes.
+
+Many methods have an optional C<\%attr> parameter which can be used to
+pass information to the driver implementing the method. Except where
+specifically documented, the C<\%attr> parameter can only be used to pass
+driver specific hints. In general, you can ignore C<\%attr> parameters
+or pass it as C<undef>.
+
+
+=head2 Naming Conventions and Name Space
+
+The DBI package and all packages below it (C<DBI::*>) are reserved for
+use by the DBI. Extensions and related modules use the C<DBIx::>
+namespace (see L<http://www.perl.com/CPAN/modules/by-module/DBIx/>).
+Package names beginning with C<DBD::> are reserved for use
+by DBI database drivers. All environment variables used by the DBI
+or by individual DBDs begin with "C<DBI_>" or "C<DBD_>".
+
+The letter case used for attribute names is significant and plays an
+important part in the portability of DBI scripts. The case of the
+attribute name is used to signify who defined the meaning of that name
+and its values.
+
+ Case of name Has a meaning defined by
+ ------------ ------------------------
+ UPPER_CASE Standards, e.g., X/Open, ISO SQL92 etc (portable)
+ MixedCase DBI API (portable), underscores are not used.
+ lower_case Driver or database engine specific (non-portable)
+
+It is of the utmost importance that Driver developers only use
+lowercase attribute names when defining private attributes. Private
+attribute names must be prefixed with the driver name or suitable
+abbreviation (e.g., "C<ora_>" for Oracle, "C<ing_>" for Ingres, etc).
+
+
+=head2 SQL - A Query Language
+
+Most DBI drivers require applications to use a dialect of SQL
+(Structured Query Language) to interact with the database engine.
+The L</"Standards Reference Information"> section provides links
+to useful information about SQL.
+
+The DBI itself does not mandate or require any particular language to
+be used; it is language independent. In ODBC terms, the DBI is in
+"pass-thru" mode, although individual drivers might not be. The only requirement
+is that queries and other statements must be expressed as a single
+string of characters passed as the first argument to the L</prepare> or
+L</do> methods.
+
+For an interesting diversion on the I<real> history of RDBMS and SQL,
+from the people who made it happen, see:
+
+ http://www.mcjones.org/System_R/SQL_Reunion_95/sqlr95.html
+
+Follow the "Full Contents" then "Intergalactic dataspeak" links for the
+SQL history.
+
+=head2 Placeholders and Bind Values
+
+Some drivers support placeholders and bind values.
+I<Placeholders>, also called parameter markers, are used to indicate
+values in a database statement that will be supplied later,
+before the prepared statement is executed. For example, an application
+might use the following to insert a row of data into the SALES table:
+
+ INSERT INTO sales (product_code, qty, price) VALUES (?, ?, ?)
+
+or the following, to select the description for a product:
+
+ SELECT description FROM products WHERE product_code = ?
+
+The C<?> characters are the placeholders. The association of actual
+values with placeholders is known as I<binding>, and the values are
+referred to as I<bind values>.
+Note that the C<?> is not enclosed in quotation marks, even when the
+placeholder represents a string.
+
+Some drivers also allow placeholders like C<:>I<name> and C<:>I<N> (e.g.,
+C<:1>, C<:2>, and so on) in addition to C<?>, but their use is not portable.
+
+If the C<:>I<N> form of placeholder is supported by the driver you're using,
+then you should be able to use either L</bind_param> or L</execute> to bind
+values. Check your driver documentation.
+
+With most drivers, placeholders can't be used for any element of a
+statement that would prevent the database server from validating the
+statement and creating a query execution plan for it. For example:
+
+ "SELECT name, age FROM ?" # wrong (will probably fail)
+ "SELECT name, ? FROM people" # wrong (but may not 'fail')
+
+Also, placeholders can only represent single scalar values.
+For example, the following
+statement won't work as expected for more than one value:
+
+ "SELECT name, age FROM people WHERE name IN (?)" # wrong
+ "SELECT name, age FROM people WHERE name IN (?,?)" # two names
+
+When using placeholders with the SQL C<LIKE> qualifier, you must
+remember that the placeholder substitutes for the whole string.
+So you should use "C<... LIKE ? ...>" and include any wildcard
+characters in the value that you bind to the placeholder.
+
+B<NULL Values>
+
+Undefined values, or C<undef>, are used to indicate NULL values.
+You can insert and update columns with a NULL value as you would a
+non-NULL value. These examples insert and update the column
+C<age> with a NULL value:
+
+ $sth = $dbh->prepare(qq{
+ INSERT INTO people (fullname, age) VALUES (?, ?)
+ });
+ $sth->execute("Joe Bloggs", undef);
+
+ $sth = $dbh->prepare(qq{
+ UPDATE people SET age = ? WHERE fullname = ?
+ });
+ $sth->execute(undef, "Joe Bloggs");
+
+However, care must be taken when trying to use NULL values in a
+C<WHERE> clause. Consider:
+
+ SELECT fullname FROM people WHERE age = ?
+
+Binding an C<undef> (NULL) to the placeholder will I<not> select rows
+which have a NULL C<age>! At least for database engines that
+conform to the SQL standard. Refer to the SQL manual for your database
+engine or any SQL book for the reasons for this. To explicitly select
+NULLs you have to say "C<WHERE age IS NULL>".
+
+A common issue is to have a code fragment handle a value that could be
+either C<defined> or C<undef> (non-NULL or NULL) at runtime.
+A simple technique is to prepare the appropriate statement as needed,
+and substitute the placeholder for non-NULL cases:
+
+ $sql_clause = defined $age? "age = ?" : "age IS NULL";
+ $sth = $dbh->prepare(qq{
+ SELECT fullname FROM people WHERE $sql_clause
+ });
+ $sth->execute(defined $age ? $age : ());
+
+The following technique illustrates qualifying a C<WHERE> clause with
+several columns, whose associated values (C<defined> or C<undef>) are
+in a hash %h:
+
+ for my $col ("age", "phone", "email") {
+ if (defined $h{$col}) {
+ push @sql_qual, "$col = ?";
+ push @sql_bind, $h{$col};
+ }
+ else {
+ push @sql_qual, "$col IS NULL";
+ }
+ }
+ $sql_clause = join(" AND ", @sql_qual);
+ $sth = $dbh->prepare(qq{
+ SELECT fullname FROM people WHERE $sql_clause
+ });
+ $sth->execute(@sql_bind);
+
+The techniques above call prepare for the SQL statement with each call to
+execute. Because calls to prepare() can be expensive, performance
+can suffer when an application iterates many times over statements
+like the above.
+
+A better solution is a single C<WHERE> clause that supports both
+NULL and non-NULL comparisons. Its SQL statement would need to be
+prepared only once for all cases, thus improving performance.
+Several examples of C<WHERE> clauses that support this are presented
+below. But each example lacks portability, robustness, or simplicity.
+Whether an example is supported on your database engine depends on
+what SQL extensions it provides, and where it supports the C<?>
+placeholder in a statement.
+
+ 0) age = ?
+ 1) NVL(age, xx) = NVL(?, xx)
+ 2) ISNULL(age, xx) = ISNULL(?, xx)
+ 3) DECODE(age, ?, 1, 0) = 1
+ 4) age = ? OR (age IS NULL AND ? IS NULL)
+ 5) age = ? OR (age IS NULL AND SP_ISNULL(?) = 1)
+ 6) age = ? OR (age IS NULL AND ? = 1)
+
+Statements formed with the above C<WHERE> clauses require execute
+statements as follows. The arguments are required, whether their
+values are C<defined> or C<undef>.
+
+ 0,1,2,3) $sth->execute($age);
+ 4,5) $sth->execute($age, $age);
+ 6) $sth->execute($age, defined($age) ? 0 : 1);
+
+Example 0 should not work (as mentioned earlier), but may work on
+a few database engines anyway (e.g. Sybase). Example 0 is part
+of examples 4, 5, and 6, so if example 0 works, these other
+examples may work, even if the engine does not properly support
+the right hand side of the C<OR> expression.
+
+Examples 1 and 2 are not robust: they require that you provide a
+valid column value xx (e.g. '~') which is not present in any row.
+That means you must have some notion of what data won't be stored
+in the column, and expect clients to adhere to that.
+
+Example 5 requires that you provide a stored procedure (SP_ISNULL
+in this example) that acts as a function: it checks whether a value
+is null, and returns 1 if it is, or 0 if not.
+
+Example 6, the least simple, is probably the most portable, i.e., it
+should work with with most, if not all, database engines.
+
+Here is a table that indicates which examples above are known to
+work on various database engines:
+
+ -----Examples------
+ 0 1 2 3 4 5 6
+ - - - - - - -
+ Oracle 9 N Y N Y Y ? Y
+ Informix IDS 9 N N N Y N Y Y
+ MS SQL N N Y N Y ? Y
+ Sybase Y N N N N N Y
+ AnyData,DBM,CSV Y N N N Y Y* Y
+ SQLite 3.3 N N N N Y N N
+ MSAccess N N N N Y N Y
+
+* Works only because Example 0 works.
+
+DBI provides a sample perl script that will test the examples above
+on your database engine and tell you which ones work. It is located
+in the F<ex/> subdirectory of the DBI source distribution, or here:
+L<http://svn.perl.org/modules/dbi/trunk/ex/perl_dbi_nulls_test.pl>
+Please use the script to help us fill-in and maintain this table.
+
+B<Performance>
+
+Without using placeholders, the insert statement shown previously would have to
+contain the literal values to be inserted and would have to be
+re-prepared and re-executed for each row. With placeholders, the insert
+statement only needs to be prepared once. The bind values for each row
+can be given to the C<execute> method each time it's called. By avoiding
+the need to re-prepare the statement for each row, the application
+typically runs many times faster. Here's an example:
+
+ my $sth = $dbh->prepare(q{
+ INSERT INTO sales (product_code, qty, price) VALUES (?, ?, ?)
+ }) or die $dbh->errstr;
+ while (<>) {
+ chomp;
+ my ($product_code, $qty, $price) = split /,/;
+ $sth->execute($product_code, $qty, $price) or die $dbh->errstr;
+ }
+ $dbh->commit or die $dbh->errstr;
+
+See L</execute> and L</bind_param> for more details.
+
+The C<q{...}> style quoting used in this example avoids clashing with
+quotes that may be used in the SQL statement. Use the double-quote like
+C<qq{...}> operator if you want to interpolate variables into the string.
+See L<perlop/"Quote and Quote-like Operators"> for more details.
+
+See also the L</bind_columns> method, which is used to associate Perl
+variables with the output columns of a C<SELECT> statement.
+
+=head1 THE DBI PACKAGE AND CLASS
+
+In this section, we cover the DBI class methods, utility functions,
+and the dynamic attributes associated with generic DBI handles.
+
+=head2 DBI Constants
+
+Constants representing the values of the SQL standard types can be
+imported individually by name, or all together by importing the
+special C<:sql_types> tag.
+
+The names and values of all the defined SQL standard types can be
+produced like this:
+
+ foreach (@{ $DBI::EXPORT_TAGS{sql_types} }) {
+ printf "%s=%d\n", $_, &{"DBI::$_"};
+ }
+
+These constants are defined by SQL/CLI, ODBC or both.
+C<SQL_BIGINT> is (currently) omitted, because SQL/CLI and ODBC provide
+conflicting codes.
+
+See the L</type_info>, L</type_info_all>, and L</bind_param> methods
+for possible uses.
+
+Note that just because the DBI defines a named constant for a given
+data type doesn't mean that drivers will support that data type.
+
+
+=head2 DBI Class Methods
+
+The following methods are provided by the DBI class:
+
+=head3 C<parse_dsn>
+
+ ($scheme, $driver, $attr_string, $attr_hash, $driver_dsn) = DBI->parse_dsn($dsn)
+ or die "Can't parse DBI DSN '$dsn'";
+
+Breaks apart a DBI Data Source Name (DSN) and returns the individual
+parts. If $dsn doesn't contain a valid DSN then parse_dsn() returns
+an empty list.
+
+$scheme is the first part of the DSN and is currently always 'dbi'.
+$driver is the driver name, possibly defaulted to $ENV{DBI_DRIVER},
+and may be undefined. $attr_string is the contents of the optional attribute
+string, which may be undefined. If $attr_string is not empty then $attr_hash
+is a reference to a hash containing the parsed attribute names and values.
+$driver_dsn is the last part of the DBI DSN string. For example:
+
+ ($scheme, $driver, $attr_string, $attr_hash, $driver_dsn)
+ = DBI->parse_dsn("DBI:MyDriver(RaiseError=>1):db=test;port=42");
+ $scheme = 'dbi';
+ $driver = 'MyDriver';
+ $attr_string = 'RaiseError=>1';
+ $attr_hash = { 'RaiseError' => '1' };
+ $driver_dsn = 'db=test;port=42';
+
+The parse_dsn() method was added in DBI 1.43.
+
+=head3 C<connect>
+
+ $dbh = DBI->connect($data_source, $username, $password)
+ or die $DBI::errstr;
+ $dbh = DBI->connect($data_source, $username, $password, \%attr)
+ or die $DBI::errstr;
+
+Establishes a database connection, or session, to the requested C<$data_source>.
+Returns a database handle object if the connection succeeds. Use
+C<$dbh-E<gt>disconnect> to terminate the connection.
+
+If the connect fails (see below), it returns C<undef> and sets both C<$DBI::err>
+and C<$DBI::errstr>. (It does I<not> explicitly set C<$!>.) You should generally
+test the return status of C<connect> and C<print $DBI::errstr> if it has failed.
+
+Multiple simultaneous connections to multiple databases through multiple
+drivers can be made via the DBI. Simply make one C<connect> call for each
+database and keep a copy of each returned database handle.
+
+The C<$data_source> value must begin with "C<dbi:>I<driver_name>C<:>".
+The I<driver_name> specifies the driver that will be used to make the
+connection. (Letter case is significant.)
+
+As a convenience, if the C<$data_source> parameter is undefined or empty,
+the DBI will substitute the value of the environment variable C<DBI_DSN>.
+If just the I<driver_name> part is empty (i.e., the C<$data_source>
+prefix is "C<dbi::>"), the environment variable C<DBI_DRIVER> is
+used. If neither variable is set, then C<connect> dies.
+
+Examples of C<$data_source> values are:
+
+ dbi:DriverName:database_name
+ dbi:DriverName:database_name@hostname:port
+ dbi:DriverName:database=database_name;host=hostname;port=port
+
+There is I<no standard> for the text following the driver name. Each
+driver is free to use whatever syntax it wants. The only requirement the
+DBI makes is that all the information is supplied in a single string.
+You must consult the documentation for the drivers you are using for a
+description of the syntax they require.
+
+It is recommended that drivers support the ODBC style, shown in the
+last example above. It is also recommended that that they support the
+three common names 'C<host>', 'C<port>', and 'C<database>' (plus 'C<db>'
+as an alias for C<database>). This simplifies automatic construction
+of basic DSNs: C<"dbi:$driver:database=$db;host=$host;port=$port">.
+Drivers should aim to 'do something reasonable' when given a DSN
+in this form, but if any part is meaningless for that driver (such
+as 'port' for Informix) it should generate an error if that part
+is not empty.
+
+If the environment variable C<DBI_AUTOPROXY> is defined (and the
+driver in C<$data_source> is not "C<Proxy>") then the connect request
+will automatically be changed to:
+
+ $ENV{DBI_AUTOPROXY};dsn=$data_source
+
+C<DBI_AUTOPROXY> is typically set as "C<dbi:Proxy:hostname=...;port=...>".
+If $ENV{DBI_AUTOPROXY} doesn't begin with 'C<dbi:>' then "dbi:Proxy:"
+will be prepended to it first. See the DBD::Proxy documentation
+for more details.
+
+If C<$username> or C<$password> are undefined (rather than just empty),
+then the DBI will substitute the values of the C<DBI_USER> and C<DBI_PASS>
+environment variables, respectively. The DBI will warn if the
+environment variables are not defined. However, the everyday use
+of these environment variables is not recommended for security
+reasons. The mechanism is primarily intended to simplify testing.
+See below for alternative way to specify the username and password.
+
+C<DBI-E<gt>connect> automatically installs the driver if it has not been
+installed yet. Driver installation either returns a valid driver
+handle, or it I<dies> with an error message that includes the string
+"C<install_driver>" and the underlying problem. So C<DBI-E<gt>connect>
+will die
+on a driver installation failure and will only return C<undef> on a
+connect failure, in which case C<$DBI::errstr> will hold the error message.
+Use C<eval { ... }> if you need to catch the "C<install_driver>" error.
+
+The C<$data_source> argument (with the "C<dbi:...:>" prefix removed) and the
+C<$username> and C<$password> arguments are then passed to the driver for
+processing. The DBI does not define any interpretation for the
+contents of these fields. The driver is free to interpret the
+C<$data_source>, C<$username>, and C<$password> fields in any way, and supply
+whatever defaults are appropriate for the engine being accessed.
+(Oracle, for example, uses the ORACLE_SID and TWO_TASK environment
+variables if no C<$data_source> is specified.)
+
+The C<AutoCommit> and C<PrintError> attributes for each connection
+default to "on". (See L</AutoCommit> and L</PrintError> for more information.)
+However, it is strongly recommended that you explicitly define C<AutoCommit>
+rather than rely on the default. The C<PrintWarn> attribute defaults to
+on if $^W is true, i.e., perl is running with warnings enabled.
+
+The C<\%attr> parameter can be used to alter the default settings of
+C<PrintError>, C<RaiseError>, C<AutoCommit>, and other attributes. For example:
+
+ $dbh = DBI->connect($data_source, $user, $pass, {
+ PrintError => 0,
+ AutoCommit => 0
+ });
+
+The username and password can also be specified using the attributes
+C<Username> and C<Password>, in which case they take precedence
+over the C<$username> and C<$password> parameters.
+
+You can also define connection attribute values within the C<$data_source>
+parameter. For example:
+
+ dbi:DriverName(PrintWarn=>1,PrintError=>0,Taint=>1):...
+
+Individual attributes values specified in this way take precedence over
+any conflicting values specified via the C<\%attr> parameter to C<connect>.
+
+The C<dbi_connect_method> attribute can be used to specify which driver
+method should be called to establish the connection. The only useful
+values are 'connect', 'connect_cached', or some specialized case like
+'Apache::DBI::connect' (which is automatically the default when running
+within Apache).
+
+Where possible, each session (C<$dbh>) is independent from the transactions
+in other sessions. This is useful when you need to hold cursors open
+across transactions--for example, if you use one session for your long lifespan
+cursors (typically read-only) and another for your short update
+transactions.
+
+For compatibility with old DBI scripts, the driver can be specified by
+passing its name as the fourth argument to C<connect> (instead of C<\%attr>):
+
+ $dbh = DBI->connect($data_source, $user, $pass, $driver);
+
+In this "old-style" form of C<connect>, the C<$data_source> should not start
+with "C<dbi:driver_name:>". (If it does, the embedded driver_name
+will be ignored). Also note that in this older form of C<connect>,
+the C<$dbh-E<gt>{AutoCommit}> attribute is I<undefined>, the
+C<$dbh-E<gt>{PrintError}> attribute is off, and the old C<DBI_DBNAME>
+environment variable is
+checked if C<DBI_DSN> is not defined. Beware that this "old-style"
+C<connect> will soon be withdrawn in a future version of DBI.
+
+=head3 C<connect_cached>
+
+ $dbh = DBI->connect_cached($data_source, $username, $password)
+ or die $DBI::errstr;
+ $dbh = DBI->connect_cached($data_source, $username, $password, \%attr)
+ or die $DBI::errstr;
+
+C<connect_cached> is like L</connect>, except that the database handle
+returned is also
+stored in a hash associated with the given parameters. If another call
+is made to C<connect_cached> with the same parameter values, then the
+corresponding cached C<$dbh> will be returned if it is still valid.
+The cached database handle is replaced with a new connection if it
+has been disconnected or if the C<ping> method fails.
+
+Note that the behaviour of this method differs in several respects from the
+behaviour of persistent connections implemented by Apache::DBI.
+However, if Apache::DBI is loaded then C<connect_cached> will use it.
+
+Caching connections can be useful in some applications, but it can
+also cause problems, such as too many connections, and so should
+be used with care. In particular, avoid changing the attributes of
+a database handle created via connect_cached() because it will affect
+other code that may be using the same handle. When connect_cached()
+returns a handle the attributes will be reset to their initial values.
+This can cause problems, especially with the C<AutoCommit> attribute.
+
+Where multiple separate parts of a program are using connect_cached()
+to connect to the same database with the same (initial) attributes
+it is a good idea to add a private attribute to the connect_cached()
+call to effectively limit the scope of the caching. For example:
+
+ DBI->connect_cached(..., { private_foo_cachekey => "Bar", ... });
+
+Handles returned from that connect_cached() call will only be returned
+by other connect_cached() call elsewhere in the code if those other
+calls also pass in the same attribute values, including the private one.
+(I've used C<private_foo_cachekey> here as an example, you can use
+any attribute name with a C<private_> prefix.)
+
+Taking that one step further, you can limit a particular connect_cached()
+call to return handles unique to that one place in the code by setting the
+private attribute to a unique value for that place:
+
+ DBI->connect_cached(..., { private_foo_cachekey => __FILE__.__LINE__, ... });
+
+By using a private attribute you still get connection caching for
+the individual calls to connect_cached() but, by making separate
+database connections for separate parts of the code, the database
+handles are isolated from any attribute changes made to other handles.
+
+The cache can be accessed (and cleared) via the L</CachedKids> attribute:
+
+ my $CachedKids_hashref = $dbh->{Driver}->{CachedKids};
+ %$CachedKids_hashref = () if $CachedKids_hashref;
+
+
+=head3 C<available_drivers>
+
+ @ary = DBI->available_drivers;
+ @ary = DBI->available_drivers($quiet);
+
+Returns a list of all available drivers by searching for C<DBD::*> modules
+through the directories in C<@INC>. By default, a warning is given if
+some drivers are hidden by others of the same name in earlier
+directories. Passing a true value for C<$quiet> will inhibit the warning.
+
+=head3 C<installed_drivers>
+
+ %drivers = DBI->installed_drivers();
+
+Returns a list of driver name and driver handle pairs for all drivers
+'installed' (loaded) into the current process. The driver name does not
+include the 'DBD::' prefix.
+
+To get a list of all drivers available in your perl installation you can use
+L</available_drivers>.
+
+Added in DBI 1.49.
+
+=head3 C<installed_versions>
+
+ DBI->installed_versions;
+ @ary = DBI->installed_versions;
+ %hash = DBI->installed_versions;
+
+Calls available_drivers() and attempts to load each of them in turn
+using install_driver(). For each load that succeeds the driver
+name and version number are added to a hash. When running under
+L<DBI::PurePerl> drivers which appear not be pure-perl are ignored.
+
+When called in array context the list of successfully loaded drivers
+is returned (without the 'DBD::' prefix).
+
+When called in scalar context a reference to the hash is returned
+and the hash will also contain other entries for the C<DBI> version,
+C<OS> name, etc.
+
+When called in a void context the installed_versions() method will
+print out a formatted list of the hash contents, one per line.
+
+Due to the potentially high memory cost and unknown risks of loading
+in an unknown number of drivers that just happen to be installed
+on the system, this method is not recommended for general use.
+Use available_drivers() instead.
+
+The installed_versions() method is primarily intended as a quick
+way to see from the command line what's installed. For example:
+
+ perl -MDBI -e 'DBI->installed_versions'
+
+The installed_versions() method was added in DBI 1.38.
+
+=head3 C<data_sources>
+
+ @ary = DBI->data_sources($driver);
+ @ary = DBI->data_sources($driver, \%attr);
+
+Returns a list of data sources (databases) available via the named
+driver. If C<$driver> is empty or C<undef>, then the value of the
+C<DBI_DRIVER> environment variable is used.
+
+The driver will be loaded if it hasn't been already. Note that if the
+driver loading fails then data_sources() I<dies> with an error message
+that includes the string "C<install_driver>" and the underlying problem.
+
+Data sources are returned in a form suitable for passing to the
+L</connect> method (that is, they will include the "C<dbi:$driver:>" prefix).
+
+Note that many drivers have no way of knowing what data sources might
+be available for it. These drivers return an empty or incomplete list
+or may require driver-specific attributes.
+
+There is also a data_sources() method defined for database handles.
+
+
+=head3 C<trace>
+
+ DBI->trace($trace_setting)
+ DBI->trace($trace_setting, $trace_filename)
+ DBI->trace($trace_setting, $trace_filehandle)
+ $trace_setting = DBI->trace;
+
+The C<DBI-E<gt>trace> method sets the I<global default> trace
+settings and returns the I<previous> trace settings. It can also
+be used to change where the trace output is sent.
+
+There's a similar method, C<$h-E<gt>trace>, which sets the trace
+settings for the specific handle it's called on.
+
+See the L</TRACING> section for full details about the DBI's powerful
+tracing facilities.
+
+
+=head3 C<visit_handles>
+
+ DBI->visit_handles( $coderef );
+ DBI->visit_handles( $coderef, $info );
+
+Where $coderef is a reference to a subroutine and $info is an arbitrary value
+which, if undefined, defaults to a reference to an empty hash. Returns $info.
+
+For each installed driver handle, if any, $coderef is invoked as:
+
+ $coderef->($driver_handle, $info);
+
+If the execution of $coderef returns a true value then L</visit_child_handles>
+is called on that child handle and passed the returned value as $info.
+
+For example:
+
+ my $info = $dbh->{Driver}->visit_child_handles(sub {
+ my ($h, $info) = @_;
+ ++$info->{ $h->{Type} }; # count types of handles (dr/db/st)
+ return $info; # visit kids
+ });
+
+See also L</visit_child_handles>.
+
+=head2 DBI Utility Functions
+
+In addition to the DBI methods listed in the previous section,
+the DBI package also provides several utility functions.
+
+These can be imported into your code by listing them in
+the C<use> statement. For example:
+
+ use DBI qw(neat data_diff);
+
+Alternatively, all these utility functions (except hash) can be
+imported using the C<:utils> import tag. For example:
+
+ use DBI qw(:utils);
+
+=head3 C<data_string_desc>
+
+ $description = data_string_desc($string);
+
+Returns an informal description of the string. For example:
+
+ UTF8 off, ASCII, 42 characters 42 bytes
+ UTF8 off, non-ASCII, 42 characters 42 bytes
+ UTF8 on, non-ASCII, 4 characters 6 bytes
+ UTF8 on but INVALID encoding, non-ASCII, 4 characters 6 bytes
+ UTF8 off, undef
+
+The initial C<UTF8> on/off refers to Perl's internal SvUTF8 flag.
+If $string has the SvUTF8 flag set but the sequence of bytes it
+contains are not a valid UTF-8 encoding then data_string_desc()
+will report C<UTF8 on but INVALID encoding>.
+
+The C<ASCII> vs C<non-ASCII> portion shows C<ASCII> if I<all> the
+characters in the string are ASCII (have code points <= 127).
+
+The data_string_desc() function was added in DBI 1.46.
+
+=head3 C<data_string_diff>
+
+ $diff = data_string_diff($a, $b);
+
+Returns an informal description of the first character difference
+between the strings. If both $a and $b contain the same sequence
+of characters then data_string_diff() returns an empty string.
+For example:
+
+ Params a & b Result
+ ------------ ------
+ 'aaa', 'aaa' ''
+ 'aaa', 'abc' 'Strings differ at index 2: a[2]=a, b[2]=b'
+ 'aaa', undef 'String b is undef, string a has 3 characters'
+ 'aaa', 'aa' 'String b truncated after 2 characters'
+
+Unicode characters are reported in C<\x{XXXX}> format. Unicode
+code points in the range U+0800 to U+08FF are unassigned and most
+likely to occur due to double-encoding. Characters in this range
+are reported as C<\x{08XX}='C'> where C<C> is the corresponding
+latin-1 character.
+
+The data_string_diff() function only considers logical I<characters>
+and not the underlying encoding. See L</data_diff> for an alternative.
+
+The data_string_diff() function was added in DBI 1.46.
+
+=head3 C<data_diff>
+
+ $diff = data_diff($a, $b);
+ $diff = data_diff($a, $b, $logical);
+
+Returns an informal description of the difference between two strings.
+It calls L</data_string_desc> and L</data_string_diff>
+and returns the combined results as a multi-line string.
+
+For example, C<data_diff("abc", "ab\x{263a}")> will return:
+
+ a: UTF8 off, ASCII, 3 characters 3 bytes
+ b: UTF8 on, non-ASCII, 3 characters 5 bytes
+ Strings differ at index 2: a[2]=c, b[2]=\x{263A}
+
+If $a and $b are identical in both the characters they contain I<and>
+their physical encoding then data_diff() returns an empty string.
+If $logical is true then physical encoding differences are ignored
+(but are still reported if there is a difference in the characters).
+
+The data_diff() function was added in DBI 1.46.
+
+=head3 C<neat>
+
+ $str = neat($value);
+ $str = neat($value, $maxlen);
+
+Return a string containing a neat (and tidy) representation of the
+supplied value.
+
+Strings will be quoted, although internal quotes will I<not> be escaped.
+Values known to be numeric will be unquoted. Undefined (NULL) values
+will be shown as C<undef> (without quotes).
+
+If the string is flagged internally as utf8 then double quotes will
+be used, otherwise single quotes are used and unprintable characters
+will be replaced by dot (.).
+
+For result strings longer than C<$maxlen> the result string will be
+truncated to C<$maxlen-4> and "C<...'>" will be appended. If C<$maxlen> is 0
+or C<undef>, it defaults to C<$DBI::neat_maxlen> which, in turn, defaults to 400.
+
+This function is designed to format values for human consumption.
+It is used internally by the DBI for L</trace> output. It should
+typically I<not> be used for formatting values for database use.
+(See also L</quote>.)
+
+=head3 C<neat_list>
+
+ $str = neat_list(\@listref, $maxlen, $field_sep);
+
+Calls C<neat> on each element of the list and returns a string
+containing the results joined with C<$field_sep>. C<$field_sep> defaults
+to C<", ">.
+
+=head3 C<looks_like_number>
+
+ @bool = looks_like_number(@array);
+
+Returns true for each element that looks like a number.
+Returns false for each element that does not look like a number.
+Returns C<undef> for each element that is undefined or empty.
+
+=head3 C<hash>
+
+ $hash_value = DBI::hash($buffer, $type);
+
+Return a 32-bit integer 'hash' value corresponding to the contents of $buffer.
+The $type parameter selects which kind of hash algorithm should be used.
+
+For the technically curious, type 0 (which is the default if $type
+isn't specified) is based on the Perl 5.1 hash except that the value
+is forced to be negative (for obscure historical reasons).
+Type 1 is the better "Fowler / Noll / Vo" (FNV) hash. See
+L<http://www.isthe.com/chongo/tech/comp/fnv/> for more information.
+Both types are implemented in C and are very fast.
+
+This function doesn't have much to do with databases, except that
+it can be handy to store hash values in a database.
+
+=head3 C<sql_type_cast>
+
+ $sts = DBI::sql_type_cast($sv, $sql_type, $flags);
+
+sql_type_cast attempts to cast C<$sv> to the SQL type (see L<DBI
+Constants>) specified in C<$sql_type>. At present only the SQL types
+C<SQL_INTEGER>, C<SQL_DOUBLE> and C<SQL_NUMERIC> are supported.
+
+For C<SQL_INTEGER> the effect is similar to using the value in an expression
+that requires an integer. It gives the perl scalar an 'integer aspect'.
+(Technically the value gains an IV, or possibly a UV or NV if the value is too
+large for an IV.)
+
+For C<SQL_DOUBLE> the effect is similar to using the value in an expression
+that requires a general numeric value. It gives the perl scalar a 'numeric
+aspect'. (Technically the value gains an NV.)
+
+C<SQL_NUMERIC> is similar to C<SQL_INTEGER> or C<SQL_DOUBLE> but more
+general and more cautious. It will look at the string first and if it
+looks like an integer (that will fit in an IV or UV) it will act like
+C<SQL_INTEGER>, if it looks like a floating point value it will act
+like C<SQL_DOUBLE>, if it looks like neither then it will do nothing -
+and thereby avoid the warnings that would be generated by
+C<SQL_INTEGER> and C<SQL_DOUBLE> when given non-numeric data.
+
+C<$flags> may be:
+
+=over 4
+
+=item C<DBIstcf_DISCARD_STRING>
+
+If this flag is specified then when the driver successfully casts the
+bound perl scalar to a non-string type then the string portion of the
+scalar will be discarded.
+
+=item C<DBIstcf_STRICT>
+
+If C<$sv> cannot be cast to the requested C<$sql_type> then by default
+it is left untouched and no error is generated. If you specify
+C<DBIstcf_STRICT> and the cast fails, this will generate an error.
+
+=back
+
+The returned C<$sts> value is:
+
+ -2 sql_type is not handled
+ -1 sv is undef so unchanged
+ 0 sv could not be cast cleanly and DBIstcf_STRICT was used
+ 1 sv could not be cast and DBIstcf_STRICT was not used
+ 2 sv was cast successfully
+
+This method is exported by the :utils tag and was introduced in DBI
+1.611.
+
+=head2 DBI Dynamic Attributes
+
+Dynamic attributes are always associated with the I<last handle used>
+(that handle is represented by C<$h> in the descriptions below).
+
+Where an attribute is equivalent to a method call, then refer to
+the method call for all related documentation.
+
+Warning: these attributes are provided as a convenience but they
+do have limitations. Specifically, they have a short lifespan:
+because they are associated with
+the last handle used, they should only be used I<immediately> after
+calling the method that "sets" them.
+If in any doubt, use the corresponding method call.
+
+=head3 C<$DBI::err>
+
+Equivalent to C<$h-E<gt>err>.
+
+=head3 C<$DBI::errstr>
+
+Equivalent to C<$h-E<gt>errstr>.
+
+=head3 C<$DBI::state>
+
+Equivalent to C<$h-E<gt>state>.
+
+=head3 C<$DBI::rows>
+
+Equivalent to C<$h-E<gt>rows>. Please refer to the documentation
+for the L</rows> method.
+
+=head3 C<$DBI::lasth>
+
+Returns the DBI object handle used for the most recent DBI method call.
+If the last DBI method call was a DESTROY then $DBI::lasth will return
+the handle of the parent of the destroyed handle, if there is one.
+
+
+=head1 METHODS COMMON TO ALL HANDLES
+
+The following methods can be used by all types of DBI handles.
+
+=head3 C<err>
+
+ $rv = $h->err;
+
+Returns the I<native> database engine error code from the last driver
+method called. The code is typically an integer but you should not
+assume that.
+
+The DBI resets $h->err to undef before almost all DBI method calls, so the
+value only has a short lifespan. Also, for most drivers, the statement
+handles share the same error variable as the parent database handle,
+so calling a method on one handle may reset the error on the
+related handles.
+
+(Methods which don't reset err before being called include err() and errstr(),
+obviously, state(), rows(), func(), trace(), trace_msg(), ping(), and the
+tied hash attribute FETCH() and STORE() methods.)
+
+If you need to test for specific error conditions I<and> have your program be
+portable to different database engines, then you'll need to determine what the
+corresponding error codes are for all those engines and test for all of them.
+
+The DBI uses the value of $DBI::stderr as the C<err> value for internal errors.
+Drivers should also do likewise. The default value for $DBI::stderr is 2000000000.
+
+A driver may return C<0> from err() to indicate a warning condition
+after a method call. Similarly, a driver may return an empty string
+to indicate a 'success with information' condition. In both these
+cases the value is false but not undef. The errstr() and state()
+methods may be used to retrieve extra information in these cases.
+
+See L</set_err> for more information.
+
+=head3 C<errstr>
+
+ $str = $h->errstr;
+
+Returns the native database engine error message from the last DBI
+method called. This has the same lifespan issues as the L</err> method
+described above.
+
+The returned string may contain multiple messages separated by
+newline characters.
+
+The errstr() method should not be used to test for errors, use err()
+for that, because drivers may return 'success with information' or
+warning messages via errstr() for methods that have not 'failed'.
+
+See L</set_err> for more information.
+
+=head3 C<state>
+
+ $str = $h->state;
+
+Returns a state code in the standard SQLSTATE five character format.
+Note that the specific success code C<00000> is translated to any empty string
+(false). If the driver does not support SQLSTATE (and most don't),
+then state() will return C<S1000> (General Error) for all errors.
+
+The driver is free to return any value via C<state>, e.g., warning
+codes, even if it has not declared an error by returning a true value
+via the L</err> method described above.
+
+The state() method should not be used to test for errors, use err()
+for that, because drivers may return a 'success with information' or
+warning state code via state() for methods that have not 'failed'.
+
+=head3 C<set_err>
+
+ $rv = $h->set_err($err, $errstr);
+ $rv = $h->set_err($err, $errstr, $state);
+ $rv = $h->set_err($err, $errstr, $state, $method);
+ $rv = $h->set_err($err, $errstr, $state, $method, $rv);
+
+Set the C<err>, C<errstr>, and C<state> values for the handle.
+This method is typically only used by DBI drivers and DBI subclasses.
+
+If the L</HandleSetErr> attribute holds a reference to a subroutine
+it is called first. The subroutine can alter the $err, $errstr, $state,
+and $method values. See L</HandleSetErr> for full details.
+If the subroutine returns a true value then the handle C<err>,
+C<errstr>, and C<state> values are not altered and set_err() returns
+an empty list (it normally returns $rv which defaults to undef, see below).
+
+Setting C<err> to a I<true> value indicates an error and will trigger
+the normal DBI error handling mechanisms, such as C<RaiseError> and
+C<HandleError>, if they are enabled, when execution returns from
+the DBI back to the application.
+
+Setting C<err> to C<""> indicates an 'information' state, and setting
+it to C<"0"> indicates a 'warning' state. Setting C<err> to C<undef>
+also sets C<errstr> to undef, and C<state> to C<"">, irrespective
+of the values of the $errstr and $state parameters.
+
+The $method parameter provides an alternate method name for the
+C<RaiseError>/C<PrintError>/C<PrintWarn> error string instead of
+the fairly unhelpful 'C<set_err>'.
+
+The C<set_err> method normally returns undef. The $rv parameter
+provides an alternate return value.
+
+Some special rules apply if the C<err> or C<errstr>
+values for the handle are I<already> set...
+
+If C<errstr> is true then: "C< [err was %s now %s]>" is appended if $err is
+true and C<err> is already true and the new err value differs from the original
+one. Similarly "C< [state was %s now %s]>" is appended if $state is true and C<state> is
+already true and the new state value differs from the original one. Finally
+"C<\n>" and the new $errstr are appended if $errstr differs from the existing
+errstr value. Obviously the C<%s>'s above are replaced by the corresponding values.
+
+The handle C<err> value is set to $err if: $err is true; or handle
+C<err> value is undef; or $err is defined and the length is greater
+than the handle C<err> length. The effect is that an 'information'
+state only overrides undef; a 'warning' overrides undef or 'information',
+and an 'error' state overrides anything.
+
+The handle C<state> value is set to $state if $state is true and
+the handle C<err> value was set (by the rules above).
+
+Support for warning and information states was added in DBI 1.41.
+
+=head3 C<trace>
+
+ $h->trace($trace_settings);
+ $h->trace($trace_settings, $trace_filename);
+ $trace_settings = $h->trace;
+
+The trace() method is used to alter the trace settings for a handle
+(and any future children of that handle). It can also be used to
+change where the trace output is sent.
+
+There's a similar method, C<DBI-E<gt>trace>, which sets the global
+default trace settings.
+
+See the L</TRACING> section for full details about the DBI's powerful
+tracing facilities.
+
+=head3 C<trace_msg>
+
+ $h->trace_msg($message_text);
+ $h->trace_msg($message_text, $min_level);
+
+Writes C<$message_text> to the trace file if the trace level is
+greater than or equal to $min_level (which defaults to 1).
+Can also be called as C<DBI-E<gt>trace_msg($msg)>.
+
+See L</TRACING> for more details.
+
+=head3 C<func>
+
+ $h->func(@func_arguments, $func_name) or die ...;
+
+The C<func> method can be used to call private non-standard and
+non-portable methods implemented by the driver. Note that the function
+name is given as the I<last> argument.
+
+It's also important to note that the func() method does not clear
+a previous error ($DBI::err etc.) and it does not trigger automatic
+error detection (RaiseError etc.) so you must check the return
+status and/or $h->err to detect errors.
+
+(This method is not directly related to calling stored procedures.
+Calling stored procedures is currently not defined by the DBI.
+Some drivers, such as DBD::Oracle, support it in non-portable ways.
+See driver documentation for more details.)
+
+See also install_method() in L<DBI::DBD> for how you can avoid needing to
+use func() and gain direct access to driver-private methods.
+
+=head3 C<can>
+
+ $is_implemented = $h->can($method_name);
+
+Returns true if $method_name is implemented by the driver or a
+default method is provided by the DBI.
+It returns false where a driver hasn't implemented a method and the
+default method is provided by the DBI is just an empty stub.
+
+=head3 C<parse_trace_flags>
+
+ $trace_settings_integer = $h->parse_trace_flags($trace_settings);
+
+Parses a string containing trace settings and returns the corresponding
+integer value used internally by the DBI and drivers.
+
+The $trace_settings argument is a string containing a trace level
+between 0 and 15 and/or trace flag names separated by vertical bar
+("C<|>") or comma ("C<,>") characters. For example: C<"SQL|3|foo">.
+
+It uses the parse_trace_flag() method, described below, to process
+the individual trace flag names.
+
+The parse_trace_flags() method was added in DBI 1.42.
+
+=head3 C<parse_trace_flag>
+
+ $bit_flag = $h->parse_trace_flag($trace_flag_name);
+
+Returns the bit flag corresponding to the trace flag name in
+$trace_flag_name. Drivers are expected to override this method and
+check if $trace_flag_name is a driver specific trace flags and, if
+not, then call the DBI's default parse_trace_flag().
+
+The parse_trace_flag() method was added in DBI 1.42.
+
+=head3 C<private_attribute_info>
+
+ $hash_ref = $h->private_attribute_info();
+
+Returns a reference to a hash whose keys are the names of driver-private
+handle attributes available for the kind of handle (driver, database, statement)
+that the method was called on.
+
+For example, the return value when called with a DBD::Sybase $dbh could look like this:
+
+ {
+ syb_dynamic_supported => undef,
+ syb_oc_version => undef,
+ syb_server_version => undef,
+ syb_server_version_string => undef,
+ }
+
+and when called with a DBD::Sybase $sth they could look like this:
+
+ {
+ syb_types => undef,
+ syb_proc_status => undef,
+ syb_result_type => undef,
+ }
+
+The values should be undef. Meanings may be assigned to particular values in future.
+
+=head3 C<swap_inner_handle>
+
+ $rc = $h1->swap_inner_handle( $h2 );
+ $rc = $h1->swap_inner_handle( $h2, $allow_reparent );
+
+Brain transplants for handles. You don't need to know about this
+unless you want to become a handle surgeon.
+
+A DBI handle is a reference to a tied hash. A tied hash has an
+I<inner> hash that actually holds the contents. The swap_inner_handle()
+method swaps the inner hashes between two handles. The $h1 and $h2
+handles still point to the same tied hashes, but what those hashes
+are tied to has been swapped. In effect $h1 I<becomes> $h2 and
+vice-versa. This is powerful stuff, expect problems. Use with care.
+
+As a small safety measure, the two handles, $h1 and $h2, have to
+share the same parent unless $allow_reparent is true.
+
+The swap_inner_handle() method was added in DBI 1.44.
+
+Here's a quick kind of 'diagram' as a worked example to help think about what's
+happening:
+
+ Original state:
+ dbh1o -> dbh1i
+ sthAo -> sthAi(dbh1i)
+ dbh2o -> dbh2i
+
+ swap_inner_handle dbh1o with dbh2o:
+ dbh2o -> dbh1i
+ sthAo -> sthAi(dbh1i)
+ dbh1o -> dbh2i
+
+ create new sth from dbh1o:
+ dbh2o -> dbh1i
+ sthAo -> sthAi(dbh1i)
+ dbh1o -> dbh2i
+ sthBo -> sthBi(dbh2i)
+
+ swap_inner_handle sthAo with sthBo:
+ dbh2o -> dbh1i
+ sthBo -> sthAi(dbh1i)
+ dbh1o -> dbh2i
+ sthAo -> sthBi(dbh2i)
+
+=head3 C<visit_child_handles>
+
+ $h->visit_child_handles( $coderef );
+ $h->visit_child_handles( $coderef, $info );
+
+Where $coderef is a reference to a subroutine and $info is an arbitrary value
+which, if undefined, defaults to a reference to an empty hash. Returns $info.
+
+For each child handle of $h, if any, $coderef is invoked as:
+
+ $coderef->($child_handle, $info);
+
+If the execution of $coderef returns a true value then C<visit_child_handles>
+is called on that child handle and passed the returned value as $info.
+
+For example:
+
+ # count database connections with names (DSN) matching a pattern
+ my $connections = 0;
+ $dbh->{Driver}->visit_child_handles(sub {
+ my ($h, $info) = @_;
+ ++$connections if $h->{Name} =~ /foo/;
+ return 0; # don't visit kids
+ })
+
+See also L</visit_handles>.
+
+=head1 ATTRIBUTES COMMON TO ALL HANDLES
+
+These attributes are common to all types of DBI handles.
+
+Some attributes are inherited by child handles. That is, the value
+of an inherited attribute in a newly created statement handle is the
+same as the value in the parent database handle. Changes to attributes
+in the new statement handle do not affect the parent database handle
+and changes to the database handle do not affect existing statement
+handles, only future ones.
+
+Attempting to set or get the value of an unknown attribute generates a warning,
+except for private driver specific attributes (which all have names
+starting with a lowercase letter).
+
+Example:
+
+ $h->{AttributeName} = ...; # set/write
+ ... = $h->{AttributeName}; # get/read
+
+=head3 C<Warn>
+
+Type: boolean, inherited
+
+The C<Warn> attribute enables useful warnings for certain bad
+practices. It is enabled by default and should only be disabled in
+rare circumstances. Since warnings are generated using the Perl
+C<warn> function, they can be intercepted using the Perl C<$SIG{__WARN__}>
+hook.
+
+The C<Warn> attribute is not related to the C<PrintWarn> attribute.
+
+=head3 C<Active>
+
+Type: boolean, read-only
+
+The C<Active> attribute is true if the handle object is "active". This is rarely used in
+applications. The exact meaning of active is somewhat vague at the
+moment. For a database handle it typically means that the handle is
+connected to a database (C<$dbh-E<gt>disconnect> sets C<Active> off). For
+a statement handle it typically means that the handle is a C<SELECT>
+that may have more data to fetch. (Fetching all the data or calling C<$sth-E<gt>finish>
+sets C<Active> off.)
+
+=head3 C<Executed>
+
+Type: boolean
+
+The C<Executed> attribute is true if the handle object has been "executed".
+Currently only the $dbh do() method and the $sth execute(), execute_array(),
+and execute_for_fetch() methods set the C<Executed> attribute.
+
+When it's set on a handle it is also set on the parent handle at the
+same time. So calling execute() on a $sth also sets the C<Executed>
+attribute on the parent $dbh.
+
+The C<Executed> attribute for a database handle is cleared by the commit() and
+rollback() methods (even if they fail). The C<Executed> attribute of a
+statement handle is not cleared by the DBI under any circumstances and so acts
+as a permanent record of whether the statement handle was ever used.
+
+The C<Executed> attribute was added in DBI 1.41.
+
+=head3 C<Kids>
+
+Type: integer, read-only
+
+For a driver handle, C<Kids> is the number of currently existing database
+handles that were created from that driver handle. For a database
+handle, C<Kids> is the number of currently existing statement handles that
+were created from that database handle.
+For a statement handle, the value is zero.
+
+=head3 C<ActiveKids>
+
+Type: integer, read-only
+
+Like C<Kids>, but only counting those that are C<Active> (as above).
+
+=head3 C<CachedKids>
+
+Type: hash ref
+
+For a database handle, C<CachedKids> returns a reference to the cache (hash) of
+statement handles created by the L</prepare_cached> method. For a
+driver handle, returns a reference to the cache (hash) of
+database handles created by the L</connect_cached> method.
+
+=head3 C<Type>
+
+Type: scalar, read-only
+
+The C<Type> attribute identifies the type of a DBI handle. Returns
+"dr" for driver handles, "db" for database handles and "st" for
+statement handles.
+
+=head3 C<ChildHandles>
+
+Type: array ref
+
+The ChildHandles attribute contains a reference to an array of all the
+handles created by this handle which are still accessible. The
+contents of the array are weak-refs and will become undef when the
+handle goes out of scope.
+
+C<ChildHandles> returns undef if your perl version does not support weak
+references (check the L<Scalar::Util|Scalar::Util> module). The referenced
+array returned should be treated as read-only.
+
+For example, to enumerate all driver handles, database handles and
+statement handles:
+
+ sub show_child_handles {
+ my ($h, $level) = @_;
+ printf "%sh %s %s\n", $h->{Type}, "\t" x $level, $h;
+ show_child_handles($_, $level + 1)
+ for (grep { defined } @{$h->{ChildHandles}});
+ }
+
+ my %drivers = DBI->installed_drivers();
+ show_child_handles($_, 0) for (values %drivers);
+
+=head3 C<CompatMode>
+
+Type: boolean, inherited
+
+The C<CompatMode> attribute is used by emulation layers (such as
+Oraperl) to enable compatible behaviour in the underlying driver
+(e.g., DBD::Oracle) for this handle. Not normally set by application code.
+
+It also has the effect of disabling the 'quick FETCH' of attribute
+values from the handles attribute cache. So all attribute values
+are handled by the drivers own FETCH method. This makes them slightly
+slower but is useful for special-purpose drivers like DBD::Multiplex.
+
+=head3 C<InactiveDestroy>
+
+Type: boolean
+
+The default value, false, means a handle will be fully destroyed
+as normal when the last reference to it is removed, just as you'd expect.
+
+If set true then the handle will be treated by the DESTROY as if it was no
+longer Active, and so the I<database engine> related effects of DESTROYing a
+handle will be skipped. Think of the name as meaning 'treat the handle as
+not-Active in the DESTROY method'.
+
+For a database handle, this attribute does not disable an I<explicit>
+call to the disconnect method, only the implicit call from DESTROY
+that happens if the handle is still marked as C<Active>.
+
+This attribute is specifically designed for use in Unix applications
+that "fork" child processes. For some drivers, when the child process exits
+the destruction of inherited handles cause the corresponding handles in the
+parent process to cease working.
+
+Either the parent or the child process, but not both, should set
+C<InactiveDestroy> true on all their shared handles. Alternatively the
+L</AutoInactiveDestroy> can be set in the parent on connect.
+
+To help tracing applications using fork the process id is shown in
+the trace log whenever a DBI or handle trace() method is called.
+The process id also shown for I<every> method call if the DBI trace
+level (not handle trace level) is set high enough to show the trace
+from the DBI's method dispatcher, e.g. >= 9.
+
+=head3 C<AutoInactiveDestroy>
+
+Type: boolean, inherited
+
+The L</InactiveDestroy> attribute, described above, needs to be explicitly set
+in the child process after a fork(). This is a problem if the code that performs
+the fork() is not under your control, perhaps in a third-party module.
+Use C<AutoInactiveDestroy> to get around this situation.
+
+If set true, the DESTROY method will check the process id of the handle and, if
+different from the current process id, it will set the I<InactiveDestroy> attribute.
+
+This is the example it's designed to deal with:
+
+ my $dbh = DBI->connect(...);
+ some_code_that_forks(); # Perhaps without your knowledge
+ # Child process dies, destroying the inherited dbh
+ $dbh->do(...); # Breaks because parent $dbh is now broken
+
+The C<AutoInactiveDestroy> attribute was added in DBI 1.614.
+
+=head3 C<PrintWarn>
+
+Type: boolean, inherited
+
+The C<PrintWarn> attribute controls the printing of warnings recorded
+by the driver. When set to a true value the DBI will check method
+calls to see if a warning condition has been set. If so, the DBI
+will effectively do a C<warn("$class $method warning: $DBI::errstr")>
+where C<$class> is the driver class and C<$method> is the name of
+the method which failed. E.g.,
+
+ DBD::Oracle::db execute warning: ... warning text here ...
+
+By default, C<DBI-E<gt>connect> sets C<PrintWarn> "on" if $^W is true,
+i.e., perl is running with warnings enabled.
+
+If desired, the warnings can be caught and processed using a C<$SIG{__WARN__}>
+handler or modules like CGI::Carp and CGI::ErrorWrap.
+
+See also L</set_err> for how warnings are recorded and L</HandleSetErr>
+for how to influence it.
+
+Fetching the full details of warnings can require an extra round-trip
+to the database server for some drivers. In which case the driver
+may opt to only fetch the full details of warnings if the C<PrintWarn>
+attribute is true. If C<PrintWarn> is false then these drivers should
+still indicate the fact that there were warnings by setting the
+warning string to, for example: "3 warnings".
+
+=head3 C<PrintError>
+
+Type: boolean, inherited
+
+The C<PrintError> attribute can be used to force errors to generate warnings (using
+C<warn>) in addition to returning error codes in the normal way. When set
+"on", any method which results in an error occurring will cause the DBI to
+effectively do a C<warn("$class $method failed: $DBI::errstr")> where C<$class>
+is the driver class and C<$method> is the name of the method which failed. E.g.,
+
+ DBD::Oracle::db prepare failed: ... error text here ...
+
+By default, C<DBI-E<gt>connect> sets C<PrintError> "on".
+
+If desired, the warnings can be caught and processed using a C<$SIG{__WARN__}>
+handler or modules like CGI::Carp and CGI::ErrorWrap.
+
+=head3 C<RaiseError>
+
+Type: boolean, inherited
+
+The C<RaiseError> attribute can be used to force errors to raise exceptions rather
+than simply return error codes in the normal way. It is "off" by default.
+When set "on", any method which results in an error will cause
+the DBI to effectively do a C<die("$class $method failed: $DBI::errstr")>,
+where C<$class> is the driver class and C<$method> is the name of the method
+that failed. E.g.,
+
+ DBD::Oracle::db prepare failed: ... error text here ...
+
+If you turn C<RaiseError> on then you'd normally turn C<PrintError> off.
+If C<PrintError> is also on, then the C<PrintError> is done first (naturally).
+
+Typically C<RaiseError> is used in conjunction with C<eval { ... }>
+to catch the exception that's been thrown and followed by an
+C<if ($@) { ... }> block to handle the caught exception.
+For example:
+
+ eval {
+ ...
+ $sth->execute();
+ ...
+ };
+ if ($@) {
+ # $sth->err and $DBI::err will be true if error was from DBI
+ warn $@; # print the error
+ ... # do whatever you need to deal with the error
+ }
+
+In that eval block the $DBI::lasth variable can be useful for
+diagnosis and reporting if you can't be sure which handle triggered
+the error. For example, $DBI::lasth->{Type} and $DBI::lasth->{Statement}.
+
+See also L</Transactions>.
+
+If you want to temporarily turn C<RaiseError> off (inside a library function
+that is likely to fail, for example), the recommended way is like this:
+
+ {
+ local $h->{RaiseError}; # localize and turn off for this block
+ ...
+ }
+
+The original value will automatically and reliably be restored by Perl,
+regardless of how the block is exited.
+The same logic applies to other attributes, including C<PrintError>.
+
+=head3 C<HandleError>
+
+Type: code ref, inherited
+
+The C<HandleError> attribute can be used to provide your own alternative behaviour
+in case of errors. If set to a reference to a subroutine then that
+subroutine is called when an error is detected (at the same point that
+C<RaiseError> and C<PrintError> are handled).
+
+The subroutine is called with three parameters: the error message
+string that C<RaiseError> and C<PrintError> would use,
+the DBI handle being used, and the first value being returned by
+the method that failed (typically undef).
+
+If the subroutine returns a false value then the C<RaiseError>
+and/or C<PrintError> attributes are checked and acted upon as normal.
+
+For example, to C<die> with a full stack trace for any error:
+
+ use Carp;
+ $h->{HandleError} = sub { confess(shift) };
+
+Or to turn errors into exceptions:
+
+ use Exception; # or your own favourite exception module
+ $h->{HandleError} = sub { Exception->new('DBI')->raise($_[0]) };
+
+It is possible to 'stack' multiple HandleError handlers by using
+closures:
+
+ sub your_subroutine {
+ my $previous_handler = $h->{HandleError};
+ $h->{HandleError} = sub {
+ return 1 if $previous_handler and &$previous_handler(@_);
+ ... your code here ...
+ };
+ }
+
+Using a C<my> inside a subroutine to store the previous C<HandleError>
+value is important. See L<perlsub> and L<perlref> for more information
+about I<closures>.
+
+It is possible for C<HandleError> to alter the error message that
+will be used by C<RaiseError> and C<PrintError> if it returns false.
+It can do that by altering the value of $_[0]. This example appends
+a stack trace to all errors and, unlike the previous example using
+Carp::confess, this will work C<PrintError> as well as C<RaiseError>:
+
+ $h->{HandleError} = sub { $_[0]=Carp::longmess($_[0]); 0; };
+
+It is also possible for C<HandleError> to hide an error, to a limited
+degree, by using L</set_err> to reset $DBI::err and $DBI::errstr,
+and altering the return value of the failed method. For example:
+
+ $h->{HandleError} = sub {
+ return 0 unless $_[0] =~ /^\S+ fetchrow_arrayref failed:/;
+ return 0 unless $_[1]->err == 1234; # the error to 'hide'
+ $h->set_err(undef,undef); # turn off the error
+ $_[2] = [ ... ]; # supply alternative return value
+ return 1;
+ };
+
+This only works for methods which return a single value and is hard
+to make reliable (avoiding infinite loops, for example) and so isn't
+recommended for general use! If you find a I<good> use for it then
+please let me know.
+
+=head3 C<HandleSetErr>
+
+Type: code ref, inherited
+
+The C<HandleSetErr> attribute can be used to intercept
+the setting of handle C<err>, C<errstr>, and C<state> values.
+If set to a reference to a subroutine then that subroutine is called
+whenever set_err() is called, typically by the driver or a subclass.
+
+The subroutine is called with five arguments, the first five that
+were passed to set_err(): the handle, the C<err>, C<errstr>, and
+C<state> values being set, and the method name. These can be altered
+by changing the values in the @_ array. The return value affects
+set_err() behaviour, see L</set_err> for details.
+
+It is possible to 'stack' multiple HandleSetErr handlers by using
+closures. See L</HandleError> for an example.
+
+The C<HandleSetErr> and C<HandleError> subroutines differ in subtle
+but significant ways. HandleError is only invoked at the point where
+the DBI is about to return to the application with C<err> set true.
+It's not invoked by the failure of a method that's been called by
+another DBI method. HandleSetErr, on the other hand, is called
+whenever set_err() is called with a defined C<err> value, even if false.
+So it's not just for errors, despite the name, but also warn and info states.
+The set_err() method, and thus HandleSetErr, may be called multiple
+times within a method and is usually invoked from deep within driver code.
+
+In theory a driver can use the return value from HandleSetErr via
+set_err() to decide whether to continue or not. If set_err() returns
+an empty list, indicating that the HandleSetErr code has 'handled'
+the 'error', the driver could then continue instead of failing (if
+that's a reasonable thing to do). This isn't excepted to be
+common and any such cases should be clearly marked in the driver
+documentation and discussed on the dbi-dev mailing list.
+
+The C<HandleSetErr> attribute was added in DBI 1.41.
+
+=head3 C<ErrCount>
+
+Type: unsigned integer
+
+The C<ErrCount> attribute is incremented whenever the set_err()
+method records an error. It isn't incremented by warnings or
+information states. It is not reset by the DBI at any time.
+
+The C<ErrCount> attribute was added in DBI 1.41. Older drivers may
+not have been updated to use set_err() to record errors and so this
+attribute may not be incremented when using them.
+
+
+=head3 C<ShowErrorStatement>
+
+Type: boolean, inherited
+
+The C<ShowErrorStatement> attribute can be used to cause the relevant
+Statement text to be appended to the error messages generated by
+the C<RaiseError>, C<PrintError>, and C<PrintWarn> attributes.
+Only applies to errors on statement handles
+plus the prepare(), do(), and the various C<select*()> database handle methods.
+(The exact format of the appended text is subject to change.)
+
+If C<$h-E<gt>{ParamValues}> returns a hash reference of parameter
+(placeholder) values then those are formatted and appended to the
+end of the Statement text in the error message.
+
+=head3 C<TraceLevel>
+
+Type: integer, inherited
+
+The C<TraceLevel> attribute can be used as an alternative to the
+L</trace> method to set the DBI trace level and trace flags for a
+specific handle. See L</TRACING> for more details.
+
+The C<TraceLevel> attribute is especially useful combined with
+C<local> to alter the trace settings for just a single block of code.
+
+=head3 C<FetchHashKeyName>
+
+Type: string, inherited
+
+The C<FetchHashKeyName> attribute is used to specify whether the fetchrow_hashref()
+method should perform case conversion on the field names used for
+the hash keys. For historical reasons it defaults to 'C<NAME>' but
+it is recommended to set it to 'C<NAME_lc>' (convert to lower case)
+or 'C<NAME_uc>' (convert to upper case) according to your preference.
+It can only be set for driver and database handles. For statement
+handles the value is frozen when prepare() is called.
+
+
+=head3 C<ChopBlanks>
+
+Type: boolean, inherited
+
+The C<ChopBlanks> attribute can be used to control the trimming of trailing space
+characters from fixed width character (CHAR) fields. No other field
+types are affected, even where field values have trailing spaces.
+
+The default is false (although it is possible that the default may change).
+Applications that need specific behaviour should set the attribute as
+needed.
+
+Drivers are not required to support this attribute, but any driver which
+does not support it must arrange to return C<undef> as the attribute value.
+
+
+=head3 C<LongReadLen>
+
+Type: unsigned integer, inherited
+
+The C<LongReadLen> attribute may be used to control the maximum
+length of 'long' type fields (LONG, BLOB, CLOB, MEMO, etc.) which the driver will
+read from the database automatically when it fetches each row of data.
+
+The C<LongReadLen> attribute only relates to fetching and reading
+long values; it is not involved in inserting or updating them.
+
+A value of 0 means not to automatically fetch any long data.
+Drivers may return undef or an empty string for long fields when
+C<LongReadLen> is 0.
+
+The default is typically 0 (zero) or 80 bytes but may vary between drivers.
+Applications fetching long fields should set this value to slightly
+larger than the longest long field value to be fetched.
+
+Some databases return some long types encoded as pairs of hex digits.
+For these types, C<LongReadLen> relates to the underlying data
+length and not the doubled-up length of the encoded string.
+
+Changing the value of C<LongReadLen> for a statement handle after it
+has been C<prepare>'d will typically have no effect, so it's common to
+set C<LongReadLen> on the C<$dbh> before calling C<prepare>.
+
+For most drivers the value used here has a direct effect on the
+memory used by the statement handle while it's active, so don't be
+too generous. If you can't be sure what value to use you could
+execute an extra select statement to determine the longest value.
+For example:
+
+ $dbh->{LongReadLen} = $dbh->selectrow_array(qq{
+ SELECT MAX(OCTET_LENGTH(long_column_name))
+ FROM table WHERE ...
+ });
+ $sth = $dbh->prepare(qq{
+ SELECT long_column_name, ... FROM table WHERE ...
+ });
+
+You may need to take extra care if the table can be modified between
+the first select and the second being executed. You may also need to
+use a different function if OCTET_LENGTH() does not work for long
+types in your database. For example, for Sybase use DATALENGTH() and
+for Oracle use LENGTHB().
+
+See also L</LongTruncOk> for information on truncation of long types.
+
+=head3 C<LongTruncOk>
+
+Type: boolean, inherited
+
+The C<LongTruncOk> attribute may be used to control the effect of
+fetching a long field value which has been truncated (typically
+because it's longer than the value of the C<LongReadLen> attribute).
+
+By default, C<LongTruncOk> is false and so fetching a long value that
+needs to be truncated will cause the fetch to fail.
+(Applications should always be sure to
+check for errors after a fetch loop in case an error, such as a divide
+by zero or long field truncation, caused the fetch to terminate
+prematurely.)
+
+If a fetch fails due to a long field truncation when C<LongTruncOk> is
+false, many drivers will allow you to continue fetching further rows.
+
+See also L</LongReadLen>.
+
+=head3 C<TaintIn>
+
+Type: boolean, inherited
+
+If the C<TaintIn> attribute is set to a true value I<and> Perl is running in
+taint mode (e.g., started with the C<-T> option), then all the arguments
+to most DBI method calls are checked for being tainted. I<This may change.>
+
+The attribute defaults to off, even if Perl is in taint mode.
+See L<perlsec> for more about taint mode. If Perl is not
+running in taint mode, this attribute has no effect.
+
+When fetching data that you trust you can turn off the TaintIn attribute,
+for that statement handle, for the duration of the fetch loop.
+
+The C<TaintIn> attribute was added in DBI 1.31.
+
+=head3 C<TaintOut>
+
+Type: boolean, inherited
+
+If the C<TaintOut> attribute is set to a true value I<and> Perl is running in
+taint mode (e.g., started with the C<-T> option), then most data fetched
+from the database is considered tainted. I<This may change.>
+
+The attribute defaults to off, even if Perl is in taint mode.
+See L<perlsec> for more about taint mode. If Perl is not
+running in taint mode, this attribute has no effect.
+
+When fetching data that you trust you can turn off the TaintOut attribute,
+for that statement handle, for the duration of the fetch loop.
+
+Currently only fetched data is tainted. It is possible that the results
+of other DBI method calls, and the value of fetched attributes, may
+also be tainted in future versions. That change may well break your
+applications unless you take great care now. If you use DBI Taint mode,
+please report your experience and any suggestions for changes.
+
+The C<TaintOut> attribute was added in DBI 1.31.
+
+=head3 C<Taint>
+
+Type: boolean, inherited
+
+The C<Taint> attribute is a shortcut for L</TaintIn> and L</TaintOut> (it is also present
+for backwards compatibility).
+
+Setting this attribute sets both L</TaintIn> and L</TaintOut>, and retrieving
+it returns a true value if and only if L</TaintIn> and L</TaintOut> are
+both set to true values.
+
+=head3 C<Profile>
+
+Type: inherited
+
+The C<Profile> attribute enables the collection and reporting of
+method call timing statistics. See the L<DBI::Profile> module
+documentation for I<much> more detail.
+
+The C<Profile> attribute was added in DBI 1.24.
+
+=head3 C<ReadOnly>
+
+Type: boolean, inherited
+
+An application can set the C<ReadOnly> attribute of a handle to a true value to
+indicate that it will not be attempting to make any changes using that handle
+or any children of it.
+
+Note that the exact definition of 'read only' is rather fuzzy.
+For more details see the documentation for the driver you're using.
+
+If the driver can make the handle truly read-only then it should
+(unless doing so would have unpleasant side effect, like changing the
+consistency level from per-statement to per-session).
+Otherwise the attribute is simply advisory.
+
+A driver can set the C<ReadOnly> attribute itself to indicate that the data it
+is connected to cannot be changed for some reason.
+
+Library modules and proxy drivers can use the attribute to influence
+their behavior. For example, the DBD::Gofer driver considers the
+C<ReadOnly> attribute when making a decision about whether to retry an
+operation that failed.
+
+The attribute should be set to 1 or 0 (or undef). Other values are reserved.
+
+=head3 C<Callbacks>
+
+Type: hash ref
+
+The DBI callback mechanism lets you intercept, and optionally replace, any
+method call on a DBI handle. At the extreme, it lets you become a puppet
+master, deceiving the application in any way you want.
+
+The C<Callbacks> attribute is a hash reference where the keys are DBI method
+names and the values are code references. For each key naming a method, the
+DBI will execute the associated code reference before executing the method.
+
+The arguments to the code reference will be the same as to the method,
+including the invocant (a database handle or statement handle). For example,
+say that to callback to some code on a call to C<prepare()>:
+
+ $dbh->{Callbacks} = {
+ prepare => sub {
+ my ($dbh, $query, $attrs) = @_;
+ print "Preparing q{$query}\n"
+ },
+ };
+
+The callback would then be executed when you called the C<prepare()> method:
+
+ $dbh->prepare('SELECT 1');
+
+And the output of course would be:
+
+ Preparing q{SELECT 1}
+
+Because callbacks are executed I<before> the methods
+they're associated with, you can modify the arguments before they're passed on
+to the method call. For example, to make sure that all calls to C<prepare()>
+are immediately prepared by L<DBD::Pg>, add a callback that makes sure that
+the C<pg_prepare_now> attribute is always set:
+
+ my $dbh = DBI->connect($dsn, $username, $auth, {
+ Callbacks => {
+ prepare => sub {
+ $_[2] ||= {};
+ $_[2]->{pg_prepare_now} = 1;
+ return; # must return nothing
+ },
+ }
+ });
+
+Note that we are editing the contents of C<@_> directly. In this case we've
+created the attributes hash if it's not passed to the C<prepare> call.
+
+You can also prevent the associated method from ever executing. While a
+callback executes, C<$_> holds the method name. (This allows multiple callbacks
+to share the same code reference and still know what method was called.)
+To prevent the method from
+executing, simply C<undef $_>. For example, if you wanted to disable calls to
+C<ping()>, you could do this:
+
+ $dbh->{Callbacks} = {
+ ping => sub {
+ # tell dispatch to not call the method:
+ undef $_;
+ # return this value instead:
+ return "42 bells";
+ }
+ };
+
+As with other attributes, Callbacks can be specified on a handle or via the
+attributes to C<connect()>. Callbacks can also be applied to a statement
+methods on a statement handle. For example:
+
+ $sth->{Callbacks} = {
+ execute => sub {
+ print "Executing ", shift->{Statement}, "\n";
+ }
+ };
+
+The C<Callbacks> attribute of a database handle isn't copied to any statement
+handles it creates. So setting callbacks for a statement handle requires you to
+set the C<Callbacks> attribute on the statement handle yourself, as in the
+example above, or use the special C<ChildCallbacks> key described below.
+
+B<Special Keys in Callbacks Attribute>
+
+In addition to DBI handle method names, the C<Callbacks> hash reference
+supports three additional keys.
+
+The first is the C<ChildCallbacks> key. When a statement handle is created from
+a database handle the C<ChildCallbacks> key of the database handle's
+C<Callbacks> attribute, if any, becomes the new C<Callbacks> attribute of the
+statement handle.
+This allows you to define callbacks for all statement handles created from a
+database handle. For example, if you wanted to count how many times C<execute>
+was called in your application, you could write:
+
+ my $exec_count = 0;
+ my $dbh = DBI->connect( $dsn, $username, $auth, {
+ Callbacks => {
+ ChildCallbacks => {
+ execute => sub { $exec_count++; return; }
+ }
+ }
+ });
+
+ END {
+ print "The execute method was called $exec_count times\n";
+ }
+
+The other two special keys are C<connect_cached.new> and
+C<connect_cached.reused>. These keys define callbacks that are called when
+C<connect_cached()> is called, but allow different behaviors depending on
+whether a new handle is created or a handle is returned. The callback is
+invoked with these arguments: C<$dbh, $dsn, $user, $auth, $attr>.
+
+For example, some applications uses C<connect_cached()> to connect with
+C<AutoCommit> enabled and then disable C<AutoCommit> temporarily for
+transactions. If C<connect_cached()> is called during a transaction, perhaps in
+a utility method, then it might select the same cached handle and then force
+C<AutoCommit> on, forcing a commit of the transaction. See the L</connect_cached>
+documentation for one way to deal with that. Here we'll describe an alternative
+approach using a callback.
+
+Because the C<connect_cached.*> callbacks are invoked before connect_cached()
+has applied the connect attributes you can use a callback to edit the attributes
+that will be applied. To prevent a cached handle from having its transactions
+committed before it's returned, you can eliminate the C<AutoCommit> attribute
+in a C<connect_cached.reused> callback, like so:
+
+ my $cb = {
+ 'connect_cached.reused' => sub { delete $_[4]->{AutoCommit} },
+ };
+
+ sub dbh {
+ my $self = shift;
+ DBI->connect_cached( $dsn, $username, $auth, {
+ PrintError => 0,
+ RaiseError => 1,
+ AutoCommit => 1,
+ Callbacks => $cb,
+ });
+ }
+
+The upshot is that new database handles are created with C<AutoCommit>
+enabled, while cached database handles are left in whatever transaction state
+they happened to be in when retrieved from the cache.
+
+A more common application for callbacks is setting connection state only when a
+new connection is made (by connect() or connect_cached()). Adding a callback to
+the connected method makes this easy.
+This method is a no-op by default (unless you subclass the DBI and change it).
+The DBI calls it to indicate that a new connection has been made and the connection
+attributes have all been set. You can
+give it a bit of added functionality by applying a callback to it. For
+example, to make sure that MySQL understands your application's ANSI-compliant
+SQL, set it up like so:
+
+ my $dbh = DBI->connect($dsn, $username, $auth, {
+ Callbacks => {
+ connected => sub {
+ shift->do(q{
+ SET SESSION sql_mode='ansi,strict_trans_tables,no_auto_value_on_zero';
+ });
+ return;
+ },
+ }
+ });
+
+One significant limitation with callbacks is that there can only be one per
+method per handle. This means it's easy for one use of callbacks to interfere
+with, or typically simply overwrite, another use of callbacks. For this reason
+modules using callbacks should document the fact clearly so application authors
+can tell if use of callbacks by the module will clash with use of callbacks by
+the application.
+
+You might be able to work around this issue by taking a copy of the original
+callback and calling it within your own. For example:
+
+ my $prev_cb = $h->{Callbacks}{method_name};
+ $h->{Callbacks}{method_name} = sub {
+ if ($prev_cb) {
+ my @result = $prev_cb->(@_);
+ return @result if not $_; # $prev_cb vetoed call
+ }
+ ... your callback logic here ...
+ };
+
+=head3 C<private_your_module_name_*>
+
+The DBI provides a way to store extra information in a DBI handle as
+"private" attributes. The DBI will allow you to store and retrieve any
+attribute which has a name starting with "C<private_>".
+
+It is I<strongly> recommended that you use just I<one> private
+attribute (e.g., use a hash ref) I<and> give it a long and unambiguous
+name that includes the module or application name that the attribute
+relates to (e.g., "C<private_YourFullModuleName_thingy>").
+
+Because of the way the Perl tie mechanism works you cannot reliably
+use the C<||=> operator directly to initialise the attribute, like this:
+
+ my $foo = $dbh->{private_yourmodname_foo} ||= { ... }; # WRONG
+
+you should use a two step approach like this:
+
+ my $foo = $dbh->{private_yourmodname_foo};
+ $foo ||= $dbh->{private_yourmodname_foo} = { ... };
+
+This attribute is primarily of interest to people sub-classing DBI,
+or for applications to piggy-back extra information onto DBI handles.
+
+=head1 DBI DATABASE HANDLE OBJECTS
+
+This section covers the methods and attributes associated with
+database handles.
+
+=head2 Database Handle Methods
+
+The following methods are specified for DBI database handles:
+
+=head3 C<clone>
+
+ $new_dbh = $dbh->clone(\%attr);
+
+The C<clone> method duplicates the $dbh connection by connecting
+with the same parameters ($dsn, $user, $password) as originally used.
+
+The attributes for the cloned connect are the same as those used
+for the I<original> connect, with any other attributes in C<\%attr>
+merged over them. Effectively the same as doing:
+
+ %attributes_used = ( %original_attributes, %attr );
+
+If \%attr is not given then it defaults to a hash containing all
+the attributes in the attribute cache of $dbh excluding any non-code
+references, plus the main boolean attributes (RaiseError, PrintError,
+AutoCommit, etc.). I<This behaviour is unreliable and so use of clone without
+an argument is deprecated and may cause a warning in a future release.>
+
+The clone method can be used even if the database handle is disconnected.
+
+The C<clone> method was added in DBI 1.33.
+
+=head3 C<data_sources>
+
+ @ary = $dbh->data_sources();
+ @ary = $dbh->data_sources(\%attr);
+
+Returns a list of data sources (databases) available via the $dbh
+driver's data_sources() method, plus any extra data sources that
+the driver can discover via the connected $dbh. Typically the extra
+data sources are other databases managed by the same server process
+that the $dbh is connected to.
+
+Data sources are returned in a form suitable for passing to the
+L</connect> method (that is, they will include the "C<dbi:$driver:>" prefix).
+
+The data_sources() method, for a $dbh, was added in DBI 1.38.
+
+=head3 C<do>
+
+ $rows = $dbh->do($statement) or die $dbh->errstr;
+ $rows = $dbh->do($statement, \%attr) or die $dbh->errstr;
+ $rows = $dbh->do($statement, \%attr, @bind_values) or die ...
+
+Prepare and execute a single statement. Returns the number of rows
+affected or C<undef> on error. A return value of C<-1> means the
+number of rows is not known, not applicable, or not available.
+
+This method is typically most useful for I<non>-C<SELECT> statements that
+either cannot be prepared in advance (due to a limitation of the
+driver) or do not need to be executed repeatedly. It should not
+be used for C<SELECT> statements because it does not return a statement
+handle (so you can't fetch any data).
+
+The default C<do> method is logically similar to:
+
+ sub do {
+ my($dbh, $statement, $attr, @bind_values) = @_;
+ my $sth = $dbh->prepare($statement, $attr) or return undef;
+ $sth->execute(@bind_values) or return undef;
+ my $rows = $sth->rows;
+ ($rows == 0) ? "0E0" : $rows; # always return true if no error
+ }
+
+For example:
+
+ my $rows_deleted = $dbh->do(q{
+ DELETE FROM table
+ WHERE status = ?
+ }, undef, 'DONE') or die $dbh->errstr;
+
+Using placeholders and C<@bind_values> with the C<do> method can be
+useful because it avoids the need to correctly quote any variables
+in the C<$statement>. But if you'll be executing the statement many
+times then it's more efficient to C<prepare> it once and call
+C<execute> many times instead.
+
+The C<q{...}> style quoting used in this example avoids clashing with
+quotes that may be used in the SQL statement. Use the double-quote-like
+C<qq{...}> operator if you want to interpolate variables into the string.
+See L<perlop/"Quote and Quote-like Operators"> for more details.
+
+Note drivers are free to avoid the overhead of creating an DBI
+statement handle for do(), especially if there are no parameters. In
+this case error handlers, if invoked during do(), will be passed the
+database handle.
+
+=head3 C<last_insert_id>
+
+ $rv = $dbh->last_insert_id($catalog, $schema, $table, $field);
+ $rv = $dbh->last_insert_id($catalog, $schema, $table, $field, \%attr);
+
+Returns a value 'identifying' the row just inserted, if possible.
+Typically this would be a value assigned by the database server
+to a column with an I<auto_increment> or I<serial> type.
+Returns undef if the driver does not support the method or can't
+determine the value.
+
+The $catalog, $schema, $table, and $field parameters may be required
+for some drivers (see below). If you don't know the parameter values
+and your driver does not need them, then use C<undef> for each.
+
+There are several caveats to be aware of with this method if you want
+to use it for portable applications:
+
+B<*> For some drivers the value may only available immediately after
+the insert statement has executed (e.g., mysql, Informix).
+
+B<*> For some drivers the $catalog, $schema, $table, and $field parameters
+are required, for others they are ignored (e.g., mysql).
+
+B<*> Drivers may return an indeterminate value if no insert has
+been performed yet.
+
+B<*> For some drivers the value may only be available if placeholders
+have I<not> been used (e.g., Sybase, MS SQL). In this case the value
+returned would be from the last non-placeholder insert statement.
+
+B<*> Some drivers may need driver-specific hints about how to get
+the value. For example, being told the name of the database 'sequence'
+object that holds the value. Any such hints are passed as driver-specific
+attributes in the \%attr parameter.
+
+B<*> If the underlying database offers nothing better, then some
+drivers may attempt to implement this method by executing
+"C<select max($field) from $table>". Drivers using any approach
+like this should issue a warning if C<AutoCommit> is true because
+it is generally unsafe - another process may have modified the table
+between your insert and the select. For situations where you know
+it is safe, such as when you have locked the table, you can silence
+the warning by passing C<Warn> => 0 in \%attr.
+
+B<*> If no insert has been performed yet, or the last insert failed,
+then the value is implementation defined.
+
+Given all the caveats above, it's clear that this method must be
+used with care.
+
+The C<last_insert_id> method was added in DBI 1.38.
+
+=head3 C<selectrow_array>
+
+ @row_ary = $dbh->selectrow_array($statement);
+ @row_ary = $dbh->selectrow_array($statement, \%attr);
+ @row_ary = $dbh->selectrow_array($statement, \%attr, @bind_values);
+
+This utility method combines L</prepare>, L</execute> and
+L</fetchrow_array> into a single call. If called in a list context, it
+returns the first row of data from the statement. The C<$statement>
+parameter can be a previously prepared statement handle, in which case
+the C<prepare> is skipped.
+
+If any method fails, and L</RaiseError> is not set, C<selectrow_array>
+will return an empty list.
+
+If called in a scalar context for a statement handle that has more
+than one column, it is undefined whether the driver will return
+the value of the first column or the last. So don't do that.
+Also, in a scalar context, an C<undef> is returned if there are no
+more rows or if an error occurred. That C<undef> can't be distinguished
+from an C<undef> returned because the first field value was NULL.
+For these reasons you should exercise some caution if you use
+C<selectrow_array> in a scalar context, or just don't do that.
+
+
+=head3 C<selectrow_arrayref>
+
+ $ary_ref = $dbh->selectrow_arrayref($statement);
+ $ary_ref = $dbh->selectrow_arrayref($statement, \%attr);
+ $ary_ref = $dbh->selectrow_arrayref($statement, \%attr, @bind_values);
+
+This utility method combines L</prepare>, L</execute> and
+L</fetchrow_arrayref> into a single call. It returns the first row of
+data from the statement. The C<$statement> parameter can be a previously
+prepared statement handle, in which case the C<prepare> is skipped.
+
+If any method fails, and L</RaiseError> is not set, C<selectrow_array>
+will return undef.
+
+
+=head3 C<selectrow_hashref>
+
+ $hash_ref = $dbh->selectrow_hashref($statement);
+ $hash_ref = $dbh->selectrow_hashref($statement, \%attr);
+ $hash_ref = $dbh->selectrow_hashref($statement, \%attr, @bind_values);
+
+This utility method combines L</prepare>, L</execute> and
+L</fetchrow_hashref> into a single call. It returns the first row of
+data from the statement. The C<$statement> parameter can be a previously
+prepared statement handle, in which case the C<prepare> is skipped.
+
+If any method fails, and L</RaiseError> is not set, C<selectrow_hashref>
+will return undef.
+
+
+=head3 C<selectall_arrayref>
+
+ $ary_ref = $dbh->selectall_arrayref($statement);
+ $ary_ref = $dbh->selectall_arrayref($statement, \%attr);
+ $ary_ref = $dbh->selectall_arrayref($statement, \%attr, @bind_values);
+
+This utility method combines L</prepare>, L</execute> and
+L</fetchall_arrayref> into a single call. It returns a reference to an
+array containing a reference to an array (or hash, see below) for each row of
+data fetched.
+
+The C<$statement> parameter can be a previously prepared statement handle,
+in which case the C<prepare> is skipped. This is recommended if the
+statement is going to be executed many times.
+
+If L</RaiseError> is not set and any method except C<fetchall_arrayref>
+fails then C<selectall_arrayref> will return C<undef>; if
+C<fetchall_arrayref> fails then it will return with whatever data
+has been fetched thus far. You should check C<$sth-E<gt>err>
+afterwards (or use the C<RaiseError> attribute) to discover if the data is
+complete or was truncated due to an error.
+
+The L</fetchall_arrayref> method called by C<selectall_arrayref>
+supports a $max_rows parameter. You can specify a value for $max_rows
+by including a 'C<MaxRows>' attribute in \%attr. In which case finish()
+is called for you after fetchall_arrayref() returns.
+
+The L</fetchall_arrayref> method called by C<selectall_arrayref>
+also supports a $slice parameter. You can specify a value for $slice by
+including a 'C<Slice>' or 'C<Columns>' attribute in \%attr. The only
+difference between the two is that if C<Slice> is not defined and
+C<Columns> is an array ref, then the array is assumed to contain column
+index values (which count from 1), rather than perl array index values.
+In which case the array is copied and each value decremented before
+passing to C</fetchall_arrayref>.
+
+You may often want to fetch an array of rows where each row is stored as a
+hash. That can be done simple using:
+
+ my $emps = $dbh->selectall_arrayref(
+ "SELECT ename FROM emp ORDER BY ename",
+ { Slice => {} }
+ );
+ foreach my $emp ( @$emps ) {
+ print "Employee: $emp->{ename}\n";
+ }
+
+Or, to fetch into an array instead of an array ref:
+
+ @result = @{ $dbh->selectall_arrayref($sql, { Slice => {} }) };
+
+See L</fetchall_arrayref> method for more details.
+
+=head3 C<selectall_hashref>
+
+ $hash_ref = $dbh->selectall_hashref($statement, $key_field);
+ $hash_ref = $dbh->selectall_hashref($statement, $key_field, \%attr);
+ $hash_ref = $dbh->selectall_hashref($statement, $key_field, \%attr, @bind_values);
+
+This utility method combines L</prepare>, L</execute> and
+L</fetchall_hashref> into a single call. It returns a reference to a
+hash containing one entry, at most, for each row, as returned by fetchall_hashref().
+
+The C<$statement> parameter can be a previously prepared statement handle,
+in which case the C<prepare> is skipped. This is recommended if the
+statement is going to be executed many times.
+
+The C<$key_field> parameter defines which column, or columns, are used as keys
+in the returned hash. It can either be the name of a single field, or a
+reference to an array containing multiple field names. Using multiple names
+yields a tree of nested hashes.
+
+If a row has the same key as an earlier row then it replaces the earlier row.
+
+If any method except C<fetchrow_hashref> fails, and L</RaiseError> is not set,
+C<selectall_hashref> will return C<undef>. If C<fetchrow_hashref> fails and
+L</RaiseError> is not set, then it will return with whatever data it
+has fetched thus far. $DBI::err should be checked to catch that.
+
+See fetchall_hashref() for more details.
+
+=head3 C<selectcol_arrayref>
+
+ $ary_ref = $dbh->selectcol_arrayref($statement);
+ $ary_ref = $dbh->selectcol_arrayref($statement, \%attr);
+ $ary_ref = $dbh->selectcol_arrayref($statement, \%attr, @bind_values);
+
+This utility method combines L</prepare>, L</execute>, and fetching one
+column from all the rows, into a single call. It returns a reference to
+an array containing the values of the first column from each row.
+
+The C<$statement> parameter can be a previously prepared statement handle,
+in which case the C<prepare> is skipped. This is recommended if the
+statement is going to be executed many times.
+
+If any method except C<fetch> fails, and L</RaiseError> is not set,
+C<selectcol_arrayref> will return C<undef>. If C<fetch> fails and
+L</RaiseError> is not set, then it will return with whatever data it
+has fetched thus far. $DBI::err should be checked to catch that.
+
+The C<selectcol_arrayref> method defaults to pushing a single column
+value (the first) from each row into the result array. However, it can
+also push another column, or even multiple columns per row, into the
+result array. This behaviour can be specified via a 'C<Columns>'
+attribute which must be a ref to an array containing the column number
+or numbers to use. For example:
+
+ # get array of id and name pairs:
+ my $ary_ref = $dbh->selectcol_arrayref("select id, name from table", { Columns=>[1,2] });
+ my %hash = @$ary_ref; # build hash from key-value pairs so $hash{$id} => name
+
+You can specify a maximum number of rows to fetch by including a
+'C<MaxRows>' attribute in \%attr.
+
+=head3 C<prepare>
+
+ $sth = $dbh->prepare($statement) or die $dbh->errstr;
+ $sth = $dbh->prepare($statement, \%attr) or die $dbh->errstr;
+
+Prepares a statement for later execution by the database
+engine and returns a reference to a statement handle object.
+
+The returned statement handle can be used to get attributes of the
+statement and invoke the L</execute> method. See L</Statement Handle Methods>.
+
+Drivers for engines without the concept of preparing a
+statement will typically just store the statement in the returned
+handle and process it when C<$sth-E<gt>execute> is called. Such drivers are
+unlikely to give much useful information about the
+statement, such as C<$sth-E<gt>{NUM_OF_FIELDS}>, until after C<$sth-E<gt>execute>
+has been called. Portable applications should take this into account.
+
+In general, DBI drivers do not parse the contents of the statement
+(other than simply counting any L</Placeholders>). The statement is
+passed directly to the database engine, sometimes known as pass-thru
+mode. This has advantages and disadvantages. On the plus side, you can
+access all the functionality of the engine being used. On the downside,
+you're limited if you're using a simple engine, and you need to take extra care if
+writing applications intended to be portable between engines.
+
+Portable applications should not assume that a new statement can be
+prepared and/or executed while still fetching results from a previous
+statement.
+
+Some command-line SQL tools use statement terminators, like a semicolon,
+to indicate the end of a statement. Such terminators should not normally
+be used with the DBI.
+
+
+=head3 C<prepare_cached>
+
+ $sth = $dbh->prepare_cached($statement)
+ $sth = $dbh->prepare_cached($statement, \%attr)
+ $sth = $dbh->prepare_cached($statement, \%attr, $if_active)
+
+Like L</prepare> except that the statement handle returned will be
+stored in a hash associated with the C<$dbh>. If another call is made to
+C<prepare_cached> with the same C<$statement> and C<%attr> parameter values,
+then the corresponding cached C<$sth> will be returned without contacting the
+database server.
+
+The C<$if_active> parameter lets you adjust the behaviour if an
+already cached statement handle is still Active. There are several
+alternatives:
+
+=over 4
+
+=item B<0>: A warning will be generated, and finish() will be called on
+the statement handle before it is returned. This is the default
+behaviour if $if_active is not passed.
+
+=item B<1>: finish() will be called on the statement handle, but the
+warning is suppressed.
+
+=item B<2>: Disables any checking.
+
+=item B<3>: The existing active statement handle will be removed from the
+cache and a new statement handle prepared and cached in its place.
+This is the safest option because it doesn't affect the state of the
+old handle, it just removes it from the cache. [Added in DBI 1.40]
+
+=back
+
+Here are some examples of C<prepare_cached>:
+
+ sub insert_hash {
+ my ($table, $field_values) = @_;
+ # sort to keep field order, and thus sql, stable for prepare_cached
+ my @fields = sort keys %$field_values;
+ my @values = @{$field_values}{@fields};
+ my $sql = sprintf "insert into %s (%s) values (%s)",
+ $table, join(",", @fields), join(",", ("?")x@fields);
+ my $sth = $dbh->prepare_cached($sql);
+ return $sth->execute(@values);
+ }
+
+ sub search_hash {
+ my ($table, $field_values) = @_;
+ # sort to keep field order, and thus sql, stable for prepare_cached
+ my @fields = sort keys %$field_values;
+ my @values = @{$field_values}{@fields};
+ my $qualifier = "";
+ $qualifier = "where ".join(" and ", map { "$_=?" } @fields) if @fields;
+ $sth = $dbh->prepare_cached("SELECT * FROM $table $qualifier");
+ return $dbh->selectall_arrayref($sth, {}, @values);
+ }
+
+I<Caveat emptor:> This caching can be useful in some applications,
+but it can also cause problems and should be used with care. Here
+is a contrived case where caching would cause a significant problem:
+
+ my $sth = $dbh->prepare_cached('SELECT * FROM foo WHERE bar=?');
+ $sth->execute(...);
+ while (my $data = $sth->fetchrow_hashref) {
+
+ # later, in some other code called within the loop...
+ my $sth2 = $dbh->prepare_cached('SELECT * FROM foo WHERE bar=?');
+ $sth2->execute(...);
+ while (my $data2 = $sth2->fetchrow_arrayref) {
+ do_stuff(...);
+ }
+ }
+
+In this example, since both handles are preparing the exact same statement,
+C<$sth2> will not be its own statement handle, but a duplicate of C<$sth>
+returned from the cache. The results will certainly not be what you expect.
+Typically the inner fetch loop will work normally, fetching all
+the records and terminating when there are no more, but now that $sth
+is the same as $sth2 the outer fetch loop will also terminate.
+
+You'll know if you run into this problem because prepare_cached()
+will generate a warning by default (when $if_active is false).
+
+The cache used by prepare_cached() is keyed by both the statement
+and any attributes so you can also avoid this issue by doing something
+like:
+
+ $sth = $dbh->prepare_cached("...", { dbi_dummy => __FILE__.__LINE__ });
+
+which will ensure that prepare_cached only returns statements cached
+by that line of code in that source file.
+
+If you'd like the cache to managed intelligently, you can tie the
+hashref returned by C<CachedKids> to an appropriate caching module,
+such as L<Tie::Cache::LRU>:
+
+ my $cache;
+ tie %$cache, 'Tie::Cache::LRU', 500;
+ $dbh->{CachedKids} = $cache;
+
+=head3 C<commit>
+
+ $rc = $dbh->commit or die $dbh->errstr;
+
+Commit (make permanent) the most recent series of database changes
+if the database supports transactions and AutoCommit is off.
+
+If C<AutoCommit> is on, then calling
+C<commit> will issue a "commit ineffective with AutoCommit" warning.
+
+See also L</Transactions> in the L</FURTHER INFORMATION> section below.
+
+=head3 C<rollback>
+
+ $rc = $dbh->rollback or die $dbh->errstr;
+
+Rollback (undo) the most recent series of uncommitted database
+changes if the database supports transactions and AutoCommit is off.
+
+If C<AutoCommit> is on, then calling
+C<rollback> will issue a "rollback ineffective with AutoCommit" warning.
+
+See also L</Transactions> in the L</FURTHER INFORMATION> section below.
+
+=head3 C<begin_work>
+
+ $rc = $dbh->begin_work or die $dbh->errstr;
+
+Enable transactions (by turning C<AutoCommit> off) until the next call
+to C<commit> or C<rollback>. After the next C<commit> or C<rollback>,
+C<AutoCommit> will automatically be turned on again.
+
+If C<AutoCommit> is already off when C<begin_work> is called then
+it does nothing except return an error. If the driver does not support
+transactions then when C<begin_work> attempts to set C<AutoCommit> off
+the driver will trigger a fatal error.
+
+See also L</Transactions> in the L</FURTHER INFORMATION> section below.
+
+
+=head3 C<disconnect>
+
+ $rc = $dbh->disconnect or warn $dbh->errstr;
+
+Disconnects the database from the database handle. C<disconnect> is typically only used
+before exiting the program. The handle is of little use after disconnecting.
+
+The transaction behaviour of the C<disconnect> method is, sadly,
+undefined. Some database systems (such as Oracle and Ingres) will
+automatically commit any outstanding changes, but others (such as
+Informix) will rollback any outstanding changes. Applications not
+using C<AutoCommit> should explicitly call C<commit> or C<rollback> before
+calling C<disconnect>.
+
+The database is automatically disconnected by the C<DESTROY> method if
+still connected when there are no longer any references to the handle.
+The C<DESTROY> method for each driver should implicitly call C<rollback> to
+undo any uncommitted changes. This is vital behaviour to ensure that
+incomplete transactions don't get committed simply because Perl calls
+C<DESTROY> on every object before exiting. Also, do not rely on the order
+of object destruction during "global destruction", as it is undefined.
+
+Generally, if you want your changes to be committed or rolled back when
+you disconnect, then you should explicitly call L</commit> or L</rollback>
+before disconnecting.
+
+If you disconnect from a database while you still have active
+statement handles (e.g., SELECT statement handles that may have
+more data to fetch), you will get a warning. The warning may indicate
+that a fetch loop terminated early, perhaps due to an uncaught error.
+To avoid the warning call the C<finish> method on the active handles.
+
+
+=head3 C<ping>
+
+ $rc = $dbh->ping;
+
+Attempts to determine, in a reasonably efficient way, if the database
+server is still running and the connection to it is still working.
+Individual drivers should implement this function in the most suitable
+manner for their database engine.
+
+The current I<default> implementation always returns true without
+actually doing anything. Actually, it returns "C<0 but true>" which is
+true but zero. That way you can tell if the return value is genuine or
+just the default. Drivers should override this method with one that
+does the right thing for their type of database.
+
+Few applications would have direct use for this method. See the specialized
+Apache::DBI module for one example usage.
+
+
+=head3 C<get_info>
+
+ $value = $dbh->get_info( $info_type );
+
+Returns information about the implementation, i.e. driver and data
+source capabilities, restrictions etc. It returns C<undef> for
+unknown or unimplemented information types. For example:
+
+ $database_version = $dbh->get_info( 18 ); # SQL_DBMS_VER
+ $max_select_tables = $dbh->get_info( 106 ); # SQL_MAXIMUM_TABLES_IN_SELECT
+
+See L</"Standards Reference Information"> for more detailed information
+about the information types and their meanings and possible return values.
+
+The DBI::Const::GetInfoType module exports a %GetInfoType hash that
+can be used to map info type names to numbers. For example:
+
+ $database_version = $dbh->get_info( $GetInfoType{SQL_DBMS_VER} );
+
+The names are a merging of the ANSI and ODBC standards (which differ
+in some cases). See L<DBI::Const::GetInfoType> for more details.
+
+Because some DBI methods make use of get_info(), drivers are strongly
+encouraged to support I<at least> the following very minimal set
+of information types to ensure the DBI itself works properly:
+
+ Type Name Example A Example B
+ ---- -------------------------- ------------ ----------------
+ 17 SQL_DBMS_NAME 'ACCESS' 'Oracle'
+ 18 SQL_DBMS_VER '03.50.0000' '08.01.0721 ...'
+ 29 SQL_IDENTIFIER_QUOTE_CHAR '`' '"'
+ 41 SQL_CATALOG_NAME_SEPARATOR '.' '@'
+ 114 SQL_CATALOG_LOCATION 1 2
+
+=head3 C<table_info>
+
+ $sth = $dbh->table_info( $catalog, $schema, $table, $type );
+ $sth = $dbh->table_info( $catalog, $schema, $table, $type, \%attr );
+
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
+Returns an active statement handle that can be used to fetch
+information about tables and views that exist in the database.
+
+The arguments $catalog, $schema and $table may accept search patterns
+according to the database/driver, for example: $table = '%FOO%';
+Remember that the underscore character ('C<_>') is a search pattern
+that means match any character, so 'FOO_%' is the same as 'FOO%'
+and 'FOO_BAR%' will match names like 'FOO1BAR'.
+
+The value of $type is a comma-separated list of one or more types of
+tables to be returned in the result set. Each value may optionally be
+quoted, e.g.:
+
+ $type = "TABLE";
+ $type = "'TABLE','VIEW'";
+
+In addition the following special cases may also be supported by some drivers:
+
+=over 4
+
+=item *
+If the value of $catalog is '%' and $schema and $table name
+are empty strings, the result set contains a list of catalog names.
+For example:
+
+ $sth = $dbh->table_info('%', '', '');
+
+=item *
+If the value of $schema is '%' and $catalog and $table are empty
+strings, the result set contains a list of schema names.
+
+=item *
+If the value of $type is '%' and $catalog, $schema, and $table are all
+empty strings, the result set contains a list of table types.
+
+=back
+
+If your driver doesn't support one or more of the selection filter
+parameters then you may get back more than you asked for and can
+do the filtering yourself.
+
+This method can be expensive, and can return a large amount of data.
+(For example, small Oracle installation returns over 2000 rows.)
+So it's a good idea to use the filters to limit the data as much as possible.
+
+The statement handle returned has at least the following fields in the
+order show below. Other fields, after these, may also be present.
+
+B<TABLE_CAT>: Table catalog identifier. This field is NULL (C<undef>) if not
+applicable to the data source, which is usually the case. This field
+is empty if not applicable to the table.
+
+B<TABLE_SCHEM>: The name of the schema containing the TABLE_NAME value.
+This field is NULL (C<undef>) if not applicable to data source, and
+empty if not applicable to the table.
+
+B<TABLE_NAME>: Name of the table (or view, synonym, etc).
+
+B<TABLE_TYPE>: One of the following: "TABLE", "VIEW", "SYSTEM TABLE",
+"GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM" or a type
+identifier that is specific to the data
+source.
+
+B<REMARKS>: A description of the table. May be NULL (C<undef>).
+
+Note that C<table_info> might not return records for all tables.
+Applications can use any valid table regardless of whether it's
+returned by C<table_info>.
+
+See also L</tables>, L</"Catalog Methods"> and
+L</"Standards Reference Information">.
+
+=head3 C<column_info>
+
+ $sth = $dbh->column_info( $catalog, $schema, $table, $column );
+
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
+Returns an active statement handle that can be used to fetch
+information about columns in specified tables.
+
+The arguments $schema, $table and $column may accept search patterns
+according to the database/driver, for example: $table = '%FOO%';
+
+Note: The support for the selection criteria is driver specific. If the
+driver doesn't support one or more of them then you may get back more
+than you asked for and can do the filtering yourself.
+
+Note: If your driver does not support column_info an undef is
+returned. This is distinct from asking for something which does not
+exist in a driver which supports column_info as a valid statement
+handle to an empty result-set will be returned in this case.
+
+If the arguments don't match any tables then you'll still get a statement
+handle, it'll just return no rows.
+
+The statement handle returned has at least the following fields in the
+order shown below. Other fields, after these, may also be present.
+
+B<TABLE_CAT>: The catalog identifier.
+This field is NULL (C<undef>) if not applicable to the data source,
+which is often the case. This field is empty if not applicable to the
+table.
+
+B<TABLE_SCHEM>: The schema identifier.
+This field is NULL (C<undef>) if not applicable to the data source,
+and empty if not applicable to the table.
+
+B<TABLE_NAME>: The table identifier.
+Note: A driver may provide column metadata not only for base tables, but
+also for derived objects like SYNONYMS etc.
+
+B<COLUMN_NAME>: The column identifier.
+
+B<DATA_TYPE>: The concise data type code.
+
+B<TYPE_NAME>: A data source dependent data type name.
+
+B<COLUMN_SIZE>: The column size.
+This is the maximum length in characters for character data types,
+the number of digits or bits for numeric data types or the length
+in the representation of temporal types.
+See the relevant specifications for detailed information.
+
+B<BUFFER_LENGTH>: The length in bytes of transferred data.
+
+B<DECIMAL_DIGITS>: The total number of significant digits to the right of
+the decimal point.
+
+B<NUM_PREC_RADIX>: The radix for numeric precision.
+The value is 10 or 2 for numeric data types and NULL (C<undef>) if not
+applicable.
+
+B<NULLABLE>: Indicates if a column can accept NULLs.
+The following values are defined:
+
+ SQL_NO_NULLS 0
+ SQL_NULLABLE 1
+ SQL_NULLABLE_UNKNOWN 2
+
+B<REMARKS>: A description of the column.
+
+B<COLUMN_DEF>: The default value of the column, in a format that can be used
+directly in an SQL statement.
+
+Note that this may be an expression and not simply the text used for the
+default value in the original CREATE TABLE statement. For example, given:
+
+ col1 char(30) default current_user -- a 'function'
+ col2 char(30) default 'string' -- a string literal
+
+where "current_user" is the name of a function, the corresponding C<COLUMN_DEF>
+values would be:
+
+ Database col1 col2
+ -------- ---- ----
+ Oracle: current_user 'string'
+ Postgres: "current_user"() 'string'::text
+ MS SQL: (user_name()) ('string')
+
+B<SQL_DATA_TYPE>: The SQL data type.
+
+B<SQL_DATETIME_SUB>: The subtype code for datetime and interval data types.
+
+B<CHAR_OCTET_LENGTH>: The maximum length in bytes of a character or binary
+data type column.
+
+B<ORDINAL_POSITION>: The column sequence number (starting with 1).
+
+B<IS_NULLABLE>: Indicates if the column can accept NULLs.
+Possible values are: 'NO', 'YES' and ''.
+
+SQL/CLI defines the following additional columns:
+
+ CHAR_SET_CAT
+ CHAR_SET_SCHEM
+ CHAR_SET_NAME
+ COLLATION_CAT
+ COLLATION_SCHEM
+ COLLATION_NAME
+ UDT_CAT
+ UDT_SCHEM
+ UDT_NAME
+ DOMAIN_CAT
+ DOMAIN_SCHEM
+ DOMAIN_NAME
+ SCOPE_CAT
+ SCOPE_SCHEM
+ SCOPE_NAME
+ MAX_CARDINALITY
+ DTD_IDENTIFIER
+ IS_SELF_REF
+
+Drivers capable of supplying any of those values should do so in
+the corresponding column and supply undef values for the others.
+
+Drivers wishing to provide extra database/driver specific information
+should do so in extra columns beyond all those listed above, and
+use lowercase field names with the driver-specific prefix (i.e.,
+'ora_...'). Applications accessing such fields should do so by name
+and not by column number.
+
+The result set is ordered by TABLE_CAT, TABLE_SCHEM, TABLE_NAME
+and ORDINAL_POSITION.
+
+Note: There is some overlap with statement handle attributes (in perl) and
+SQLDescribeCol (in ODBC). However, SQLColumns provides more metadata.
+
+See also L</"Catalog Methods"> and L</"Standards Reference Information">.
+
+=head3 C<primary_key_info>
+
+ $sth = $dbh->primary_key_info( $catalog, $schema, $table );
+
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
+Returns an active statement handle that can be used to fetch information
+about columns that make up the primary key for a table.
+The arguments don't accept search patterns (unlike table_info()).
+
+The statement handle will return one row per column, ordered by
+TABLE_CAT, TABLE_SCHEM, TABLE_NAME, and KEY_SEQ.
+If there is no primary key then the statement handle will fetch no rows.
+
+Note: The support for the selection criteria, such as $catalog, is
+driver specific. If the driver doesn't support catalogs and/or
+schemas, it may ignore these criteria.
+
+The statement handle returned has at least the following fields in the
+order shown below. Other fields, after these, may also be present.
+
+B<TABLE_CAT>: The catalog identifier.
+This field is NULL (C<undef>) if not applicable to the data source,
+which is often the case. This field is empty if not applicable to the
+table.
+
+B<TABLE_SCHEM>: The schema identifier.
+This field is NULL (C<undef>) if not applicable to the data source,
+and empty if not applicable to the table.
+
+B<TABLE_NAME>: The table identifier.
+
+B<COLUMN_NAME>: The column identifier.
+
+B<KEY_SEQ>: The column sequence number (starting with 1).
+Note: This field is named B<ORDINAL_POSITION> in SQL/CLI.
+
+B<PK_NAME>: The primary key constraint identifier.
+This field is NULL (C<undef>) if not applicable to the data source.
+
+See also L</"Catalog Methods"> and L</"Standards Reference Information">.
+
+=head3 C<primary_key>
+
+ @key_column_names = $dbh->primary_key( $catalog, $schema, $table );
+
+Simple interface to the primary_key_info() method. Returns a list of
+the column names that comprise the primary key of the specified table.
+The list is in primary key column sequence order.
+If there is no primary key then an empty list is returned.
+
+=head3 C<foreign_key_info>
+
+ $sth = $dbh->foreign_key_info( $pk_catalog, $pk_schema, $pk_table
+ , $fk_catalog, $fk_schema, $fk_table );
+
+ $sth = $dbh->foreign_key_info( $pk_catalog, $pk_schema, $pk_table
+ , $fk_catalog, $fk_schema, $fk_table
+ , \%attr );
+
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
+Returns an active statement handle that can be used to fetch information
+about foreign keys in and/or referencing the specified table(s).
+The arguments don't accept search patterns (unlike table_info()).
+
+C<$pk_catalog>, C<$pk_schema>, C<$pk_table>
+identify the primary (unique) key table (B<PKT>).
+
+C<$fk_catalog>, C<$fk_schema>, C<$fk_table>
+identify the foreign key table (B<FKT>).
+
+If both B<PKT> and B<FKT> are given, the function returns the foreign key, if
+any, in table B<FKT> that refers to the primary (unique) key of table B<PKT>.
+(Note: In SQL/CLI, the result is implementation-defined.)
+
+If only B<PKT> is given, then the result set contains the primary key
+of that table and all foreign keys that refer to it.
+
+If only B<FKT> is given, then the result set contains all foreign keys
+in that table and the primary keys to which they refer.
+(Note: In SQL/CLI, the result includes unique keys too.)
+
+For example:
+
+ $sth = $dbh->foreign_key_info( undef, $user, 'master');
+ $sth = $dbh->foreign_key_info( undef, undef, undef , undef, $user, 'detail');
+ $sth = $dbh->foreign_key_info( undef, $user, 'master', undef, $user, 'detail');
+
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
+Note: The support for the selection criteria, such as C<$catalog>, is
+driver specific. If the driver doesn't support catalogs and/or
+schemas, it may ignore these criteria.
+
+The statement handle returned has the following fields in the order shown below.
+Because ODBC never includes unique keys, they define different columns in the
+result set than SQL/CLI. SQL/CLI column names are shown in parentheses.
+
+B<PKTABLE_CAT ( UK_TABLE_CAT )>:
+The primary (unique) key table catalog identifier.
+This field is NULL (C<undef>) if not applicable to the data source,
+which is often the case. This field is empty if not applicable to the
+table.
+
+B<PKTABLE_SCHEM ( UK_TABLE_SCHEM )>:
+The primary (unique) key table schema identifier.
+This field is NULL (C<undef>) if not applicable to the data source,
+and empty if not applicable to the table.
+
+B<PKTABLE_NAME ( UK_TABLE_NAME )>:
+The primary (unique) key table identifier.
+
+B<PKCOLUMN_NAME (UK_COLUMN_NAME )>:
+The primary (unique) key column identifier.
+
+B<FKTABLE_CAT ( FK_TABLE_CAT )>:
+The foreign key table catalog identifier.
+This field is NULL (C<undef>) if not applicable to the data source,
+which is often the case. This field is empty if not applicable to the
+table.
+
+B<FKTABLE_SCHEM ( FK_TABLE_SCHEM )>:
+The foreign key table schema identifier.
+This field is NULL (C<undef>) if not applicable to the data source,
+and empty if not applicable to the table.
+
+B<FKTABLE_NAME ( FK_TABLE_NAME )>:
+The foreign key table identifier.
+
+B<FKCOLUMN_NAME ( FK_COLUMN_NAME )>:
+The foreign key column identifier.
+
+B<KEY_SEQ ( ORDINAL_POSITION )>:
+The column sequence number (starting with 1).
+
+B<UPDATE_RULE ( UPDATE_RULE )>:
+The referential action for the UPDATE rule.
+The following codes are defined:
+
+ CASCADE 0
+ RESTRICT 1
+ SET NULL 2
+ NO ACTION 3
+ SET DEFAULT 4
+
+B<DELETE_RULE ( DELETE_RULE )>:
+The referential action for the DELETE rule.
+The codes are the same as for UPDATE_RULE.
+
+B<FK_NAME ( FK_NAME )>:
+The foreign key name.
+
+B<PK_NAME ( UK_NAME )>:
+The primary (unique) key name.
+
+B<DEFERRABILITY ( DEFERABILITY )>:
+The deferrability of the foreign key constraint.
+The following codes are defined:
+
+ INITIALLY DEFERRED 5
+ INITIALLY IMMEDIATE 6
+ NOT DEFERRABLE 7
+
+B< ( UNIQUE_OR_PRIMARY )>:
+This column is necessary if a driver includes all candidate (i.e. primary and
+alternate) keys in the result set (as specified by SQL/CLI).
+The value of this column is UNIQUE if the foreign key references an alternate
+key and PRIMARY if the foreign key references a primary key, or it
+may be undefined if the driver doesn't have access to the information.
+
+See also L</"Catalog Methods"> and L</"Standards Reference Information">.
+
+=head3 C<statistics_info>
+
+B<Warning:> This method is experimental and may change.
+
+ $sth = $dbh->statistics_info( $catalog, $schema, $table, $unique_only, $quick );
+
+ # then $sth->fetchall_arrayref or $sth->fetchall_hashref etc
+
+Returns an active statement handle that can be used to fetch statistical
+information about a table and its indexes.
+
+The arguments don't accept search patterns (unlike L</table_info>).
+
+If the boolean argument $unique_only is true, only UNIQUE indexes will be
+returned in the result set, otherwise all indexes will be returned.
+
+If the boolean argument $quick is set, the actual statistical information
+columns (CARDINALITY and PAGES) will only be returned if they are readily
+available from the server, and might not be current. Some databases may
+return stale statistics or no statistics at all with this flag set.
+
+The statement handle will return at most one row per column name per index,
+plus at most one row for the entire table itself, ordered by NON_UNIQUE, TYPE,
+INDEX_QUALIFIER, INDEX_NAME, and ORDINAL_POSITION.
+
+Note: The support for the selection criteria, such as $catalog, is
+driver specific. If the driver doesn't support catalogs and/or
+schemas, it may ignore these criteria.
+
+The statement handle returned has at least the following fields in the
+order shown below. Other fields, after these, may also be present.
+
+B<TABLE_CAT>: The catalog identifier.
+This field is NULL (C<undef>) if not applicable to the data source,
+which is often the case. This field is empty if not applicable to the
+table.
+
+B<TABLE_SCHEM>: The schema identifier.
+This field is NULL (C<undef>) if not applicable to the data source,
+and empty if not applicable to the table.
+
+B<TABLE_NAME>: The table identifier.
+
+B<NON_UNIQUE>: Unique index indicator.
+Returns 0 for unique indexes, 1 for non-unique indexes
+
+B<INDEX_QUALIFIER>: Index qualifier identifier.
+The identifier that is used to qualify the index name when doing a
+C<DROP INDEX>; NULL (C<undef>) is returned if an index qualifier is not
+supported by the data source.
+If a non-NULL (defined) value is returned in this column, it must be used
+to qualify the index name on a C<DROP INDEX> statement; otherwise,
+the TABLE_SCHEM should be used to qualify the index name.
+
+B<INDEX_NAME>: The index identifier.
+
+B<TYPE>: The type of information being returned. Can be any of the
+following values: 'table', 'btree', 'clustered', 'content', 'hashed',
+or 'other'.
+
+In the case that this field is 'table', all fields
+other than TABLE_CAT, TABLE_SCHEM, TABLE_NAME, TYPE,
+CARDINALITY, and PAGES will be NULL (C<undef>).
+
+B<ORDINAL_POSITION>: Column sequence number (starting with 1).
+
+B<COLUMN_NAME>: The column identifier.
+
+B<ASC_OR_DESC>: Column sort sequence.
+C<A> for Ascending, C<D> for Descending, or NULL (C<undef>) if
+not supported for this index.
+
+B<CARDINALITY>: Cardinality of the table or index.
+For indexes, this is the number of unique values in the index.
+For tables, this is the number of rows in the table.
+If not supported, the value will be NULL (C<undef>).
+
+B<PAGES>: Number of storage pages used by this table or index.
+If not supported, the value will be NULL (C<undef>).
+
+B<FILTER_CONDITION>: The index filter condition as a string.
+If the index is not a filtered index, or it cannot be determined
+whether the index is a filtered index, this value is NULL (C<undef>).
+If the index is a filtered index, but the filter condition
+cannot be determined, this value is the empty string C<''>.
+Otherwise it will be the literal filter condition as a string,
+such as C<SALARY <= 4500>.
+
+See also L</"Catalog Methods"> and L</"Standards Reference Information">.
+
+=head3 C<tables>
+
+ @names = $dbh->tables( $catalog, $schema, $table, $type );
+ @names = $dbh->tables; # deprecated
+
+Simple interface to table_info(). Returns a list of matching
+table names, possibly including a catalog/schema prefix.
+
+See L</table_info> for a description of the parameters.
+
+If C<$dbh-E<gt>get_info(29)> returns true (29 is SQL_IDENTIFIER_QUOTE_CHAR)
+then the table names are constructed and quoted by L</quote_identifier>
+to ensure they are usable even if they contain whitespace or reserved
+words etc. This means that the table names returned will include
+quote characters.
+
+=head3 C<type_info_all>
+
+ $type_info_all = $dbh->type_info_all;
+
+Returns a reference to an array which holds information about each data
+type variant supported by the database and driver. The array and its
+contents should be treated as read-only.
+
+The first item is a reference to an 'index' hash of C<Name =>E<gt> C<Index> pairs.
+The items following that are references to arrays, one per supported data
+type variant. The leading index hash defines the names and order of the
+fields within the arrays that follow it.
+For example:
+
+ $type_info_all = [
+ { TYPE_NAME => 0,
+ DATA_TYPE => 1,
+ COLUMN_SIZE => 2, # was PRECISION originally
+ LITERAL_PREFIX => 3,
+ LITERAL_SUFFIX => 4,
+ CREATE_PARAMS => 5,
+ NULLABLE => 6,
+ CASE_SENSITIVE => 7,
+ SEARCHABLE => 8,
+ UNSIGNED_ATTRIBUTE=> 9,
+ FIXED_PREC_SCALE => 10, # was MONEY originally
+ AUTO_UNIQUE_VALUE => 11, # was AUTO_INCREMENT originally
+ LOCAL_TYPE_NAME => 12,
+ MINIMUM_SCALE => 13,
+ MAXIMUM_SCALE => 14,
+ SQL_DATA_TYPE => 15,
+ SQL_DATETIME_SUB => 16,
+ NUM_PREC_RADIX => 17,
+ INTERVAL_PRECISION=> 18,
+ },
+ [ 'VARCHAR', SQL_VARCHAR,
+ undef, "'","'", undef,0, 1,1,0,0,0,undef,1,255, undef
+ ],
+ [ 'INTEGER', SQL_INTEGER,
+ undef, "", "", undef,0, 0,1,0,0,0,undef,0, 0, 10
+ ],
+ ];
+
+More than one row may have the same value in the C<DATA_TYPE>
+field if there are different ways to spell the type name and/or there
+are variants of the type with different attributes (e.g., with and
+without C<AUTO_UNIQUE_VALUE> set, with and without C<UNSIGNED_ATTRIBUTE>, etc).
+
+The rows are ordered by C<DATA_TYPE> first and then by how closely each
+type maps to the corresponding ODBC SQL data type, closest first.
+
+The meaning of the fields is described in the documentation for
+the L</type_info> method.
+
+An 'index' hash is provided so you don't need to rely on index
+values defined above. However, using DBD::ODBC with some old ODBC
+drivers may return older names, shown as comments in the example above.
+Another issue with the index hash is that the lettercase of the
+keys is not defined. It is usually uppercase, as show here, but
+drivers may return names with any lettercase.
+
+Drivers are also free to return extra driver-specific columns of
+information - though it's recommended that they start at column
+index 50 to leave room for expansion of the DBI/ODBC specification.
+
+The type_info_all() method is not normally used directly.
+The L</type_info> method provides a more usable and useful interface
+to the data.
+
+=head3 C<type_info>
+
+ @type_info = $dbh->type_info($data_type);
+
+Returns a list of hash references holding information about one or more
+variants of $data_type. The list is ordered by C<DATA_TYPE> first and
+then by how closely each type maps to the corresponding ODBC SQL data
+type, closest first. If called in a scalar context then only the first
+(best) element is returned.
+
+If $data_type is undefined or C<SQL_ALL_TYPES>, then the list will
+contain hashes for all data type variants supported by the database and driver.
+
+If $data_type is an array reference then C<type_info> returns the
+information for the I<first> type in the array that has any matches.
+
+The keys of the hash follow the same letter case conventions as the
+rest of the DBI (see L</Naming Conventions and Name Space>). The
+following uppercase items should always exist, though may be undef:
+
+=over 4
+
+=item TYPE_NAME (string)
+
+Data type name for use in CREATE TABLE statements etc.
+
+=item DATA_TYPE (integer)
+
+SQL data type number.
+
+=item COLUMN_SIZE (integer)
+
+For numeric types, this is either the total number of digits (if the
+NUM_PREC_RADIX value is 10) or the total number of bits allowed in the
+column (if NUM_PREC_RADIX is 2).
+
+For string types, this is the maximum size of the string in characters.
+
+For date and interval types, this is the maximum number of characters
+needed to display the value.
+
+=item LITERAL_PREFIX (string)
+
+Characters used to prefix a literal. A typical prefix is "C<'>" for characters,
+or possibly "C<0x>" for binary values passed as hexadecimal. NULL (C<undef>) is
+returned for data types for which this is not applicable.
+
+
+=item LITERAL_SUFFIX (string)
+
+Characters used to suffix a literal. Typically "C<'>" for characters.
+NULL (C<undef>) is returned for data types where this is not applicable.
+
+=item CREATE_PARAMS (string)
+
+Parameter names for data type definition. For example, C<CREATE_PARAMS> for a
+C<DECIMAL> would be "C<precision,scale>" if the DECIMAL type should be
+declared as C<DECIMAL(>I<precision,scale>C<)> where I<precision> and I<scale>
+are integer values. For a C<VARCHAR> it would be "C<max length>".
+NULL (C<undef>) is returned for data types for which this is not applicable.
+
+=item NULLABLE (integer)
+
+Indicates whether the data type accepts a NULL value:
+C<0> or an empty string = no, C<1> = yes, C<2> = unknown.
+
+=item CASE_SENSITIVE (boolean)
+
+Indicates whether the data type is case sensitive in collations and
+comparisons.
+
+=item SEARCHABLE (integer)
+
+Indicates how the data type can be used in a WHERE clause, as
+follows:
+
+ 0 - Cannot be used in a WHERE clause
+ 1 - Only with a LIKE predicate
+ 2 - All comparison operators except LIKE
+ 3 - Can be used in a WHERE clause with any comparison operator
+
+=item UNSIGNED_ATTRIBUTE (boolean)
+
+Indicates whether the data type is unsigned. NULL (C<undef>) is returned
+for data types for which this is not applicable.
+
+=item FIXED_PREC_SCALE (boolean)
+
+Indicates whether the data type always has the same precision and scale
+(such as a money type). NULL (C<undef>) is returned for data types
+for which
+this is not applicable.
+
+=item AUTO_UNIQUE_VALUE (boolean)
+
+Indicates whether a column of this data type is automatically set to a
+unique value whenever a new row is inserted. NULL (C<undef>) is returned
+for data types for which this is not applicable.
+
+=item LOCAL_TYPE_NAME (string)
+
+Localized version of the C<TYPE_NAME> for use in dialog with users.
+NULL (C<undef>) is returned if a localized name is not available (in which
+case C<TYPE_NAME> should be used).
+
+=item MINIMUM_SCALE (integer)
+
+The minimum scale of the data type. If a data type has a fixed scale,
+then C<MAXIMUM_SCALE> holds the same value. NULL (C<undef>) is returned for
+data types for which this is not applicable.
+
+=item MAXIMUM_SCALE (integer)
+
+The maximum scale of the data type. If a data type has a fixed scale,
+then C<MINIMUM_SCALE> holds the same value. NULL (C<undef>) is returned for
+data types for which this is not applicable.
+
+=item SQL_DATA_TYPE (integer)
+
+This column is the same as the C<DATA_TYPE> column, except for interval
+and datetime data types. For interval and datetime data types, the
+C<SQL_DATA_TYPE> field will return C<SQL_INTERVAL> or C<SQL_DATETIME>, and the
+C<SQL_DATETIME_SUB> field below will return the subcode for the specific
+interval or datetime data type. If this field is NULL, then the driver
+does not support or report on interval or datetime subtypes.
+
+=item SQL_DATETIME_SUB (integer)
+
+For interval or datetime data types, where the C<SQL_DATA_TYPE>
+field above is C<SQL_INTERVAL> or C<SQL_DATETIME>, this field will
+hold the I<subcode> for the specific interval or datetime data type.
+Otherwise it will be NULL (C<undef>).
+
+Although not mentioned explicitly in the standards, it seems there
+is a simple relationship between these values:
+
+ DATA_TYPE == (10 * SQL_DATA_TYPE) + SQL_DATETIME_SUB
+
+=item NUM_PREC_RADIX (integer)
+
+The radix value of the data type. For approximate numeric types,
+C<NUM_PREC_RADIX>
+contains the value 2 and C<COLUMN_SIZE> holds the number of bits. For
+exact numeric types, C<NUM_PREC_RADIX> contains the value 10 and C<COLUMN_SIZE> holds
+the number of decimal digits. NULL (C<undef>) is returned either for data types
+for which this is not applicable or if the driver cannot report this information.
+
+=item INTERVAL_PRECISION (integer)
+
+The interval leading precision for interval types. NULL is returned
+either for data types for which this is not applicable or if the driver
+cannot report this information.
+
+=back
+
+For example, to find the type name for the fields in a select statement
+you can do:
+
+ @names = map { scalar $dbh->type_info($_)->{TYPE_NAME} } @{ $sth->{TYPE} }
+
+Since DBI and ODBC drivers vary in how they map their types into the
+ISO standard types you may need to search for more than one type.
+Here's an example looking for a usable type to store a date:
+
+ $my_date_type = $dbh->type_info( [ SQL_DATE, SQL_TIMESTAMP ] );
+
+Similarly, to more reliably find a type to store small integers, you could
+use a list starting with C<SQL_SMALLINT>, C<SQL_INTEGER>, C<SQL_DECIMAL>, etc.
+
+See also L</"Standards Reference Information">.
+
+
+=head3 C<quote>
+
+ $sql = $dbh->quote($value);
+ $sql = $dbh->quote($value, $data_type);
+
+Quote a string literal for use as a literal value in an SQL statement,
+by escaping any special characters (such as quotation marks)
+contained within the string and adding the required type of outer
+quotation marks.
+
+ $sql = sprintf "SELECT foo FROM bar WHERE baz = %s",
+ $dbh->quote("Don't");
+
+For most database types, at least those that conform to SQL standards, quote
+would return C<'Don''t'> (including the outer quotation marks). For others it
+may return something like C<'Don\'t'>
+
+An undefined C<$value> value will be returned as the string C<NULL> (without
+single quotation marks) to match how NULLs are represented in SQL.
+
+If C<$data_type> is supplied, it is used to try to determine the required
+quoting behaviour by using the information returned by L</type_info>.
+As a special case, the standard numeric types are optimized to return
+C<$value> without calling C<type_info>.
+
+Quote will probably I<not> be able to deal with all possible input
+(such as binary data or data containing newlines), and is not related in
+any way with escaping or quoting shell meta-characters.
+
+It is valid for the quote() method to return an SQL expression that
+evaluates to the desired string. For example:
+
+ $quoted = $dbh->quote("one\ntwo\0three")
+
+may return something like:
+
+ CONCAT('one', CHAR(12), 'two', CHAR(0), 'three')
+
+The quote() method should I<not> be used with L</"Placeholders and
+Bind Values">.
+
+=head3 C<quote_identifier>
+
+ $sql = $dbh->quote_identifier( $name );
+ $sql = $dbh->quote_identifier( $catalog, $schema, $table, \%attr );
+
+Quote an identifier (table name etc.) for use in an SQL statement,
+by escaping any special characters (such as double quotation marks)
+it contains and adding the required type of outer quotation marks.
+
+Undefined names are ignored and the remainder are quoted and then
+joined together, typically with a dot (C<.>) character. For example:
+
+ $id = $dbh->quote_identifier( undef, 'Her schema', 'My table' );
+
+would, for most database types, return C<"Her schema"."My table">
+(including all the double quotation marks).
+
+If three names are supplied then the first is assumed to be a
+catalog name and special rules may be applied based on what L</get_info>
+returns for SQL_CATALOG_NAME_SEPARATOR (41) and SQL_CATALOG_LOCATION (114).
+For example, for Oracle:
+
+ $id = $dbh->quote_identifier( 'link', 'schema', 'table' );
+
+would return C<"schema"."table"@"link">.
+
+=head3 C<take_imp_data>
+
+ $imp_data = $dbh->take_imp_data;
+
+Leaves the $dbh in an almost dead, zombie-like, state and returns
+a binary string of raw implementation data from the driver which
+describes the current database connection. Effectively it detaches
+the underlying database API connection data from the DBI handle.
+After calling take_imp_data(), all other methods except C<DESTROY>
+will generate a warning and return undef.
+
+Why would you want to do this? You don't, forget I even mentioned it.
+Unless, that is, you're implementing something advanced like a
+multi-threaded connection pool. See L<DBI::Pool>.
+
+The returned $imp_data can be passed as a C<dbi_imp_data> attribute
+to a later connect() call, even in a separate thread in the same
+process, where the driver can use it to 'adopt' the existing
+connection that the implementation data was taken from.
+
+Some things to keep in mind...
+
+B<*> the $imp_data holds the only reference to the underlying
+database API connection data. That connection is still 'live' and
+won't be cleaned up properly unless the $imp_data is used to create
+a new $dbh which is then allowed to disconnect() normally.
+
+B<*> using the same $imp_data to create more than one other new
+$dbh at a time may well lead to unpleasant problems. Don't do that.
+
+Any child statement handles are effectively destroyed when take_imp_data() is
+called.
+
+The C<take_imp_data> method was added in DBI 1.36 but wasn't useful till 1.49.
+
+
+=head2 Database Handle Attributes
+
+This section describes attributes specific to database handles.
+
+Changes to these database handle attributes do not affect any other
+existing or future database handles.
+
+Attempting to set or get the value of an unknown attribute generates a warning,
+except for private driver-specific attributes (which all have names
+starting with a lowercase letter).
+
+Example:
+
+ $h->{AutoCommit} = ...; # set/write
+ ... = $h->{AutoCommit}; # get/read
+
+=head3 C<AutoCommit>
+
+Type: boolean
+
+If true, then database changes cannot be rolled-back (undone). If false,
+then database changes automatically occur within a "transaction", which
+must either be committed or rolled back using the C<commit> or C<rollback>
+methods.
+
+Drivers should always default to C<AutoCommit> mode (an unfortunate
+choice largely forced on the DBI by ODBC and JDBC conventions.)
+
+Attempting to set C<AutoCommit> to an unsupported value is a fatal error.
+This is an important feature of the DBI. Applications that need
+full transaction behaviour can set C<$dbh-E<gt>{AutoCommit} = 0> (or
+set C<AutoCommit> to 0 via L</connect>)
+without having to check that the value was assigned successfully.
+
+For the purposes of this description, we can divide databases into three
+categories:
+
+ Databases which don't support transactions at all.
+ Databases in which a transaction is always active.
+ Databases in which a transaction must be explicitly started (C<'BEGIN WORK'>).
+
+B<* Databases which don't support transactions at all>
+
+For these databases, attempting to turn C<AutoCommit> off is a fatal error.
+C<commit> and C<rollback> both issue warnings about being ineffective while
+C<AutoCommit> is in effect.
+
+B<* Databases in which a transaction is always active>
+
+These are typically mainstream commercial relational databases with
+"ANSI standard" transaction behaviour.
+If C<AutoCommit> is off, then changes to the database won't have any
+lasting effect unless L</commit> is called (but see also
+L</disconnect>). If L</rollback> is called then any changes since the
+last commit are undone.
+
+If C<AutoCommit> is on, then the effect is the same as if the DBI
+called C<commit> automatically after every successful database
+operation. So calling C<commit> or C<rollback> explicitly while
+C<AutoCommit> is on would be ineffective because the changes would
+have already been committed.
+
+Changing C<AutoCommit> from off to on will trigger a L</commit>.
+
+For databases which don't support a specific auto-commit mode, the
+driver has to commit each statement automatically using an explicit
+C<COMMIT> after it completes successfully (and roll it back using an
+explicit C<ROLLBACK> if it fails). The error information reported to the
+application will correspond to the statement which was executed, unless
+it succeeded and the commit or rollback failed.
+
+B<* Databases in which a transaction must be explicitly started>
+
+For these databases, the intention is to have them act like databases in
+which a transaction is always active (as described above).
+
+To do this, the driver will automatically begin an explicit transaction
+when C<AutoCommit> is turned off, or after a L</commit> or
+L</rollback> (or when the application issues the next database
+operation after one of those events).
+
+In this way, the application does not have to treat these databases
+as a special case.
+
+See L</commit>, L</disconnect> and L</Transactions> for other important
+notes about transactions.
+
+
+=head3 C<Driver>
+
+Type: handle
+
+Holds the handle of the parent driver. The only recommended use for this
+is to find the name of the driver using:
+
+ $dbh->{Driver}->{Name}
+
+
+=head3 C<Name>
+
+Type: string
+
+Holds the "name" of the database. Usually (and recommended to be) the
+same as the "C<dbi:DriverName:...>" string used to connect to the database,
+but with the leading "C<dbi:DriverName:>" removed.
+
+
+=head3 C<Statement>
+
+Type: string, read-only
+
+Returns the statement string passed to the most recent L</prepare> or
+L</do> method called in this database handle, even if that method
+failed. This is especially useful where C<RaiseError> is enabled and
+the exception handler checks $@ and sees that a 'prepare' method call
+failed.
+
+
+=head3 C<RowCacheSize>
+
+Type: integer
+
+A hint to the driver indicating the size of the local row cache that the
+application would like the driver to use for future C<SELECT> statements.
+If a row cache is not implemented, then setting C<RowCacheSize> is ignored
+and getting the value returns C<undef>.
+
+Some C<RowCacheSize> values have special meaning, as follows:
+
+ 0 - Automatically determine a reasonable cache size for each C<SELECT>
+ 1 - Disable the local row cache
+ >1 - Cache this many rows
+ <0 - Cache as many rows that will fit into this much memory for each C<SELECT>.
+
+Note that large cache sizes may require a very large amount of memory
+(I<cached rows * maximum size of row>). Also, a large cache will cause
+a longer delay not only for the first fetch, but also whenever the
+cache needs refilling.
+
+See also the L</RowsInCache> statement handle attribute.
+
+=head3 C<Username>
+
+Type: string
+
+Returns the username used to connect to the database.
+
+
+=head1 DBI STATEMENT HANDLE OBJECTS
+
+This section lists the methods and attributes associated with DBI
+statement handles.
+
+=head2 Statement Handle Methods
+
+The DBI defines the following methods for use on DBI statement handles:
+
+=head3 C<bind_param>
+
+ $sth->bind_param($p_num, $bind_value)
+ $sth->bind_param($p_num, $bind_value, \%attr)
+ $sth->bind_param($p_num, $bind_value, $bind_type)
+
+The C<bind_param> method takes a copy of $bind_value and associates it
+(binds it) with a placeholder, identified by $p_num, embedded in
+the prepared statement. Placeholders are indicated with question
+mark character (C<?>). For example:
+
+ $dbh->{RaiseError} = 1; # save having to check each method call
+ $sth = $dbh->prepare("SELECT name, age FROM people WHERE name LIKE ?");
+ $sth->bind_param(1, "John%"); # placeholders are numbered from 1
+ $sth->execute;
+ DBI::dump_results($sth);
+
+See L</"Placeholders and Bind Values"> for more information.
+
+
+B<Data Types for Placeholders>
+
+The C<\%attr> parameter can be used to hint at the data type the
+placeholder should have. This is rarely needed. Typically, the driver is only
+interested in knowing if the placeholder should be bound as a number or a string.
+
+ $sth->bind_param(1, $value, { TYPE => SQL_INTEGER });
+
+As a short-cut for the common case, the data type can be passed
+directly, in place of the C<\%attr> hash reference. This example is
+equivalent to the one above:
+
+ $sth->bind_param(1, $value, SQL_INTEGER);
+
+The C<TYPE> value indicates the standard (non-driver-specific) type for
+this parameter. To specify the driver-specific type, the driver may
+support a driver-specific attribute, such as C<{ ora_type =E<gt> 97 }>.
+
+The SQL_INTEGER and other related constants can be imported using
+
+ use DBI qw(:sql_types);
+
+See L</"DBI Constants"> for more information.
+
+The data type is 'sticky' in that bind values passed to execute() are bound
+with the data type specified by earlier bind_param() calls, if any.
+Portable applications should not rely on being able to change the data type
+after the first C<bind_param> call.
+
+Perl only has string and number scalar data types. All database types
+that aren't numbers are bound as strings and must be in a format the
+database will understand except where the bind_param() TYPE attribute
+specifies a type that implies a particular format. For example, given:
+
+ $sth->bind_param(1, $value, SQL_DATETIME);
+
+the driver should expect $value to be in the ODBC standard SQL_DATETIME
+format, which is 'YYYY-MM-DD HH:MM:SS'. Similarly for SQL_DATE, SQL_TIME etc.
+
+As an alternative to specifying the data type in the C<bind_param> call,
+you can let the driver pass the value as the default type (C<VARCHAR>).
+You can then use an SQL function to convert the type within the statement.
+For example:
+
+ INSERT INTO price(code, price) VALUES (?, CONVERT(MONEY,?))
+
+The C<CONVERT> function used here is just an example. The actual function
+and syntax will vary between different databases and is non-portable.
+
+See also L</"Placeholders and Bind Values"> for more information.
+
+
+=head3 C<bind_param_inout>
+
+ $rc = $sth->bind_param_inout($p_num, \$bind_value, $max_len) or die $sth->errstr;
+ $rv = $sth->bind_param_inout($p_num, \$bind_value, $max_len, \%attr) or ...
+ $rv = $sth->bind_param_inout($p_num, \$bind_value, $max_len, $bind_type) or ...
+
+This method acts like L</bind_param>, but also enables values to be
+updated by the statement. The statement is typically
+a call to a stored procedure. The C<$bind_value> must be passed as a
+reference to the actual value to be used.
+
+Note that unlike L</bind_param>, the C<$bind_value> variable is not
+copied when C<bind_param_inout> is called. Instead, the value in the
+variable is read at the time L</execute> is called.
+
+The additional C<$max_len> parameter specifies the minimum amount of
+memory to allocate to C<$bind_value> for the new value. If the value
+returned from the database is too
+big to fit, then the execution should fail. If unsure what value to use,
+pick a generous length, i.e., a length larger than the longest value that would ever be
+returned. The only cost of using a larger value than needed is wasted memory.
+
+Undefined values or C<undef> are used to indicate null values.
+See also L</"Placeholders and Bind Values"> for more information.
+
+
+=head3 C<bind_param_array>
+
+ $rc = $sth->bind_param_array($p_num, $array_ref_or_value)
+ $rc = $sth->bind_param_array($p_num, $array_ref_or_value, \%attr)
+ $rc = $sth->bind_param_array($p_num, $array_ref_or_value, $bind_type)
+
+The C<bind_param_array> method is used to bind an array of values
+to a placeholder embedded in the prepared statement which is to be executed
+with L</execute_array>. For example:
+
+ $dbh->{RaiseError} = 1; # save having to check each method call
+ $sth = $dbh->prepare("INSERT INTO staff (first_name, last_name, dept) VALUES(?, ?, ?)");
+ $sth->bind_param_array(1, [ 'John', 'Mary', 'Tim' ]);
+ $sth->bind_param_array(2, [ 'Booth', 'Todd', 'Robinson' ]);
+ $sth->bind_param_array(3, "SALES"); # scalar will be reused for each row
+ $sth->execute_array( { ArrayTupleStatus => \my @tuple_status } );
+
+The C<%attr> ($bind_type) argument is the same as defined for L</bind_param>.
+Refer to L</bind_param> for general details on using placeholders.
+
+(Note that bind_param_array() can I<not> be used to expand a
+placeholder into a list of values for a statement like "SELECT foo
+WHERE bar IN (?)". A placeholder can only ever represent one value
+per execution.)
+
+Scalar values, including C<undef>, may also be bound by
+C<bind_param_array>. In which case the same value will be used for each
+L</execute> call. Driver-specific implementations may behave
+differently, e.g., when binding to a stored procedure call, some
+databases may permit mixing scalars and arrays as arguments.
+
+The default implementation provided by DBI (for drivers that have
+not implemented array binding) is to iteratively call L</execute> for
+each parameter tuple provided in the bound arrays. Drivers may
+provide more optimized implementations using whatever bulk operation
+support the database API provides. The default driver behaviour should
+match the default DBI behaviour, but always consult your driver
+documentation as there may be driver specific issues to consider.
+
+Note that the default implementation currently only supports non-data
+returning statements (INSERT, UPDATE, but not SELECT). Also,
+C<bind_param_array> and L</bind_param> cannot be mixed in the same
+statement execution, and C<bind_param_array> must be used with
+L</execute_array>; using C<bind_param_array> will have no effect
+for L</execute>.
+
+The C<bind_param_array> method was added in DBI 1.22.
+
+=head3 C<execute>
+
+ $rv = $sth->execute or die $sth->errstr;
+ $rv = $sth->execute(@bind_values) or die $sth->errstr;
+
+Perform whatever processing is necessary to execute the prepared
+statement. An C<undef> is returned if an error occurs. A successful
+C<execute> always returns true regardless of the number of rows affected,
+even if it's zero (see below). It is always important to check the
+return status of C<execute> (and most other DBI methods) for errors
+if you're not using L</RaiseError>.
+
+For a I<non>-C<SELECT> statement, C<execute> returns the number of rows
+affected, if known. If no rows were affected, then C<execute> returns
+"C<0E0>", which Perl will treat as 0 but will regard as true. Note that it
+is I<not> an error for no rows to be affected by a statement. If the
+number of rows affected is not known, then C<execute> returns -1.
+
+For C<SELECT> statements, execute simply "starts" the query within the
+database engine. Use one of the fetch methods to retrieve the data after
+calling C<execute>. The C<execute> method does I<not> return the number of
+rows that will be returned by the query (because most databases can't
+tell in advance), it simply returns a true value.
+
+You can tell if the statement was a C<SELECT> statement by checking if
+C<$sth-E<gt>{NUM_OF_FIELDS}> is greater than zero after calling C<execute>.
+
+If any arguments are given, then C<execute> will effectively call
+L</bind_param> for each value before executing the statement. Values
+bound in this way are usually treated as C<SQL_VARCHAR> types unless
+the driver can determine the correct type (which is rare), or unless
+C<bind_param> (or C<bind_param_inout>) has already been used to
+specify the type.
+
+Note that passing C<execute> an empty array is the same as passing no arguments
+at all, which will execute the statement with previously bound values.
+That's probably not what you want.
+
+If execute() is called on a statement handle that's still active
+($sth->{Active} is true) then it should effectively call finish()
+to tidy up the previous execution results before starting this new
+execution.
+
+=head3 C<execute_array>
+
+ $tuples = $sth->execute_array(\%attr) or die $sth->errstr;
+ $tuples = $sth->execute_array(\%attr, @bind_values) or die $sth->errstr;
+
+ ($tuples, $rows) = $sth->execute_array(\%attr) or die $sth->errstr;
+ ($tuples, $rows) = $sth->execute_array(\%attr, @bind_values) or die $sth->errstr;
+
+Execute the prepared statement once for each parameter tuple
+(group of values) provided either in the @bind_values, or by prior
+calls to L</bind_param_array>, or via a reference passed in \%attr.
+
+When called in scalar context the execute_array() method returns the
+number of tuples executed, or C<undef> if an error occurred. Like
+execute(), a successful execute_array() always returns true regardless
+of the number of tuples executed, even if it's zero. If there were any
+errors the ArrayTupleStatus array can be used to discover which tuples
+failed and with what errors.
+
+When called in list context the execute_array() method returns two scalars;
+$tuples is the same as calling execute_array() in scalar context and $rows is
+the number of rows affected for each tuple, if available or
+-1 if the driver cannot determine this. NOTE, some drivers cannot determine
+the number of rows affected per tuple but can provide the number of rows
+affected for the batch.
+If you are doing an update operation the returned rows affected may not be what
+you expect if, for instance, one or more of the tuples affected the same row
+multiple times. Some drivers may not yet support list context, in which case
+$rows will be undef, or may not be able to provide the number of rows affected
+when performing this batch operation, in which case $rows will be -1.
+
+Bind values for the tuples to be executed may be supplied row-wise
+by an C<ArrayTupleFetch> attribute, or else column-wise in the
+C<@bind_values> argument, or else column-wise by prior calls to
+L</bind_param_array>.
+
+Where column-wise binding is used (via the C<@bind_values> argument
+or calls to bind_param_array()) the maximum number of elements in
+any one of the bound value arrays determines the number of tuples
+executed. Placeholders with fewer values in their parameter arrays
+are treated as if padded with undef (NULL) values.
+
+If a scalar value is bound, instead of an array reference, it is
+treated as a I<variable> length array with all elements having the
+same value. It does not influence the number of tuples executed,
+so if all bound arrays have zero elements then zero tuples will
+be executed. If I<all> bound values are scalars then one tuple
+will be executed, making execute_array() act just like execute().
+
+The C<ArrayTupleFetch> attribute can be used to specify a reference
+to a subroutine that will be called to provide the bind values for
+each tuple execution. The subroutine should return an reference to
+an array which contains the appropriate number of bind values, or
+return an undef if there is no more data to execute.
+
+As a convenience, the C<ArrayTupleFetch> attribute can also be
+used to specify a statement handle. In which case the fetchrow_arrayref()
+method will be called on the given statement handle in order to
+provide the bind values for each tuple execution.
+
+The values specified via bind_param_array() or the @bind_values
+parameter may be either scalars, or arrayrefs. If any C<@bind_values>
+are given, then C<execute_array> will effectively call L</bind_param_array>
+for each value before executing the statement. Values bound in
+this way are usually treated as C<SQL_VARCHAR> types unless the
+driver can determine the correct type (which is rare), or unless
+C<bind_param>, C<bind_param_inout>, C<bind_param_array>, or
+C<bind_param_inout_array> has already been used to specify the type.
+See L</bind_param_array> for details.
+
+The C<ArrayTupleStatus> attribute can be used to specify a
+reference to an array which will receive the execute status of each
+executed parameter tuple. Note the C<ArrayTupleStatus> attribute was
+mandatory until DBI 1.38.
+
+For tuples which are successfully executed, the element at the same
+ordinal position in the status array is the resulting rowcount (or -1
+if unknown).
+If the execution of a tuple causes an error, then the corresponding
+status array element will be set to a reference to an array containing
+L</err>, L</errstr> and L</state> set by the failed execution.
+
+If B<any> tuple execution returns an error, C<execute_array> will
+return C<undef>. In that case, the application should inspect the
+status array to determine which parameter tuples failed.
+Some databases may not continue executing tuples beyond the first
+failure. In this case the status array will either hold fewer
+elements, or the elements beyond the failure will be undef.
+
+If all parameter tuples are successfully executed, C<execute_array>
+returns the number tuples executed. If no tuples were executed,
+then execute_array() returns "C<0E0>", just like execute() does,
+which Perl will treat as 0 but will regard as true.
+
+For example:
+
+ $sth = $dbh->prepare("INSERT INTO staff (first_name, last_name) VALUES (?, ?)");
+ my $tuples = $sth->execute_array(
+ { ArrayTupleStatus => \my @tuple_status },
+ \@first_names,
+ \@last_names,
+ );
+ if ($tuples) {
+ print "Successfully inserted $tuples records\n";
+ }
+ else {
+ for my $tuple (0..@last_names-1) {
+ my $status = $tuple_status[$tuple];
+ $status = [0, "Skipped"] unless defined $status;
+ next unless ref $status;
+ printf "Failed to insert (%s, %s): %s\n",
+ $first_names[$tuple], $last_names[$tuple], $status->[1];
+ }
+ }
+
+Support for data returning statements such as SELECT is driver-specific
+and subject to change. At present, the default implementation
+provided by DBI only supports non-data returning statements.
+
+Transaction semantics when using array binding are driver and
+database specific. If C<AutoCommit> is on, the default DBI
+implementation will cause each parameter tuple to be individually
+committed (or rolled back in the event of an error). If C<AutoCommit>
+is off, the application is responsible for explicitly committing
+the entire set of bound parameter tuples. Note that different
+drivers and databases may have different behaviours when some
+parameter tuples cause failures. In some cases, the driver or
+database may automatically rollback the effect of all prior parameter
+tuples that succeeded in the transaction; other drivers or databases
+may retain the effect of prior successfully executed parameter
+tuples. Be sure to check your driver and database for its specific
+behaviour.
+
+Note that, in general, performance will usually be better with
+C<AutoCommit> turned off, and using explicit C<commit> after each
+C<execute_array> call.
+
+The C<execute_array> method was added in DBI 1.22, and ArrayTupleFetch
+was added in 1.36.
+
+=head3 C<execute_for_fetch>
+
+ $tuples = $sth->execute_for_fetch($fetch_tuple_sub);
+ $tuples = $sth->execute_for_fetch($fetch_tuple_sub, \@tuple_status);
+
+ ($tuples, $rows) = $sth->execute_for_fetch($fetch_tuple_sub);
+ ($tuples, $rows) = $sth->execute_for_fetch($fetch_tuple_sub, \@tuple_status);
+
+The execute_for_fetch() method is used to perform bulk operations and
+although it is most often used via the execute_array() method you can
+use it directly. The main difference between execute_array and
+execute_for_fetch is the former does column or row-wise binding and
+the latter uses row-wise binding.
+
+The fetch subroutine, referenced by $fetch_tuple_sub, is expected
+to return a reference to an array (known as a 'tuple') or undef.
+
+The execute_for_fetch() method calls $fetch_tuple_sub, without any
+parameters, until it returns a false value. Each tuple returned is
+used to provide bind values for an $sth->execute(@$tuple) call.
+
+In scalar context execute_for_fetch() returns C<undef> if there were any
+errors and the number of tuples executed otherwise. Like execute() and
+execute_array() a zero is returned as "0E0" so execute_for_fetch() is
+only false on error. If there were any errors the @tuple_status array
+can be used to discover which tuples failed and with what errors.
+
+When called in list context execute_for_fetch() returns two scalars;
+$tuples is the same as calling execute_for_fetch() in scalar context and $rows is
+the sum of the number of rows affected for each tuple, if available or -1
+if the driver cannot determine this.
+If you are doing an update operation the returned rows affected may not be what
+you expect if, for instance, one or more of the tuples affected the same row
+multiple times. Some drivers may not yet support list context, in which case
+$rows will be undef, or may not be able to provide the number of rows affected
+when performing this batch operation, in which case $rows will be -1.
+
+If \@tuple_status is passed then the execute_for_fetch method uses
+it to return status information. The tuple_status array holds one
+element per tuple. If the corresponding execute() did not fail then
+the element holds the return value from execute(), which is typically
+a row count. If the execute() did fail then the element holds a
+reference to an array containing ($sth->err, $sth->errstr, $sth->state).
+
+If the driver detects an error that it knows means no further tuples can be
+executed then it may return, with an error status, even though $fetch_tuple_sub
+may still have more tuples to be executed.
+
+Although each tuple returned by $fetch_tuple_sub is effectively used
+to call $sth->execute(@$tuple_array_ref) the exact timing may vary.
+Drivers are free to accumulate sets of tuples to pass to the
+database server in bulk group operations for more efficient execution.
+However, the $fetch_tuple_sub is specifically allowed to return
+the same array reference each time (which is what fetchrow_arrayref()
+usually does).
+
+For example:
+
+ my $sel = $dbh1->prepare("select foo, bar from table1");
+ $sel->execute;
+
+ my $ins = $dbh2->prepare("insert into table2 (foo, bar) values (?,?)");
+ my $fetch_tuple_sub = sub { $sel->fetchrow_arrayref };
+
+ my @tuple_status;
+ $rc = $ins->execute_for_fetch($fetch_tuple_sub, \@tuple_status);
+ my @errors = grep { ref $_ } @tuple_status;
+
+Similarly, if you already have an array containing the data rows
+to be processed you'd use a subroutine to shift off and return
+each array ref in turn:
+
+ $ins->execute_for_fetch( sub { shift @array_of_arrays }, \@tuple_status);
+
+The C<execute_for_fetch> method was added in DBI 1.38.
+
+
+=head3 C<fetchrow_arrayref>
+
+ $ary_ref = $sth->fetchrow_arrayref;
+ $ary_ref = $sth->fetch; # alias
+
+Fetches the next row of data and returns a reference to an array
+holding the field values. Null fields are returned as C<undef>
+values in the array.
+This is the fastest way to fetch data, particularly if used with
+C<$sth-E<gt>bind_columns>.
+
+If there are no more rows or if an error occurs, then C<fetchrow_arrayref>
+returns an C<undef>. You should check C<$sth-E<gt>err> afterwards (or use the
+C<RaiseError> attribute) to discover if the C<undef> returned was due to an
+error.
+
+Note that the same array reference is returned for each fetch, so don't
+store the reference and then use it after a later fetch. Also, the
+elements of the array are also reused for each row, so take care if you
+want to take a reference to an element. See also L</bind_columns>.
+
+=head3 C<fetchrow_array>
+
+ @ary = $sth->fetchrow_array;
+
+An alternative to C<fetchrow_arrayref>. Fetches the next row of data
+and returns it as a list containing the field values. Null fields
+are returned as C<undef> values in the list.
+
+If there are no more rows or if an error occurs, then C<fetchrow_array>
+returns an empty list. You should check C<$sth-E<gt>err> afterwards (or use
+the C<RaiseError> attribute) to discover if the empty list returned was
+due to an error.
+
+If called in a scalar context for a statement handle that has more
+than one column, it is undefined whether the driver will return
+the value of the first column or the last. So don't do that.
+Also, in a scalar context, an C<undef> is returned if there are no
+more rows or if an error occurred. That C<undef> can't be distinguished
+from an C<undef> returned because the first field value was NULL.
+For these reasons you should exercise some caution if you use
+C<fetchrow_array> in a scalar context.
+
+=head3 C<fetchrow_hashref>
+
+ $hash_ref = $sth->fetchrow_hashref;
+ $hash_ref = $sth->fetchrow_hashref($name);
+
+An alternative to C<fetchrow_arrayref>. Fetches the next row of data
+and returns it as a reference to a hash containing field name and field
+value pairs. Null fields are returned as C<undef> values in the hash.
+
+If there are no more rows or if an error occurs, then C<fetchrow_hashref>
+returns an C<undef>. You should check C<$sth-E<gt>err> afterwards (or use the
+C<RaiseError> attribute) to discover if the C<undef> returned was due to an
+error.
+
+The optional C<$name> parameter specifies the name of the statement handle
+attribute. For historical reasons it defaults to "C<NAME>", however using
+either "C<NAME_lc>" or "C<NAME_uc>" is recommended for portability.
+
+The keys of the hash are the same names returned by C<$sth-E<gt>{$name}>. If
+more than one field has the same name, there will only be one entry in the
+returned hash for those fields, so statements like "C<select foo, foo from bar>"
+will return only a single key from C<fetchrow_hashref>. In these cases use
+column aliases or C<fetchrow_arrayref>. Note that it is the database server
+(and not the DBD implementation) which provides the I<name> for fields
+containing functions like "C<count(*)>" or "C<max(c_foo)>" and they may clash
+with existing column names (most databases don't care about duplicate column
+names in a result-set). If you want these to return as unique names that are
+the same across databases, use I<aliases>, as in "C<select count(*) as cnt>"
+or "C<select max(c_foo) mx_foo, ...>" depending on the syntax your database
+supports.
+
+Because of the extra work C<fetchrow_hashref> and Perl have to perform, it
+is not as efficient as C<fetchrow_arrayref> or C<fetchrow_array>.
+
+By default a reference to a new hash is returned for each row.
+It is likely that a future version of the DBI will support an
+attribute which will enable the same hash to be reused for each
+row. This will give a significant performance boost, but it won't
+be enabled by default because of the risk of breaking old code.
+
+
+=head3 C<fetchall_arrayref>
+
+ $tbl_ary_ref = $sth->fetchall_arrayref;
+ $tbl_ary_ref = $sth->fetchall_arrayref( $slice );
+ $tbl_ary_ref = $sth->fetchall_arrayref( $slice, $max_rows );
+
+The C<fetchall_arrayref> method can be used to fetch all the data to be
+returned from a prepared and executed statement handle. It returns a
+reference to an array that contains one reference per row.
+
+If called on an I<inactive> statement handle, C<fetchall_arrayref> returns undef.
+
+If there are no rows left to return from an I<active> statement handle, C<fetchall_arrayref> returns a reference
+to an empty array. If an error occurs, C<fetchall_arrayref> returns the
+data fetched thus far, which may be none. You should check C<$sth-E<gt>err>
+afterwards (or use the C<RaiseError> attribute) to discover if the data is
+complete or was truncated due to an error.
+
+If $slice is an array reference, C<fetchall_arrayref> uses L</fetchrow_arrayref>
+to fetch each row as an array ref. If the $slice array is not empty
+then it is used as a slice to select individual columns by perl array
+index number (starting at 0, unlike column and parameter numbers which
+start at 1).
+
+With no parameters, or if $slice is undefined, C<fetchall_arrayref>
+acts as if passed an empty array ref.
+
+For example, to fetch just the first column of every row:
+
+ $tbl_ary_ref = $sth->fetchall_arrayref([0]);
+
+To fetch the second to last and last column of every row:
+
+ $tbl_ary_ref = $sth->fetchall_arrayref([-2,-1]);
+
+Those two examples both return a reference to an array of array refs.
+
+If $slice is a hash reference, C<fetchall_arrayref> fetches each row as a hash
+reference. If the $slice hash is empty then the keys in the hashes have
+whatever name lettercase is returned by default. (See L</FetchHashKeyName>
+attribute.) If the $slice hash is I<not> empty, then it is used as a slice to
+select individual columns by name. The values of the hash should be set to 1.
+The key names of the returned hashes match the letter case of the names in the
+parameter hash, regardless of the L</FetchHashKeyName> attribute.
+
+For example, to fetch all fields of every row as a hash ref:
+
+ $tbl_ary_ref = $sth->fetchall_arrayref({});
+
+To fetch only the fields called "foo" and "bar" of every row as a hash ref
+(with keys named "foo" and "BAR", regardless of the original capitalization):
+
+ $tbl_ary_ref = $sth->fetchall_arrayref({ foo=>1, BAR=>1 });
+
+Those two examples both return a reference to an array of hash refs.
+
+If $slice is a I<reference to a hash reference>, that hash is used to select
+and rename columns. The keys are 0-based column index numbers and the values
+are the corresponding keys for the returned row hashes.
+
+For example, to fetch only the first and second columns of every row as a hash
+ref (with keys named "k" and "v" regardless of their original names):
+
+ $tbl_ary_ref = $sth->fetchall_arrayref( \{ 0 => 'k', 1 => 'v' } );
+
+If $max_rows is defined and greater than or equal to zero then it
+is used to limit the number of rows fetched before returning.
+fetchall_arrayref() can then be called again to fetch more rows.
+This is especially useful when you need the better performance of
+fetchall_arrayref() but don't have enough memory to fetch and return
+all the rows in one go.
+
+Here's an example (assumes RaiseError is enabled):
+
+ my $rows = []; # cache for batches of rows
+ while( my $row = ( shift(@$rows) || # get row from cache, or reload cache:
+ shift(@{$rows=$sth->fetchall_arrayref(undef,10_000)||[]}) )
+ ) {
+ ...
+ }
+
+That I<might> be the fastest way to fetch and process lots of rows using the DBI,
+but it depends on the relative cost of method calls vs memory allocation.
+
+A standard C<while> loop with column binding is often faster because
+the cost of allocating memory for the batch of rows is greater than
+the saving by reducing method calls. It's possible that the DBI may
+provide a way to reuse the memory of a previous batch in future, which
+would then shift the balance back towards fetchall_arrayref().
+
+
+=head3 C<fetchall_hashref>
+
+ $hash_ref = $sth->fetchall_hashref($key_field);
+
+The C<fetchall_hashref> method can be used to fetch all the data to be
+returned from a prepared and executed statement handle. It returns a reference
+to a hash containing a key for each distinct value of the $key_field column
+that was fetched. For each key the corresponding value is a reference to a hash
+containing all the selected columns and their values, as returned by
+C<fetchrow_hashref()>.
+
+If there are no rows to return, C<fetchall_hashref> returns a reference
+to an empty hash. If an error occurs, C<fetchall_hashref> returns the
+data fetched thus far, which may be none. You should check
+C<$sth-E<gt>err> afterwards (or use the C<RaiseError> attribute) to
+discover if the data is complete or was truncated due to an error.
+
+The $key_field parameter provides the name of the field that holds the
+value to be used for the key for the returned hash. For example:
+
+ $dbh->{FetchHashKeyName} = 'NAME_lc';
+ $sth = $dbh->prepare("SELECT FOO, BAR, ID, NAME, BAZ FROM TABLE");
+ $sth->execute;
+ $hash_ref = $sth->fetchall_hashref('id');
+ print "Name for id 42 is $hash_ref->{42}->{name}\n";
+
+The $key_field parameter can also be specified as an integer column
+number (counting from 1). If $key_field doesn't match any column in
+the statement, as a name first then as a number, then an error is
+returned.
+
+For queries returning more than one 'key' column, you can specify
+multiple column names by passing $key_field as a reference to an
+array containing one or more key column names (or index numbers).
+For example:
+
+ $sth = $dbh->prepare("SELECT foo, bar, baz FROM table");
+ $sth->execute;
+ $hash_ref = $sth->fetchall_hashref( [ qw(foo bar) ] );
+ print "For foo 42 and bar 38, baz is $hash_ref->{42}->{38}->{baz}\n";
+
+The fetchall_hashref() method is normally used only where the key
+fields values for each row are unique. If multiple rows are returned
+with the same values for the key fields then later rows overwrite
+earlier ones.
+
+=head3 C<finish>
+
+ $rc = $sth->finish;
+
+Indicate that no more data will be fetched from this statement handle
+before it is either executed again or destroyed. You almost certainly
+do I<not> need to call this method.
+
+Adding calls to C<finish> after loop that fetches all rows is a common mistake,
+don't do it, it can mask genuine problems like uncaught fetch errors.
+
+When all the data has been fetched from a C<SELECT> statement, the driver will
+automatically call C<finish> for you. So you should I<not> call it explicitly
+I<except> when you know that you've not fetched all the data from a statement
+handle I<and> the handle won't be destroyed soon.
+
+The most common example is when you only want to fetch just one row,
+but in that case the C<selectrow_*> methods are usually better anyway.
+
+Consider a query like:
+
+ SELECT foo FROM table WHERE bar=? ORDER BY baz
+
+on a very large table. When executed, the database server will have to use
+temporary buffer space to store the sorted rows. If, after executing
+the handle and selecting just a few rows, the handle won't be re-executed for
+some time and won't be destroyed, the C<finish> method can be used to tell
+the server that the buffer space can be freed.
+
+Calling C<finish> resets the L</Active> attribute for the statement. It
+may also make some statement handle attributes (such as C<NAME> and C<TYPE>)
+unavailable if they have not already been accessed (and thus cached).
+
+The C<finish> method does not affect the transaction status of the
+database connection. It has nothing to do with transactions. It's mostly an
+internal "housekeeping" method that is rarely needed.
+See also L</disconnect> and the L</Active> attribute.
+
+The C<finish> method should have been called C<discard_pending_rows>.
+
+
+=head3 C<rows>
+
+ $rv = $sth->rows;
+
+Returns the number of rows affected by the last row affecting command,
+or -1 if the number of rows is not known or not available.
+
+Generally, you can only rely on a row count after a I<non>-C<SELECT>
+C<execute> (for some specific operations like C<UPDATE> and C<DELETE>), or
+after fetching all the rows of a C<SELECT> statement.
+
+For C<SELECT> statements, it is generally not possible to know how many
+rows will be returned except by fetching them all. Some drivers will
+return the number of rows the application has fetched so far, but
+others may return -1 until all rows have been fetched. So use of the
+C<rows> method or C<$DBI::rows> with C<SELECT> statements is not
+recommended.
+
+One alternative method to get a row count for a C<SELECT> is to execute a
+"SELECT COUNT(*) FROM ..." SQL statement with the same "..." as your
+query and then fetch the row count from that.
+
+
+=head3 C<bind_col>
+
+ $rc = $sth->bind_col($column_number, \$var_to_bind);
+ $rc = $sth->bind_col($column_number, \$var_to_bind, \%attr );
+ $rc = $sth->bind_col($column_number, \$var_to_bind, $bind_type );
+
+Binds a Perl variable and/or some attributes to an output column
+(field) of a C<SELECT> statement. Column numbers count up from 1.
+You do not need to bind output columns in order to fetch data.
+For maximum portability between drivers, bind_col() should be called
+after execute() and not before.
+See also L</bind_columns> for an example.
+
+The binding is performed at a low level using Perl aliasing.
+Whenever a row is fetched from the database $var_to_bind appears
+to be automatically updated simply because it now refers to the same
+memory location as the corresponding column value. This makes using
+bound variables very efficient.
+Binding a tied variable doesn't work, currently.
+
+The L</bind_param> method
+performs a similar, but opposite, function for input variables.
+
+B<Data Types for Column Binding>
+
+The C<\%attr> parameter can be used to hint at the data type
+formatting the column should have. For example, you can use:
+
+ $sth->bind_col(1, undef, { TYPE => SQL_DATETIME });
+
+to specify that you'd like the column (which presumably is some
+kind of datetime type) to be returned in the standard format for
+SQL_DATETIME, which is 'YYYY-MM-DD HH:MM:SS', rather than the
+native formatting the database would normally use.
+
+There's no $var_to_bind in that example to emphasize the point
+that bind_col() works on the underlying column and not just
+a particular bound variable.
+
+As a short-cut for the common case, the data type can be passed
+directly, in place of the C<\%attr> hash reference. This example is
+equivalent to the one above:
+
+ $sth->bind_col(1, undef, SQL_DATETIME);
+
+The C<TYPE> value indicates the standard (non-driver-specific) type for
+this parameter. To specify the driver-specific type, the driver may
+support a driver-specific attribute, such as C<{ ora_type =E<gt> 97 }>.
+
+The SQL_DATETIME and other related constants can be imported using
+
+ use DBI qw(:sql_types);
+
+See L</"DBI Constants"> for more information.
+
+Few drivers support specifying a data type via a C<bind_col> call
+(most will simply ignore the data type). Fewer still allow the data
+type to be altered once set.
+
+The TYPE attribute for bind_col() was first specified in DBI 1.41.
+
+From DBI 1.611, drivers can use the C<TYPE> attribute to attempt to
+cast the bound scalar to a perl type which more closely matches
+C<TYPE>. At present DBI supports C<SQL_INTEGER>, C<SQL_DOUBLE> and
+C<SQL_NUMERIC>. See L</sql_type_cast> for details of how types are
+cast.
+
+B<Other attributes for Column Binding>
+
+The C<\%attr> parameter may also contain the following attributes:
+
+=over
+
+=item C<StrictlyTyped>
+
+If a C<TYPE> attribute is passed to bind_col, then the driver will
+attempt to change the bound perl scalar to match the type more
+closely. If the bound value cannot be cast to the requested C<TYPE>
+then by default it is left untouched and no error is generated. If you
+specify C<StrictlyTyped> as 1 and the cast fails, this will generate
+an error.
+
+This attribute was first added in DBI 1.611. When 1.611 was released
+few drivers actually supported this attribute but DBD::Oracle and
+DBD::ODBC should from versions 1.24.
+
+=item C<DiscardString>
+
+When the C<TYPE> attribute is passed to L</bind_col> and the driver
+successfully casts the bound perl scalar to a non-string type
+then if C<DiscardString> is set to 1, the string portion of the
+scalar will be discarded. By default, C<DiscardString> is not set.
+
+This attribute was first added in DBI 1.611. When 1.611 was released
+few drivers actually supported this attribute but DBD::Oracle and
+DBD::ODBC should from versions 1.24.
+
+=back
+
+
+=head3 C<bind_columns>
+
+ $rc = $sth->bind_columns(@list_of_refs_to_vars_to_bind);
+
+Calls L</bind_col> for each column of the C<SELECT> statement.
+
+The list of references should have the same number of elements as the number of
+columns in the C<SELECT> statement. If it doesn't then C<bind_columns> will
+bind the elements given, up to the number of columns, and then return an error.
+
+For maximum portability between drivers, bind_columns() should be called
+after execute() and not before.
+
+For example:
+
+ $dbh->{RaiseError} = 1; # do this, or check every call for errors
+ $sth = $dbh->prepare(q{ SELECT region, sales FROM sales_by_region });
+ $sth->execute;
+ my ($region, $sales);
+
+ # Bind Perl variables to columns:
+ $rv = $sth->bind_columns(\$region, \$sales);
+
+ # you can also use Perl's \(...) syntax (see perlref docs):
+ # $sth->bind_columns(\($region, $sales));
+
+ # Column binding is the most efficient way to fetch data
+ while ($sth->fetch) {
+ print "$region: $sales\n";
+ }
+
+For compatibility with old scripts, the first parameter will be
+ignored if it is C<undef> or a hash reference.
+
+Here's a more fancy example that binds columns to the values I<inside>
+a hash (thanks to H.Merijn Brand):
+
+ $sth->execute;
+ my %row;
+ $sth->bind_columns( \( @row{ @{$sth->{NAME_lc} } } ));
+ while ($sth->fetch) {
+ print "$row{region}: $row{sales}\n";
+ }
+
+
+=head3 C<dump_results>
+
+ $rows = $sth->dump_results($maxlen, $lsep, $fsep, $fh);
+
+Fetches all the rows from C<$sth>, calls C<DBI::neat_list> for each row, and
+prints the results to C<$fh> (defaults to C<STDOUT>) separated by C<$lsep>
+(default C<"\n">). C<$fsep> defaults to C<", "> and C<$maxlen> defaults to 35.
+
+This method is designed as a handy utility for prototyping and
+testing queries. Since it uses L</neat_list> to
+format and edit the string for reading by humans, it is not recommended
+for data transfer applications.
+
+
+=head2 Statement Handle Attributes
+
+This section describes attributes specific to statement handles. Most
+of these attributes are read-only.
+
+Changes to these statement handle attributes do not affect any other
+existing or future statement handles.
+
+Attempting to set or get the value of an unknown attribute generates a warning,
+except for private driver specific attributes (which all have names
+starting with a lowercase letter).
+
+Example:
+
+ ... = $h->{NUM_OF_FIELDS}; # get/read
+
+Some drivers cannot provide valid values for some or all of these
+attributes until after C<$sth-E<gt>execute> has been successfully
+called. Typically the attribute will be C<undef> in these situations.
+
+Some attributes, like NAME, are not appropriate to some types of
+statement, like SELECT. Typically the attribute will be C<undef>
+in these situations.
+
+For drivers which support stored procedures and multiple result sets
+(see L</more_results>) these attributes relate to the I<current> result set.
+
+See also L</finish> to learn more about the effect it
+may have on some attributes.
+
+=head3 C<NUM_OF_FIELDS>
+
+Type: integer, read-only
+
+Number of fields (columns) in the data the prepared statement may return.
+Statements that don't return rows of data, like C<DELETE> and C<CREATE>
+set C<NUM_OF_FIELDS> to 0 (though it may be undef in some drivers).
+
+
+=head3 C<NUM_OF_PARAMS>
+
+Type: integer, read-only
+
+The number of parameters (placeholders) in the prepared statement.
+See SUBSTITUTION VARIABLES below for more details.
+
+
+=head3 C<NAME>
+
+Type: array-ref, read-only
+
+Returns a reference to an array of field names for each column. The
+names may contain spaces but should not be truncated or have any
+trailing space. Note that the names have the letter case (upper, lower
+or mixed) as returned by the driver being used. Portable applications
+should use L</NAME_lc> or L</NAME_uc>.
+
+ print "First column name: $sth->{NAME}->[0]\n";
+
+Also note that the name returned for (aggregate) functions like C<count(*)>
+or C<max(c_foo)> is determined by the database server and not by C<DBI> or
+the C<DBD> backend.
+
+=head3 C<NAME_lc>
+
+Type: array-ref, read-only
+
+Like L</NAME> but always returns lowercase names.
+
+=head3 C<NAME_uc>
+
+Type: array-ref, read-only
+
+Like L</NAME> but always returns uppercase names.
+
+=head3 C<NAME_hash>
+
+Type: hash-ref, read-only
+
+=head3 C<NAME_lc_hash>
+
+Type: hash-ref, read-only
+
+=head3 C<NAME_uc_hash>
+
+Type: hash-ref, read-only
+
+The C<NAME_hash>, C<NAME_lc_hash>, and C<NAME_uc_hash> attributes
+return column name information as a reference to a hash.
+
+The keys of the hash are the names of the columns. The letter case of
+the keys corresponds to the letter case returned by the C<NAME>,
+C<NAME_lc>, and C<NAME_uc> attributes respectively (as described above).
+
+The value of each hash entry is the perl index number of the
+corresponding column (counting from 0). For example:
+
+ $sth = $dbh->prepare("select Id, Name from table");
+ $sth->execute;
+ @row = $sth->fetchrow_array;
+ print "Name $row[ $sth->{NAME_lc_hash}{name} ]\n";
+
+
+=head3 C<TYPE>
+
+Type: array-ref, read-only
+
+Returns a reference to an array of integer values for each
+column. The value indicates the data type of the corresponding column.
+
+The values correspond to the international standards (ANSI X3.135
+and ISO/IEC 9075) which, in general terms, means ODBC. Driver-specific
+types that don't exactly match standard types should generally return
+the same values as an ODBC driver supplied by the makers of the
+database. That might include private type numbers in ranges the vendor
+has officially registered with the ISO working group:
+
+ ftp://sqlstandards.org/SC32/SQL_Registry/
+
+Where there's no vendor-supplied ODBC driver to be compatible with,
+the DBI driver can use type numbers in the range that is now
+officially reserved for use by the DBI: -9999 to -9000.
+
+All possible values for C<TYPE> should have at least one entry in the
+output of the C<type_info_all> method (see L</type_info_all>).
+
+=head3 C<PRECISION>
+
+Type: array-ref, read-only
+
+Returns a reference to an array of integer values for each column.
+
+For numeric columns, the value is the maximum number of digits
+(without considering a sign character or decimal point). Note that
+the "display size" for floating point types (REAL, FLOAT, DOUBLE)
+can be up to 7 characters greater than the precision (for the
+sign + decimal point + the letter E + a sign + 2 or 3 digits).
+
+For any character type column the value is the OCTET_LENGTH,
+in other words the number of bytes, not characters.
+
+(More recent standards refer to this as COLUMN_SIZE but we stick
+with PRECISION for backwards compatibility.)
+
+=head3 C<SCALE>
+
+Type: array-ref, read-only
+
+Returns a reference to an array of integer values for each column.
+NULL (C<undef>) values indicate columns where scale is not applicable.
+
+=head3 C<NULLABLE>
+
+Type: array-ref, read-only
+
+Returns a reference to an array indicating the possibility of each
+column returning a null. Possible values are C<0>
+(or an empty string) = no, C<1> = yes, C<2> = unknown.
+
+ print "First column may return NULL\n" if $sth->{NULLABLE}->[0];
+
+
+=head3 C<CursorName>
+
+Type: string, read-only
+
+Returns the name of the cursor associated with the statement handle, if
+available. If not available or if the database driver does not support the
+C<"where current of ..."> SQL syntax, then it returns C<undef>.
+
+
+=head3 C<Database>
+
+Type: dbh, read-only
+
+Returns the parent $dbh of the statement handle.
+
+
+=head3 C<Statement>
+
+Type: string, read-only
+
+Returns the statement string passed to the L</prepare> method.
+
+
+=head3 C<ParamValues>
+
+Type: hash ref, read-only
+
+Returns a reference to a hash containing the values currently bound
+to placeholders. The keys of the hash are the 'names' of the
+placeholders, typically integers starting at 1. Returns undef if
+not supported by the driver.
+
+See L</ShowErrorStatement> for an example of how this is used.
+
+* Keys:
+
+If the driver supports C<ParamValues> but no values have been bound
+yet then the driver should return a hash with placeholders names
+in the keys but all the values undef, but some drivers may return
+a ref to an empty hash because they can't pre-determine the names.
+
+It is possible that the keys in the hash returned by C<ParamValues>
+are not exactly the same as those implied by the prepared statement.
+For example, DBD::Oracle translates 'C<?>' placeholders into 'C<:pN>'
+where N is a sequence number starting at 1.
+
+* Values:
+
+It is possible that the values in the hash returned by C<ParamValues>
+are not I<exactly> the same as those passed to bind_param() or execute().
+The driver may have slightly modified values in some way based on the
+TYPE the value was bound with. For example a floating point value
+bound as an SQL_INTEGER type may be returned as an integer.
+The values returned by C<ParamValues> can be passed to another
+bind_param() method with the same TYPE and will be seen by the
+database as the same value. See also L</ParamTypes> below.
+
+The C<ParamValues> attribute was added in DBI 1.28.
+
+=head3 C<ParamTypes>
+
+Type: hash ref, read-only
+
+Returns a reference to a hash containing the type information
+currently bound to placeholders.
+Returns undef if not supported by the driver.
+
+* Keys:
+
+See L</ParamValues> above.
+
+* Values:
+
+The hash values are hashrefs of type information in the same form as that
+passed to the various bind_param() methods (See L</bind_param> for the format
+and values).
+
+It is possible that the values in the hash returned by C<ParamTypes>
+are not exactly the same as those passed to bind_param() or execute().
+Param attributes specified using the abbreviated form, like this:
+
+ $sth->bind_param(1, SQL_INTEGER);
+
+are returned in the expanded form, as if called like this:
+
+ $sth->bind_param(1, { TYPE => SQL_INTEGER });
+
+The driver may have modified the type information in some way based
+on the bound values, other hints provided by the prepare()'d
+SQL statement, or alternate type mappings required by the driver or target
+database system. The driver may also add private keys (with names beginning
+with the drivers reserved prefix, e.g., odbc_xxx).
+
+* Example:
+
+The keys and values in the returned hash can be passed to the various
+bind_param() methods to effectively reproduce a previous param binding.
+For example:
+
+ # assuming $sth1 is a previously prepared statement handle
+ my $sth2 = $dbh->prepare( $sth1->{Statement} );
+ my $ParamValues = $sth1->{ParamValues} || {};
+ my $ParamTypes = $sth1->{ParamTypes} || {};
+ $sth2->bind_param($_, $ParamValues->{$_} $ParamTypes->{$_})
+ for keys %{ {%$ParamValues, %$ParamTypes} };
+ $sth2->execute();
+
+The C<ParamTypes> attribute was added in DBI 1.49. Implementation
+is the responsibility of individual drivers; the DBI layer default
+implementation simply returns undef.
+
+
+=head3 C<ParamArrays>
+
+Type: hash ref, read-only
+
+Returns a reference to a hash containing the values currently bound to
+placeholders with L</execute_array> or L</bind_param_array>. The
+keys of the hash are the 'names' of the placeholders, typically
+integers starting at 1. Returns undef if not supported by the driver
+or no arrays of parameters are bound.
+
+Each key value is an array reference containing a list of the bound
+parameters for that column.
+
+For example:
+
+ $sth = $dbh->prepare("INSERT INTO staff (id, name) values (?,?)");
+ $sth->execute_array({},[1,2], ['fred','dave']);
+ if ($sth->{ParamArrays}) {
+ foreach $param (keys %{$sth->{ParamArrays}}) {
+ printf "Parameters for %s : %s\n", $param,
+ join(",", @{$sth->{ParamArrays}->{$param}});
+ }
+ }
+
+It is possible that the values in the hash returned by C<ParamArrays>
+are not I<exactly> the same as those passed to L</bind_param_array> or
+L</execute_array>. The driver may have slightly modified values in some
+way based on the TYPE the value was bound with. For example a floating
+point value bound as an SQL_INTEGER type may be returned as an
+integer.
+
+It is also possible that the keys in the hash returned by
+C<ParamArrays> are not exactly the same as those implied by the
+prepared statement. For example, DBD::Oracle translates 'C<?>'
+placeholders into 'C<:pN>' where N is a sequence number starting at 1.
+
+=head3 C<RowsInCache>
+
+Type: integer, read-only
+
+If the driver supports a local row cache for C<SELECT> statements, then
+this attribute holds the number of un-fetched rows in the cache. If the
+driver doesn't, then it returns C<undef>. Note that some drivers pre-fetch
+rows on execute, whereas others wait till the first fetch.
+
+See also the L</RowCacheSize> database handle attribute.
+
+=head1 FURTHER INFORMATION
+
+=head2 Catalog Methods
+
+An application can retrieve metadata information from the DBMS by issuing
+appropriate queries on the views of the Information Schema. Unfortunately,
+C<INFORMATION_SCHEMA> views are seldom supported by the DBMS.
+Special methods (catalog methods) are available to return result sets
+for a small but important portion of that metadata:
+
+ column_info
+ foreign_key_info
+ primary_key_info
+ table_info
+ statistics_info
+
+All catalog methods accept arguments in order to restrict the result sets.
+Passing C<undef> to an optional argument does not constrain the search for
+that argument.
+However, an empty string ('') is treated as a regular search criteria
+and will only match an empty value.
+
+B<Note>: SQL/CLI and ODBC differ in the handling of empty strings. An
+empty string will not restrict the result set in SQL/CLI.
+
+Most arguments in the catalog methods accept only I<ordinary values>, e.g.
+the arguments of C<primary_key_info()>.
+Such arguments are treated as a literal string, i.e. the case is significant
+and quote characters are taken literally.
+
+Some arguments in the catalog methods accept I<search patterns> (strings
+containing '_' and/or '%'), e.g. the C<$table> argument of C<column_info()>.
+Passing '%' is equivalent to leaving the argument C<undef>.
+
+B<Caveat>: The underscore ('_') is valid and often used in SQL identifiers.
+Passing such a value to a search pattern argument may return more rows than
+expected!
+To include pattern characters as literals, they must be preceded by an
+escape character which can be achieved with
+
+ $esc = $dbh->get_info( 14 ); # SQL_SEARCH_PATTERN_ESCAPE
+ $search_pattern =~ s/([_%])/$esc$1/g;
+
+The ODBC and SQL/CLI specifications define a way to change the default
+behaviour described above: All arguments (except I<list value arguments>)
+are treated as I<identifier> if the C<SQL_ATTR_METADATA_ID> attribute is
+set to C<SQL_TRUE>.
+I<Quoted identifiers> are very similar to I<ordinary values>, i.e. their
+body (the string within the quotes) is interpreted literally.
+I<Unquoted identifiers> are compared in UPPERCASE.
+
+The DBI (currently) does not support the C<SQL_ATTR_METADATA_ID> attribute,
+i.e. it behaves like an ODBC driver where C<SQL_ATTR_METADATA_ID> is set to
+C<SQL_FALSE>.
+
+
+=head2 Transactions
+
+Transactions are a fundamental part of any robust database system. They
+protect against errors and database corruption by ensuring that sets of
+related changes to the database take place in atomic (indivisible,
+all-or-nothing) units.
+
+This section applies to databases that support transactions and where
+C<AutoCommit> is off. See L</AutoCommit> for details of using C<AutoCommit>
+with various types of databases.
+
+The recommended way to implement robust transactions in Perl
+applications is to use C<RaiseError> and S<C<eval { ... }>>
+(which is very fast, unlike S<C<eval "...">>). For example:
+
+ $dbh->{AutoCommit} = 0; # enable transactions, if possible
+ $dbh->{RaiseError} = 1;
+ eval {
+ foo(...) # do lots of work here
+ bar(...) # including inserts
+ baz(...) # and updates
+ $dbh->commit; # commit the changes if we get this far
+ };
+ if ($@) {
+ warn "Transaction aborted because $@";
+ # now rollback to undo the incomplete changes
+ # but do it in an eval{} as it may also fail
+ eval { $dbh->rollback };
+ # add other application on-error-clean-up code here
+ }
+
+If the C<RaiseError> attribute is not set, then DBI calls would need to be
+manually checked for errors, typically like this:
+
+ $h->method(@args) or die $h->errstr;
+
+With C<RaiseError> set, the DBI will automatically C<die> if any DBI method
+call on that handle (or a child handle) fails, so you don't have to
+test the return value of each method call. See L</RaiseError> for more
+details.
+
+A major advantage of the C<eval> approach is that the transaction will be
+properly rolled back if I<any> code (not just DBI calls) in the inner
+application dies for any reason. The major advantage of using the
+C<$h-E<gt>{RaiseError}> attribute is that all DBI calls will be checked
+automatically. Both techniques are strongly recommended.
+
+After calling C<commit> or C<rollback> many drivers will not let you
+fetch from a previously active C<SELECT> statement handle that's a child
+of the same database handle. A typical way round this is to connect the
+the database twice and use one connection for C<SELECT> statements.
+
+See L</AutoCommit> and L</disconnect> for other important information
+about transactions.
+
+
+=head2 Handling BLOB / LONG / Memo Fields
+
+Many databases support "blob" (binary large objects), "long", or similar
+datatypes for holding very long strings or large amounts of binary
+data in a single field. Some databases support variable length long
+values over 2,000,000,000 bytes in length.
+
+Since values of that size can't usually be held in memory, and because
+databases can't usually know in advance the length of the longest long
+that will be returned from a C<SELECT> statement (unlike other data
+types), some special handling is required.
+
+In this situation, the value of the C<$h-E<gt>{LongReadLen}>
+attribute is used to determine how much buffer space to allocate
+when fetching such fields. The C<$h-E<gt>{LongTruncOk}> attribute
+is used to determine how to behave if a fetched value can't fit
+into the buffer.
+
+See the description of L</LongReadLen> for more information.
+
+When trying to insert long or binary values, placeholders should be used
+since there are often limits on the maximum size of an C<INSERT>
+statement and the L</quote> method generally can't cope with binary
+data. See L</Placeholders and Bind Values>.
+
+
+=head2 Simple Examples
+
+Here's a complete example program to select and fetch some data:
+
+ my $data_source = "dbi::DriverName:db_name";
+ my $dbh = DBI->connect($data_source, $user, $password)
+ or die "Can't connect to $data_source: $DBI::errstr";
+
+ my $sth = $dbh->prepare( q{
+ SELECT name, phone
+ FROM mytelbook
+ }) or die "Can't prepare statement: $DBI::errstr";
+
+ my $rc = $sth->execute
+ or die "Can't execute statement: $DBI::errstr";
+
+ print "Query will return $sth->{NUM_OF_FIELDS} fields.\n\n";
+ print "Field names: @{ $sth->{NAME} }\n";
+
+ while (($name, $phone) = $sth->fetchrow_array) {
+ print "$name: $phone\n";
+ }
+ # check for problems which may have terminated the fetch early
+ die $sth->errstr if $sth->err;
+
+ $dbh->disconnect;
+
+Here's a complete example program to insert some data from a file.
+(This example uses C<RaiseError> to avoid needing to check each call).
+
+ my $dbh = DBI->connect("dbi:DriverName:db_name", $user, $password, {
+ RaiseError => 1, AutoCommit => 0
+ });
+
+ my $sth = $dbh->prepare( q{
+ INSERT INTO table (name, phone) VALUES (?, ?)
+ });
+
+ open FH, "<phone.csv" or die "Unable to open phone.csv: $!";
+ while (<FH>) {
+ chomp;
+ my ($name, $phone) = split /,/;
+ $sth->execute($name, $phone);
+ }
+ close FH;
+
+ $dbh->commit;
+ $dbh->disconnect;
+
+Here's how to convert fetched NULLs (undefined values) into empty strings:
+
+ while($row = $sth->fetchrow_arrayref) {
+ # this is a fast and simple way to deal with nulls:
+ foreach (@$row) { $_ = '' unless defined }
+ print "@$row\n";
+ }
+
+The C<q{...}> style quoting used in these examples avoids clashing with
+quotes that may be used in the SQL statement. Use the double-quote like
+C<qq{...}> operator if you want to interpolate variables into the string.
+See L<perlop/"Quote and Quote-like Operators"> for more details.
+
+=head2 Threads and Thread Safety
+
+Perl 5.7 and later support a new threading model called iThreads.
+(The old "5.005 style" threads are not supported by the DBI.)
+
+In the iThreads model each thread has it's own copy of the perl
+interpreter. When a new thread is created the original perl
+interpreter is 'cloned' to create a new copy for the new thread.
+
+If the DBI and drivers are loaded and handles created before the
+thread is created then it will get a cloned copy of the DBI, the
+drivers and the handles.
+
+However, the internal pointer data within the handles will refer
+to the DBI and drivers in the original interpreter. Using those
+handles in the new interpreter thread is not safe, so the DBI detects
+this and croaks on any method call using handles that don't belong
+to the current thread (except for DESTROY).
+
+Because of this (possibly temporary) restriction, newly created
+threads must make their own connections to the database. Handles
+can't be shared across threads.
+
+But BEWARE, some underlying database APIs (the code the DBD driver
+uses to talk to the database, often supplied by the database vendor)
+are not thread safe. If it's not thread safe, then allowing more
+than one thread to enter the code at the same time may cause
+subtle/serious problems. In some cases allowing more than
+one thread to enter the code, even if I<not> at the same time,
+can cause problems. You have been warned.
+
+Using DBI with perl threads is not yet recommended for production
+environments. For more information see
+L<http://www.perlmonks.org/index.pl?node_id=288022>
+
+Note: There is a bug in perl 5.8.2 when configured with threads
+and debugging enabled (bug #24463) which causes a DBI test to fail.
+
+=head2 Signal Handling and Canceling Operations
+
+[The following only applies to systems with unix-like signal handling.
+I'd welcome additions for other systems, especially Windows.]
+
+The first thing to say is that signal handling in Perl versions less
+than 5.8 is I<not> safe. There is always a small risk of Perl
+crashing and/or core dumping when, or after, handling a signal
+because the signal could arrive and be handled while internal data
+structures are being changed. If the signal handling code
+used those same internal data structures it could cause all manner
+of subtle and not-so-subtle problems. The risk was reduced with
+5.4.4 but was still present in all perls up through 5.8.0.
+
+Beginning in perl 5.8.0 perl implements 'safe' signal handling if
+your system has the POSIX sigaction() routine. Now when a signal
+is delivered perl just makes a note of it but does I<not> run the
+%SIG handler. The handling is 'deferred' until a 'safe' moment.
+
+Although this change made signal handling safe, it also lead to
+a problem with signals being deferred for longer than you'd like.
+If a signal arrived while executing a system call, such as waiting
+for data on a network connection, the signal is noted and then the
+system call that was executing returns with an EINTR error code
+to indicate that it was interrupted. All fine so far.
+
+The problem comes when the code that made the system call sees the
+EINTR code and decides it's going to call it again. Perl doesn't
+do that, but database code sometimes does. If that happens then the
+signal handler doesn't get called until later. Maybe much later.
+
+Fortunately there are ways around this which we'll discuss below.
+Unfortunately they make signals unsafe again.
+
+The two most common uses of signals in relation to the DBI are for
+canceling operations when the user types Ctrl-C (interrupt), and for
+implementing a timeout using C<alarm()> and C<$SIG{ALRM}>.
+
+=over 4
+
+=item Cancel
+
+The DBI provides a C<cancel> method for statement handles. The
+C<cancel> method should abort the current operation and is designed
+to be called from a signal handler. For example:
+
+ $SIG{INT} = sub { $sth->cancel };
+
+However, few drivers implement this (the DBI provides a default
+method that just returns C<undef>) and, even if implemented, there
+is still a possibility that the statement handle, and even the
+parent database handle, will not be usable afterwards.
+
+If C<cancel> returns true, then it has successfully
+invoked the database engine's own cancel function. If it returns false,
+then C<cancel> failed. If it returns C<undef>, then the database
+driver does not have cancel implemented - very few do.
+
+=item Timeout
+
+The traditional way to implement a timeout is to set C<$SIG{ALRM}>
+to refer to some code that will be executed when an ALRM signal
+arrives and then to call alarm($seconds) to schedule an ALRM signal
+to be delivered $seconds in the future. For example:
+
+ eval {
+ local $SIG{ALRM} = sub { die "TIMEOUT\n" }; # N.B. \n required
+ eval {
+ alarm($seconds);
+ ... code to execute with timeout here (which may die) ...
+ };
+ # outer eval catches alarm that might fire JUST before this alarm(0)
+ alarm(0); # cancel alarm (if code ran fast)
+ die "$@" if $@;
+ };
+ if ( $@ eq "TIMEOUT\n" ) { ... }
+ elsif ($@) { ... } # some other error
+
+The first (outer) eval is used to avoid the unlikely but possible
+chance that the "code to execute" dies and the alarm fires before it
+is cancelled. Without the outer eval, if this happened your program
+will die if you have no ALRM handler or a non-local alarm handler
+will be called.
+
+Unfortunately, as described above, this won't always work as expected,
+depending on your perl version and the underlying database code.
+
+With Oracle for instance (DBD::Oracle), if the system which hosts
+the database is down the DBI->connect() call will hang for several
+minutes before returning an error.
+
+=back
+
+The solution on these systems is to use the C<POSIX::sigaction()>
+routine to gain low level access to how the signal handler is installed.
+
+The code would look something like this (for the DBD-Oracle connect()):
+
+ use POSIX qw(:signal_h);
+
+ my $mask = POSIX::SigSet->new( SIGALRM ); # signals to mask in the handler
+ my $action = POSIX::SigAction->new(
+ sub { die "connect timeout\n" }, # the handler code ref
+ $mask,
+ # not using (perl 5.8.2 and later) 'safe' switch or sa_flags
+ );
+ my $oldaction = POSIX::SigAction->new();
+ sigaction( SIGALRM, $action, $oldaction );
+ my $dbh;
+ eval {
+ eval {
+ alarm(5); # seconds before time out
+ $dbh = DBI->connect("dbi:Oracle:$dsn" ... );
+ };
+ alarm(0); # cancel alarm (if connect worked fast)
+ die "$@\n" if $@; # connect died
+ };
+ sigaction( SIGALRM, $oldaction ); # restore original signal handler
+ if ( $@ ) {
+ if ($@ eq "connect timeout\n") {...}
+ else { # connect died }
+ }
+
+See previous example for the reasoning around the double eval.
+
+Similar techniques can be used for canceling statement execution.
+
+Unfortunately, this solution is somewhat messy, and it does I<not> work with
+perl versions less than perl 5.8 where C<POSIX::sigaction()> appears to be broken.
+
+For a cleaner implementation that works across perl versions, see Lincoln Baxter's
+Sys::SigAction module at L<http://search.cpan.org/~lbaxter/Sys-SigAction/>.
+The documentation for Sys::SigAction includes an longer discussion
+of this problem, and a DBD::Oracle test script.
+
+Be sure to read all the signal handling sections of the L<perlipc> manual.
+
+And finally, two more points to keep firmly in mind. Firstly,
+remember that what we've done here is essentially revert to old
+style I<unsafe> handling of these signals. So do as little as
+possible in the handler. Ideally just die(). Secondly, the handles
+in use at the time the signal is handled may not be safe to use
+afterwards.
+
+
+=head2 Subclassing the DBI
+
+DBI can be subclassed and extended just like any other object
+oriented module. Before we talk about how to do that, it's important
+to be clear about the various DBI classes and how they work together.
+
+By default C<$dbh = DBI-E<gt>connect(...)> returns a $dbh blessed
+into the C<DBI::db> class. And the C<$dbh-E<gt>prepare> method
+returns an $sth blessed into the C<DBI::st> class (actually it
+simply changes the last four characters of the calling handle class
+to be C<::st>).
+
+The leading 'C<DBI>' is known as the 'root class' and the extra
+'C<::db>' or 'C<::st>' are the 'handle type suffixes'. If you want
+to subclass the DBI you'll need to put your overriding methods into
+the appropriate classes. For example, if you want to use a root class
+of C<MySubDBI> and override the do(), prepare() and execute() methods,
+then your do() and prepare() methods should be in the C<MySubDBI::db>
+class and the execute() method should be in the C<MySubDBI::st> class.
+
+To setup the inheritance hierarchy the @ISA variable in C<MySubDBI::db>
+should include C<DBI::db> and the @ISA variable in C<MySubDBI::st>
+should include C<DBI::st>. The C<MySubDBI> root class itself isn't
+currently used for anything visible and so, apart from setting @ISA
+to include C<DBI>, it can be left empty.
+
+So, having put your overriding methods into the right classes, and
+setup the inheritance hierarchy, how do you get the DBI to use them?
+You have two choices, either a static method call using the name
+of your subclass:
+
+ $dbh = MySubDBI->connect(...);
+
+or specifying a C<RootClass> attribute:
+
+ $dbh = DBI->connect(..., { RootClass => 'MySubDBI' });
+
+If both forms are used then the attribute takes precedence.
+
+The only differences between the two are that using an explicit
+RootClass attribute will a) make the DBI automatically attempt to load
+a module by that name if the class doesn't exist, and b) won't call
+your MySubDBI::connect() method, if you have one.
+
+When subclassing is being used then, after a successful new
+connect, the DBI->connect method automatically calls:
+
+ $dbh->connected($dsn, $user, $pass, \%attr);
+
+The default method does nothing. The call is made just to simplify
+any post-connection setup that your subclass may want to perform.
+The parameters are the same as passed to DBI->connect.
+If your subclass supplies a connected method, it should be part of the
+MySubDBI::db package.
+
+One more thing to note: you must let the DBI do the handle creation. If you
+want to override the connect() method in your *::dr class then it must still
+call SUPER::connect to get a $dbh to work with. Similarly, an overridden
+prepare() method in *::db must still call SUPER::prepare to get a $sth.
+If you try to create your own handles using bless() then you'll find the DBI
+will reject them with an "is not a DBI handle (has no magic)" error.
+
+Here's a brief example of a DBI subclass. A more thorough example
+can be found in F<t/subclass.t> in the DBI distribution.
+
+ package MySubDBI;
+
+ use strict;
+
+ use DBI;
+ use vars qw(@ISA);
+ @ISA = qw(DBI);
+
+ package MySubDBI::db;
+ use vars qw(@ISA);
+ @ISA = qw(DBI::db);
+
+ sub prepare {
+ my ($dbh, @args) = @_;
+ my $sth = $dbh->SUPER::prepare(@args)
+ or return;
+ $sth->{private_mysubdbi_info} = { foo => 'bar' };
+ return $sth;
+ }
+
+ package MySubDBI::st;
+ use vars qw(@ISA);
+ @ISA = qw(DBI::st);
+
+ sub fetch {
+ my ($sth, @args) = @_;
+ my $row = $sth->SUPER::fetch(@args)
+ or return;
+ do_something_magical_with_row_data($row)
+ or return $sth->set_err(1234, "The magic failed", undef, "fetch");
+ return $row;
+ }
+
+When calling a SUPER::method that returns a handle, be careful to
+check the return value before trying to do other things with it in
+your overridden method. This is especially important if you want to
+set a hash attribute on the handle, as Perl's autovivification will
+bite you by (in)conveniently creating an unblessed hashref, which your
+method will then return with usually baffling results later on like
+the error "dbih_getcom handle HASH(0xa4451a8) is not a DBI handle (has
+no magic". It's best to check right after the call and return undef
+immediately on error, just like DBI would and just like the example
+above.
+
+If your method needs to record an error it should call the set_err()
+method with the error code and error string, as shown in the example
+above. The error code and error string will be recorded in the
+handle and available via C<$h-E<gt>err> and C<$DBI::errstr> etc.
+The set_err() method always returns an undef or empty list as
+appropriate. Since your method should nearly always return an undef
+or empty list as soon as an error is detected it's handy to simply
+return what set_err() returns, as shown in the example above.
+
+If the handle has C<RaiseError>, C<PrintError>, or C<HandleError>
+etc. set then the set_err() method will honour them. This means
+that if C<RaiseError> is set then set_err() won't return in the
+normal way but will 'throw an exception' that can be caught with
+an C<eval> block.
+
+You can stash private data into DBI handles
+via C<$h-E<gt>{private_..._*}>. See the entry under L</ATTRIBUTES
+COMMON TO ALL HANDLES> for info and important caveats.
+
+
+=head1 TRACING
+
+The DBI has a powerful tracing mechanism built in. It enables you
+to see what's going on 'behind the scenes', both within the DBI and
+the drivers you're using.
+
+=head2 Trace Settings
+
+Which details are written to the trace output is controlled by a
+combination of a I<trace level>, an integer from 0 to 15, and a set
+of I<trace flags> that are either on or off. Together these are known
+as the I<trace settings> and are stored together in a single integer.
+For normal use you only need to set the trace level, and generally
+only to a value between 1 and 4.
+
+Each handle has it's own trace settings, and so does the DBI.
+When you call a method the DBI merges the handles settings into its
+own for the duration of the call: the trace flags of the handle are
+OR'd into the trace flags of the DBI, and if the handle has a higher
+trace level then the DBI trace level is raised to match it.
+The previous DBI trace settings are restored when the called method
+returns.
+
+=head2 Trace Levels
+
+Trace I<levels> are as follows:
+
+ 0 - Trace disabled.
+ 1 - Trace top-level DBI method calls returning with results or errors.
+ 2 - As above, adding tracing of top-level method entry with parameters.
+ 3 - As above, adding some high-level information from the driver
+ and some internal information from the DBI.
+ 4 - As above, adding more detailed information from the driver.
+ This is the first level to trace all the rows being fetched.
+ 5 to 15 - As above but with more and more internal information.
+
+Trace level 1 is best for a simple overview of what's happening.
+Trace levels 2 thru 4 a good choice for general purpose tracing.
+Levels 5 and above are best reserved for investigating a specific
+problem, when you need to see "inside" the driver and DBI.
+
+The trace output is detailed and typically very useful. Much of the
+trace output is formatted using the L</neat> function, so strings
+in the trace output may be edited and truncated by that function.
+
+=head2 Trace Flags
+
+Trace I<flags> are used to enable tracing of specific activities
+within the DBI and drivers. The DBI defines some trace flags and
+drivers can define others. DBI trace flag names begin with a capital
+letter and driver specific names begin with a lowercase letter, as
+usual.
+
+Currently the DBI only defines two trace flags:
+
+ ALL - turn on all DBI and driver flags (not recommended)
+ SQL - trace SQL statements executed
+ (not yet implemented in DBI but implemented in some DBDs)
+ CON - trace connection process
+ ENC - trace encoding (unicode translations etc)
+ (not yet implemented in DBI but implemented in some DBDs)
+ DBD - trace only DBD messages
+ (not implemented by all DBDs yet)
+ TXN - trace transactions
+ (not implemented in all DBDs yet)
+
+The L</parse_trace_flags> and L</parse_trace_flag> methods are used
+to convert trace flag names into the corresponding integer bit flags.
+
+=head2 Enabling Trace
+
+The C<$h-E<gt>trace> method sets the trace settings for a handle
+and C<DBI-E<gt>trace> does the same for the DBI.
+
+In addition to the L</trace> method, you can enable the same trace
+information, and direct the output to a file, by setting the
+C<DBI_TRACE> environment variable before starting Perl.
+See L</DBI_TRACE> for more information.
+
+Finally, you can set, or get, the trace settings for a handle using
+the C<TraceLevel> attribute.
+
+All of those methods use parse_trace_flags() and so allow you set
+both the trace level and multiple trace flags by using a string
+containing the trace level and/or flag names separated by vertical
+bar ("C<|>") or comma ("C<,>") characters. For example:
+
+ local $h->{TraceLevel} = "3|SQL|foo";
+
+=head2 Trace Output
+
+Initially trace output is written to C<STDERR>. Both the
+C<$h-E<gt>trace> and C<DBI-E<gt>trace> methods take an optional
+$trace_file parameter, which may be either the name of a file to be
+opened by DBI in append mode, or a reference to an existing writable
+(possibly layered) filehandle. If $trace_file is a filename,
+and can be opened in append mode, or $trace_file is a writable
+filehandle, then I<all> trace output (currently including that from
+other handles) is redirected to that file. A warning is generated
+if $trace_file can't be opened or is not writable.
+
+Further calls to trace() without $trace_file do not alter where
+the trace output is sent. If $trace_file is undefined, then
+trace output is sent to C<STDERR> and, if the prior trace was opened with
+$trace_file as a filename, the previous trace file is closed; if $trace_file was
+a filehandle, the filehandle is B<not> closed.
+
+B<NOTE>: If $trace_file is specified as a filehandle, the filehandle
+should not be closed until all DBI operations are completed, or the
+application has reset the trace file via another call to
+C<trace()> that changes the trace file.
+
+=head2 Tracing to Layered Filehandles
+
+B<NOTE>:
+
+=over 4
+
+=item *
+Tied filehandles are not currently supported, as
+tie operations are not available to the PerlIO
+methods used by the DBI.
+
+=item *
+PerlIO layer support requires Perl version 5.8 or higher.
+
+=back
+
+As of version 5.8, Perl provides the ability to layer various
+"disciplines" on an open filehandle via the L<PerlIO> module.
+
+A simple example of using PerlIO layers is to use a scalar as the output:
+
+ my $scalar = '';
+ open( my $fh, "+>:scalar", \$scalar );
+ $dbh->trace( 2, $fh );
+
+Now all trace output is simply appended to $scalar.
+
+A more complex application of tracing to a layered filehandle is the
+use of a custom layer (I<Refer to >L<Perlio::via> I<for details
+on creating custom PerlIO layers.>). Consider an application with the
+following logger module:
+
+ package MyFancyLogger;
+
+ sub new
+ {
+ my $self = {};
+ my $fh;
+ open $fh, '>', 'fancylog.log';
+ $self->{_fh} = $fh;
+ $self->{_buf} = '';
+ return bless $self, shift;
+ }
+
+ sub log
+ {
+ my $self = shift;
+ return unless exists $self->{_fh};
+ my $fh = $self->{_fh};
+ $self->{_buf} .= shift;
+ #
+ # DBI feeds us pieces at a time, so accumulate a complete line
+ # before outputing
+ #
+ print $fh "At ", scalar localtime(), ':', $self->{_buf}, "\n" and
+ $self->{_buf} = ''
+ if $self->{_buf}=~tr/\n//;
+ }
+
+ sub close {
+ my $self = shift;
+ return unless exists $self->{_fh};
+ my $fh = $self->{_fh};
+ print $fh "At ", scalar localtime(), ':', $self->{_buf}, "\n" and
+ $self->{_buf} = ''
+ if $self->{_buf};
+ close $fh;
+ delete $self->{_fh};
+ }
+
+ 1;
+
+To redirect DBI traces to this logger requires creating
+a package for the layer:
+
+ package PerlIO::via::MyFancyLogLayer;
+
+ sub PUSHED
+ {
+ my ($class,$mode,$fh) = @_;
+ my $logger;
+ return bless \$logger,$class;
+ }
+
+ sub OPEN {
+ my ($self, $path, $mode, $fh) = @_;
+ #
+ # $path is actually our logger object
+ #
+ $$self = $path;
+ return 1;
+ }
+
+ sub WRITE
+ {
+ my ($self, $buf, $fh) = @_;
+ $$self->log($buf);
+ return length($buf);
+ }
+
+ sub CLOSE {
+ my $self = shift;
+ $$self->close();
+ return 0;
+ }
+
+ 1;
+
+
+The application can then cause DBI traces to be routed to the
+logger using
+
+ use PerlIO::via::MyFancyLogLayer;
+
+ open my $fh, '>:via(MyFancyLogLayer)', MyFancyLogger->new();
+
+ $dbh->trace('SQL', $fh);
+
+Now all trace output will be processed by MyFancyLogger's
+log() method.
+
+=head2 Trace Content
+
+Many of the values embedded in trace output are formatted using the neat()
+utility function. This means they may be quoted, sanitized, and possibly
+truncated if longer than C<$DBI::neat_maxlen>. See L</neat> for more details.
+
+=head2 Tracing Tips
+
+You can add tracing to your own application code using the L</trace_msg> method.
+
+It can sometimes be handy to compare trace files from two different runs of the
+same script. However using a tool like C<diff> on the original log output
+doesn't work well because the trace file is full of object addresses that may
+differ on each run.
+
+The DBI includes a handy utility called dbilogstrip that can be used to
+'normalize' the log content. It can be used as a filter like this:
+
+ DBI_TRACE=2 perl yourscript.pl ...args1... 2>&1 | dbilogstrip > dbitrace1.log
+ DBI_TRACE=2 perl yourscript.pl ...args2... 2>&1 | dbilogstrip > dbitrace2.log
+ diff -u dbitrace1.log dbitrace2.log
+
+See L<dbilogstrip> for more information.
+
+=head1 DBI ENVIRONMENT VARIABLES
+
+The DBI module recognizes a number of environment variables, but most of
+them should not be used most of the time.
+It is better to be explicit about what you are doing to avoid the need
+for environment variables, especially in a web serving system where web
+servers are stingy about which environment variables are available.
+
+=head2 DBI_DSN
+
+The DBI_DSN environment variable is used by DBI->connect if you do not
+specify a data source when you issue the connect.
+It should have a format such as "dbi:Driver:databasename".
+
+=head2 DBI_DRIVER
+
+The DBI_DRIVER environment variable is used to fill in the database
+driver name in DBI->connect if the data source string starts "dbi::"
+(thereby omitting the driver).
+If DBI_DSN omits the driver name, DBI_DRIVER can fill the gap.
+
+=head2 DBI_AUTOPROXY
+
+The DBI_AUTOPROXY environment variable takes a string value that starts
+"dbi:Proxy:" and is typically followed by "hostname=...;port=...".
+It is used to alter the behaviour of DBI->connect.
+For full details, see DBI::Proxy documentation.
+
+=head2 DBI_USER
+
+The DBI_USER environment variable takes a string value that is used as
+the user name if the DBI->connect call is given undef (as distinct from
+an empty string) as the username argument.
+Be wary of the security implications of using this.
+
+=head2 DBI_PASS
+
+The DBI_PASS environment variable takes a string value that is used as
+the password if the DBI->connect call is given undef (as distinct from
+an empty string) as the password argument.
+Be extra wary of the security implications of using this.
+
+=head2 DBI_DBNAME (obsolete)
+
+The DBI_DBNAME environment variable takes a string value that is used only when the
+obsolescent style of DBI->connect (with driver name as fourth parameter) is used, and
+when no value is provided for the first (database name) argument.
+
+=head2 DBI_TRACE
+
+The DBI_TRACE environment variable specifies the global default
+trace settings for the DBI at startup. Can also be used to direct
+trace output to a file. When the DBI is loaded it does:
+
+ DBI->trace(split /=/, $ENV{DBI_TRACE}, 2) if $ENV{DBI_TRACE};
+
+So if C<DBI_TRACE> contains an "C<=>" character then what follows
+it is used as the name of the file to append the trace to.
+
+output appended to that file. If the name begins with a number
+followed by an equal sign (C<=>), then the number and the equal sign are
+stripped off from the name, and the number is used to set the trace
+level. For example:
+
+ DBI_TRACE=1=dbitrace.log perl your_test_script.pl
+
+On Unix-like systems using a Bourne-like shell, you can do this easily
+on the command line:
+
+ DBI_TRACE=2 perl your_test_script.pl
+
+See L</TRACING> for more information.
+
+=head2 PERL_DBI_DEBUG (obsolete)
+
+An old variable that should no longer be used; equivalent to DBI_TRACE.
+
+=head2 DBI_PROFILE
+
+The DBI_PROFILE environment variable can be used to enable profiling
+of DBI method calls. See L<DBI::Profile> for more information.
+
+=head2 DBI_PUREPERL
+
+The DBI_PUREPERL environment variable can be used to enable the
+use of DBI::PurePerl. See L<DBI::PurePerl> for more information.
+
+=head1 WARNING AND ERROR MESSAGES
+
+=head2 Fatal Errors
+
+=over 4
+
+=item Can't call method "prepare" without a package or object reference
+
+The C<$dbh> handle you're using to call C<prepare> is probably undefined because
+the preceding C<connect> failed. You should always check the return status of
+DBI methods, or use the L</RaiseError> attribute.
+
+=item Can't call method "execute" without a package or object reference
+
+The C<$sth> handle you're using to call C<execute> is probably undefined because
+the preceding C<prepare> failed. You should always check the return status of
+DBI methods, or use the L</RaiseError> attribute.
+
+=item DBI/DBD internal version mismatch
+
+The DBD driver module was built with a different version of DBI than
+the one currently being used. You should rebuild the DBD module under
+the current version of DBI.
+
+(Some rare platforms require "static linking". On those platforms, there
+may be an old DBI or DBD driver version actually embedded in the Perl
+executable being used.)
+
+=item DBD driver has not implemented the AutoCommit attribute
+
+The DBD driver implementation is incomplete. Consult the author.
+
+=item Can't [sg]et %s->{%s}: unrecognised attribute
+
+You attempted to set or get an unknown attribute of a handle. Make
+sure you have spelled the attribute name correctly; case is significant
+(e.g., "Autocommit" is not the same as "AutoCommit").
+
+=back
+
+=head1 Pure-Perl DBI
+
+A pure-perl emulation of the DBI is included in the distribution
+for people using pure-perl drivers who, for whatever reason, can't
+install the compiled DBI. See L<DBI::PurePerl>.
+
+=head1 SEE ALSO
+
+=head2 Driver and Database Documentation
+
+Refer to the documentation for the DBD driver that you are using.
+
+Refer to the SQL Language Reference Manual for the database engine that you are using.
+
+=head2 ODBC and SQL/CLI Standards Reference Information
+
+More detailed information about the semantics of certain DBI methods
+that are based on ODBC and SQL/CLI standards is available on-line
+via microsoft.com, for ODBC, and www.jtc1sc32.org for the SQL/CLI
+standard:
+
+ DBI method ODBC function SQL/CLI Working Draft
+ ---------- ------------- ---------------------
+ column_info SQLColumns Page 124
+ foreign_key_info SQLForeignKeys Page 163
+ get_info SQLGetInfo Page 214
+ primary_key_info SQLPrimaryKeys Page 254
+ table_info SQLTables Page 294
+ type_info SQLGetTypeInfo Page 239
+ statistics_info SQLStatistics
+
+To find documentation on the ODBC function you can use
+the MSDN search facility at:
+
+ http://msdn.microsoft.com/Search
+
+and search for something like C<"SQLColumns returns">.
+
+And for SQL/CLI standard information on SQLColumns you'd read page 124 of
+the (very large) SQL/CLI Working Draft available from:
+
+ http://jtc1sc32.org/doc/N0701-0750/32N0744T.pdf
+
+=head2 Standards Reference Information
+
+A hyperlinked, browsable version of the BNF syntax for SQL92 (plus
+Oracle 7 SQL and PL/SQL) is available here:
+
+ http://cui.unige.ch/db-research/Enseignement/analyseinfo/SQL92/BNFindex.html
+
+A BNF syntax for SQL3 is available here:
+
+ http://www.sqlstandards.org/SC32/WG3/Progression_Documents/Informal_working_drafts/iso-9075-2-1999.bnf
+
+The following links provide further useful information about SQL.
+Some of these are rather dated now but may still be useful.
+
+ http://www.jcc.com/SQLPages/jccs_sql.htm
+ http://www.contrib.andrew.cmu.edu/~shadow/sql.html
+ http://www.altavista.com/query?q=sql+tutorial
+
+
+=head2 Books and Articles
+
+Programming the Perl DBI, by Alligator Descartes and Tim Bunce.
+L<http://books.perl.org/book/154>
+
+Programming Perl 3rd Ed. by Larry Wall, Tom Christiansen & Jon Orwant.
+L<http://books.perl.org/book/134>
+
+Learning Perl by Randal Schwartz.
+L<http://books.perl.org/book/101>
+
+Details of many other books related to perl can be found at L<http://books.perl.org>
+
+=head2 Perl Modules
+
+Index of DBI related modules available from CPAN:
+
+ http://search.cpan.org/search?mode=module&query=DBIx%3A%3A
+ http://search.cpan.org/search?mode=doc&query=DBI
+
+For a good comparison of RDBMS-OO mappers and some OO-RDBMS mappers
+(including Class::DBI, Alzabo, and DBIx::RecordSet in the former
+category and Tangram and SPOPS in the latter) see the Perl
+Object-Oriented Persistence project pages at:
+
+ http://poop.sourceforge.net
+
+A similar page for Java toolkits can be found at:
+
+ http://c2.com/cgi-bin/wiki?ObjectRelationalToolComparison
+
+=head2 Mailing List
+
+The I<dbi-users> mailing list is the primary means of communication among
+users of the DBI and its related modules. For details send email to:
+
+ dbi-users-help@perl.org
+
+There are typically between 700 and 900 messages per month. You have
+to subscribe in order to be able to post. However you can opt for a
+'post-only' subscription.
+
+Mailing list archives (of variable quality) are held at:
+
+ http://groups.google.com/groups?group=perl.dbi.users
+ http://www.xray.mpe.mpg.de/mailing-lists/dbi/
+ http://www.mail-archive.com/dbi-users%40perl.org/
+
+=head2 Assorted Related WWW Links
+
+The DBI "Home Page":
+
+ http://dbi.perl.org/
+
+Other DBI related links:
+
+ http://tegan.deltanet.com/~phlip/DBUIdoc.html
+ http://dc.pm.org/perl_db.html
+ http://wdvl.com/Authoring/DB/Intro/toc.html
+ http://www.hotwired.com/webmonkey/backend/tutorials/tutorial1.html
+ http://bumppo.net/lists/macperl/1999/06/msg00197.html
+ http://www.perlmonks.org/?node=DBI%20recipes
+ http://www.perlmonks.org/?node=Speeding%20up%20the%20DBI
+
+Other database related links:
+
+ http://www.jcc.com/sql_stnd.html
+ http://cuiwww.unige.ch/OSG/info/FreeDB/FreeDB.home.html
+ http://www.connectionstrings.com/
+
+Security, especially the "SQL Injection" attack:
+
+ http://www.ngssoftware.com/research/papers.html
+ http://www.ngssoftware.com/papers/advanced_sql_injection.pdf
+ http://www.ngssoftware.com/papers/more_advanced_sql_injection.pdf
+ http://www.esecurityplanet.com/trends/article.php/2243461
+ http://www.spidynamics.com/papers/SQLInjectionWhitePaper.pdf
+ http://www.imperva.com/application_defense_center/white_papers/blind_sql_server_injection.html
+ http://online.securityfocus.com/infocus/1644
+
+Commercial and Data Warehouse Links
+
+ http://www.dwinfocenter.org
+ http://www.datawarehouse.com
+ http://www.datamining.org
+ http://www.olapcouncil.org
+ http://www.idwa.org
+ http://www.knowledgecenters.org/dwcenter.asp
+
+Recommended Perl Programming Links
+
+ http://language.perl.com/style/
+
+
+=head2 FAQ
+
+See L<http://faq.dbi-support.com/>
+
+=head1 AUTHORS
+
+DBI by Tim Bunce, L<http://www.tim.bunce.name>
+
+This pod text by Tim Bunce, J. Douglas Dunlop, Jonathan Leffler and others.
+Perl by Larry Wall and the C<perl5-porters>.
+
+=head1 COPYRIGHT
+
+The DBI module is Copyright (c) 1994-2012 Tim Bunce. Ireland.
+All rights reserved.
+
+You may distribute under the terms of either the GNU General Public
+License or the Artistic License, as specified in the Perl 5.10.0 README file.
+
+=head1 SUPPORT / WARRANTY
+
+The DBI is free Open Source software. IT COMES WITHOUT WARRANTY OF ANY KIND.
+
+=head2 Support
+
+My consulting company, Data Plan Services, offers annual and
+multi-annual support contracts for the DBI. These provide sustained
+support for DBI development, and sustained value for you in return.
+Contact me for details.
+
+=head2 Sponsor Enhancements
+
+The DBI Roadmap is available at L<http://search.cpan.org/~timb/DBI/Roadmap.pod>
+
+If your company would benefit from a specific new DBI feature,
+please consider sponsoring its development. Work is performed
+rapidly, and usually on a fixed-price payment-on-delivery basis.
+Contact me for details.
+
+Using such targeted financing allows you to contribute to DBI
+development, and rapidly get something specific and valuable in return.
+
+=head1 ACKNOWLEDGEMENTS
+
+I would like to acknowledge the valuable contributions of the many
+people I have worked with on the DBI project, especially in the early
+years (1992-1994). In no particular order: Kevin Stock, Buzz Moschetti,
+Kurt Andersen, Ted Lemon, William Hails, Garth Kennedy, Michael Peppler,
+Neil S. Briscoe, Jeff Urlwin, David J. Hughes, Jeff Stander,
+Forrest D Whitcher, Larry Wall, Jeff Fried, Roy Johnson, Paul Hudson,
+Georg Rehfeld, Steve Sizemore, Ron Pool, Jon Meek, Tom Christiansen,
+Steve Baumgarten, Randal Schwartz, and a whole lot more.
+
+Then, of course, there are the poor souls who have struggled through
+untold and undocumented obstacles to actually implement DBI drivers.
+Among their ranks are Jochen Wiedmann, Alligator Descartes, Jonathan
+Leffler, Jeff Urlwin, Michael Peppler, Henrik Tougaard, Edwin Pratomo,
+Davide Migliavacca, Jan Pazdziora, Peter Haworth, Edmund Mergl, Steve
+Williams, Thomas Lowery, and Phlip Plumlee. Without them, the DBI would
+not be the practical reality it is today. I'm also especially grateful
+to Alligator Descartes for starting work on the first edition of the
+"Programming the Perl DBI" book and letting me jump on board.
+
+The DBI and DBD::Oracle were originally developed while I was Technical
+Director (CTO) of Ingeneering in the UK (L<http://www.ig.co.uk>) (formerly known as the
+Paul Ingram Group). So I'd especially like to thank Paul for his generosity
+and vision in supporting this work for many years.
+
+A couple of specific DBI features have been sponsored by enlightened companies:
+
+The development of the swap_inner_handle() method was sponsored by BizRate.com (L<http://BizRate.com>)
+
+The development of DBD::Gofer and related modules was sponsored by
+Shopzilla.com (L<http://Shopzilla.com>), where I currently work.
+
+
+=head1 CONTRIBUTING
+
+As you can see above, many people have contributed to the DBI and
+drivers in many ways over many years.
+
+If you'd like to help then see L<http://dbi.perl.org/contributing>
+and L<http://search.cpan.org/~timb/DBI/Roadmap.pod>
+
+If you'd like the DBI to do something new or different then a good way
+to make that happen is to do it yourself and send me a patch to the
+source code that shows the changes. (But read "Speak before you patch"
+below.)
+
+=head2 Browsing the source code repository
+
+Use http://svn.perl.org/modules/dbi/trunk (basic)
+or http://svn.perl.org/viewcvs/modules/ (more useful)
+
+=head2 How to create a patch using Subversion
+
+The DBI source code is maintained using Subversion (a replacement
+for CVS, see L<http://subversion.tigris.org/>). To access the source
+you'll need to install a Subversion client. Then, to get the source
+code, do:
+
+ svn checkout http://svn.perl.org/modules/dbi/trunk
+
+If it prompts for a username and password use your perl.org account
+if you have one, else just 'guest' and 'guest'. The source code will
+be in a new subdirectory called C<trunk>.
+
+To keep informed about changes to the source you can send an empty email
+to svn-commit-modules-dbi-subscribe@perl.org after which you'll get an email
+with the change log message and diff of each change checked-in to the source.
+
+After making your changes you can generate a patch file, but before
+you do, make sure your source is still up to date using:
+
+ svn update
+
+If you get any conflicts reported you'll need to fix them first.
+Then generate the patch file from within the C<trunk> directory using:
+
+ svn diff > foo.patch
+
+Read the patch file, as a sanity check, and then email it to dbi-dev@perl.org.
+
+=head2 How to create a patch without Subversion
+
+Unpack a fresh copy of the distribution:
+
+ tar xfz DBI-1.40.tar.gz
+
+Rename the newly created top level directory:
+
+ mv DBI-1.40 DBI-1.40.your_foo
+
+Edit the contents of DBI-1.40.your_foo/* till it does what you want.
+
+Test your changes and then remove all temporary files:
+
+ make test && make distclean
+
+Go back to the directory you originally unpacked the distribution:
+
+ cd ..
+
+Unpack I<another> copy of the original distribution you started with:
+
+ tar xfz DBI-1.40.tar.gz
+
+Then create a patch file by performing a recursive C<diff> on the two
+top level directories:
+
+ diff -r -u DBI-1.40 DBI-1.40.your_foo > DBI-1.40.your_foo.patch
+
+=head2 Speak before you patch
+
+For anything non-trivial or possibly controversial it's a good idea
+to discuss (on dbi-dev@perl.org) the changes you propose before
+actually spending time working on them. Otherwise you run the risk
+of them being rejected because they don't fit into some larger plans
+you may not be aware of.
+
+=head1 TRANSLATIONS
+
+A German translation of this manual (possibly slightly out of date) is
+available, thanks to O'Reilly, at:
+
+ http://www.oreilly.de/catalog/perldbiger/
+
+Some other translations:
+
+ http://cronopio.net/perl/ - Spanish
+ http://member.nifty.ne.jp/hippo2000/dbimemo.htm - Japanese
+
+
+=head1 TRAINING
+
+References to DBI related training resources. No recommendation implied.
+
+ http://www.treepax.co.uk/
+ http://www.keller.com/dbweb/
+
+(If you offer professional DBI related training services,
+please send me your details so I can add them here.)
+
+=head1 OTHER RELATED WORK AND PERL MODULES
+
+=over 4
+
+=item Apache::DBI by E.Mergl@bawue.de
+
+To be used with the Apache daemon together with an embedded Perl
+interpreter like C<mod_perl>. Establishes a database connection which
+remains open for the lifetime of the HTTP daemon. This way the CGI
+connect and disconnect for every database access becomes superfluous.
+
+=item SQL Parser
+
+See also the L<SQL::Statement> module, SQL parser and engine.
+
+=back
+
+=cut
+
+# LocalWords: DBI
diff --git a/DBI.xs b/DBI.xs
new file mode 100644
index 0000000..514007a
--- /dev/null
+++ b/DBI.xs
@@ -0,0 +1,5560 @@
+/* vim: ts=8:sw=4:expandtab
+ *
+ * $Id: DBI.xs 15304 2012-05-14 08:17:22Z mjevans $
+ *
+ * Copyright (c) 1994-2012 Tim Bunce Ireland.
+ *
+ * See COPYRIGHT section in DBI.pm for usage and distribution rights.
+ */
+#define NEED_grok_number
+#define NEED_grok_numeric_radix
+#define NEED_newRV_noinc
+#define NEED_sv_2pv_flags
+
+#define IN_DBI_XS 1 /* see DBIXS.h */
+#define PERL_NO_GET_CONTEXT
+
+#include "DBIXS.h" /* DBI public interface for DBD's written in C */
+
+# if (defined(_WIN32) && (! defined(HAS_GETTIMEOFDAY)))
+#include <sys/timeb.h>
+# endif
+
+/* The XS dispatcher code can optimize calls to XS driver methods,
+ * bypassing the usual call_sv() and argument handling overheads.
+ * Just-in-case it causes problems there's an (undocumented) way
+ * to disable it by setting an env var.
+ */
+static int use_xsbypass = 1; /* set in dbi_bootinit() */
+
+#ifndef CvISXSUB
+#define CvISXSUB(sv) CvXSUB(sv)
+#endif
+
+#define DBI_MAGIC '~'
+
+/* HvMROMETA introduced in 5.9.5, but mro_meta_init not exported in 5.10.0 */
+#if (PERL_VERSION < 10)
+# define MY_cache_gen(stash) 0
+#else
+# if ((PERL_VERSION == 10) && (PERL_SUBVERSION == 0))
+# define MY_cache_gen(stash) \
+ (HvAUX(stash)->xhv_mro_meta \
+ ? HvAUX(stash)->xhv_mro_meta->cache_gen \
+ : 0)
+# else
+# define MY_cache_gen(stash) HvMROMETA(stash)->cache_gen
+# endif
+#endif
+
+/* If the tests fail with errors about 'setlinebuf' then try */
+/* deleting the lines in the block below except the setvbuf one */
+#ifndef PerlIO_setlinebuf
+#ifdef HAS_SETLINEBUF
+#define PerlIO_setlinebuf(f) setlinebuf(f)
+#else
+#ifndef USE_PERLIO
+#define PerlIO_setlinebuf(f) setvbuf(f, Nullch, _IOLBF, 0)
+#endif
+#endif
+#endif
+
+#ifndef CopFILEGV
+# define CopFILEGV(cop) cop->cop_filegv
+# define CopLINE(cop) cop->cop_line
+# define CopSTASH(cop) cop->cop_stash
+# define CopSTASHPV(cop) (CopSTASH(cop) ? HvNAME(CopSTASH(cop)) : Nullch)
+#endif
+#ifndef PERL_GET_THX
+#define PERL_GET_THX ((void*)0)
+#endif
+#ifndef PerlProc_getpid
+#define PerlProc_getpid() getpid()
+extern Pid_t getpid (void);
+#endif
+#ifndef aTHXo_
+#define aTHXo_
+#endif
+
+#if (PERL_VERSION < 8) || ((PERL_VERSION == 8) && (PERL_SUBVERSION == 0))
+#define DBI_save_hv_fetch_ent
+#endif
+
+/* prior to 5.8.9: when a CV is duped, the mg dup method is called,
+ * then *afterwards*, any_ptr is copied from the old CV to the new CV.
+ * This wipes out anything which the dup method did to any_ptr.
+ * This needs working around */
+#if defined(USE_ITHREADS) && (PERL_VERSION == 8) && (PERL_SUBVERSION < 9)
+# define BROKEN_DUP_ANY_PTR
+#endif
+
+/* types of method name */
+
+typedef enum {
+ methtype_ordinary, /* nothing special about this method name */
+ methtype_DESTROY,
+ methtype_FETCH,
+ methtype_can,
+ methtype_fetch_star, /* fetch*, i.e. fetch() or fetch_...() */
+ methtype_set_err
+} meth_types;
+
+
+static imp_xxh_t *dbih_getcom _((SV *h));
+static imp_xxh_t *dbih_getcom2 _((pTHX_ SV *h, MAGIC **mgp));
+static void dbih_clearcom _((imp_xxh_t *imp_xxh));
+static int dbih_logmsg _((imp_xxh_t *imp_xxh, const char *fmt, ...));
+static SV *dbih_make_com _((SV *parent_h, imp_xxh_t *p_imp_xxh, const char *imp_class, STRLEN imp_size, STRLEN extra, SV *copy));
+static SV *dbih_make_fdsv _((SV *sth, const char *imp_class, STRLEN imp_size, const char *col_name));
+static AV *dbih_get_fbav _((imp_sth_t *imp_sth));
+static SV *dbih_event _((SV *h, const char *name, SV*, SV*));
+static int dbih_set_attr_k _((SV *h, SV *keysv, int dbikey, SV *valuesv));
+static SV *dbih_get_attr_k _((SV *h, SV *keysv, int dbikey));
+static int dbih_sth_bind_col _((SV *sth, SV *col, SV *ref, SV *attribs));
+
+static int set_err_char _((SV *h, imp_xxh_t *imp_xxh, const char *err_c, IV err_i, const char *errstr, const char *state, const char *method));
+static int set_err_sv _((SV *h, imp_xxh_t *imp_xxh, SV *err, SV *errstr, SV *state, SV *method));
+static int quote_type _((int sql_type, int p, int s, int *base_type, void *v));
+static int sql_type_cast_svpv _((pTHX_ SV *sv, int sql_type, U32 flags, void *v));
+static I32 dbi_hash _((const char *string, long i));
+static void dbih_dumphandle _((pTHX_ SV *h, const char *msg, int level));
+static int dbih_dumpcom _((pTHX_ imp_xxh_t *imp_xxh, const char *msg, int level));
+static int dbi_ima_free(pTHX_ SV* sv, MAGIC* mg);
+#if defined(USE_ITHREADS) && !defined(BROKEN_DUP_ANY_PTR)
+static int dbi_ima_dup(pTHX_ MAGIC* mg, CLONE_PARAMS *param);
+#endif
+char *neatsvpv _((SV *sv, STRLEN maxlen));
+SV * preparse(SV *dbh, const char *statement, IV ps_return, IV ps_accept, void *foo);
+static meth_types get_meth_type(const char * const name);
+
+struct imp_drh_st { dbih_drc_t com; };
+struct imp_dbh_st { dbih_dbc_t com; };
+struct imp_sth_st { dbih_stc_t com; };
+struct imp_fdh_st { dbih_fdc_t com; };
+
+/* identify the type of a method name for dispatch behaviour */
+/* (should probably be folded into the IMA flags mechanism) */
+
+static meth_types
+get_meth_type(const char * const name)
+{
+ switch (name[0]) {
+ case 'D':
+ if strEQ(name,"DESTROY")
+ return methtype_DESTROY;
+ break;
+ case 'F':
+ if strEQ(name,"FETCH")
+ return methtype_FETCH;
+ break;
+ case 'c':
+ if strEQ(name,"can")
+ return methtype_can;
+ break;
+ case 'f':
+ if strnEQ(name,"fetch", 5) /* fetch* */
+ return methtype_fetch_star;
+ break;
+ case 's':
+ if strEQ(name,"set_err")
+ return methtype_set_err;
+ break;
+ }
+ return methtype_ordinary;
+}
+
+
+/* Internal Method Attributes (attached to dispatch methods when installed) */
+/* NOTE: when adding SVs to dbi_ima_t, update dbi_ima_dup() dbi_ima_free()
+ * to ensure that they are duped and correctly ref-counted */
+
+typedef struct dbi_ima_st {
+ U8 minargs;
+ U8 maxargs;
+ IV hidearg;
+ /* method_trace controls tracing of method calls in the dispatcher:
+ - if the current trace flags include a trace flag in method_trace
+ then set trace_level to min(2,trace_level) for duration of the call.
+ - else, if trace_level < (method_trace & DBIc_TRACE_LEVEL_MASK)
+ then don't trace the call
+ */
+ U32 method_trace;
+ const char *usage_msg;
+ U32 flags;
+ meth_types meth_type;
+
+ /* cached outer to inner method mapping */
+ HV *stash; /* the stash we found the GV in */
+ GV *gv; /* the GV containing the inner sub */
+ U32 generation; /* cache invalidation */
+#ifdef BROKEN_DUP_ANY_PTR
+ PerlInterpreter *my_perl; /* who owns this struct */
+#endif
+
+} dbi_ima_t;
+
+/* These values are embedded in the data passed to install_method */
+#define IMA_HAS_USAGE 0x00000001 /* check parameter usage */
+#define IMA_FUNC_REDIRECT 0x00000002 /* is $h->func(..., "method") */
+#define IMA_KEEP_ERR 0x00000004 /* don't reset err & errstr */
+#define IMA_KEEP_ERR_SUB 0x00000008 /* '' if in a nested call */
+#define IMA_NO_TAINT_IN 0x00000010 /* don't check for tainted args */
+#define IMA_NO_TAINT_OUT 0x00000020 /* don't taint results */
+#define IMA_COPY_UP_STMT 0x00000040 /* copy sth Statement to dbh */
+#define IMA_END_WORK 0x00000080 /* method is commit or rollback */
+#define IMA_STUB 0x00000100 /* donothing eg $dbh->connected */
+#define IMA_CLEAR_STMT 0x00000200 /* clear Statement before call */
+#define IMA_UNRELATED_TO_STMT 0x00000400 /* profile as empty Statement */
+#define IMA_NOT_FOUND_OKAY 0x00000800 /* no error if not found */
+#define IMA_EXECUTE 0x00001000 /* do/execute: DBIcf_Executed */
+#define IMA_SHOW_ERR_STMT 0x00002000 /* dbh meth relates to Statement*/
+#define IMA_HIDE_ERR_PARAMVALUES 0x00004000 /* ParamValues are not relevant */
+#define IMA_IS_FACTORY 0x00008000 /* new h ie connect and prepare */
+#define IMA_CLEAR_CACHED_KIDS 0x00010000 /* clear CachedKids before call */
+
+#define DBIc_STATE_adjust(imp_xxh, state) \
+ (SvOK(state) /* SQLSTATE is implemented by driver */ \
+ ? (strEQ(SvPV_nolen(state),"00000") ? &PL_sv_no : sv_mortalcopy(state))\
+ : (SvTRUE(DBIc_ERR(imp_xxh)) \
+ ? sv_2mortal(newSVpv("S1000",5)) /* General error */ \
+ : &PL_sv_no) /* Success ("00000") */ \
+ )
+
+#define DBI_LAST_HANDLE g_dbi_last_h /* special fake inner handle */
+#define DBI_IS_LAST_HANDLE(h) ((DBI_LAST_HANDLE) == SvRV(h))
+#define DBI_SET_LAST_HANDLE(h) ((DBI_LAST_HANDLE) = SvRV(h))
+#define DBI_UNSET_LAST_HANDLE ((DBI_LAST_HANDLE) = &PL_sv_undef)
+#define DBI_LAST_HANDLE_OK ((DBI_LAST_HANDLE) != &PL_sv_undef)
+
+#define DBIS_TRACE_LEVEL (DBIS->debug & DBIc_TRACE_LEVEL_MASK)
+#define DBIS_TRACE_FLAGS (DBIS->debug) /* includes level */
+
+#ifdef PERL_LONG_MAX
+#define MAX_LongReadLen PERL_LONG_MAX
+#else
+#define MAX_LongReadLen 2147483647L
+#endif
+
+#ifdef DBI_USE_THREADS
+static char *dbi_build_opt = "-ithread";
+#else
+static char *dbi_build_opt = "-nothread";
+#endif
+
+/* 32 bit magic FNV-0 and FNV-1 prime */
+#define FNV_32_PRIME ((UV)0x01000193)
+
+
+/* perl doesn't know anything about the dbi_ima_t struct attached to the
+ * CvXSUBANY(cv).any_ptr slot, so add some magic to the CV to handle
+ * duping and freeing.
+ */
+
+static MGVTBL dbi_ima_vtbl = { 0, 0, 0, 0, dbi_ima_free,
+ 0,
+#if defined(USE_ITHREADS) && !defined(BROKEN_DUP_ANY_PTR)
+ dbi_ima_dup
+#else
+ 0
+#endif
+#if (PERL_VERSION > 8) || ((PERL_VERSION == 8) && (PERL_SUBVERSION >= 9))
+ , 0
+#endif
+ };
+
+static int dbi_ima_free(pTHX_ SV* sv, MAGIC* mg)
+{
+ dbi_ima_t *ima = (dbi_ima_t *)(CvXSUBANY((CV*)sv).any_ptr);
+#ifdef BROKEN_DUP_ANY_PTR
+ if (ima->my_perl != my_perl)
+ return 0;
+#endif
+ SvREFCNT_dec(ima->stash);
+ SvREFCNT_dec(ima->gv);
+ Safefree(ima);
+ return 0;
+}
+
+#if defined(USE_ITHREADS) && !defined(BROKEN_DUP_ANY_PTR)
+static int dbi_ima_dup(pTHX_ MAGIC* mg, CLONE_PARAMS *param)
+{
+ dbi_ima_t *ima, *nima;
+ CV *cv = (CV*) mg->mg_ptr;
+ CV *ncv = (CV*)ptr_table_fetch(PL_ptr_table, (cv));
+
+ (void)param; /* avoid 'unused variable' warning */
+ mg->mg_ptr = (char *)ncv;
+ ima = (dbi_ima_t*) CvXSUBANY(cv).any_ptr;
+ Newx(nima, 1, dbi_ima_t);
+ *nima = *ima; /* structure copy */
+ CvXSUBANY(ncv).any_ptr = nima;
+ nima->stash = NULL;
+ nima->gv = NULL;
+ return 0;
+}
+#endif
+
+
+
+/* --- make DBI safe for multiple perl interpreters --- */
+/* Originally contributed by Murray Nesbitt of ActiveState, */
+/* but later updated to use MY_CTX */
+
+#define MY_CXT_KEY "DBI::_guts" XS_VERSION
+
+typedef struct {
+ SV *dbi_last_h; /* maybe better moved into dbistate_t? */
+ dbistate_t* dbi_state;
+} my_cxt_t;
+
+START_MY_CXT
+
+#undef DBIS
+#define DBIS (MY_CXT.dbi_state)
+
+#define g_dbi_last_h (MY_CXT.dbi_last_h)
+
+/* allow the 'static' dbi_state struct to be accessed from other files */
+dbistate_t**
+_dbi_state_lval(pTHX)
+{
+ dMY_CXT;
+ return &(MY_CXT.dbi_state);
+}
+
+
+/* --- */
+
+static void *
+malloc_using_sv(STRLEN len)
+{
+ dTHX;
+ SV *sv = newSV(len);
+ void *p = SvPVX(sv);
+ memzero(p, len);
+ return p;
+}
+
+static char *
+savepv_using_sv(char *str)
+{
+ char *buf = malloc_using_sv(strlen(str));
+ strcpy(buf, str);
+ return buf;
+}
+
+
+/* --- support functions for concat_hash_sorted --- */
+
+typedef struct str_uv_sort_pair_st {
+ char *key;
+ UV numeric;
+} str_uv_sort_pair_t;
+
+static int
+_cmp_number(const void *val1, const void *val2)
+{
+ UV first = ((str_uv_sort_pair_t *)val1)->numeric;
+ UV second = ((str_uv_sort_pair_t *)val2)->numeric;
+
+ if (first > second)
+ return 1;
+ if (first < second)
+ return -1;
+ /* only likely to reach here if numeric sort forced for non-numeric keys */
+ /* fallback to comparing the key strings */
+ return strcmp(
+ ((str_uv_sort_pair_t *)val1)->key,
+ ((str_uv_sort_pair_t *)val2)->key
+ );
+}
+
+static int
+_cmp_str (const void *val1, const void *val2)
+{
+ return strcmp( *(char **)val1, *(char **)val2);
+}
+
+static char **
+_sort_hash_keys (HV *hash, int num_sort, STRLEN *total_length)
+{
+ dTHX;
+ I32 hv_len, key_len;
+ HE *entry;
+ char **keys;
+ unsigned int idx = 0;
+ STRLEN tot_len = 0;
+ bool has_non_numerics = 0;
+ str_uv_sort_pair_t *numbers;
+
+ hv_len = hv_iterinit(hash);
+ if (!hv_len)
+ return 0;
+
+ Newz(0, keys, hv_len, char *);
+ Newz(0, numbers, hv_len, str_uv_sort_pair_t);
+
+ while ((entry = hv_iternext(hash))) {
+ *(keys+idx) = hv_iterkey(entry, &key_len);
+ tot_len += key_len;
+
+ if (grok_number(*(keys+idx), key_len, &(numbers+idx)->numeric) != IS_NUMBER_IN_UV) {
+ has_non_numerics = 1;
+ (numbers+idx)->numeric = 0;
+ }
+
+ (numbers+idx)->key = *(keys+idx);
+ ++idx;
+ }
+
+ if (total_length)
+ *total_length = tot_len;
+
+ if (num_sort < 0)
+ num_sort = (has_non_numerics) ? 0 : 1;
+
+ if (!num_sort) {
+ qsort(keys, hv_len, sizeof(char*), _cmp_str);
+ }
+ else {
+ qsort(numbers, hv_len, sizeof(str_uv_sort_pair_t), _cmp_number);
+ for (idx = 0; idx < hv_len; ++idx)
+ *(keys+idx) = (numbers+idx)->key;
+ }
+
+ Safefree(numbers);
+ return keys;
+}
+
+
+static SV *
+_join_hash_sorted(HV *hash, char *kv_sep, STRLEN kv_sep_len, char *pair_sep, STRLEN pair_sep_len, int use_neat, int num_sort)
+{
+ dTHX;
+ I32 hv_len;
+ STRLEN total_len = 0;
+ char **keys;
+ unsigned int i = 0;
+ SV *return_sv;
+
+ keys = _sort_hash_keys(hash, num_sort, &total_len);
+ if (!keys)
+ return newSVpv("", 0);
+
+ if (!kv_sep_len)
+ kv_sep_len = strlen(kv_sep);
+ if (!pair_sep_len)
+ pair_sep_len = strlen(pair_sep);
+
+ hv_len = hv_iterinit(hash);
+ /* total_len += Separators + quotes + term null */
+ total_len += kv_sep_len*hv_len + pair_sep_len*hv_len+2*hv_len+1;
+ return_sv = newSV(total_len);
+ sv_setpv(return_sv, ""); /* quell undef warnings */
+
+ for (i=0; i<hv_len; ++i) {
+ SV **hash_svp = hv_fetch(hash, keys[i], strlen(keys[i]), 0);
+
+ sv_catpv(return_sv, keys[i]); /* XXX keys can't contain nul chars */
+ sv_catpvn(return_sv, kv_sep, kv_sep_len);
+
+ if (!hash_svp) { /* should never happen */
+ warn("No hash entry with key '%s'", keys[i]);
+ sv_catpvn(return_sv, "???", 3);
+ continue;
+ }
+
+ if (use_neat) {
+ sv_catpv(return_sv, neatsvpv(*hash_svp,0));
+ }
+ else {
+ if (SvOK(*hash_svp)) {
+ STRLEN hv_val_len;
+ char *hv_val = SvPV(*hash_svp, hv_val_len);
+ sv_catpvn(return_sv, "'", 1);
+ sv_catpvn(return_sv, hv_val, hv_val_len);
+ sv_catpvn(return_sv, "'", 1);
+ }
+ else sv_catpvn(return_sv, "undef", 5);
+ }
+
+ if (i < hv_len-1)
+ sv_catpvn(return_sv, pair_sep, pair_sep_len);
+ }
+
+ Safefree(keys);
+
+ return return_sv;
+}
+
+
+
+/* handy for embedding into condition expression for debugging */
+/*
+static int warn1(char *s) { warn(s); return 1; }
+static int dump1(SV *sv) { dTHX; sv_dump(sv); return 1; }
+*/
+
+
+/* --- */
+
+static void
+check_version(const char *name, int dbis_cv, int dbis_cs, int need_dbixs_cv, int drc_s,
+ int dbc_s, int stc_s, int fdc_s)
+{
+ dTHX;
+ dMY_CXT;
+ static const char msg[] = "you probably need to rebuild the DBD driver (or possibly the DBI)";
+ (void)need_dbixs_cv;
+ if (dbis_cv != DBISTATE_VERSION || dbis_cs != sizeof(*DBIS))
+ croak("DBI/DBD internal version mismatch (DBI is v%d/s%lu, DBD %s expected v%d/s%d) %s.\n",
+ DBISTATE_VERSION, (long unsigned int)sizeof(*DBIS), name, dbis_cv, dbis_cs, msg);
+ /* Catch structure size changes - We should probably force a recompile if the DBI */
+ /* runtime version is different from the build time. That would be harsh but safe. */
+ if (drc_s != sizeof(dbih_drc_t) || dbc_s != sizeof(dbih_dbc_t) ||
+ stc_s != sizeof(dbih_stc_t) || fdc_s != sizeof(dbih_fdc_t) )
+ croak("%s (dr:%d/%ld, db:%d/%ld, st:%d/%ld, fd:%d/%ld), %s.\n",
+ "DBI/DBD internal structure mismatch",
+ drc_s, (long)sizeof(dbih_drc_t), dbc_s, (long)sizeof(dbih_dbc_t),
+ stc_s, (long)sizeof(dbih_stc_t), fdc_s, (long)sizeof(dbih_fdc_t), msg);
+}
+
+static void
+dbi_bootinit(dbistate_t * parent_dbis)
+{
+ dTHX;
+ dMY_CXT;
+ dbistate_t* DBISx;
+
+ DBISx = (struct dbistate_st*)malloc_using_sv(sizeof(struct dbistate_st));
+ DBIS = DBISx;
+
+ /* make DBIS available to DBD modules the "old" (<= 1.618) way,
+ * so that unrecompiled DBD's will still work against a newer DBI */
+ sv_setiv(get_sv("DBI::_dbistate", GV_ADDMULTI),
+ PTR2IV(MY_CXT.dbi_state));
+
+ /* store version and size so we can spot DBI/DBD version mismatch */
+ DBIS->check_version = check_version;
+ DBIS->version = DBISTATE_VERSION;
+ DBIS->size = sizeof(*DBIS);
+ DBIS->xs_version = DBIXS_VERSION;
+
+ DBIS->logmsg = dbih_logmsg;
+ DBIS->logfp = PerlIO_stderr();
+ DBIS->debug = (parent_dbis) ? parent_dbis->debug
+ : SvIV(get_sv("DBI::dbi_debug",0x5));
+ DBIS->neatsvpvlen = (parent_dbis) ? parent_dbis->neatsvpvlen
+ : get_sv("DBI::neat_maxlen", GV_ADDMULTI);
+#ifdef DBI_USE_THREADS
+ DBIS->thr_owner = PERL_GET_THX;
+#endif
+
+ /* store some function pointers so DBD's can call our functions */
+ DBIS->getcom = dbih_getcom;
+ DBIS->clearcom = dbih_clearcom;
+ DBIS->event = dbih_event;
+ DBIS->set_attr_k = dbih_set_attr_k;
+ DBIS->get_attr_k = dbih_get_attr_k;
+ DBIS->get_fbav = dbih_get_fbav;
+ DBIS->make_fdsv = dbih_make_fdsv;
+ DBIS->neat_svpv = neatsvpv;
+ DBIS->bind_as_num = quote_type; /* XXX deprecated */
+ DBIS->hash = dbi_hash;
+ DBIS->set_err_sv = set_err_sv;
+ DBIS->set_err_char= set_err_char;
+ DBIS->bind_col = dbih_sth_bind_col;
+ DBIS->sql_type_cast_svpv = sql_type_cast_svpv;
+
+
+ /* Remember the last handle used. BEWARE! Sneaky stuff here! */
+ /* We want a handle reference but we don't want to increment */
+ /* the handle's reference count and we don't want perl to try */
+ /* to destroy it during global destruction. Take care! */
+ DBI_UNSET_LAST_HANDLE; /* ensure setup the correct way */
+
+ /* trick to avoid 'possible typo' warnings */
+ gv_fetchpv("DBI::state", GV_ADDMULTI, SVt_PV);
+ gv_fetchpv("DBI::err", GV_ADDMULTI, SVt_PV);
+ gv_fetchpv("DBI::errstr", GV_ADDMULTI, SVt_PV);
+ gv_fetchpv("DBI::lasth", GV_ADDMULTI, SVt_PV);
+ gv_fetchpv("DBI::rows", GV_ADDMULTI, SVt_PV);
+
+ /* we only need to check the env var on the initial boot
+ * which is handy because it can core dump during CLONE on windows
+ */
+ if (!parent_dbis && getenv("PERL_DBI_XSBYPASS"))
+ use_xsbypass = atoi(getenv("PERL_DBI_XSBYPASS"));
+}
+
+
+/* ----------------------------------------------------------------- */
+/* Utility functions */
+
+
+static char *
+dbih_htype_name(int htype)
+{
+ switch(htype) {
+ case DBIt_DR: return "dr";
+ case DBIt_DB: return "db";
+ case DBIt_ST: return "st";
+ case DBIt_FD: return "fd";
+ default: return "??";
+ }
+}
+
+
+char *
+neatsvpv(SV *sv, STRLEN maxlen) /* return a tidy ascii value, for debugging only */
+{
+ dTHX;
+ dMY_CXT;
+ STRLEN len;
+ SV *nsv = Nullsv;
+ SV *infosv = Nullsv;
+ char *v, *quote;
+
+ /* We take care not to alter the supplied sv in any way at all. */
+ /* (but if it is SvGMAGICAL we have to call mg_get and that can */
+ /* have side effects, especially as it may be called twice overall.) */
+
+ if (!sv)
+ return "Null!"; /* should never happen */
+
+ /* try to do the right thing with magical values */
+ if (SvMAGICAL(sv)) {
+ if (DBIS_TRACE_LEVEL >= 5) { /* add magic details to help debugging */
+ MAGIC* mg;
+ infosv = sv_2mortal(newSVpv(" (magic-",0));
+ if (SvSMAGICAL(sv)) sv_catpvn(infosv,"s",1);
+ if (SvGMAGICAL(sv)) sv_catpvn(infosv,"g",1);
+ if (SvRMAGICAL(sv)) sv_catpvn(infosv,"r",1);
+ sv_catpvn(infosv,":",1);
+ for (mg = SvMAGIC(sv); mg; mg = mg->mg_moremagic)
+ sv_catpvn(infosv, &mg->mg_type, 1);
+ sv_catpvn(infosv, ")", 1);
+ }
+ if (SvGMAGICAL(sv))
+ mg_get(sv); /* trigger magic to FETCH the value */
+ }
+
+ if (!SvOK(sv)) {
+ if (SvTYPE(sv) >= SVt_PVAV)
+ return (char *)sv_reftype(sv,0); /* raw AV/HV etc, not via a ref */
+ if (!infosv)
+ return "undef";
+ sv_insert(infosv, 0,0, "undef",5);
+ return SvPVX(infosv);
+ }
+
+ if (SvNIOK(sv)) { /* is a numeric value - so no surrounding quotes */
+ if (SvPOK(sv)) { /* already has string version of the value, so use it */
+ v = SvPV(sv,len);
+ if (len == 0) { v="''"; len=2; } /* catch &sv_no style special case */
+ if (!infosv)
+ return v;
+ sv_insert(infosv, 0,0, v, len);
+ return SvPVX(infosv);
+ }
+ /* we don't use SvPV here since we don't want to alter sv in _any_ way */
+ if (SvUOK(sv))
+ nsv = newSVpvf("%"UVuf, SvUVX(sv));
+ else if (SvIOK(sv))
+ nsv = newSVpvf("%"IVdf, SvIVX(sv));
+ else nsv = newSVpvf("%"NVgf, SvNVX(sv));
+ if (infosv)
+ sv_catsv(nsv, infosv);
+ return SvPVX(sv_2mortal(nsv));
+ }
+
+ nsv = sv_newmortal();
+ sv_upgrade(nsv, SVt_PV);
+
+ if (SvROK(sv)) {
+ if (!SvAMAGIC(sv)) /* (un-amagic'd) refs get no special treatment */
+ v = SvPV(sv,len);
+ else {
+ /* handle Overload magic refs */
+ (void)SvAMAGIC_off(sv); /* should really be done via local scoping */
+ v = SvPV(sv,len); /* XXX how does this relate to SvGMAGIC? */
+ SvAMAGIC_on(sv);
+ }
+ sv_setpvn(nsv, v, len);
+ if (infosv)
+ sv_catsv(nsv, infosv);
+ return SvPV(nsv, len);
+ }
+
+ if (SvPOK(sv)) /* usual simple string case */
+ v = SvPV(sv,len);
+ else /* handles all else via sv_2pv() */
+ v = SvPV(sv,len); /* XXX how does this relate to SvGMAGIC? */
+
+ /* for strings we limit the length and translate codes */
+ if (maxlen == 0)
+ maxlen = SvIV(DBIS->neatsvpvlen);
+ if (maxlen < 6) /* handle daft values */
+ maxlen = 6;
+ maxlen -= 2; /* account for quotes */
+
+ quote = (SvUTF8(sv)) ? "\"" : "'";
+ if (len > maxlen) {
+ SvGROW(nsv, (1+maxlen+1+1));
+ sv_setpvn(nsv, quote, 1);
+ sv_catpvn(nsv, v, maxlen-3); /* account for three dots */
+ sv_catpvn(nsv, "...", 3);
+ } else {
+ SvGROW(nsv, (1+len+1+1));
+ sv_setpvn(nsv, quote, 1);
+ sv_catpvn(nsv, v, len);
+ }
+ sv_catpvn(nsv, quote, 1);
+ if (infosv)
+ sv_catsv(nsv, infosv);
+ v = SvPV(nsv, len);
+ if (!SvUTF8(sv)) {
+ while(len-- > 0) { /* cleanup string (map control chars to ascii etc) */
+ const char c = v[len] & 0x7F; /* ignore top bit for multinational chars */
+ if (!isPRINT(c) && !isSPACE(c))
+ v[len] = '.';
+ }
+ }
+ return v;
+}
+
+
+static int
+set_err_char(SV *h, imp_xxh_t *imp_xxh, const char *err_c, IV err_i, const char *errstr, const char *state, const char *method)
+{
+ dTHX;
+ char err_buf[28];
+ SV *err_sv, *errstr_sv, *state_sv, *method_sv;
+ if (!err_c) {
+ sprintf(err_buf, "%ld", (long)err_i);
+ err_c = &err_buf[0];
+ }
+ err_sv = (strEQ(err_c,"1")) ? &PL_sv_yes : sv_2mortal(newSVpvn(err_c, strlen(err_c)));
+ errstr_sv = sv_2mortal(newSVpvn(errstr, strlen(errstr)));
+ state_sv = (state && *state) ? sv_2mortal(newSVpvn(state, strlen(state))) : &PL_sv_undef;
+ method_sv = (method && *method) ? sv_2mortal(newSVpvn(method, strlen(method))) : &PL_sv_undef;
+ return set_err_sv(h, imp_xxh, err_sv, errstr_sv, state_sv, method_sv);
+}
+
+static int
+set_err_sv(SV *h, imp_xxh_t *imp_xxh, SV *err, SV *errstr, SV *state, SV *method)
+{
+ dTHX;
+ SV *h_err;
+ SV *h_errstr;
+ SV *h_state;
+ SV **hook_svp;
+ int err_changed = 0;
+
+ if ( DBIc_has(imp_xxh, DBIcf_HandleSetErr)
+ && (hook_svp = hv_fetch((HV*)SvRV(h),"HandleSetErr",12,0))
+ && hook_svp
+ && ((void)(SvGMAGICAL(*hook_svp) && mg_get(*hook_svp)), SvOK(*hook_svp))
+ ) {
+ dSP;
+ IV items;
+ SV *response_sv;
+ if (SvREADONLY(err)) err = sv_mortalcopy(err);
+ if (SvREADONLY(errstr)) errstr = sv_mortalcopy(errstr);
+ if (SvREADONLY(state)) state = sv_mortalcopy(state);
+ if (SvREADONLY(method)) method = sv_mortalcopy(method);
+ if (DBIc_TRACE_LEVEL(imp_xxh) >= 2)
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh)," -> HandleSetErr(%s, err=%s, errstr=%s, state=%s, %s)\n",
+ neatsvpv(h,0), neatsvpv(err,0), neatsvpv(errstr,0), neatsvpv(state,0),
+ neatsvpv(method,0)
+ );
+ PUSHMARK(SP);
+ XPUSHs(sv_2mortal(newRV_inc((SV*)DBIc_MY_H(imp_xxh))));
+ XPUSHs(err);
+ XPUSHs(errstr);
+ XPUSHs(state);
+ XPUSHs(method);
+ PUTBACK;
+ items = call_sv(*hook_svp, G_SCALAR);
+ SPAGAIN;
+ response_sv = (items) ? POPs : &PL_sv_undef;
+ PUTBACK;
+ if (DBIc_TRACE_LEVEL(imp_xxh) >= 1)
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh)," <- HandleSetErr= %s (err=%s, errstr=%s, state=%s, %s)\n",
+ neatsvpv(response_sv,0), neatsvpv(err,0), neatsvpv(errstr,0), neatsvpv(state,0),
+ neatsvpv(method,0)
+ );
+ if (SvTRUE(response_sv)) /* handler says it has handled it, so... */
+ return 0;
+ }
+
+ if (!SvOK(err)) { /* clear err / errstr / state */
+ DBIh_CLEAR_ERROR(imp_xxh);
+ return 1;
+ }
+
+ /* fetch these after calling HandleSetErr */
+ h_err = DBIc_ERR(imp_xxh);
+ h_errstr = DBIc_ERRSTR(imp_xxh);
+ h_state = DBIc_STATE(imp_xxh);
+
+ if (SvTRUE(h_errstr)) {
+ /* append current err, if any, to errstr if it's going to change */
+ if (SvTRUE(h_err) && SvTRUE(err) && strNE(SvPV_nolen(h_err), SvPV_nolen(err)))
+ sv_catpvf(h_errstr, " [err was %s now %s]", SvPV_nolen(h_err), SvPV_nolen(err));
+ if (SvTRUE(h_state) && SvTRUE(state) && strNE(SvPV_nolen(h_state), SvPV_nolen(state)))
+ sv_catpvf(h_errstr, " [state was %s now %s]", SvPV_nolen(h_state), SvPV_nolen(state));
+ if (strNE(SvPV_nolen(h_errstr), SvPV_nolen(errstr))) {
+ sv_catpvn(h_errstr, "\n", 1);
+ sv_catsv(h_errstr, errstr);
+ }
+ }
+ else
+ sv_setsv(h_errstr, errstr);
+
+ /* SvTRUE(err) > "0" > "" > undef */
+ if (SvTRUE(err) /* new error: so assign */
+ || !SvOK(h_err) /* no existing warn/info: so assign */
+ /* new warn ("0" len 1) > info ("" len 0): so assign */
+ || (SvOK(err) && strlen(SvPV_nolen(err)) > strlen(SvPV_nolen(h_err)))
+ ) {
+ sv_setsv(h_err, err);
+ err_changed = 1;
+ if (SvTRUE(h_err)) /* new error */
+ ++DBIc_ErrCount(imp_xxh);
+ }
+
+ if (err_changed) {
+ if (SvTRUE(state)) {
+ if (strlen(SvPV_nolen(state)) != 5) {
+ warn("set_err: state (%s) is not a 5 character string, using 'S1000' instead", neatsvpv(state,0));
+ sv_setpv(h_state, "S1000");
+ }
+ else
+ sv_setsv(h_state, state);
+ }
+ else
+ (void)SvOK_off(h_state); /* see DBIc_STATE_adjust */
+ }
+
+ return 1;
+}
+
+
+static char *
+mkvname(pTHX_ HV *stash, const char *item, int uplevel) /* construct a variable name */
+{
+ SV *sv = sv_newmortal();
+ sv_setpv(sv, HvNAME(stash));
+ if(uplevel) {
+ while(SvCUR(sv) && *SvEND(sv)!=':')
+ --SvCUR(sv);
+ if (SvCUR(sv))
+ --SvCUR(sv);
+ }
+ sv_catpv(sv, "::");
+ sv_catpv(sv, item);
+ return SvPV_nolen(sv);
+}
+
+/* 32 bit magic FNV-0 and FNV-1 prime */
+#define FNV_32_PRIME ((UV)0x01000193)
+
+static I32
+dbi_hash(const char *key, long type)
+{
+ if (type == 0) {
+ STRLEN klen = strlen(key);
+ U32 hash = 0;
+ while (klen--)
+ hash = hash * 33 + *key++;
+ hash &= 0x7FFFFFFF; /* limit to 31 bits */
+ hash |= 0x40000000; /* set bit 31 */
+ return -(I32)hash; /* return negative int */
+ }
+ else if (type == 1) { /* Fowler/Noll/Vo hash */
+ /* see http://www.isthe.com/chongo/tech/comp/fnv/ */
+ U32 hash = 0x811c9dc5;
+ const unsigned char *s = (unsigned char *)key; /* unsigned string */
+ while (*s) {
+ /* multiply by the 32 bit FNV magic prime mod 2^32 */
+ hash *= FNV_32_PRIME;
+ /* xor the bottom with the current octet */
+ hash ^= (U32)*s++;
+ }
+ return hash;
+ }
+ croak("DBI::hash(%ld): invalid type", type);
+ return 0; /* NOT REACHED */
+}
+
+
+static int
+dbih_logmsg(imp_xxh_t *imp_xxh, const char *fmt, ...)
+{
+ dTHX;
+ va_list args;
+#ifdef I_STDARG
+ va_start(args, fmt);
+#else
+ va_start(args);
+#endif
+ (void) PerlIO_vprintf(DBIc_DBISTATE(imp_xxh)->logfp, fmt, args);
+ va_end(args);
+ (void)imp_xxh;
+ return 1;
+}
+
+static void
+close_trace_file(pTHX)
+{
+ dMY_CXT;
+ if (DBILOGFP == PerlIO_stderr() || DBILOGFP == PerlIO_stdout())
+ return;
+
+ if (DBIS->logfp_ref == NULL)
+ PerlIO_close(DBILOGFP);
+ else {
+ /* DAA dec refcount and discard */
+ SvREFCNT_dec(DBIS->logfp_ref);
+ DBIS->logfp_ref = NULL;
+ }
+}
+
+static int
+set_trace_file(SV *file)
+{
+ dTHX;
+ dMY_CXT;
+ const char *filename;
+ PerlIO *fp = Nullfp;
+ IO *io;
+
+ if (!file) /* no arg == no change */
+ return 0;
+
+ /* DAA check for a filehandle */
+ if (SvROK(file)) {
+ io = sv_2io(file);
+ if (!io || !(fp = IoOFP(io))) {
+ warn("DBI trace filehandle is not valid");
+ return 0;
+ }
+ close_trace_file(aTHX);
+ SvREFCNT_inc(io);
+ DBIS->logfp_ref = io;
+ }
+ else if (isGV_with_GP(file)) {
+ io = GvIO(file);
+ if (!io || !(fp = IoOFP(io))) {
+ warn("DBI trace filehandle from GLOB is not valid");
+ return 0;
+ }
+ close_trace_file(aTHX);
+ SvREFCNT_inc(io);
+ DBIS->logfp_ref = io;
+ }
+ else {
+ filename = (SvOK(file)) ? SvPV_nolen(file) : Nullch;
+ /* undef arg == reset back to stderr */
+ if (!filename || strEQ(filename,"STDERR")
+ || strEQ(filename,"*main::STDERR")) {
+ close_trace_file(aTHX);
+ DBILOGFP = PerlIO_stderr();
+ return 1;
+ }
+ if (strEQ(filename,"STDOUT")) {
+ close_trace_file(aTHX);
+ DBILOGFP = PerlIO_stdout();
+ return 1;
+ }
+ fp = PerlIO_open(filename, "a+");
+ if (fp == Nullfp) {
+ warn("Can't open trace file %s: %s", filename, Strerror(errno));
+ return 0;
+ }
+ close_trace_file(aTHX);
+ }
+ DBILOGFP = fp;
+ /* if this line causes your compiler or linker to choke */
+ /* then just comment it out, it's not essential. */
+ PerlIO_setlinebuf(fp); /* force line buffered output */
+ return 1;
+}
+
+static IV
+parse_trace_flags(SV *h, SV *level_sv, IV old_level)
+{
+ dTHX;
+ IV level;
+ if (!level_sv || !SvOK(level_sv))
+ level = old_level; /* undef: no change */
+ else
+ if (SvTRUE(level_sv)) {
+ if (looks_like_number(level_sv))
+ level = SvIV(level_sv); /* number: number */
+ else { /* string: parse it */
+ dSP;
+ PUSHMARK(sp);
+ XPUSHs(h);
+ XPUSHs(level_sv);
+ PUTBACK;
+ if (call_method("parse_trace_flags", G_SCALAR) != 1)
+ croak("panic: parse_trace_flags");/* should never happen */
+ SPAGAIN;
+ level = POPi;
+ PUTBACK;
+ }
+ }
+ else /* defined but false: 0 */
+ level = 0;
+ return level;
+}
+
+
+static int
+set_trace(SV *h, SV *level_sv, SV *file)
+{
+ dTHX;
+ D_imp_xxh(h);
+ int RETVAL = DBIc_DBISTATE(imp_xxh)->debug; /* Return trace level in effect now */
+ IV level = parse_trace_flags(h, level_sv, RETVAL);
+ set_trace_file(file);
+ if (level != RETVAL) { /* set value */
+ if ((level & DBIc_TRACE_LEVEL_MASK) > 0) {
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh),
+ " %s trace level set to 0x%lx/%ld (DBI @ 0x%lx/%ld) in DBI %s%s (pid %d)\n",
+ neatsvpv(h,0),
+ (long)(level & DBIc_TRACE_FLAGS_MASK),
+ (long)(level & DBIc_TRACE_LEVEL_MASK),
+ (long)DBIc_TRACE_FLAGS(imp_xxh), (long)DBIc_TRACE_LEVEL(imp_xxh),
+ XS_VERSION, dbi_build_opt, (int)PerlProc_getpid());
+ if (!PL_dowarn)
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh)," Note: perl is running without the recommended perl -w option\n");
+ PerlIO_flush(DBIc_LOGPIO(imp_xxh));
+ }
+ sv_setiv(DBIc_DEBUG(imp_xxh), level);
+ }
+ return RETVAL;
+}
+
+
+static SV *
+dbih_inner(pTHX_ SV *orv, const char *what)
+{ /* convert outer to inner handle else croak(what) if what is not NULL */
+ /* if what is NULL then return NULL for invalid handles */
+ MAGIC *mg;
+ SV *ohv; /* outer HV after derefing the RV */
+ SV *hrv; /* dbi inner handle RV-to-HV */
+
+ /* enable a raw HV (not ref-to-HV) to be passed in, eg DBIc_MY_H */
+ ohv = SvROK(orv) ? SvRV(orv) : orv;
+
+ if (!ohv || SvTYPE(ohv) != SVt_PVHV) {
+ if (!what)
+ return NULL;
+ if (1) {
+ dMY_CXT;
+ if (DBIS_TRACE_LEVEL)
+ sv_dump(orv);
+ }
+ if (!SvOK(orv))
+ croak("%s given an undefined handle %s",
+ what, "(perhaps returned from a previous call which failed)");
+ croak("%s handle %s is not a DBI handle", what, neatsvpv(orv,0));
+ }
+ if (!SvMAGICAL(ohv)) {
+ if (!what)
+ return NULL;
+ sv_dump(orv);
+ croak("%s handle %s is not a DBI handle (has no magic)",
+ what, neatsvpv(orv,0));
+ }
+
+ if ( (mg=mg_find(ohv,'P')) == NULL) { /* hash tie magic */
+ /* not tied, maybe it's already an inner handle... */
+ if (mg_find(ohv, DBI_MAGIC) == NULL) {
+ if (!what)
+ return NULL;
+ sv_dump(orv);
+ croak("%s handle %s is not a valid DBI handle",
+ what, neatsvpv(orv,0));
+ }
+ hrv = orv; /* was already a DBI handle inner hash */
+ }
+ else {
+ hrv = mg->mg_obj; /* inner hash of tie */
+ }
+
+ return hrv;
+}
+
+
+
+/* -------------------------------------------------------------------- */
+/* Functions to manage a DBI handle (magic and attributes etc). */
+
+static imp_xxh_t *
+dbih_getcom(SV *hrv) /* used by drivers via DBIS func ptr */
+{
+ MAGIC *mg;
+ SV *sv;
+
+ /* short-cut common case */
+ if ( SvROK(hrv)
+ && (sv = SvRV(hrv))
+ && SvRMAGICAL(sv)
+ && (mg = SvMAGIC(sv))
+ && mg->mg_type == DBI_MAGIC
+ && mg->mg_ptr
+ )
+ return (imp_xxh_t *) mg->mg_ptr;
+
+ {
+ dTHX;
+ imp_xxh_t *imp_xxh = dbih_getcom2(aTHX_ hrv, 0);
+ if (!imp_xxh) /* eg after take_imp_data */
+ croak("Invalid DBI handle %s, has no dbi_imp_data", neatsvpv(hrv,0));
+ return imp_xxh;
+ }
+}
+
+static imp_xxh_t *
+dbih_getcom2(pTHX_ SV *hrv, MAGIC **mgp) /* Get com struct for handle. Must be fast. */
+{
+ MAGIC *mg;
+ SV *sv;
+
+ /* important and quick sanity check (esp non-'safe' Oraperl) */
+ if (SvROK(hrv)) /* must at least be a ref */
+ sv = SvRV(hrv);
+ else {
+ dMY_CXT;
+ if (hrv == DBI_LAST_HANDLE) /* special for var::FETCH */
+ sv = DBI_LAST_HANDLE;
+ else if (sv_derived_from(hrv, "DBI::common")) {
+ /* probably a class name, if ref($h)->foo() */
+ return 0;
+ }
+ else {
+ sv_dump(hrv);
+ croak("Invalid DBI handle %s", neatsvpv(hrv,0));
+ sv = &PL_sv_undef; /* avoid "might be used uninitialized" warning */
+ }
+ }
+
+ /* Short cut for common case. We assume that a magic var always */
+ /* has magic and that DBI_MAGIC, if present, will be the first. */
+ if (SvRMAGICAL(sv) && (mg=SvMAGIC(sv))->mg_type == DBI_MAGIC) {
+ /* nothing to do here */
+ }
+ else {
+ /* Validate handle (convert outer to inner if required) */
+ hrv = dbih_inner(aTHX_ hrv, "dbih_getcom");
+ mg = mg_find(SvRV(hrv), DBI_MAGIC);
+ }
+ if (mgp) /* let caller pickup magic struct for this handle */
+ *mgp = mg;
+
+ return (imp_xxh_t *) mg->mg_ptr;
+}
+
+
+static SV *
+dbih_setup_attrib(pTHX_ SV *h, imp_xxh_t *imp_xxh, char *attrib, SV *parent, int read_only, int optional)
+{
+ STRLEN len = strlen(attrib);
+ SV **asvp;
+
+ asvp = hv_fetch((HV*)SvRV(h), attrib, len, !optional);
+ /* we assume that we won't have any existing 'undef' attributes here */
+ /* (or, alternately, we take undef to mean 'copy from parent') */
+ if (!(asvp && SvOK(*asvp))) { /* attribute doesn't already exists (the common case) */
+ SV **psvp;
+ if ((!parent || !SvROK(parent)) && !optional) {
+ croak("dbih_setup_attrib(%s): %s not set and no parent supplied",
+ neatsvpv(h,0), attrib);
+ }
+ psvp = hv_fetch((HV*)SvRV(parent), attrib, len, 0);
+ if (psvp) {
+ if (!asvp)
+ asvp = hv_fetch((HV*)SvRV(h), attrib, len, 1);
+ sv_setsv(*asvp, *psvp); /* copy attribute from parent to handle */
+ }
+ else {
+ if (!optional)
+ croak("dbih_setup_attrib(%s): %s not set and not in parent",
+ neatsvpv(h,0), attrib);
+ }
+ }
+ if (DBIc_TRACE_LEVEL(imp_xxh) >= 5) {
+ PerlIO *logfp = DBIc_LOGPIO(imp_xxh);
+ PerlIO_printf(logfp," dbih_setup_attrib(%s, %s, %s)",
+ neatsvpv(h,0), attrib, neatsvpv(parent,0));
+ if (!asvp)
+ PerlIO_printf(logfp," undef (not defined)\n");
+ else
+ if (SvOK(*asvp))
+ PerlIO_printf(logfp," %s (already defined)\n", neatsvpv(*asvp,0));
+ else PerlIO_printf(logfp," %s (copied from parent)\n", neatsvpv(*asvp,0));
+ }
+ if (read_only && asvp)
+ SvREADONLY_on(*asvp);
+ return asvp ? *asvp : &PL_sv_undef;
+}
+
+
+static SV *
+dbih_make_fdsv(SV *sth, const char *imp_class, STRLEN imp_size, const char *col_name)
+{
+ dTHX;
+ D_imp_sth(sth);
+ const STRLEN cn_len = strlen(col_name);
+ imp_fdh_t *imp_fdh;
+ SV *fdsv;
+ if (imp_size < sizeof(imp_fdh_t) || cn_len<10 || strNE("::fd",&col_name[cn_len-4]))
+ croak("panic: dbih_makefdsv %s '%s' imp_size %ld invalid",
+ imp_class, col_name, (long)imp_size);
+ if (DBIc_TRACE_LEVEL(imp_sth) >= 5)
+ PerlIO_printf(DBIc_LOGPIO(imp_sth)," dbih_make_fdsv(%s, %s, %ld, '%s')\n",
+ neatsvpv(sth,0), imp_class, (long)imp_size, col_name);
+ fdsv = dbih_make_com(sth, (imp_xxh_t*)imp_sth, imp_class, imp_size, cn_len+2, 0);
+ imp_fdh = (imp_fdh_t*)(void*)SvPVX(fdsv);
+ imp_fdh->com.col_name = ((char*)imp_fdh) + imp_size;
+ strcpy(imp_fdh->com.col_name, col_name);
+ return fdsv;
+}
+
+
+static SV *
+dbih_make_com(SV *p_h, imp_xxh_t *p_imp_xxh, const char *imp_class, STRLEN imp_size, STRLEN extra, SV* imp_templ)
+{
+ dTHX;
+ static const char *errmsg = "Can't make DBI com handle for %s: %s";
+ HV *imp_stash;
+ SV *dbih_imp_sv;
+ imp_xxh_t *imp;
+ int trace_level;
+ (void)extra; /* unused arg */
+
+ if ( (imp_stash = gv_stashpv(imp_class, FALSE)) == NULL)
+ croak(errmsg, imp_class, "unknown package");
+
+ if (imp_size == 0) {
+ /* get size of structure to allocate for common and imp specific data */
+ const char *imp_size_name = mkvname(aTHX_ imp_stash, "imp_data_size", 0);
+ imp_size = SvIV(get_sv(imp_size_name, 0x05));
+ if (imp_size == 0) {
+ imp_size = sizeof(imp_sth_t);
+ if (sizeof(imp_dbh_t) > imp_size)
+ imp_size = sizeof(imp_dbh_t);
+ if (sizeof(imp_drh_t) > imp_size)
+ imp_size = sizeof(imp_drh_t);
+ imp_size += 4;
+ }
+ }
+
+ if (p_imp_xxh) {
+ trace_level = DBIc_TRACE_LEVEL(p_imp_xxh);
+ }
+ else {
+ dMY_CXT;
+ trace_level = DBIS_TRACE_LEVEL;
+ }
+ if (trace_level >= 5) {
+ dMY_CXT;
+ PerlIO_printf(DBILOGFP," dbih_make_com(%s, %p, %s, %ld, %p) thr#%p\n",
+ neatsvpv(p_h,0), (void*)p_imp_xxh, imp_class, (long)imp_size, (void*)imp_templ, (void*)PERL_GET_THX);
+ }
+
+ if (imp_templ && SvOK(imp_templ)) {
+ U32 imp_templ_flags;
+ /* validate the supplied dbi_imp_data looks reasonable, */
+ if (SvCUR(imp_templ) != imp_size)
+ croak("Can't use dbi_imp_data of wrong size (%ld not %ld)",
+ (long)SvCUR(imp_templ), (long)imp_size);
+
+ /* copy the whole template */
+ dbih_imp_sv = newSVsv(imp_templ);
+ imp = (imp_xxh_t*)(void*)SvPVX(dbih_imp_sv);
+
+ /* sanity checks on the supplied imp_data */
+ if (DBIc_TYPE(imp) != ((p_imp_xxh) ? DBIc_TYPE(p_imp_xxh)+1 :1) )
+ croak("Can't use dbi_imp_data from different type of handle");
+ if (!DBIc_has(imp, DBIcf_IMPSET))
+ croak("Can't use dbi_imp_data that not from a setup handle");
+
+ /* copy flags, zero out our imp_xxh struct, restore some flags */
+ imp_templ_flags = DBIc_FLAGS(imp);
+ switch ( (p_imp_xxh) ? DBIc_TYPE(p_imp_xxh)+1 : DBIt_DR ) {
+ case DBIt_DR: memzero((char*)imp, sizeof(imp_drh_t)); break;
+ case DBIt_DB: memzero((char*)imp, sizeof(imp_dbh_t)); break;
+ case DBIt_ST: memzero((char*)imp, sizeof(imp_sth_t)); break;
+ default: croak("dbih_make_com dbi_imp_data bad h type");
+ }
+ /* Only pass on DBIcf_IMPSET to indicate to driver that the imp */
+ /* structure has been copied and it doesn't need to reconnect. */
+ /* Similarly DBIcf_ACTIVE is also passed along but isn't key. */
+ DBIc_FLAGS(imp) = imp_templ_flags & (DBIcf_IMPSET|DBIcf_ACTIVE);
+ }
+ else {
+ dbih_imp_sv = newSV(imp_size); /* is grown to at least imp_size+1 */
+ imp = (imp_xxh_t*)(void*)SvPVX(dbih_imp_sv);
+ memzero((char*)imp, imp_size);
+ /* set up SV with SvCUR set ready for take_imp_data */
+ SvCUR_set(dbih_imp_sv, imp_size);
+ *SvEND(dbih_imp_sv) = '\0';
+ }
+
+ if (p_imp_xxh) {
+ DBIc_DBISTATE(imp) = DBIc_DBISTATE(p_imp_xxh);
+ }
+ else {
+ dMY_CXT;
+ DBIc_DBISTATE(imp) = DBIS;
+ }
+ DBIc_IMP_STASH(imp) = imp_stash;
+
+ if (!p_h) { /* only a driver (drh) has no parent */
+ DBIc_PARENT_H(imp) = &PL_sv_undef;
+ DBIc_PARENT_COM(imp) = NULL;
+ DBIc_TYPE(imp) = DBIt_DR;
+ DBIc_on(imp,DBIcf_WARN /* set only here, children inherit */
+ |DBIcf_ACTIVE /* drivers are 'Active' by default */
+ |DBIcf_AutoCommit /* advisory, driver must manage this */
+ );
+ DBIc_set(imp, DBIcf_PrintWarn, PL_dowarn); /* set if warnings enabled */
+ }
+ else {
+ DBIc_PARENT_H(imp) = (SV*)SvREFCNT_inc(p_h); /* ensure it lives */
+ DBIc_PARENT_COM(imp) = p_imp_xxh; /* shortcut for speed */
+ DBIc_TYPE(imp) = DBIc_TYPE(p_imp_xxh) + 1;
+ /* inherit some flags from parent and carry forward some from template */
+ DBIc_FLAGS(imp) = (DBIc_FLAGS(p_imp_xxh) & ~DBIcf_INHERITMASK)
+ | (DBIc_FLAGS(imp) & (DBIcf_IMPSET|DBIcf_ACTIVE));
+ ++DBIc_KIDS(p_imp_xxh);
+ }
+#ifdef DBI_USE_THREADS
+ DBIc_THR_USER(imp) = PERL_GET_THX ;
+#endif
+
+ if (DBIc_TYPE(imp) == DBIt_ST) {
+ imp_sth_t *imp_sth = (imp_sth_t*)imp;
+ DBIc_ROW_COUNT(imp_sth) = -1;
+ }
+
+ DBIc_COMSET_on(imp); /* common data now set up */
+
+ /* The implementor should DBIc_IMPSET_on(imp) when setting up */
+ /* any private data which will need clearing/freeing later. */
+
+ return dbih_imp_sv;
+}
+
+
+static void
+dbih_setup_handle(pTHX_ SV *orv, char *imp_class, SV *parent, SV *imp_datasv)
+{
+ SV *h;
+ char *errmsg = "Can't setup DBI handle of %s to %s: %s";
+ SV *dbih_imp_sv;
+ SV *dbih_imp_rv;
+ SV *dbi_imp_data = Nullsv;
+ SV **svp;
+ char imp_mem_name[300];
+ HV *imp_mem_stash;
+ imp_xxh_t *imp;
+ imp_xxh_t *parent_imp;
+ int trace_level;
+
+ h = dbih_inner(aTHX_ orv, "dbih_setup_handle");
+ parent = dbih_inner(aTHX_ parent, NULL); /* check parent valid (& inner) */
+ if (parent) {
+ parent_imp = DBIh_COM(parent);
+ trace_level = DBIc_TRACE_LEVEL(parent_imp);
+ }
+ else {
+ dMY_CXT;
+ parent_imp = NULL;
+ trace_level = DBIS_TRACE_LEVEL;
+ }
+
+ if (trace_level >= 5) {
+ dMY_CXT;
+ PerlIO_printf(DBILOGFP," dbih_setup_handle(%s=>%s, %s, %lx, %s)\n",
+ neatsvpv(orv,0), neatsvpv(h,0), imp_class, (long)parent, neatsvpv(imp_datasv,0));
+ }
+
+ if (mg_find(SvRV(h), DBI_MAGIC) != NULL)
+ croak(errmsg, neatsvpv(orv,0), imp_class, "already a DBI (or ~magic) handle");
+
+ strcpy(imp_mem_name, imp_class);
+ strcat(imp_mem_name, "_mem");
+ if ( (imp_mem_stash = gv_stashpv(imp_mem_name, FALSE)) == NULL)
+ croak(errmsg, neatsvpv(orv,0), imp_mem_name, "unknown _mem package");
+
+ if ((svp = hv_fetch((HV*)SvRV(h), "dbi_imp_data", 12, 0))) {
+ dbi_imp_data = *svp;
+ if (SvGMAGICAL(dbi_imp_data)) /* call FETCH via magic */
+ mg_get(dbi_imp_data);
+ }
+
+ DBI_LOCK;
+
+ dbih_imp_sv = dbih_make_com(parent, parent_imp, imp_class, 0, 0, dbi_imp_data);
+ imp = (imp_xxh_t*)(void*)SvPVX(dbih_imp_sv);
+
+ dbih_imp_rv = newRV_inc(dbih_imp_sv); /* just needed for sv_bless */
+ sv_bless(dbih_imp_rv, imp_mem_stash);
+ sv_free(dbih_imp_rv);
+
+ DBIc_MY_H(imp) = (HV*)SvRV(orv); /* take _copy_ of pointer, not new ref */
+ DBIc_IMP_DATA(imp) = (imp_datasv) ? newSVsv(imp_datasv) : &PL_sv_undef;
+ _imp2com(imp, std.pid) = (U32)PerlProc_getpid();
+
+ if (DBIc_TYPE(imp) <= DBIt_ST) {
+ SV **tmp_svp;
+ /* Copy some attributes from parent if not defined locally and */
+ /* also take address of attributes for speed of direct access. */
+ /* parent is null for drh, in which case h must hold the values */
+#define COPY_PARENT(name,ro,opt) SvREFCNT_inc(dbih_setup_attrib(aTHX_ h,imp,(name),parent,ro,opt))
+#define DBIc_ATTR(imp, f) _imp2com(imp, attr.f)
+ /* XXX we should validate that these are the right type (refs etc) */
+ DBIc_ATTR(imp, Err) = COPY_PARENT("Err",1,0); /* scalar ref */
+ DBIc_ATTR(imp, State) = COPY_PARENT("State",1,0); /* scalar ref */
+ DBIc_ATTR(imp, Errstr) = COPY_PARENT("Errstr",1,0); /* scalar ref */
+ DBIc_ATTR(imp, TraceLevel)=COPY_PARENT("TraceLevel",0,0);/* scalar (int)*/
+ DBIc_ATTR(imp, FetchHashKeyName) = COPY_PARENT("FetchHashKeyName",0,0); /* scalar ref */
+
+ if (parent) {
+ dbih_setup_attrib(aTHX_ h,imp,"HandleSetErr",parent,0,1);
+ dbih_setup_attrib(aTHX_ h,imp,"HandleError",parent,0,1);
+ dbih_setup_attrib(aTHX_ h,imp,"ReadOnly",parent,0,1);
+ dbih_setup_attrib(aTHX_ h,imp,"Profile",parent,0,1);
+
+ /* setup Callbacks from parents' ChildCallbacks */
+ if (DBIc_has(parent_imp, DBIcf_Callbacks)
+ && (tmp_svp = hv_fetch((HV*)SvRV(parent), "Callbacks", 9, 0))
+ && SvROK(*tmp_svp) && SvTYPE(SvRV(*tmp_svp)) == SVt_PVHV
+ && (tmp_svp = hv_fetch((HV*)SvRV(*tmp_svp), "ChildCallbacks", 14, 0))
+ && SvROK(*tmp_svp) && SvTYPE(SvRV(*tmp_svp)) == SVt_PVHV
+ ) {
+ /* XXX mirrors behaviour of dbih_set_attr_k() of Callbacks */
+ (void)hv_store((HV*)SvRV(h), "Callbacks", 9, newRV_inc(SvRV(*tmp_svp)), 0);
+ DBIc_set(imp, DBIcf_Callbacks, 1);
+ }
+
+ DBIc_LongReadLen(imp) = DBIc_LongReadLen(parent_imp);
+#ifdef sv_rvweaken
+ if (1) {
+ AV *av;
+ /* add weakref to new (outer) handle into parents ChildHandles array */
+ tmp_svp = hv_fetch((HV*)SvRV(parent), "ChildHandles", 12, 1);
+ if (!SvROK(*tmp_svp)) {
+ SV *ChildHandles_rvav = newRV_noinc((SV*)newAV());
+ sv_setsv(*tmp_svp, ChildHandles_rvav);
+ sv_free(ChildHandles_rvav);
+ }
+ av = (AV*)SvRV(*tmp_svp);
+ av_push(av, (SV*)sv_rvweaken(newRV_inc((SV*)SvRV(orv))));
+ if (av_len(av) % 120 == 0) {
+ /* time to do some housekeeping to remove dead handles */
+ I32 i = av_len(av); /* 0 = 1 element */
+ while (i-- >= 0) {
+ SV *sv = av_shift(av);
+ if (SvOK(sv))
+ av_push(av, sv);
+ else
+ sv_free(sv); /* keep it leak-free by Doru Petrescu pdoru.dbi@from.ro */
+ }
+ }
+ }
+#endif
+ }
+ else {
+ DBIc_LongReadLen(imp) = DBIc_LongReadLen_init;
+ }
+
+ switch (DBIc_TYPE(imp)) {
+ case DBIt_DB:
+ /* cache _inner_ handle, but also see quick_FETCH */
+ (void)hv_store((HV*)SvRV(h), "Driver", 6, newRV_inc(SvRV(parent)), 0);
+ (void)hv_fetch((HV*)SvRV(h), "Statement", 9, 1); /* store writable undef */
+ break;
+ case DBIt_ST:
+ DBIc_NUM_FIELDS((imp_sth_t*)imp) = -1;
+ /* cache _inner_ handle, but also see quick_FETCH */
+ (void)hv_store((HV*)SvRV(h), "Database", 8, newRV_inc(SvRV(parent)), 0);
+ /* copy (alias) Statement from the sth up into the dbh */
+ tmp_svp = hv_fetch((HV*)SvRV(h), "Statement", 9, 1);
+ (void)hv_store((HV*)SvRV(parent), "Statement", 9, SvREFCNT_inc(*tmp_svp), 0);
+ break;
+ }
+ }
+
+ /* Use DBI magic on inner handle to carry handle attributes */
+ /* Note that we store the imp_sv in mg_obj, but as a shortcut, */
+ /* also store a direct pointer to imp, aka PVX(dbih_imp_sv), */
+ /* in mg_ptr (with mg_len set to null, so it wont be freed) */
+ sv_magic(SvRV(h), dbih_imp_sv, DBI_MAGIC, (char*)imp, 0);
+ SvREFCNT_dec(dbih_imp_sv); /* since sv_magic() incremented it */
+ SvRMAGICAL_on(SvRV(h)); /* so DBI magic gets sv_clear'd ok */
+
+ {
+ dMY_CXT; /* XXX would be nice to get rid of this */
+ DBI_SET_LAST_HANDLE(h);
+ }
+
+ if (1) {
+ /* This is a hack to work-around the fast but poor way old versions of
+ * DBD::Oracle (and possibly other drivers) check for a valid handle
+ * using (SvMAGIC(SvRV(h)))->mg_type == 'P'). That doesn't work now
+ * because the weakref magic is inserted ahead of the tie magic.
+ * So here we swap the tie and weakref magic so the tie comes first.
+ */
+ MAGIC *tie_mg = mg_find(SvRV(orv),'P');
+ MAGIC *first = SvMAGIC(SvRV(orv));
+ if (tie_mg && first->mg_moremagic == tie_mg && !tie_mg->mg_moremagic) {
+ MAGIC *next = tie_mg->mg_moremagic;
+ SvMAGIC(SvRV(orv)) = tie_mg;
+ tie_mg->mg_moremagic = first;
+ first->mg_moremagic = next;
+ }
+ }
+
+ DBI_UNLOCK;
+}
+
+
+static void
+dbih_dumphandle(pTHX_ SV *h, const char *msg, int level)
+{
+ D_imp_xxh(h);
+ if (level >= 9) {
+ sv_dump(h);
+ }
+ dbih_dumpcom(aTHX_ imp_xxh, msg, level);
+}
+
+static int
+dbih_dumpcom(pTHX_ imp_xxh_t *imp_xxh, const char *msg, int level)
+{
+ dMY_CXT;
+ SV *flags = sv_2mortal(newSVpv("",0));
+ SV *inner;
+ static const char pad[] = " ";
+ if (!msg)
+ msg = "dbih_dumpcom";
+ PerlIO_printf(DBILOGFP," %s (%sh 0x%lx, com 0x%lx, imp %s):\n",
+ msg, dbih_htype_name(DBIc_TYPE(imp_xxh)),
+ (long)DBIc_MY_H(imp_xxh), (long)imp_xxh,
+ (PL_dirty) ? "global destruction" : HvNAME(DBIc_IMP_STASH(imp_xxh)));
+ if (DBIc_COMSET(imp_xxh)) sv_catpv(flags,"COMSET ");
+ if (DBIc_IMPSET(imp_xxh)) sv_catpv(flags,"IMPSET ");
+ if (DBIc_ACTIVE(imp_xxh)) sv_catpv(flags,"Active ");
+ if (DBIc_WARN(imp_xxh)) sv_catpv(flags,"Warn ");
+ if (DBIc_COMPAT(imp_xxh)) sv_catpv(flags,"CompatMode ");
+ if (DBIc_is(imp_xxh, DBIcf_ChopBlanks)) sv_catpv(flags,"ChopBlanks ");
+ if (DBIc_is(imp_xxh, DBIcf_HandleSetErr)) sv_catpv(flags,"HandleSetErr ");
+ if (DBIc_is(imp_xxh, DBIcf_HandleError)) sv_catpv(flags,"HandleError ");
+ if (DBIc_is(imp_xxh, DBIcf_RaiseError)) sv_catpv(flags,"RaiseError ");
+ if (DBIc_is(imp_xxh, DBIcf_PrintError)) sv_catpv(flags,"PrintError ");
+ if (DBIc_is(imp_xxh, DBIcf_PrintWarn)) sv_catpv(flags,"PrintWarn ");
+ if (DBIc_is(imp_xxh, DBIcf_ShowErrorStatement)) sv_catpv(flags,"ShowErrorStatement ");
+ if (DBIc_is(imp_xxh, DBIcf_AutoCommit)) sv_catpv(flags,"AutoCommit ");
+ if (DBIc_is(imp_xxh, DBIcf_BegunWork)) sv_catpv(flags,"BegunWork ");
+ if (DBIc_is(imp_xxh, DBIcf_LongTruncOk)) sv_catpv(flags,"LongTruncOk ");
+ if (DBIc_is(imp_xxh, DBIcf_MultiThread)) sv_catpv(flags,"MultiThread ");
+ if (DBIc_is(imp_xxh, DBIcf_TaintIn)) sv_catpv(flags,"TaintIn ");
+ if (DBIc_is(imp_xxh, DBIcf_TaintOut)) sv_catpv(flags,"TaintOut ");
+ if (DBIc_is(imp_xxh, DBIcf_Profile)) sv_catpv(flags,"Profile ");
+ if (DBIc_is(imp_xxh, DBIcf_Callbacks)) sv_catpv(flags,"Callbacks ");
+ PerlIO_printf(DBILOGFP,"%s FLAGS 0x%lx: %s\n", pad, (long)DBIc_FLAGS(imp_xxh), SvPV_nolen(flags));
+ if (SvOK(DBIc_ERR(imp_xxh)))
+ PerlIO_printf(DBILOGFP,"%s ERR %s\n", pad, neatsvpv((SV*)DBIc_ERR(imp_xxh),0));
+ if (SvOK(DBIc_ERR(imp_xxh)))
+ PerlIO_printf(DBILOGFP,"%s ERRSTR %s\n", pad, neatsvpv((SV*)DBIc_ERRSTR(imp_xxh),0));
+ PerlIO_printf(DBILOGFP,"%s PARENT %s\n", pad, neatsvpv((SV*)DBIc_PARENT_H(imp_xxh),0));
+ PerlIO_printf(DBILOGFP,"%s KIDS %ld (%ld Active)\n", pad,
+ (long)DBIc_KIDS(imp_xxh), (long)DBIc_ACTIVE_KIDS(imp_xxh));
+ if (DBIc_IMP_DATA(imp_xxh) && SvOK(DBIc_IMP_DATA(imp_xxh)))
+ PerlIO_printf(DBILOGFP,"%s IMP_DATA %s\n", pad, neatsvpv(DBIc_IMP_DATA(imp_xxh),0));
+ if (DBIc_LongReadLen(imp_xxh) != DBIc_LongReadLen_init)
+ PerlIO_printf(DBILOGFP,"%s LongReadLen %ld\n", pad, (long)DBIc_LongReadLen(imp_xxh));
+
+ if (DBIc_TYPE(imp_xxh) == DBIt_ST) {
+ const imp_sth_t *imp_sth = (imp_sth_t*)imp_xxh;
+ PerlIO_printf(DBILOGFP,"%s NUM_OF_FIELDS %d\n", pad, DBIc_NUM_FIELDS(imp_sth));
+ PerlIO_printf(DBILOGFP,"%s NUM_OF_PARAMS %d\n", pad, DBIc_NUM_PARAMS(imp_sth));
+ }
+ inner = dbih_inner(aTHX_ (SV*)DBIc_MY_H(imp_xxh), msg);
+ if (!inner || !SvROK(inner))
+ return 1;
+ if (DBIc_TYPE(imp_xxh) <= DBIt_DB) {
+ SV **svp = hv_fetch((HV*)SvRV(inner), "CachedKids", 10, 0);
+ if (svp && SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVHV) {
+ HV *hv = (HV*)SvRV(*svp);
+ PerlIO_printf(DBILOGFP,"%s CachedKids %d\n", pad, (int)HvKEYS(hv));
+ }
+ }
+ if (level > 0) {
+ SV* value;
+ char *key;
+ I32 keylen;
+ PerlIO_printf(DBILOGFP,"%s cached attributes:\n", pad);
+ while ( (value = hv_iternextsv((HV*)SvRV(inner), &key, &keylen)) ) {
+ PerlIO_printf(DBILOGFP,"%s '%s' => %s\n", pad, key, neatsvpv(value,0));
+ }
+ }
+ else if (DBIc_TYPE(imp_xxh) == DBIt_DB) {
+ SV **svp = hv_fetch((HV*)SvRV(inner), "Name", 4, 0);
+ if (svp && SvOK(*svp))
+ PerlIO_printf(DBILOGFP,"%s Name %s\n", pad, neatsvpv(*svp,0));
+ }
+ else if (DBIc_TYPE(imp_xxh) == DBIt_ST) {
+ SV **svp = hv_fetch((HV*)SvRV(inner), "Statement", 9, 0);
+ if (svp && SvOK(*svp))
+ PerlIO_printf(DBILOGFP,"%s Statement %s\n", pad, neatsvpv(*svp,0));
+ }
+ return 1;
+}
+
+
+static void
+dbih_clearcom(imp_xxh_t *imp_xxh)
+{
+ dTHX;
+ dTHR;
+ int dump = FALSE;
+ int debug = DBIc_TRACE_LEVEL(imp_xxh);
+ int auto_dump = (debug >= 6);
+ imp_xxh_t * const parent_xxh = DBIc_PARENT_COM(imp_xxh);
+ /* Note that we're very much on our own here. DBIc_MY_H(imp_xxh) almost */
+ /* certainly points to memory which has been freed. Don't use it! */
+
+ /* --- pre-clearing sanity checks --- */
+
+#ifdef DBI_USE_THREADS
+ if (DBIc_THR_USER(imp_xxh) != my_perl) { /* don't clear handle that belongs to another thread */
+ if (debug >= 3) {
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh)," skipped dbih_clearcom: DBI handle (type=%d, %s) is owned by thread %p not current thread %p\n",
+ DBIc_TYPE(imp_xxh), HvNAME(DBIc_IMP_STASH(imp_xxh)), (void*)DBIc_THR_USER(imp_xxh), (void*)my_perl) ;
+ PerlIO_flush(DBIc_LOGPIO(imp_xxh));
+ }
+ return;
+ }
+#endif
+
+ if (!DBIc_COMSET(imp_xxh)) { /* should never happen */
+ dbih_dumpcom(aTHX_ imp_xxh, "dbih_clearcom: DBI handle already cleared", 0);
+ return;
+ }
+
+ if (auto_dump)
+ dbih_dumpcom(aTHX_ imp_xxh,"DESTROY (dbih_clearcom)", 0);
+
+ if (!PL_dirty) {
+
+ if (DBIc_ACTIVE(imp_xxh)) { /* bad news, potentially */
+ /* warn for sth, warn for dbh only if it has active sth or isn't AutoCommit */
+ if (DBIc_TYPE(imp_xxh) >= DBIt_ST
+ || (DBIc_ACTIVE_KIDS(imp_xxh) || !DBIc_has(imp_xxh, DBIcf_AutoCommit))
+ ) {
+ warn("DBI %s handle 0x%lx cleared whilst still active",
+ dbih_htype_name(DBIc_TYPE(imp_xxh)), (unsigned long)DBIc_MY_H(imp_xxh));
+ dump = TRUE;
+ }
+ }
+
+ /* check that the implementor has done its own housekeeping */
+ if (DBIc_IMPSET(imp_xxh)) {
+ warn("DBI %s handle 0x%lx has uncleared implementors data",
+ dbih_htype_name(DBIc_TYPE(imp_xxh)), (unsigned long)DBIc_MY_H(imp_xxh));
+ dump = TRUE;
+ }
+
+ if (DBIc_KIDS(imp_xxh)) {
+ warn("DBI %s handle 0x%lx has %d uncleared child handles",
+ dbih_htype_name(DBIc_TYPE(imp_xxh)),
+ (unsigned long)DBIc_MY_H(imp_xxh), (int)DBIc_KIDS(imp_xxh));
+ dump = TRUE;
+ }
+ }
+
+ if (dump && !auto_dump) /* else was already dumped above */
+ dbih_dumpcom(aTHX_ imp_xxh, "dbih_clearcom", 0);
+
+ /* --- pre-clearing adjustments --- */
+
+ if (!PL_dirty) {
+ if (parent_xxh) {
+ if (DBIc_ACTIVE(imp_xxh)) /* see also DBIc_ACTIVE_off */
+ --DBIc_ACTIVE_KIDS(parent_xxh);
+ --DBIc_KIDS(parent_xxh);
+ }
+ }
+
+ /* --- clear fields (may invoke object destructors) --- */
+
+ if (DBIc_TYPE(imp_xxh) == DBIt_ST) {
+ imp_sth_t *imp_sth = (imp_sth_t*)imp_xxh;
+ sv_free((SV*)DBIc_FIELDS_AV(imp_sth));
+ }
+
+ sv_free(DBIc_IMP_DATA(imp_xxh)); /* do this first */
+ if (DBIc_TYPE(imp_xxh) <= DBIt_ST) { /* DBIt_FD doesn't have attr */
+ sv_free(_imp2com(imp_xxh, attr.TraceLevel));
+ sv_free(_imp2com(imp_xxh, attr.State));
+ sv_free(_imp2com(imp_xxh, attr.Err));
+ sv_free(_imp2com(imp_xxh, attr.Errstr));
+ sv_free(_imp2com(imp_xxh, attr.FetchHashKeyName));
+ }
+
+
+ sv_free((SV*)DBIc_PARENT_H(imp_xxh)); /* do this last */
+
+ DBIc_COMSET_off(imp_xxh);
+
+ if (debug >= 4)
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh)," dbih_clearcom 0x%lx (com 0x%lx, type %d) done.\n\n",
+ (long)DBIc_MY_H(imp_xxh), (long)imp_xxh, DBIc_TYPE(imp_xxh));
+}
+
+
+/* --- Functions for handling field buffer arrays --- */
+
+static AV *
+dbih_setup_fbav(imp_sth_t *imp_sth)
+{
+ /* Usually called to setup the row buffer for new sth.
+ * Also called if the value of NUM_OF_FIELDS is altered,
+ * in which case it adjusts the row buffer to match NUM_OF_FIELDS.
+ */
+ dTHX;
+ I32 i = DBIc_NUM_FIELDS(imp_sth);
+ AV *av = DBIc_FIELDS_AV(imp_sth);
+
+ if (i < 0)
+ i = 0;
+
+ if (av) {
+ if (av_len(av)+1 == i) /* is existing array the right size? */
+ return av;
+ /* we need to adjust the size of the array */
+ if (DBIc_TRACE_LEVEL(imp_sth) >= 2)
+ PerlIO_printf(DBIc_LOGPIO(imp_sth)," dbih_setup_fbav realloc from %ld to %ld fields\n", (long)(av_len(av)+1), (long)i);
+ SvREADONLY_off(av);
+ if (i < av_len(av)+1) /* trim to size if too big */
+ av_fill(av, i-1);
+ }
+ else {
+ if (DBIc_TRACE_LEVEL(imp_sth) >= 5)
+ PerlIO_printf(DBIc_LOGPIO(imp_sth)," dbih_setup_fbav alloc for %ld fields\n", (long)i);
+ av = newAV();
+ DBIc_FIELDS_AV(imp_sth) = av;
+
+ /* row_count will need to be manually reset by the driver if the */
+ /* sth is re-executed (since this code won't get rerun) */
+ DBIc_ROW_COUNT(imp_sth) = 0;
+ }
+
+ /* load array with writeable SV's. Do this backwards so */
+ /* the array only gets extended once. */
+ while(i--) /* field 1 stored at index 0 */
+ av_store(av, i, newSV(0));
+ if (DBIc_TRACE_LEVEL(imp_sth) >= 6)
+ PerlIO_printf(DBIc_LOGPIO(imp_sth)," dbih_setup_fbav now %ld fields\n", (long)(av_len(av)+1));
+ SvREADONLY_on(av); /* protect against shift @$row etc */
+ return av;
+}
+
+
+static AV *
+dbih_get_fbav(imp_sth_t *imp_sth)
+{
+ AV *av;
+
+ if ( (av = DBIc_FIELDS_AV(imp_sth)) == Nullav) {
+ av = dbih_setup_fbav(imp_sth);
+ }
+ else {
+ dTHX;
+ int i = av_len(av) + 1;
+ if (i != DBIc_NUM_FIELDS(imp_sth)) {
+ /*SV *sth = dbih_inner(aTHX_ (SV*)DBIc_MY_H(imp_sth), "_get_fbav");*/
+ /* warn via PrintWarn */
+ set_err_char(SvRV(DBIc_MY_H(imp_sth)), (imp_xxh_t*)imp_sth,
+ "0", 0, "Number of row fields inconsistent with NUM_OF_FIELDS (driver bug)", "", "_get_fbav");
+ /*
+ DBIc_NUM_FIELDS(imp_sth) = i;
+ hv_delete((HV*)SvRV(sth), "NUM_OF_FIELDS", 13, G_DISCARD);
+ */
+ }
+ /* don't let SvUTF8 flag persist from one row to the next */
+ /* (only affects drivers that use sv_setpv, but most XS do) */
+ /* XXX turn into option later (force on/force off/ignore) */
+ while(i--) /* field 1 stored at index 0 */
+ SvUTF8_off(AvARRAY(av)[i]);
+ }
+
+ if (DBIc_is(imp_sth, DBIcf_TaintOut)) {
+ dTHX;
+ dTHR;
+ TAINT; /* affects sv_setsv()'s called within same perl statement */
+ }
+
+ /* XXX fancy stuff to happen here later (re scrolling etc) */
+ ++DBIc_ROW_COUNT(imp_sth);
+ return av;
+}
+
+
+static int
+dbih_sth_bind_col(SV *sth, SV *col, SV *ref, SV *attribs)
+{
+ dTHX;
+ D_imp_sth(sth);
+ AV *av;
+ int idx = SvIV(col);
+ int fields = DBIc_NUM_FIELDS(imp_sth);
+
+ if (fields <= 0) {
+ attribs = attribs; /* avoid 'unused variable' warning */
+ croak("Statement has no result columns to bind%s",
+ DBIc_ACTIVE(imp_sth)
+ ? "" : " (perhaps you need to call execute first)");
+ }
+
+ if ( (av = DBIc_FIELDS_AV(imp_sth)) == Nullav)
+ av = dbih_setup_fbav(imp_sth);
+
+ if (DBIc_TRACE_LEVEL(imp_sth) >= 5)
+ PerlIO_printf(DBIc_LOGPIO(imp_sth)," dbih_sth_bind_col %s => %s %s\n",
+ neatsvpv(col,0), neatsvpv(ref,0), neatsvpv(attribs,0));
+
+ if (idx < 1 || idx > fields)
+ croak("bind_col: column %d is not a valid column (1..%d)",
+ idx, fields);
+
+ if (!SvOK(ref) && SvREADONLY(ref)) { /* binding to literal undef */
+ /* presumably the call is just setting the TYPE or other atribs */
+ /* but this default method ignores attribs, so we just return */
+ return 1;
+ }
+
+ /* Write this as > SVt_PVMG because in 5.8.x the next type */
+ /* is SVt_PVBM, whereas in 5.9.x it's SVt_PVGV. */
+ if (!SvROK(ref) || SvTYPE(SvRV(ref)) > SVt_PVMG) /* XXX LV */
+ croak("Can't %s->bind_col(%s, %s,...), need a reference to a scalar",
+ neatsvpv(sth,0), neatsvpv(col,0), neatsvpv(ref,0));
+
+ /* use supplied scalar as storage for this column */
+ SvREADONLY_off(av);
+ av_store(av, idx-1, SvREFCNT_inc(SvRV(ref)) );
+ SvREADONLY_on(av);
+ return 1;
+}
+
+
+static int
+quote_type(int sql_type, int p, int s, int *t, void *v)
+{
+ /* Returns true if type should be bound as a number else */
+ /* false implying that binding as a string should be okay. */
+ /* The true value is either SQL_INTEGER or SQL_DOUBLE which */
+ /* can be used as a hint if desired. */
+ (void)p;
+ (void)s;
+ (void)t;
+ (void)v;
+ /* looks like it's never been used, and doesn't make much sense anyway */
+ warn("Use of DBI internal bind_as_num/quote_type function is deprecated");
+ switch(sql_type) {
+ case SQL_INTEGER:
+ case SQL_SMALLINT:
+ case SQL_TINYINT:
+ case SQL_BIGINT:
+ return 0;
+ case SQL_FLOAT:
+ case SQL_REAL:
+ case SQL_DOUBLE:
+ return 0;
+ case SQL_NUMERIC:
+ case SQL_DECIMAL:
+ return 0; /* bind as string to attempt to retain precision */
+ }
+ return 1;
+}
+
+
+/* Convert a simple string representation of a value into a more specific
+ * perl type based on an sql_type value.
+ * The semantics of SQL standard TYPE values are interpreted _very_ loosely
+ * on the basis of "be liberal in what you accept and let's throw in some
+ * extra semantics while we're here" :)
+ * Returns:
+ * -2: sql_type isn't handled, value unchanged
+ * -1: sv is undef, value unchanged
+ * 0: sv couldn't be cast cleanly and DBIstcf_STRICT was used
+ * 1: sv couldn't be cast cleanly and DBIstcf_STRICT was not used
+ * 2: sv was cast ok
+ */
+
+int
+sql_type_cast_svpv(pTHX_ SV *sv, int sql_type, U32 flags, void *v)
+{
+ int cast_ok = 0;
+ int grok_flags;
+ UV uv;
+
+ /* do nothing for undef (NULL) or non-string values */
+ if (!sv || !SvOK(sv))
+ return -1;
+
+ switch(sql_type) {
+
+ default:
+ return -2; /* not a recognised SQL TYPE, value unchanged */
+
+ case SQL_INTEGER:
+ /* sv_2iv is liberal, may return SvIV, SvUV, or SvNV */
+ sv_2iv(sv);
+ /* SvNOK will be set if value is out of range for IV/UV.
+ * SvIOK should be set but won't if sv is not numeric (in which
+ * case perl would have warn'd already if -w or warnings are in effect)
+ */
+ cast_ok = (SvIOK(sv) && !SvNOK(sv));
+ break;
+
+ case SQL_DOUBLE:
+ sv_2nv(sv);
+ /* SvNOK should be set but won't if sv is not numeric (in which
+ * case perl would have warn'd already if -w or warnings are in effect)
+ */
+ cast_ok = SvNOK(sv);
+ break;
+
+ /* caller would like IV else UV else NV */
+ /* else no error and sv is untouched */
+ case SQL_NUMERIC:
+ /* based on the code in perl's toke.c */
+ uv = 0;
+ grok_flags = grok_number(SvPVX(sv), SvCUR(sv), &uv);
+ cast_ok = 1;
+ if (grok_flags == IS_NUMBER_IN_UV) { /* +ve int */
+ if (uv <= IV_MAX) /* prefer IV over UV */
+ sv_2iv(sv);
+ else sv_2uv(sv);
+ }
+ else if (grok_flags == (IS_NUMBER_IN_UV | IS_NUMBER_NEG)
+ && uv <= IV_MAX
+ ) {
+ sv_2iv(sv);
+ }
+ else if (grok_flags) { /* is numeric */
+ sv_2nv(sv);
+ }
+ else
+ cast_ok = 0;
+ break;
+
+#if 0 /* XXX future possibilities */
+ case SQL_BIGINT: /* use Math::BigInt if too large for IV/UV */
+#endif
+ }
+
+ if (cast_ok) {
+
+ if (flags & DBIstcf_DISCARD_STRING
+ && SvNIOK(sv) /* we set a numeric value */
+ && SvPVX(sv) /* we have a buffer to discard */
+ ) {
+ SvOOK_off(sv);
+ if (SvLEN(sv))
+ Safefree(SvPVX(sv));
+ SvPOK_off(sv);
+ SvPV_set(sv, NULL);
+ SvLEN_set(sv, 0);
+ SvCUR_set(sv, 0);
+ }
+ }
+
+ if (cast_ok)
+ return 2;
+ else if (flags & DBIstcf_STRICT)
+ return 0;
+ else return 1;
+}
+
+
+
+/* --- Generic Handle Attributes (for all handle types) --- */
+
+static int
+dbih_set_attr_k(SV *h, SV *keysv, int dbikey, SV *valuesv)
+{
+ dTHX;
+ dTHR;
+ D_imp_xxh(h);
+ STRLEN keylen;
+ const char *key = SvPV(keysv, keylen);
+ const int htype = DBIc_TYPE(imp_xxh);
+ int on = (SvTRUE(valuesv));
+ int internal = 1; /* DBIh_IN_PERL_DBD(imp_xxh); -- for DBD's in perl */
+ int cacheit = 0;
+ (void)dbikey;
+
+ if (DBIc_TRACE_LEVEL(imp_xxh) >= 3)
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh)," STORE %s %s => %s\n",
+ neatsvpv(h,0), neatsvpv(keysv,0), neatsvpv(valuesv,0));
+
+ if (internal && strEQ(key, "Active")) {
+ if (on) {
+ D_imp_sth(h);
+ DBIc_ACTIVE_on(imp_xxh);
+ /* for pure-perl drivers on second and subsequent */
+ /* execute()'s, else row count keeps rising. */
+ if (htype==DBIt_ST && DBIc_FIELDS_AV(imp_sth))
+ DBIc_ROW_COUNT(imp_sth) = 0;
+ }
+ else {
+ DBIc_ACTIVE_off(imp_xxh);
+ }
+ }
+ else if (strEQ(key, "FetchHashKeyName")) {
+ if (htype >= DBIt_ST)
+ croak("Can't set FetchHashKeyName for a statement handle, set in parent before prepare()");
+ cacheit = 1; /* just save it */
+ }
+ else if (strEQ(key, "CompatMode")) {
+ (on) ? DBIc_COMPAT_on(imp_xxh) : DBIc_COMPAT_off(imp_xxh);
+ }
+ else if (strEQ(key, "Warn")) {
+ (on) ? DBIc_WARN_on(imp_xxh) : DBIc_WARN_off(imp_xxh);
+ }
+ else if (strEQ(key, "AutoInactiveDestroy")) {
+ (on) ? DBIc_AIADESTROY_on(imp_xxh) : DBIc_AIADESTROY_off(imp_xxh);
+ }
+ else if (strEQ(key, "InactiveDestroy")) {
+ (on) ? DBIc_IADESTROY_on(imp_xxh) : DBIc_IADESTROY_off(imp_xxh);
+ }
+ else if (strEQ(key, "RootClass")) {
+ cacheit = 1; /* just save it */
+ }
+ else if (strEQ(key, "RowCacheSize")) {
+ cacheit = 0; /* ignore it */
+ }
+ else if (strEQ(key, "Executed")) {
+ DBIc_set(imp_xxh, DBIcf_Executed, on);
+ }
+ else if (strEQ(key, "ChopBlanks")) {
+ DBIc_set(imp_xxh, DBIcf_ChopBlanks, on);
+ }
+ else if (strEQ(key, "ErrCount")) {
+ DBIc_ErrCount(imp_xxh) = SvUV(valuesv);
+ }
+ else if (strEQ(key, "LongReadLen")) {
+ if (SvNV(valuesv) < 0 || SvNV(valuesv) > MAX_LongReadLen)
+ croak("Can't set LongReadLen < 0 or > %ld",MAX_LongReadLen);
+ DBIc_LongReadLen(imp_xxh) = SvIV(valuesv);
+ cacheit = 1; /* save it for clone */
+ }
+ else if (strEQ(key, "LongTruncOk")) {
+ DBIc_set(imp_xxh,DBIcf_LongTruncOk, on);
+ }
+ else if (strEQ(key, "RaiseError")) {
+ DBIc_set(imp_xxh,DBIcf_RaiseError, on);
+ }
+ else if (strEQ(key, "PrintError")) {
+ DBIc_set(imp_xxh,DBIcf_PrintError, on);
+ }
+ else if (strEQ(key, "PrintWarn")) {
+ DBIc_set(imp_xxh,DBIcf_PrintWarn, on);
+ }
+ else if (strEQ(key, "HandleError")) {
+ if ( on && (!SvROK(valuesv) || (SvTYPE(SvRV(valuesv)) != SVt_PVCV)) ) {
+ croak("Can't set %s to '%s'", "HandleError", neatsvpv(valuesv,0));
+ }
+ DBIc_set(imp_xxh,DBIcf_HandleError, on);
+ cacheit = 1; /* child copy setup by dbih_setup_handle() */
+ }
+ else if (strEQ(key, "HandleSetErr")) {
+ if ( on && (!SvROK(valuesv) || (SvTYPE(SvRV(valuesv)) != SVt_PVCV)) ) {
+ croak("Can't set %s to '%s'","HandleSetErr",neatsvpv(valuesv,0));
+ }
+ DBIc_set(imp_xxh,DBIcf_HandleSetErr, on);
+ cacheit = 1; /* child copy setup by dbih_setup_handle() */
+ }
+ else if (strEQ(key, "ChildHandles")) {
+ if ( on && (!SvROK(valuesv) || (SvTYPE(SvRV(valuesv)) != SVt_PVAV)) ) {
+ croak("Can't set %s to '%s'", "ChildHandles", neatsvpv(valuesv,0));
+ }
+ cacheit = 1; /* just save it in the hash */
+ }
+ else if (strEQ(key, "Profile")) {
+ static const char profile_class[] = "DBI::Profile";
+ if (on && (!SvROK(valuesv) || (SvTYPE(SvRV(valuesv)) != SVt_PVHV)) ) {
+ /* not a hash ref so use DBI::Profile to work out what to do */
+ dTHR;
+ dSP;
+ I32 returns;
+ TAINT_NOT; /* the require is presumed innocent till proven guilty */
+ perl_require_pv("DBI/Profile.pm");
+ if (SvTRUE(ERRSV)) {
+ warn("Can't load %s: %s", profile_class, SvPV_nolen(ERRSV));
+ valuesv = &PL_sv_undef;
+ }
+ else {
+ PUSHMARK(SP);
+ XPUSHs(sv_2mortal(newSVpv(profile_class,0)));
+ XPUSHs(valuesv);
+ PUTBACK;
+ returns = call_method("_auto_new", G_SCALAR);
+ if (returns != 1)
+ croak("%s _auto_new", profile_class);
+ SPAGAIN;
+ valuesv = POPs;
+ PUTBACK;
+ }
+ on = SvTRUE(valuesv); /* in case it returns undef */
+ }
+ if (on && !sv_isobject(valuesv)) {
+ /* not blessed already - so default to DBI::Profile */
+ HV *stash;
+ perl_require_pv(profile_class);
+ stash = gv_stashpv(profile_class, GV_ADDWARN);
+ sv_bless(valuesv, stash);
+ }
+ DBIc_set(imp_xxh,DBIcf_Profile, on);
+ cacheit = 1; /* child copy setup by dbih_setup_handle() */
+ }
+ else if (strEQ(key, "ShowErrorStatement")) {
+ DBIc_set(imp_xxh,DBIcf_ShowErrorStatement, on);
+ }
+ else if (strEQ(key, "MultiThread") && internal) {
+ /* here to allow pure-perl drivers to set MultiThread */
+ DBIc_set(imp_xxh,DBIcf_MultiThread, on);
+ if (on && DBIc_WARN(imp_xxh)) {
+ warn("MultiThread support not yet implemented in DBI");
+ }
+ }
+ else if (strEQ(key, "Taint")) {
+ /* 'Taint' is a shortcut for both in and out mode */
+ DBIc_set(imp_xxh,DBIcf_TaintIn|DBIcf_TaintOut, on);
+ }
+ else if (strEQ(key, "TaintIn")) {
+ DBIc_set(imp_xxh,DBIcf_TaintIn, on);
+ }
+ else if (strEQ(key, "TaintOut")) {
+ DBIc_set(imp_xxh,DBIcf_TaintOut, on);
+ }
+ else if (htype<=DBIt_DB && keylen==10 && strEQ(key, "CachedKids")
+ /* only allow hash refs */
+ && SvROK(valuesv) && SvTYPE(SvRV(valuesv))==SVt_PVHV
+ ) {
+ cacheit = 1;
+ }
+ else if (keylen==9 && strEQ(key, "Callbacks")) {
+ if ( on && (!SvROK(valuesv) || (SvTYPE(SvRV(valuesv)) != SVt_PVHV)) )
+ croak("Can't set Callbacks to '%s'",neatsvpv(valuesv,0));
+ /* see also dbih_setup_handle for ChildCallbacks handling */
+ DBIc_set(imp_xxh, DBIcf_Callbacks, on);
+ cacheit = 1;
+ }
+ else if (htype<=DBIt_DB && keylen==10 && strEQ(key, "AutoCommit")) {
+ /* driver should have intercepted this and either handled it */
+ /* or set valuesv to either the 'magic' on or off value. */
+ if (SvIV(valuesv) != -900 && SvIV(valuesv) != -901)
+ croak("DBD driver has not implemented the AutoCommit attribute");
+ DBIc_set(imp_xxh,DBIcf_AutoCommit, (SvIV(valuesv)==-901));
+ }
+ else if (htype==DBIt_DB && keylen==9 && strEQ(key, "BegunWork")) {
+ DBIc_set(imp_xxh,DBIcf_BegunWork, on);
+ }
+ else if (keylen==10 && strEQ(key, "TraceLevel")) {
+ set_trace(h, valuesv, Nullsv);
+ }
+ else if (keylen==9 && strEQ(key, "TraceFile")) { /* XXX undocumented and readonly */
+ set_trace_file(valuesv);
+ }
+ else if (htype==DBIt_ST && strEQ(key, "NUM_OF_FIELDS")) {
+ D_imp_sth(h);
+ int new_num_fields = (SvOK(valuesv)) ? SvIV(valuesv) : -1;
+ DBIc_NUM_FIELDS(imp_sth) = new_num_fields;
+ if (DBIc_FIELDS_AV(imp_sth)) { /* modify existing fbav */
+ dbih_setup_fbav(imp_sth);
+ }
+ cacheit = 1;
+ }
+ else if (htype==DBIt_ST && strEQ(key, "NUM_OF_PARAMS")) {
+ D_imp_sth(h);
+ DBIc_NUM_PARAMS(imp_sth) = SvIV(valuesv);
+ cacheit = 1;
+ }
+ /* these are here due to clone() needing to set attribs through a public api */
+ else if (htype<=DBIt_DB && (strEQ(key, "Name")
+ || strEQ(key,"ImplementorClass")
+ || strEQ(key,"ReadOnly")
+ || strEQ(key,"Statement")
+ || strEQ(key,"Username")
+ /* these are here for backwards histerical raisons */
+ || strEQ(key,"USER") || strEQ(key,"CURRENT_USER")
+ ) ) {
+ cacheit = 1;
+ }
+ else { /* XXX should really be an event ? */
+ if (isUPPER(*key)) {
+ char *msg = "Can't set %s->{%s}: unrecognised attribute name or invalid value%s";
+ char *hint = "";
+ if (strEQ(key, "NUM_FIELDS"))
+ hint = ", perhaps you meant NUM_OF_FIELDS";
+ warn(msg, neatsvpv(h,0), key, hint);
+ return FALSE; /* don't store it */
+ }
+ /* Allow private_* attributes to be stored in the cache. */
+ /* This is designed to make life easier for people subclassing */
+ /* the DBI classes and may be of use to simple perl DBD's. */
+ if (strnNE(key,"private_",8) && strnNE(key,"dbd_",4) && strnNE(key,"dbi_",4)) {
+ if (DBIc_TRACE_LEVEL(imp_xxh)) { /* change to DBIc_WARN(imp_xxh) once we can validate prefix against registry */
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh),"$h->{%s}=%s ignored for invalid driver-specific attribute\n",
+ neatsvpv(keysv,0), neatsvpv(valuesv,0));
+ }
+ return FALSE;
+ }
+ cacheit = 1;
+ }
+ if (cacheit) {
+ (void)hv_store((HV*)SvRV(h), key, keylen, newSVsv(valuesv), 0);
+ }
+ return TRUE;
+}
+
+
+static SV *
+dbih_get_attr_k(SV *h, SV *keysv, int dbikey)
+{
+ dTHX;
+ dTHR;
+ D_imp_xxh(h);
+ STRLEN keylen;
+ char *key = SvPV(keysv, keylen);
+ int htype = DBIc_TYPE(imp_xxh);
+ SV *valuesv = Nullsv;
+ int cacheit = FALSE;
+ char *p;
+ int i;
+ SV *sv;
+ SV **svp;
+ (void)dbikey;
+
+ /* DBI quick_FETCH will service some requests (e.g., cached values) */
+
+ if (htype == DBIt_ST) {
+ switch (*key) {
+
+ case 'D':
+ if (keylen==8 && strEQ(key, "Database")) {
+ D_imp_from_child(imp_dbh, imp_dbh_t, imp_xxh);
+ valuesv = newRV_inc((SV*)DBIc_MY_H(imp_dbh));
+ cacheit = FALSE; /* else creates ref loop */
+ }
+ break;
+
+ case 'N':
+ if (keylen==8 && strEQ(key, "NULLABLE")) {
+ valuesv = &PL_sv_undef;
+ break;
+ }
+
+ if (keylen==4 && strEQ(key, "NAME")) {
+ valuesv = &PL_sv_undef;
+ break;
+ }
+
+ /* deal with: NAME_(uc|lc), NAME_hash, NAME_(uc|lc)_hash */
+ if ((keylen==7 || keylen==9 || keylen==12)
+ && strnEQ(key, "NAME_", 5)
+ && ( (keylen==9 && strEQ(key, "NAME_hash"))
+ || ((key[5]=='u' || key[5]=='l') && key[6] == 'c'
+ && (!key[7] || strnEQ(&key[7], "_hash", 5)))
+ )
+ ) {
+ D_imp_sth(h);
+ valuesv = &PL_sv_undef;
+
+ /* fetch from tied outer handle to trigger FETCH magic */
+ svp = hv_fetch((HV*)DBIc_MY_H(imp_sth), "NAME",4, FALSE);
+ sv = (svp) ? *svp : &PL_sv_undef;
+ if (SvGMAGICAL(sv)) /* call FETCH via magic */
+ mg_get(sv);
+
+ if (SvROK(sv)) {
+ AV *name_av = (AV*)SvRV(sv);
+ char *name;
+ int upcase = (key[5] == 'u');
+ AV *av = Nullav;
+ HV *hv = Nullhv;
+ int num_fields_mismatch = 0;
+
+ if (strEQ(&key[strlen(key)-5], "_hash"))
+ hv = newHV();
+ else av = newAV();
+ i = DBIc_NUM_FIELDS(imp_sth);
+
+ /* catch invalid NUM_FIELDS */
+ if (i != AvFILL(name_av)+1) {
+ /* flag as mismatch, except for "-1 and empty" case */
+ if ( ! (i == -1 && 0 == AvFILL(name_av)+1) )
+ num_fields_mismatch = 1;
+ i = AvFILL(name_av)+1; /* limit for safe iteration over array */
+ }
+
+ if (DBIc_TRACE_LEVEL(imp_sth) >= 10 || (num_fields_mismatch && DBIc_WARN(imp_xxh))) {
+ PerlIO_printf(DBIc_LOGPIO(imp_sth)," FETCH $h->{%s} from $h->{NAME} with $h->{NUM_OF_FIELDS} = %d"
+ " and %ld entries in $h->{NAME}%s\n",
+ neatsvpv(keysv,0), DBIc_NUM_FIELDS(imp_sth), AvFILL(name_av)+1,
+ (num_fields_mismatch) ? " (possible bug in driver)" : "");
+ }
+
+ while (--i >= 0) {
+ sv = newSVsv(AvARRAY(name_av)[i]);
+ name = SvPV_nolen(sv);
+ if (key[5] != 'h') { /* "NAME_hash" */
+ for (p = name; p && *p; ++p) {
+#ifdef toUPPER_LC
+ *p = (upcase) ? toUPPER_LC(*p) : toLOWER_LC(*p);
+#else
+ *p = (upcase) ? toUPPER(*p) : toLOWER(*p);
+#endif
+ }
+ }
+ if (av)
+ av_store(av, i, sv);
+ else {
+ (void)hv_store(hv, name, SvCUR(sv), newSViv(i), 0);
+ sv_free(sv);
+ }
+ }
+ valuesv = newRV_noinc( (av ? (SV*)av : (SV*)hv) );
+ cacheit = TRUE; /* can't change */
+ }
+ }
+ else if (keylen==13 && strEQ(key, "NUM_OF_FIELDS")) {
+ D_imp_sth(h);
+ IV num_fields = DBIc_NUM_FIELDS(imp_sth);
+ valuesv = (num_fields < 0) ? &PL_sv_undef : newSViv(num_fields);
+ if (num_fields > 0)
+ cacheit = TRUE; /* can't change once set (XXX except for multiple result sets) */
+ }
+ else if (keylen==13 && strEQ(key, "NUM_OF_PARAMS")) {
+ D_imp_sth(h);
+ valuesv = newSViv(DBIc_NUM_PARAMS(imp_sth));
+ cacheit = TRUE; /* can't change */
+ }
+ break;
+
+ case 'P':
+ if (strEQ(key, "PRECISION"))
+ valuesv = &PL_sv_undef;
+ else if (strEQ(key, "ParamValues"))
+ valuesv = &PL_sv_undef;
+ else if (strEQ(key, "ParamTypes"))
+ valuesv = &PL_sv_undef;
+ break;
+
+ case 'R':
+ if (strEQ(key, "RowsInCache"))
+ valuesv = &PL_sv_undef;
+ break;
+
+ case 'S':
+ if (strEQ(key, "SCALE"))
+ valuesv = &PL_sv_undef;
+ break;
+
+ case 'T':
+ if (strEQ(key, "TYPE"))
+ valuesv = &PL_sv_undef;
+ break;
+ }
+
+ }
+ else
+ if (htype == DBIt_DB) {
+ /* this is here but is, sadly, not called because
+ * not-preloading them into the handle attrib cache caused
+ * wierdness in t/proxy.t that I never got to the bottom
+ * of. One day maybe. */
+ if (keylen==6 && strEQ(key, "Driver")) {
+ D_imp_from_child(imp_dbh, imp_dbh_t, imp_xxh);
+ valuesv = newRV_inc((SV*)DBIc_MY_H(imp_dbh));
+ cacheit = FALSE; /* else creates ref loop */
+ }
+ }
+
+ if (valuesv == Nullsv && htype <= DBIt_DB) {
+ if (keylen==10 && strEQ(key, "AutoCommit")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_AutoCommit));
+ }
+ }
+
+ if (valuesv == Nullsv) {
+ switch (*key) {
+ case 'A':
+ if (keylen==6 && strEQ(key, "Active")) {
+ valuesv = boolSV(DBIc_ACTIVE(imp_xxh));
+ }
+ else if (keylen==10 && strEQ(key, "ActiveKids")) {
+ valuesv = newSViv(DBIc_ACTIVE_KIDS(imp_xxh));
+ }
+ else if (strEQ(key, "AutoInactiveDestroy")) {
+ valuesv = boolSV(DBIc_AIADESTROY(imp_xxh));
+ }
+ break;
+
+ case 'B':
+ if (keylen==9 && strEQ(key, "BegunWork")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_BegunWork));
+ }
+ break;
+
+ case 'C':
+ if (strEQ(key, "ChildHandles")) {
+ svp = hv_fetch((HV*)SvRV(h), key, keylen, FALSE);
+ /* if something has been stored then return it.
+ * otherwise return a dummy empty array if weakrefs are
+ * available, else an undef to indicate that they're not */
+ if (svp) {
+ valuesv = newSVsv(*svp);
+ } else {
+#ifdef sv_rvweaken
+ valuesv = newRV_noinc((SV*)newAV());
+#else
+ valuesv = &PL_sv_undef;
+#endif
+ }
+ }
+ else if (strEQ(key, "ChopBlanks")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_ChopBlanks));
+ }
+ else if (strEQ(key, "CachedKids")) {
+ valuesv = &PL_sv_undef;
+ }
+ else if (strEQ(key, "CompatMode")) {
+ valuesv = boolSV(DBIc_COMPAT(imp_xxh));
+ }
+ break;
+
+ case 'E':
+ if (strEQ(key, "Executed")) {
+ valuesv = boolSV(DBIc_is(imp_xxh, DBIcf_Executed));
+ }
+ else if (strEQ(key, "ErrCount")) {
+ valuesv = newSVuv(DBIc_ErrCount(imp_xxh));
+ }
+ break;
+
+ case 'I':
+ if (strEQ(key, "InactiveDestroy")) {
+ valuesv = boolSV(DBIc_IADESTROY(imp_xxh));
+ }
+ break;
+
+ case 'K':
+ if (keylen==4 && strEQ(key, "Kids")) {
+ valuesv = newSViv(DBIc_KIDS(imp_xxh));
+ }
+ break;
+
+ case 'L':
+ if (keylen==11 && strEQ(key, "LongReadLen")) {
+ valuesv = newSVnv((NV)DBIc_LongReadLen(imp_xxh));
+ }
+ else if (keylen==11 && strEQ(key, "LongTruncOk")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_LongTruncOk));
+ }
+ break;
+
+ case 'M':
+ if (keylen==10 && strEQ(key, "MultiThread")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_MultiThread));
+ }
+ break;
+
+ case 'P':
+ if (keylen==10 && strEQ(key, "PrintError")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_PrintError));
+ }
+ else if (keylen==9 && strEQ(key, "PrintWarn")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_PrintWarn));
+ }
+ break;
+
+ case 'R':
+ if (keylen==10 && strEQ(key, "RaiseError")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_RaiseError));
+ }
+ else if (keylen==12 && strEQ(key, "RowCacheSize")) {
+ valuesv = &PL_sv_undef;
+ }
+ break;
+
+ case 'S':
+ if (keylen==18 && strEQ(key, "ShowErrorStatement")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_ShowErrorStatement));
+ }
+ break;
+
+ case 'T':
+ if (keylen==4 && strEQ(key, "Type")) {
+ char *type = dbih_htype_name(htype);
+ valuesv = newSVpv(type,0);
+ cacheit = TRUE; /* can't change */
+ }
+ else if (keylen==10 && strEQ(key, "TraceLevel")) {
+ valuesv = newSViv( DBIc_DEBUGIV(imp_xxh) );
+ }
+ else if (keylen==5 && strEQ(key, "Taint")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_TaintIn) &&
+ DBIc_has(imp_xxh,DBIcf_TaintOut));
+ }
+ else if (keylen==7 && strEQ(key, "TaintIn")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_TaintIn));
+ }
+ else if (keylen==8 && strEQ(key, "TaintOut")) {
+ valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_TaintOut));
+ }
+ break;
+
+ case 'W':
+ if (keylen==4 && strEQ(key, "Warn")) {
+ valuesv = boolSV(DBIc_WARN(imp_xxh));
+ }
+ break;
+ }
+ }
+
+ /* finally check the actual hash */
+ if (valuesv == Nullsv) {
+ valuesv = &PL_sv_undef;
+ cacheit = 0;
+ svp = hv_fetch((HV*)SvRV(h), key, keylen, FALSE);
+ if (svp)
+ valuesv = newSVsv(*svp); /* take copy to mortalize */
+ else /* warn unless it's known attribute name */
+ if ( !( (*key=='H' && strEQ(key, "HandleError"))
+ || (*key=='H' && strEQ(key, "HandleSetErr"))
+ || (*key=='S' && strEQ(key, "Statement"))
+ || (*key=='P' && strEQ(key, "ParamArrays"))
+ || (*key=='P' && strEQ(key, "ParamValues"))
+ || (*key=='P' && strEQ(key, "Profile"))
+ || (*key=='R' && strEQ(key, "ReadOnly"))
+ || (*key=='C' && strEQ(key, "CursorName"))
+ || (*key=='C' && strEQ(key, "Callbacks"))
+ || (*key=='U' && strEQ(key, "Username"))
+ || !isUPPER(*key) /* dbd_*, private_* etc */
+ ))
+ warn("Can't get %s->{%s}: unrecognised attribute name",neatsvpv(h,0),key);
+ }
+
+ if (cacheit) {
+ (void)hv_store((HV*)SvRV(h), key, keylen, newSVsv(valuesv), 0);
+ }
+ if (DBIc_TRACE_LEVEL(imp_xxh) >= 3)
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh)," .. FETCH %s %s = %s%s\n", neatsvpv(h,0),
+ neatsvpv(keysv,0), neatsvpv(valuesv,0), cacheit?" (cached)":"");
+ if (valuesv == &PL_sv_yes || valuesv == &PL_sv_no || valuesv == &PL_sv_undef)
+ return valuesv; /* no need to mortalize yes or no */
+ return sv_2mortal(valuesv);
+}
+
+
+
+/* -------------------------------------------------------------------- */
+/* Functions implementing Error and Event Handling. */
+
+
+static SV *
+dbih_event(SV *hrv, const char *evtype, SV *a1, SV *a2)
+{
+ dTHX;
+ /* We arrive here via DBIh_EVENT* macros (see DBIXS.h) called from */
+ /* DBD driver C code OR $h->event() method (in DBD::_::common) */
+ /* XXX VERY OLD INTERFACE/CONCEPT MAY GO SOON */
+ /* OR MAY EVOLVE INTO A WAY TO HANDLE 'SUCCESS_WITH_INFO'/'WARNINGS' from db */
+ (void)hrv;
+ (void)evtype;
+ (void)a1;
+ (void)a2;
+ return &PL_sv_undef;
+}
+
+
+/* ----------------------------------------------------------------- */
+
+
+STATIC I32
+dbi_dopoptosub_at(PERL_CONTEXT *cxstk, I32 startingblock)
+{
+ dTHX;
+ I32 i;
+ register PERL_CONTEXT *cx;
+ for (i = startingblock; i >= 0; i--) {
+ cx = &cxstk[i];
+ switch (CxTYPE(cx)) {
+ default:
+ continue;
+ case CXt_EVAL:
+ case CXt_SUB:
+#ifdef CXt_FORMAT
+ case CXt_FORMAT:
+#endif
+ DEBUG_l( Perl_deb(aTHX_ "(Found sub #%ld)\n", (long)i));
+ return i;
+ }
+ }
+ return i;
+}
+
+
+static COP *
+dbi_caller_cop()
+{
+ dTHX;
+ register I32 cxix;
+ register PERL_CONTEXT *cx;
+ register PERL_CONTEXT *ccstack = cxstack;
+ PERL_SI *top_si = PL_curstackinfo;
+ char *stashname;
+
+ for ( cxix = dbi_dopoptosub_at(ccstack, cxstack_ix) ;; cxix = dbi_dopoptosub_at(ccstack, cxix - 1)) {
+ /* we may be in a higher stacklevel, so dig down deeper */
+ while (cxix < 0 && top_si->si_type != PERLSI_MAIN) {
+ top_si = top_si->si_prev;
+ ccstack = top_si->si_cxstack;
+ cxix = dbi_dopoptosub_at(ccstack, top_si->si_cxix);
+ }
+ if (cxix < 0) {
+ break;
+ }
+ if (PL_DBsub && cxix >= 0 && ccstack[cxix].blk_sub.cv == GvCV(PL_DBsub))
+ continue;
+ cx = &ccstack[cxix];
+ stashname = CopSTASHPV(cx->blk_oldcop);
+ if (!stashname)
+ continue;
+ if (!(stashname[0] == 'D' && stashname[1] == 'B'
+ && strchr("DI", stashname[2])
+ && (!stashname[3] || (stashname[3] == ':' && stashname[4] == ':'))))
+ {
+ return cx->blk_oldcop;
+ }
+ cxix = dbi_dopoptosub_at(ccstack, cxix - 1);
+ }
+ return NULL;
+}
+
+static void
+dbi_caller_string(SV *buf, COP *cop, char *prefix, int show_line, int show_path)
+{
+ dTHX;
+ STRLEN len;
+ long line = CopLINE(cop);
+ char *file = SvPV(GvSV(CopFILEGV(cop)), len);
+ if (!show_path) {
+ char *sep;
+ if ( (sep=strrchr(file,'/')) || (sep=strrchr(file,'\\')))
+ file = sep+1;
+ }
+ if (show_line) {
+ sv_catpvf(buf, "%s%s line %ld", (prefix) ? prefix : "", file, line);
+ }
+ else {
+ sv_catpvf(buf, "%s%s", (prefix) ? prefix : "", file);
+ }
+}
+
+static char *
+log_where(SV *buf, int append, char *prefix, char *suffix, int show_line, int show_caller, int show_path)
+{
+ dTHX;
+ dTHR;
+ if (!buf)
+ buf = sv_2mortal(newSVpv("",0));
+ else if (!append)
+ sv_setpv(buf,"");
+ if (CopLINE(PL_curcop)) {
+ COP *cop;
+ dbi_caller_string(buf, PL_curcop, prefix, show_line, show_path);
+ if (show_caller && (cop = dbi_caller_cop())) {
+ SV *via = sv_2mortal(newSVpv("",0));
+ dbi_caller_string(via, cop, prefix, show_line, show_path);
+ sv_catpvf(buf, " via %s", SvPV_nolen(via));
+ }
+ }
+ if (PL_dirty)
+ sv_catpvf(buf, " during global destruction");
+ if (suffix)
+ sv_catpv(buf, suffix);
+ return SvPVX(buf);
+}
+
+
+static void
+clear_cached_kids(pTHX_ SV *h, imp_xxh_t *imp_xxh, const char *meth_name, int trace_level)
+{
+ if (DBIc_TYPE(imp_xxh) <= DBIt_DB) {
+ SV **svp = hv_fetch((HV*)SvRV(h), "CachedKids", 10, 0);
+ if (svp && SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVHV) {
+ HV *hv = (HV*)SvRV(*svp);
+ if (HvKEYS(hv)) {
+ if (DBIc_TRACE_LEVEL(imp_xxh) > trace_level)
+ trace_level = DBIc_TRACE_LEVEL(imp_xxh);
+ if (trace_level >= 2) {
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh)," >> %s %s clearing %d CachedKids\n",
+ meth_name, neatsvpv(h,0), (int)HvKEYS(hv));
+ PerlIO_flush(DBIc_LOGPIO(imp_xxh));
+ }
+ /* This will probably recurse through dispatch to DESTROY the kids */
+ /* For drh we should probably explicitly do dbh disconnects */
+ hv_clear(hv);
+ }
+ }
+ }
+}
+
+
+static NV
+dbi_time() {
+# ifdef HAS_GETTIMEOFDAY
+# ifdef PERL_IMPLICIT_SYS
+ dTHX;
+# endif
+ struct timeval when;
+ gettimeofday(&when, (struct timezone *) 0);
+ return when.tv_sec + (when.tv_usec / 1000000.0);
+# else /* per-second is almost useless */
+# ifdef _WIN32 /* use _ftime() on Win32 (MS Visual C++ 6.0) */
+# if defined(__BORLANDC__)
+# define _timeb timeb
+# define _ftime ftime
+# endif
+ struct _timeb when;
+ _ftime( &when );
+ return when.time + (when.millitm / 1000.0);
+# else
+ return time(NULL);
+# endif
+# endif
+}
+
+
+static SV *
+_profile_next_node(SV *node, const char *name)
+{
+ /* step one level down profile Data tree and auto-vivify if required */
+ dTHX;
+ SV *orig_node = node;
+ if (SvROK(node))
+ node = SvRV(node);
+ if (SvTYPE(node) != SVt_PVHV) {
+ HV *hv = newHV();
+ if (SvOK(node)) {
+ char *key = "(demoted)";
+ warn("Profile data element %s replaced with new hash ref (for %s) and original value stored with key '%s'",
+ neatsvpv(orig_node,0), name, key);
+ (void)hv_store(hv, key, strlen(key), SvREFCNT_inc(orig_node), 0);
+ }
+ sv_setsv(node, newRV_noinc((SV*)hv));
+ node = (SV*)hv;
+ }
+ node = *hv_fetch((HV*)node, name, strlen(name), 1);
+ return node;
+}
+
+
+static SV*
+dbi_profile(SV *h, imp_xxh_t *imp_xxh, SV *statement_sv, SV *method, NV t1, NV t2)
+{
+#define DBIprof_MAX_PATH_ELEM 100
+#define DBIprof_COUNT 0
+#define DBIprof_TOTAL_TIME 1
+#define DBIprof_FIRST_TIME 2
+#define DBIprof_MIN_TIME 3
+#define DBIprof_MAX_TIME 4
+#define DBIprof_FIRST_CALLED 5
+#define DBIprof_LAST_CALLED 6
+#define DBIprof_max_index 6
+ dTHX;
+ NV ti = t2 - t1;
+ int src_idx = 0;
+ HV *dbh_outer_hv = NULL;
+ HV *dbh_inner_hv = NULL;
+ char *statement_pv;
+ char *method_pv;
+ SV *profile;
+ SV *tmp;
+ SV *dest_node;
+ AV *av;
+ HV *h_hv;
+
+ const int call_depth = DBIc_CALL_DEPTH(imp_xxh);
+ const int parent_call_depth = DBIc_PARENT_COM(imp_xxh) ? DBIc_CALL_DEPTH(DBIc_PARENT_COM(imp_xxh)) : 0;
+ /* Only count calls originating from the application code */
+ if (call_depth > 1 || parent_call_depth > 0)
+ return &PL_sv_undef;
+
+ if (!DBIc_has(imp_xxh, DBIcf_Profile))
+ return &PL_sv_undef;
+
+ method_pv = (SvTYPE(method)==SVt_PVCV) ? GvNAME(CvGV(method))
+ : isGV(method) ? GvNAME(method)
+ : SvOK(method) ? SvPV_nolen(method)
+ : "";
+
+ /* we don't profile DESTROY during global destruction */
+ if (PL_dirty && instr(method_pv, "DESTROY"))
+ return &PL_sv_undef;
+
+ h_hv = (HV*)SvRV(dbih_inner(aTHX_ h, "dbi_profile"));
+
+ profile = *hv_fetch(h_hv, "Profile", 7, 1);
+ if (profile && SvMAGICAL(profile))
+ mg_get(profile); /* FETCH */
+ if (!profile || !SvROK(profile)) {
+ DBIc_set(imp_xxh, DBIcf_Profile, 0); /* disable */
+ if (SvOK(profile) && !PL_dirty)
+ warn("Profile attribute isn't a hash ref (%s,%ld)", neatsvpv(profile,0), (long)SvTYPE(profile));
+ return &PL_sv_undef;
+ }
+
+ /* statement_sv: undef = use $h->{Statement}, "" (&sv_no) = use empty string */
+
+ if (!SvOK(statement_sv)) {
+ SV **psv = hv_fetch(h_hv, "Statement", 9, 0);
+ statement_sv = (psv && SvOK(*psv)) ? *psv : &PL_sv_no;
+ }
+ statement_pv = SvPV_nolen(statement_sv);
+
+ if (DBIc_TRACE_LEVEL(imp_xxh) >= 4)
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh), " dbi_profile +%" NVff "s %s %s\n",
+ ti, method_pv, neatsvpv(statement_sv,0));
+
+ dest_node = _profile_next_node(profile, "Data");
+
+ tmp = *hv_fetch((HV*)SvRV(profile), "Path", 4, 1);
+ if (SvROK(tmp) && SvTYPE(SvRV(tmp))==SVt_PVAV) {
+ int len;
+ av = (AV*)SvRV(tmp);
+ len = av_len(av); /* -1=empty, 0=one element */
+
+ while ( src_idx <= len ) {
+ SV *pathsv = AvARRAY(av)[src_idx++];
+
+ if (SvROK(pathsv) && SvTYPE(SvRV(pathsv))==SVt_PVCV) {
+ /* call sub, use returned list of values as path */
+ /* returning a ref to undef vetos this profile data */
+ dSP;
+ I32 ax;
+ SV *code_sv = SvRV(pathsv);
+ I32 items;
+ I32 item_idx;
+ EXTEND(SP, 4);
+ PUSHMARK(SP);
+ PUSHs(h); /* push inner handle, then others params */
+ PUSHs( sv_2mortal(newSVpv(method_pv,0)));
+ PUTBACK;
+ SAVE_DEFSV; /* local($_) = $statement */
+ DEFSV = statement_sv;
+ items = call_sv(code_sv, G_ARRAY);
+ SPAGAIN;
+ SP -= items ;
+ ax = (SP - PL_stack_base) + 1 ;
+ for (item_idx=0; item_idx < items; ++item_idx) {
+ SV *item_sv = ST(item_idx);
+ if (SvROK(item_sv)) {
+ if (!SvOK(SvRV(item_sv)))
+ items = -2; /* flag that we're rejecting this profile data */
+ else /* other refs reserved */
+ warn("Ignored ref returned by code ref in Profile Path");
+ break;
+ }
+ dest_node = _profile_next_node(dest_node, SvPV_nolen(item_sv));
+ }
+ PUTBACK;
+ if (items == -2) /* this profile data was vetoed */
+ return &PL_sv_undef;
+ }
+ else if (SvROK(pathsv)) {
+ /* only meant for refs to scalars currently */
+ const char *p = SvPV_nolen(SvRV(pathsv));
+ dest_node = _profile_next_node(dest_node, p);
+ }
+ else if (SvOK(pathsv)) {
+ STRLEN len;
+ const char *p = SvPV(pathsv,len);
+ if (p[0] == '!') { /* special cases */
+ if (p[1] == 'S' && strEQ(p, "!Statement")) {
+ dest_node = _profile_next_node(dest_node, statement_pv);
+ }
+ else if (p[1] == 'M' && strEQ(p, "!MethodName")) {
+ dest_node = _profile_next_node(dest_node, method_pv);
+ }
+ else if (p[1] == 'M' && strEQ(p, "!MethodClass")) {
+ if (SvTYPE(method) == SVt_PVCV) {
+ p = SvPV_nolen((SV*)CvGV(method));
+ }
+ else if (isGV(method)) {
+ /* just using SvPV_nolen(method) sometimes causes an error: */
+ /* "Can't coerce GLOB to string" so we use gv_efullname() */
+ SV *tmpsv = sv_2mortal(newSVpv("",0));
+#if (PERL_VERSION < 6)
+ gv_efullname(tmpsv, (GV*)method);
+#else
+ gv_efullname4(tmpsv, (GV*)method, "", TRUE);
+#endif
+ p = SvPV_nolen(tmpsv);
+ if (*p == '*') ++p; /* skip past leading '*' glob sigil */
+ }
+ else {
+ p = method_pv;
+ }
+ dest_node = _profile_next_node(dest_node, p);
+ }
+ else if (p[1] == 'F' && strEQ(p, "!File")) {
+ dest_node = _profile_next_node(dest_node, log_where(0, 0, "", "", 0, 0, 0));
+ }
+ else if (p[1] == 'F' && strEQ(p, "!File2")) {
+ dest_node = _profile_next_node(dest_node, log_where(0, 0, "", "", 0, 1, 0));
+ }
+ else if (p[1] == 'C' && strEQ(p, "!Caller")) {
+ dest_node = _profile_next_node(dest_node, log_where(0, 0, "", "", 1, 0, 0));
+ }
+ else if (p[1] == 'C' && strEQ(p, "!Caller2")) {
+ dest_node = _profile_next_node(dest_node, log_where(0, 0, "", "", 1, 1, 0));
+ }
+ else if (p[1] == 'T' && (strEQ(p, "!Time") || strnEQ(p, "!Time~", 6))) {
+ char timebuf[20];
+ int factor = 1;
+ if (p[5] == '~') {
+ factor = atoi(&p[6]);
+ if (factor == 0) /* sanity check to avoid div by zero error */
+ factor = 3600;
+ }
+ sprintf(timebuf, "%ld", ((long)(dbi_time()/factor))*factor);
+ dest_node = _profile_next_node(dest_node, timebuf);
+ }
+ else {
+ warn("Unknown ! element in DBI::Profile Path: %s", p);
+ dest_node = _profile_next_node(dest_node, p);
+ }
+ }
+ else if (p[0] == '{' && p[len-1] == '}') { /* treat as name of dbh attribute to use */
+ SV **attr_svp;
+ if (!dbh_inner_hv) { /* cache dbh handles the first time we need them */
+ imp_dbh_t *imp_dbh = (DBIc_TYPE(imp_xxh) <= DBIt_DB) ? (imp_dbh_t*)imp_xxh : (imp_dbh_t*)DBIc_PARENT_COM(imp_xxh);
+ dbh_outer_hv = DBIc_MY_H(imp_dbh);
+ if (SvTYPE(dbh_outer_hv) != SVt_PVHV)
+ return &PL_sv_undef; /* presumably global destruction - bail */
+ dbh_inner_hv = (HV*)SvRV(dbih_inner(aTHX_ (SV*)dbh_outer_hv, "profile"));
+ if (SvTYPE(dbh_inner_hv) != SVt_PVHV)
+ return &PL_sv_undef; /* presumably global destruction - bail */
+ }
+ /* fetch from inner first, then outer if key doesn't exist */
+ /* (yes, this is an evil premature optimization) */
+ p += 1; len -= 2; /* ignore the braces */
+ if ((attr_svp = hv_fetch(dbh_inner_hv, p, len, 0)) == NULL) {
+ /* try outer (tied) hash - for things like AutoCommit */
+ /* (will always return something even for unknowns) */
+ if ((attr_svp = hv_fetch(dbh_outer_hv, p, len, 0))) {
+ if (SvGMAGICAL(*attr_svp))
+ mg_get(*attr_svp); /* FETCH */
+ }
+ }
+ if (!attr_svp)
+ p -= 1; /* unignore the braces */
+ else if (!SvOK(*attr_svp))
+ p = "";
+ else if (!SvTRUE(*attr_svp) && SvPOK(*attr_svp) && SvNIOK(*attr_svp))
+ p = "0"; /* catch &sv_no style special case */
+ else
+ p = SvPV_nolen(*attr_svp);
+ dest_node = _profile_next_node(dest_node, p);
+ }
+ else {
+ dest_node = _profile_next_node(dest_node, p);
+ }
+ }
+ /* else undef, so ignore */
+ }
+ }
+ else { /* a bad Path value is treated as a Path of just Statement */
+ dest_node = _profile_next_node(dest_node, statement_pv);
+ }
+
+
+ if (!SvOK(dest_node)) {
+ av = newAV();
+ sv_setsv(dest_node, newRV_noinc((SV*)av));
+ av_store(av, DBIprof_COUNT, newSViv(1));
+ av_store(av, DBIprof_TOTAL_TIME, newSVnv(ti));
+ av_store(av, DBIprof_FIRST_TIME, newSVnv(ti));
+ av_store(av, DBIprof_MIN_TIME, newSVnv(ti));
+ av_store(av, DBIprof_MAX_TIME, newSVnv(ti));
+ av_store(av, DBIprof_FIRST_CALLED, newSVnv(t1));
+ av_store(av, DBIprof_LAST_CALLED, newSVnv(t1));
+ }
+ else {
+ tmp = dest_node;
+ if (SvROK(tmp))
+ tmp = SvRV(tmp);
+ if (SvTYPE(tmp) != SVt_PVAV)
+ croak("Invalid Profile data leaf element: %s (type %ld)",
+ neatsvpv(tmp,0), (long)SvTYPE(tmp));
+ av = (AV*)tmp;
+ sv_inc( *av_fetch(av, DBIprof_COUNT, 1));
+ tmp = *av_fetch(av, DBIprof_TOTAL_TIME, 1);
+ sv_setnv(tmp, SvNV(tmp) + ti);
+ tmp = *av_fetch(av, DBIprof_MIN_TIME, 1);
+ if (ti < SvNV(tmp)) sv_setnv(tmp, ti);
+ tmp = *av_fetch(av, DBIprof_MAX_TIME, 1);
+ if (ti > SvNV(tmp)) sv_setnv(tmp, ti);
+ sv_setnv( *av_fetch(av, DBIprof_LAST_CALLED, 1), t1);
+ }
+ return dest_node; /* use with caution - copy first, ie sv_mortalcopy() */
+}
+
+
+static void
+dbi_profile_merge_nodes(SV *dest, SV *increment)
+{
+ dTHX;
+ AV *d_av, *i_av;
+ SV *tmp;
+ SV *tmp2;
+ NV i_nv;
+ int i_is_earlier;
+
+ if (!SvROK(dest) || SvTYPE(SvRV(dest)) != SVt_PVAV)
+ croak("dbi_profile_merge_nodes(%s, ...) requires array ref", neatsvpv(dest,0));
+ d_av = (AV*)SvRV(dest);
+
+ if (av_len(d_av) < DBIprof_max_index) {
+ int idx;
+ av_extend(d_av, DBIprof_max_index);
+ for(idx=0; idx<=DBIprof_max_index; ++idx) {
+ tmp = *av_fetch(d_av, idx, 1);
+ if (!SvOK(tmp) && idx != DBIprof_MIN_TIME && idx != DBIprof_FIRST_CALLED)
+ sv_setnv(tmp, 0.0); /* leave 'min' values as undef */
+ }
+ }
+
+ if (!SvOK(increment))
+ return;
+
+ if (SvROK(increment) && SvTYPE(SvRV(increment)) == SVt_PVHV) {
+ HV *hv = (HV*)SvRV(increment);
+ char *key;
+ I32 keylen = 0;
+ hv_iterinit(hv);
+ while ( (tmp = hv_iternextsv(hv, &key, &keylen)) != NULL ) {
+ dbi_profile_merge_nodes(dest, tmp);
+ };
+ return;
+ }
+
+ if (!SvROK(increment) || SvTYPE(SvRV(increment)) != SVt_PVAV)
+ croak("dbi_profile_merge_nodes: increment %s not an array or hash ref", neatsvpv(increment,0));
+ i_av = (AV*)SvRV(increment);
+
+ tmp = *av_fetch(d_av, DBIprof_COUNT, 1);
+ tmp2 = *av_fetch(i_av, DBIprof_COUNT, 1);
+ if (SvIOK(tmp) && SvIOK(tmp2))
+ sv_setiv( tmp, SvIV(tmp) + SvIV(tmp2) );
+ else
+ sv_setnv( tmp, SvNV(tmp) + SvNV(tmp2) );
+
+ tmp = *av_fetch(d_av, DBIprof_TOTAL_TIME, 1);
+ sv_setnv( tmp, SvNV(tmp) + SvNV( *av_fetch(i_av, DBIprof_TOTAL_TIME, 1)) );
+
+ i_nv = SvNV(*av_fetch(i_av, DBIprof_MIN_TIME, 1));
+ tmp = *av_fetch(d_av, DBIprof_MIN_TIME, 1);
+ if (!SvOK(tmp) || i_nv < SvNV(tmp)) sv_setnv(tmp, i_nv);
+
+ i_nv = SvNV(*av_fetch(i_av, DBIprof_MAX_TIME, 1));
+ tmp = *av_fetch(d_av, DBIprof_MAX_TIME, 1);
+ if (i_nv > SvNV(tmp)) sv_setnv(tmp, i_nv);
+
+ i_nv = SvNV(*av_fetch(i_av, DBIprof_FIRST_CALLED, 1));
+ tmp = *av_fetch(d_av, DBIprof_FIRST_CALLED, 1);
+ i_is_earlier = (!SvOK(tmp) || i_nv < SvNV(tmp));
+ if (i_is_earlier)
+ sv_setnv(tmp, i_nv);
+
+ i_nv = SvNV(*av_fetch(i_av, DBIprof_FIRST_TIME, 1));
+ tmp = *av_fetch(d_av, DBIprof_FIRST_TIME, 1);
+ if (i_is_earlier || !SvOK(tmp)) {
+ /* If the increment has an earlier DBIprof_FIRST_CALLED
+ then we set the DBIprof_FIRST_TIME from the increment */
+ sv_setnv(tmp, i_nv);
+ }
+
+ i_nv = SvNV(*av_fetch(i_av, DBIprof_LAST_CALLED, 1));
+ tmp = *av_fetch(d_av, DBIprof_LAST_CALLED, 1);
+ if (i_nv > SvNV(tmp)) sv_setnv(tmp, i_nv);
+}
+
+
+/* ----------------------------------------------------------------- */
+/* --- The DBI dispatcher. The heart of the perl DBI. --- */
+
+XS(XS_DBI_dispatch); /* prototype to pass -Wmissing-prototypes */
+XS(XS_DBI_dispatch)
+{
+ dXSARGS;
+ dMY_CXT;
+
+ SV *h = ST(0); /* the DBI handle we are working with */
+ SV *st1 = ST(1); /* used in debugging */
+ SV *st2 = ST(2); /* used in debugging */
+ SV *orig_h = h;
+ SV *err_sv;
+ SV **tmp_svp;
+ SV **hook_svp = 0;
+ MAGIC *mg;
+ int gimme = GIMME;
+ I32 trace_flags = DBIS->debug; /* local copy may change during dispatch */
+ I32 trace_level = (trace_flags & DBIc_TRACE_LEVEL_MASK);
+ int is_DESTROY;
+ meth_types meth_type;
+ int is_unrelated_to_Statement = 0;
+ int keep_error = FALSE;
+ UV ErrCount = UV_MAX;
+ int i, outitems;
+ int call_depth;
+ int is_nested_call;
+ NV profile_t1 = 0.0;
+ int is_orig_method_name = 1;
+
+ const char *meth_name = GvNAME(CvGV(cv));
+ dbi_ima_t *ima = (dbi_ima_t*)CvXSUBANY(cv).any_ptr;
+ U32 ima_flags;
+ imp_xxh_t *imp_xxh = NULL;
+ SV *imp_msv = Nullsv;
+ SV *qsv = Nullsv; /* quick result from a shortcut method */
+
+
+#ifdef BROKEN_DUP_ANY_PTR
+ if (ima->my_perl != my_perl) {
+ /* we couldn't dup the ima struct at clone time, so do it now */
+ dbi_ima_t *nima;
+ Newx(nima, 1, dbi_ima_t);
+ *nima = *ima; /* structure copy */
+ CvXSUBANY(cv).any_ptr = nima;
+ nima->stash = NULL;
+ nima->gv = NULL;
+ nima->my_perl = my_perl;
+ ima = nima;
+ }
+#endif
+
+ ima_flags = ima->flags;
+ meth_type = ima->meth_type;
+ if (trace_level >= 9) {
+ PerlIO *logfp = DBILOGFP;
+ PerlIO_printf(logfp,"%c >> %-11s DISPATCH (%s rc%ld/%ld @%ld g%x ima%lx pid#%ld)",
+ (PL_dirty?'!':' '), meth_name, neatsvpv(h,0),
+ (long)SvREFCNT(h), (SvROK(h) ? (long)SvREFCNT(SvRV(h)) : (long)-1),
+ (long)items, (int)gimme, (long)ima_flags, (long)PerlProc_getpid());
+ PerlIO_puts(logfp, log_where(0, 0, " at ","\n", 1, (trace_level >= 3), (trace_level >= 4)));
+ PerlIO_flush(logfp);
+ }
+
+ if ( ( (is_DESTROY=(meth_type == methtype_DESTROY))) ) {
+ /* note that croak()'s won't propagate, only append to $@ */
+ keep_error = TRUE;
+ }
+
+ /* If h is a tied hash ref, switch to the inner ref 'behind' the tie.
+ This means *all* DBI methods work with the inner (non-tied) ref.
+ This makes it much easier for methods to access the real hash
+ data (without having to go through FETCH and STORE methods) and
+ for tie and non-tie methods to call each other.
+ */
+ if (SvROK(h)
+ && SvRMAGICAL(SvRV(h))
+ && (
+ ((mg=SvMAGIC(SvRV(h)))->mg_type == 'P')
+ || ((mg=mg_find(SvRV(h),'P')) != NULL)
+ )
+ ) {
+ if (mg->mg_obj==NULL || !SvOK(mg->mg_obj) || SvRV(mg->mg_obj)==NULL) { /* maybe global destruction */
+ if (trace_level >= 3)
+ PerlIO_printf(DBILOGFP,
+ "%c <> %s for %s ignored (inner handle gone)\n",
+ (PL_dirty?'!':' '), meth_name, neatsvpv(h,0));
+ XSRETURN(0);
+ }
+ /* Distinguish DESTROY of tie (outer) from DESTROY of inner ref */
+ /* This may one day be used to manually destroy extra internal */
+ /* refs if the application ceases to use the handle. */
+ if (is_DESTROY) {
+ imp_xxh = DBIh_COM(mg->mg_obj);
+#ifdef DBI_USE_THREADS
+ if (imp_xxh && DBIc_THR_USER(imp_xxh) != my_perl) {
+ goto is_DESTROY_wrong_thread;
+ }
+#endif
+ if (imp_xxh && DBIc_TYPE(imp_xxh) <= DBIt_DB)
+ clear_cached_kids(aTHX_ mg->mg_obj, imp_xxh, meth_name, trace_level);
+ /* XXX might be better to move this down to after call_depth has been
+ * incremented and then also SvREFCNT_dec(mg->mg_obj) to force an immediate
+ * DESTROY of the inner handle if there are no other refs to it.
+ * That way the inner DESTROY is properly flagged as a nested call,
+ * and the outer DESTROY gets profiled more accurately, and callbacks work.
+ */
+ if (trace_level >= 3) {
+ PerlIO_printf(DBILOGFP,
+ "%c <> DESTROY(%s) ignored for outer handle (inner %s has ref cnt %ld)\n",
+ (PL_dirty?'!':' '), neatsvpv(h,0), neatsvpv(mg->mg_obj,0),
+ (long)SvREFCNT(SvRV(mg->mg_obj))
+ );
+ }
+ /* for now we ignore it since it'll be followed soon by */
+ /* a destroy of the inner hash and that'll do the real work */
+
+ /* However, we must at least modify DBIc_MY_H() as that is */
+ /* pointing (without a refcnt inc) to the scalar that is */
+ /* being destroyed, so it'll contain random values later. */
+ if (imp_xxh)
+ DBIc_MY_H(imp_xxh) = (HV*)SvRV(mg->mg_obj); /* inner (untied) HV */
+
+ XSRETURN(0);
+ }
+ h = mg->mg_obj; /* switch h to inner ref */
+ ST(0) = h; /* switch handle on stack to inner ref */
+ }
+
+ imp_xxh = dbih_getcom2(aTHX_ h, 0); /* get common Internal Handle Attributes */
+ if (!imp_xxh) {
+ if (meth_type == methtype_can) { /* ref($h)->can("foo") */
+ const char *can_meth = SvPV_nolen(st1);
+ SV *rv = &PL_sv_undef;
+ GV *gv = gv_fetchmethod_autoload(gv_stashsv(orig_h,FALSE), can_meth, FALSE);
+ if (gv && isGV(gv))
+ rv = sv_2mortal(newRV_inc((SV*)GvCV(gv)));
+ if (trace_level >= 1) {
+ PerlIO_printf(DBILOGFP," <- %s(%s) = %p\n", meth_name, can_meth, neatsvpv(rv,0));
+ }
+ ST(0) = rv;
+ XSRETURN(1);
+ }
+ if (trace_level)
+ PerlIO_printf(DBILOGFP, "%c <> %s for %s ignored (no imp_data)\n",
+ (PL_dirty?'!':' '), meth_name, neatsvpv(h,0));
+ if (!is_DESTROY)
+ warn("Can't call %s method on handle %s%s", meth_name, neatsvpv(h,0),
+ SvROK(h) ? " after take_imp_data()" : " (not a reference)");
+ XSRETURN(0);
+ }
+
+ if (DBIc_has(imp_xxh,DBIcf_Profile)) {
+ profile_t1 = dbi_time(); /* just get start time here */
+ }
+
+ if ((i = DBIc_DEBUGIV(imp_xxh))) { /* merge handle into global */
+ I32 h_trace_level = (i & DBIc_TRACE_LEVEL_MASK);
+ if ( h_trace_level > trace_level )
+ trace_level = h_trace_level;
+ trace_flags = (trace_flags & ~DBIc_TRACE_LEVEL_MASK)
+ | ( i & ~DBIc_TRACE_LEVEL_MASK)
+ | trace_level;
+ }
+
+#ifdef DBI_USE_THREADS
+{
+ PerlInterpreter * h_perl;
+ is_DESTROY_wrong_thread:
+ h_perl = DBIc_THR_USER(imp_xxh) ;
+ if (h_perl != my_perl) {
+ /* XXX could call a 'handle clone' method here?, for dbh's at least */
+ if (is_DESTROY) {
+ if (trace_level >= 3) {
+ PerlIO_printf(DBILOGFP," DESTROY ignored because DBI %sh handle (%s) is owned by thread %p not current thread %p\n",
+ dbih_htype_name(DBIc_TYPE(imp_xxh)), HvNAME(DBIc_IMP_STASH(imp_xxh)),
+ (void*)DBIc_THR_USER(imp_xxh), (void*)my_perl) ;
+ PerlIO_flush(DBILOGFP);
+ }
+ XSRETURN(0); /* don't DESTROY handle, if it is not our's !*/
+ }
+ croak("%s %s failed: handle %d is owned by thread %lx not current thread %lx (%s)",
+ HvNAME(DBIc_IMP_STASH(imp_xxh)), meth_name, DBIc_TYPE(imp_xxh),
+ (unsigned long)h_perl, (unsigned long)my_perl,
+ "handles can't be shared between threads and your driver may need a CLONE method added");
+ }
+}
+#endif
+
+ /* Check method call against Internal Method Attributes */
+ if (ima_flags) {
+
+ if (ima_flags & (IMA_STUB|IMA_FUNC_REDIRECT|IMA_KEEP_ERR|IMA_KEEP_ERR_SUB|IMA_CLEAR_STMT)) {
+
+ if (ima_flags & IMA_STUB) {
+ if (meth_type == methtype_can) {
+ const char *can_meth = SvPV_nolen(st1);
+ SV *dbi_msv = Nullsv;
+ /* find handle implementors method (GV or CV) */
+ if ( (imp_msv = (SV*)gv_fetchmethod_autoload(DBIc_IMP_STASH(imp_xxh), can_meth, FALSE)) ) {
+ /* return DBI's CV, not the implementors CV (else we'd bypass dispatch) */
+ /* and anyway, we may have hit a private method not part of the DBI */
+ GV *gv = gv_fetchmethod_autoload(SvSTASH(SvRV(orig_h)), can_meth, FALSE);
+ if (gv && isGV(gv))
+ dbi_msv = (SV*)GvCV(gv);
+ }
+ if (trace_level >= 1) {
+ PerlIO *logfp = DBILOGFP;
+ PerlIO_printf(logfp," <- %s(%s) = %p (%s %p)\n", meth_name, can_meth, (void*)dbi_msv,
+ (imp_msv && isGV(imp_msv)) ? HvNAME(GvSTASH(imp_msv)) : "?", (void*)imp_msv);
+ }
+ ST(0) = (dbi_msv) ? sv_2mortal(newRV_inc(dbi_msv)) : &PL_sv_undef;
+ XSRETURN(1);
+ }
+ XSRETURN(0);
+ }
+ if (ima_flags & IMA_FUNC_REDIRECT) {
+ /* XXX this doesn't redispatch, nor consider the IMA of the new method */
+ SV *meth_name_sv = POPs;
+ PUTBACK;
+ --items;
+ if (!SvPOK(meth_name_sv) || SvNIOK(meth_name_sv))
+ croak("%s->%s() invalid redirect method name %s",
+ neatsvpv(h,0), meth_name, neatsvpv(meth_name_sv,0));
+ meth_name = SvPV_nolen(meth_name_sv);
+ meth_type = get_meth_type(meth_name);
+ is_orig_method_name = 0;
+ }
+ if (ima_flags & IMA_KEEP_ERR)
+ keep_error = TRUE;
+ if (ima_flags & IMA_KEEP_ERR_SUB
+ && DBIc_PARENT_COM(imp_xxh) && DBIc_CALL_DEPTH(DBIc_PARENT_COM(imp_xxh)) > 0)
+ keep_error = TRUE;
+ if (ima_flags & IMA_CLEAR_STMT) {
+ /* don't use SvOK_off: dbh's Statement may be ref to sth's */
+ (void)hv_store((HV*)SvRV(h), "Statement", 9, &PL_sv_undef, 0);
+ }
+ if (ima_flags & IMA_CLEAR_CACHED_KIDS)
+ clear_cached_kids(aTHX_ h, imp_xxh, meth_name, trace_flags);
+
+ }
+
+ if (ima_flags & IMA_HAS_USAGE) {
+ const char *err = NULL;
+ char msg[200];
+
+ if (ima->minargs && (items < ima->minargs
+ || (ima->maxargs>0 && items > ima->maxargs))) {
+ sprintf(msg,
+ "DBI %s: invalid number of arguments: got handle + %ld, expected handle + between %d and %d\n",
+ meth_name, (long)items-1, (int)ima->minargs-1, (int)ima->maxargs-1);
+ err = msg;
+ }
+ /* arg type checking could be added here later */
+ if (err) {
+ croak("%sUsage: %s->%s(%s)", err, "$h", meth_name,
+ (ima->usage_msg) ? ima->usage_msg : "...?");
+ }
+ }
+ }
+
+ is_unrelated_to_Statement = ( (DBIc_TYPE(imp_xxh) == DBIt_ST) ? 0
+ : (DBIc_TYPE(imp_xxh) == DBIt_DR) ? 1
+ : (ima_flags & IMA_UNRELATED_TO_STMT) );
+
+ if (PL_tainting && items > 1 /* method call has args */
+ && DBIc_is(imp_xxh, DBIcf_TaintIn) /* taint checks requested */
+ && !(ima_flags & IMA_NO_TAINT_IN)
+ ) {
+ for(i=1; i < items; ++i) {
+ if (SvTAINTED(ST(i))) {
+ char buf[100];
+ sprintf(buf,"parameter %d of %s->%s method call",
+ i, SvPV_nolen(h), meth_name);
+ PL_tainted = 1; /* needed for TAINT_PROPER to work */
+ TAINT_PROPER(buf); /* die's */
+ }
+ }
+ }
+
+ /* record this inner handle for use by DBI::var::FETCH */
+ if (is_DESTROY) {
+
+ if (DBIc_TYPE(imp_xxh) <= DBIt_DB ) { /* is dbh or drh */
+ imp_xxh_t *parent_imp;
+
+ if (SvOK(DBIc_ERR(imp_xxh)) && (parent_imp = DBIc_PARENT_COM(imp_xxh))
+ && !PL_dirty
+ ) {
+ /* copy err/errstr/state values to $DBI::err etc still work */
+ sv_setsv(DBIc_ERR(parent_imp), DBIc_ERR(imp_xxh));
+ sv_setsv(DBIc_ERRSTR(parent_imp), DBIc_ERRSTR(imp_xxh));
+ sv_setsv(DBIc_STATE(parent_imp), DBIc_STATE(imp_xxh));
+ }
+ }
+
+ if (DBIc_AIADESTROY(imp_xxh)) { /* wants ineffective destroy after fork */
+ if ((U32)PerlProc_getpid() != _imp2com(imp_xxh, std.pid))
+ DBIc_set(imp_xxh, DBIcf_IADESTROY, 1);
+ }
+ if (DBIc_IADESTROY(imp_xxh)) { /* wants ineffective destroy */
+ DBIc_ACTIVE_off(imp_xxh);
+ }
+ call_depth = 0;
+ }
+ else {
+ DBI_SET_LAST_HANDLE(h);
+ SAVEINT(DBIc_CALL_DEPTH(imp_xxh));
+ call_depth = ++DBIc_CALL_DEPTH(imp_xxh);
+
+ if (ima_flags & IMA_COPY_UP_STMT) { /* execute() */
+ SV *parent = DBIc_PARENT_H(imp_xxh);
+ SV *tmp_sv = *hv_fetch((HV*)SvRV(h), "Statement", 9, 1);
+ /* XXX sv_copy() if Profiling? */
+ (void)hv_store((HV*)SvRV(parent), "Statement", 9, SvREFCNT_inc(tmp_sv), 0);
+ }
+ }
+
+ is_nested_call = ( call_depth > 1 || (DBIc_PARENT_COM(imp_xxh) && (DBIc_CALL_DEPTH(DBIc_PARENT_COM(imp_xxh)) >= 1)) );
+
+
+ /* --- dispatch --- */
+
+ if (!keep_error && meth_type != methtype_set_err) {
+ SV *err_sv;
+ if (trace_level && SvOK(err_sv=DBIc_ERR(imp_xxh))) {
+ PerlIO *logfp = DBILOGFP;
+ PerlIO_printf(logfp, " !! %s: %s CLEARED by call to %s method\n",
+ SvTRUE(err_sv) ? "ERROR" : strlen(SvPV_nolen(err_sv)) ? "warn" : "info",
+ neatsvpv(DBIc_ERR(imp_xxh),0), meth_name);
+ }
+ DBIh_CLEAR_ERROR(imp_xxh);
+ }
+ else { /* we check for change in ErrCount during call */
+ ErrCount = DBIc_ErrCount(imp_xxh);
+ }
+
+ if (DBIc_has(imp_xxh,DBIcf_Callbacks)
+ && (tmp_svp = hv_fetch((HV*)SvRV(h), "Callbacks", 9, 0))
+ && ( (hook_svp = hv_fetch((HV*)SvRV(*tmp_svp), meth_name, strlen(meth_name), 0))
+ /* the "*" fallback callback only applies to non-nested calls
+ * and also doesn't apply to the 'set_err' or DESTROY methods.
+ * Nor during global destruction.
+ * Other restrictions may be added over time.
+ * It's an undocumented hack.
+ */
+ || (!is_nested_call && !PL_dirty && meth_type != methtype_set_err &&
+ meth_type != methtype_DESTROY &&
+ (hook_svp = hv_fetch((HV*)SvRV(*tmp_svp), "*", 1, 0))
+ )
+ )
+ && SvROK(*hook_svp)
+ ) {
+ SV *orig_defsv;
+ SV *code = SvRV(*hook_svp);
+ I32 skip_dispatch = 0;
+ if (trace_level)
+ PerlIO_printf(DBILOGFP, "%c {{ %s callback %s being invoked\n",
+ (PL_dirty?'!':' '), meth_name, neatsvpv(*hook_svp,0));
+
+ /* we don't use ENTER,SAVETMPS & FREETMPS,LEAVE because we may need mortal
+ * results to live long enough to be returned to our caller
+ */
+ /* we want to localize $_ for the callback but can't just do that alone
+ * because we're not using SAVETMPS & FREETMPS, so we have to get sneaky.
+ * We still localize, so we're safe from the callback dieing,
+ * but after the callback we manually restore the original $_.
+ */
+ orig_defsv = DEFSV; /* remember the current $_ */
+ SAVE_DEFSV; /* local($_) = $method_name */
+ DEFSV = sv_2mortal(newSVpv(meth_name,0));
+
+ EXTEND(SP, items+1);
+ PUSHMARK(SP);
+ PUSHs(h); /* push inner handle, then others params */
+ for (i=1; i < items; ++i) { /* start at 1 to skip handle */
+ PUSHs( ST(i) );
+ }
+ PUTBACK;
+ outitems = call_sv(code, G_ARRAY); /* call the callback code */
+ SPAGAIN;
+
+ /* The callback code can undef $_ to indicate to skip dispatch */
+ skip_dispatch = !SvOK(DEFSV);
+ /* put $_ back now, but with an incremented ref count to compensate
+ * for the ref count decrement that will happen when we exit the scope.
+ */
+ DEFSV = SvREFCNT_inc(orig_defsv);
+
+ if (trace_level)
+ PerlIO_printf(DBILOGFP, "%c }} %s callback %s returned%s\n",
+ (PL_dirty?'!':' '), meth_name, neatsvpv(*hook_svp,0),
+ skip_dispatch ? ", actual method will not be called" : ""
+ );
+ if (skip_dispatch) { /* XXX experimental */
+ int ix = outitems;
+ /* copy the new items down to the destination list */
+ while (ix-- > 0) {
+ if(0)warn("\tcopy down %d: %s overwriting %s\n", ix, SvPV_nolen(TOPs), SvPV_nolen(ST(ix)) );
+ ST(ix) = POPs;
+ }
+ imp_msv = *hook_svp; /* for trace and profile */
+ goto post_dispatch;
+ }
+ else {
+ if (outitems != 0)
+ die("Callback for %s returned %d values but must not return any (temporary restriction in current version)",
+ meth_name, (int)outitems);
+ /* POP's and PUTBACK? to clear stack */
+ }
+ }
+
+ /* set Executed after Callbacks so it's not set if callback elects to skip the method */
+ if (ima_flags & IMA_EXECUTE) {
+ imp_xxh_t *parent = DBIc_PARENT_COM(imp_xxh);
+ DBIc_on(imp_xxh, DBIcf_Executed);
+ if (parent)
+ DBIc_on(parent, DBIcf_Executed);
+ }
+
+ /* The "quick_FETCH" logic... */
+ /* Shortcut for fetching attributes to bypass method call overheads */
+ if (meth_type == methtype_FETCH && !DBIc_COMPAT(imp_xxh)) {
+ STRLEN kl;
+ const char *key = SvPV(st1, kl);
+ SV **attr_svp;
+ if (*key != '_' && (attr_svp=hv_fetch((HV*)SvRV(h), key, kl, 0))) {
+ qsv = *attr_svp;
+ /* disable FETCH from cache for special attributes */
+ if (SvROK(qsv) && SvTYPE(SvRV(qsv))==SVt_PVHV && *key=='D' &&
+ ( (kl==6 && DBIc_TYPE(imp_xxh)==DBIt_DB && strEQ(key,"Driver"))
+ || (kl==8 && DBIc_TYPE(imp_xxh)==DBIt_ST && strEQ(key,"Database")) )
+ ) {
+ qsv = Nullsv;
+ }
+ /* disable profiling of FETCH of Profile data */
+ if (*key == 'P' && strEQ(key, "Profile"))
+ profile_t1 = 0.0;
+ }
+ if (qsv) { /* skip real method call if we already have a 'quick' value */
+ ST(0) = sv_mortalcopy(qsv);
+ outitems = 1;
+ goto post_dispatch;
+ }
+ }
+
+ {
+ CV *meth_cv;
+#ifdef DBI_save_hv_fetch_ent
+ HE save_mh;
+ if (meth_type == methtype_FETCH)
+ save_mh = PL_hv_fetch_ent_mh; /* XXX nested tied FETCH bug17575 workaround */
+#endif
+
+ if (trace_flags) {
+ SAVEI32(DBIS->debug); /* fall back to orig value later */
+ DBIS->debug = trace_flags; /* make new value global (for now) */
+ if (ima) {
+ /* enabling trace via flags takes precedence over disabling due to min level */
+ if ((trace_flags & DBIc_TRACE_FLAGS_MASK) & (ima->method_trace & DBIc_TRACE_FLAGS_MASK))
+ trace_level = (trace_level < 2) ? 2 : trace_level; /* min */
+ else
+ if (trace_level < (DBIc_TRACE_LEVEL_MASK & ima->method_trace))
+ trace_level = 0; /* silence dispatch log for this method */
+ }
+ }
+
+ if (is_orig_method_name
+ && ima->stash == DBIc_IMP_STASH(imp_xxh)
+ && ima->generation == PL_sub_generation +
+ MY_cache_gen(DBIc_IMP_STASH(imp_xxh))
+ )
+ imp_msv = (SV*)ima->gv;
+ else {
+ imp_msv = (SV*)gv_fetchmethod_autoload(DBIc_IMP_STASH(imp_xxh),
+ meth_name, FALSE);
+ if (is_orig_method_name) {
+ /* clear stale entry, if any */
+ SvREFCNT_dec(ima->stash);
+ SvREFCNT_dec(ima->gv);
+ if (!imp_msv) {
+ ima->stash = NULL;
+ ima->gv = NULL;
+ }
+ else {
+ ima->stash = (HV*)SvREFCNT_inc(DBIc_IMP_STASH(imp_xxh));
+ ima->gv = (GV*)SvREFCNT_inc(imp_msv);
+ ima->generation = PL_sub_generation +
+ MY_cache_gen(DBIc_IMP_STASH(imp_xxh));
+ }
+ }
+ }
+
+ /* if method was a 'func' then try falling back to real 'func' method */
+ if (!imp_msv && (ima_flags & IMA_FUNC_REDIRECT)) {
+ imp_msv = (SV*)gv_fetchmethod_autoload(DBIc_IMP_STASH(imp_xxh), "func", FALSE);
+ if (imp_msv) {
+ /* driver does have func method so undo the earlier 'func' stack changes */
+ PUSHs(sv_2mortal(newSVpv(meth_name,0)));
+ PUTBACK;
+ ++items;
+ meth_name = "func";
+ meth_type = methtype_ordinary;
+ }
+ }
+
+ if (trace_level >= (is_nested_call ? 4 : 2)) {
+ PerlIO *logfp = DBILOGFP;
+ /* Full pkg method name (or just meth_name for ANON CODE) */
+ const char *imp_meth_name = (imp_msv && isGV(imp_msv)) ? GvNAME(imp_msv) : meth_name;
+ HV *imp_stash = DBIc_IMP_STASH(imp_xxh);
+ PerlIO_printf(logfp, "%c -> %s ",
+ call_depth>1 ? '0'+call_depth-1 : (PL_dirty?'!':' '), imp_meth_name);
+ if (imp_meth_name[0] == 'A' && strEQ(imp_meth_name,"AUTOLOAD"))
+ PerlIO_printf(logfp, "\"%s\" ", meth_name);
+ if (imp_msv && isGV(imp_msv) && GvSTASH(imp_msv) != imp_stash)
+ PerlIO_printf(logfp, "in %s ", HvNAME(GvSTASH(imp_msv)));
+ PerlIO_printf(logfp, "for %s (%s", HvNAME(imp_stash),
+ SvPV_nolen(orig_h));
+ if (h != orig_h) /* show inner handle to aid tracing */
+ PerlIO_printf(logfp, "~0x%lx", (long)SvRV(h));
+ else PerlIO_printf(logfp, "~INNER");
+ for(i=1; i<items; ++i) {
+ PerlIO_printf(logfp," %s",
+ (ima && i==ima->hidearg) ? "****" : neatsvpv(ST(i),0));
+ }
+#ifdef DBI_USE_THREADS
+ PerlIO_printf(logfp, ") thr#%p\n", (void*)DBIc_THR_USER(imp_xxh));
+#else
+ PerlIO_printf(logfp, ")\n");
+#endif
+ PerlIO_flush(logfp);
+ }
+
+ if (!imp_msv || ! ((meth_cv = GvCV(imp_msv))) ) {
+ if (PL_dirty || is_DESTROY) {
+ outitems = 0;
+ goto post_dispatch;
+ }
+ if (ima_flags & IMA_NOT_FOUND_OKAY) {
+ outitems = 0;
+ goto post_dispatch;
+ }
+ croak("Can't locate DBI object method \"%s\" via package \"%s\"",
+ meth_name, HvNAME(DBIc_IMP_STASH(imp_xxh)));
+ }
+
+ PUSHMARK(mark); /* mark arguments again so we can pass them on */
+
+ /* Note: the handle on the stack is still an object blessed into a
+ * DBI::* class and not the DBD::*::* class whose method is being
+ * invoked. This is correct and should be largely transparent.
+ */
+
+ /* SHORT-CUT ALERT! */
+ if (use_xsbypass && CvISXSUB(meth_cv) && CvXSUB(meth_cv)) {
+
+ /* If we are calling an XSUB we jump directly to its C code and
+ * bypass perl_call_sv(), pp_entersub() etc. This is fast.
+ * This code is based on a small section of pp_entersub().
+ */
+ (void)(*CvXSUB(meth_cv))(aTHXo_ meth_cv); /* Call the C code directly */
+
+ if (gimme == G_SCALAR) { /* Enforce sanity in scalar context */
+ if (ax != PL_stack_sp - PL_stack_base ) { /* outitems != 1 */
+ ST(0) =
+ (ax > PL_stack_sp - PL_stack_base)
+ ? &PL_sv_undef /* outitems == 0 */
+ : *PL_stack_sp; /* outitems > 1 */
+ PL_stack_sp = PL_stack_base + ax;
+ }
+ outitems = 1;
+ }
+ else {
+ outitems = PL_stack_sp - (PL_stack_base + ax - 1);
+ }
+
+ }
+ else {
+ /* sv_dump(imp_msv); */
+ outitems = call_sv((SV*)meth_cv,
+ (is_DESTROY ? gimme | G_EVAL | G_KEEPERR : gimme) );
+ }
+
+ XSprePUSH; /* reset SP to base of stack frame */
+
+#ifdef DBI_save_hv_fetch_ent
+ if (meth_type == methtype_FETCH)
+ PL_hv_fetch_ent_mh = save_mh; /* see start of block */
+#endif
+ }
+
+ post_dispatch:
+
+ if (is_DESTROY && DBI_IS_LAST_HANDLE(h)) { /* if destroying _this_ handle */
+ SV *lhp = DBIc_PARENT_H(imp_xxh);
+ if (lhp && SvROK(lhp)) {
+ DBI_SET_LAST_HANDLE(lhp);
+ }
+ else {
+ DBI_UNSET_LAST_HANDLE;
+ }
+ }
+
+ /* if we didn't clear err before the call, check if ErrCount has gone up */
+ /* if so, we turn off keep_error so error is acted on */
+ if (keep_error && DBIc_ErrCount(imp_xxh) > ErrCount)
+ keep_error = 0;
+
+ err_sv = DBIc_ERR(imp_xxh);
+
+ if (trace_level >= (is_nested_call ? 3 : 1)) {
+ PerlIO *logfp = DBILOGFP;
+ const int is_fetch = (meth_type == methtype_fetch_star && DBIc_TYPE(imp_xxh)==DBIt_ST);
+ const int row_count = (is_fetch) ? DBIc_ROW_COUNT((imp_sth_t*)imp_xxh) : 0;
+ if (is_fetch && row_count>=2 && trace_level<=4 && SvOK(ST(0))) {
+ /* skip the 'middle' rows to reduce output */
+ goto skip_meth_return_trace;
+ }
+ if (SvOK(err_sv)) {
+ PerlIO_printf(logfp, " %s %s %s %s (err#%ld)\n", (keep_error) ? " " : "!!",
+ SvTRUE(err_sv) ? "ERROR:" : strlen(SvPV_nolen(err_sv)) ? "warn:" : "info:",
+ neatsvpv(err_sv,0), neatsvpv(DBIc_ERRSTR(imp_xxh),0), (long)DBIc_ErrCount(imp_xxh));
+ }
+ PerlIO_printf(logfp,"%c%c <%c %s",
+ (call_depth > 1) ? '0'+call_depth-1 : (PL_dirty?'!':' '),
+ (DBIc_is(imp_xxh, DBIcf_TaintIn|DBIcf_TaintOut)) ? 'T' : ' ',
+ (qsv) ? '>' : '-',
+ meth_name);
+ if (trace_level==1 && (items>=2||is_DESTROY)) { /* make level 1 more useful */
+ /* we only have the first two parameters available here */
+ if (is_DESTROY) /* show handle as first arg to DESTROY */
+ /* want to show outer handle so trace makes sense */
+ /* but outer handle has been destroyed so we fake it */
+ PerlIO_printf(logfp,"(%s=HASH(0x%p)", HvNAME(SvSTASH(SvRV(orig_h))), (void*)DBIc_MY_H(imp_xxh));
+ else
+ PerlIO_printf(logfp,"(%s", neatsvpv(st1,0));
+ if (items >= 3)
+ PerlIO_printf(logfp,", %s", neatsvpv(st2,0));
+ PerlIO_printf(logfp,"%s)", (items > 3) ? ", ..." : "");
+ }
+
+ if (gimme & G_ARRAY)
+ PerlIO_printf(logfp,"= (");
+ else PerlIO_printf(logfp,"=");
+ for(i=0; i < outitems; ++i) {
+ SV *s = ST(i);
+ if ( SvROK(s) && SvTYPE(SvRV(s))==SVt_PVAV) {
+ AV *av = (AV*)SvRV(s);
+ int avi;
+ int avi_last = SvIV(DBIS->neatsvpvlen) / 10;
+ if (avi_last < 39)
+ avi_last = 39;
+ PerlIO_printf(logfp, " [");
+ for (avi=0; avi <= AvFILL(av); ++avi) {
+ PerlIO_printf(logfp, " %s", neatsvpv(AvARRAY(av)[avi],0));
+ if (avi >= avi_last && AvFILL(av) - avi > 1) {
+ PerlIO_printf(logfp, " ... %ld others skipped", AvFILL(av) - avi);
+ break;
+ }
+ }
+ PerlIO_printf(logfp, " ]");
+ }
+ else {
+ PerlIO_printf(logfp, " %s", neatsvpv(s,0));
+ if ( SvROK(s) && SvTYPE(SvRV(s))==SVt_PVHV && !SvOBJECT(SvRV(s)) )
+ PerlIO_printf(logfp, "%ldkeys", (long)HvKEYS(SvRV(s)));
+ }
+ }
+ if (gimme & G_ARRAY) {
+ PerlIO_printf(logfp," ) [%d items]", outitems);
+ }
+ if (is_fetch && row_count) {
+ PerlIO_printf(logfp," row%d", row_count);
+ }
+ if (qsv) /* flag as quick and peek at the first arg (still on the stack) */
+ PerlIO_printf(logfp," (%s from cache)", neatsvpv(st1,0));
+ else if (!imp_msv)
+ PerlIO_printf(logfp," (not implemented)");
+ /* XXX add flag to show pid here? */
+ /* add file and line number information */
+ PerlIO_puts(logfp, log_where(0, 0, " at ", "\n", 1, (trace_level >= 3), (trace_level >= 4)));
+ skip_meth_return_trace:
+ PerlIO_flush(logfp);
+ }
+
+ if (ima_flags & IMA_END_WORK) { /* commit() or rollback() */
+ /* XXX does not consider if the method call actually worked or not */
+ DBIc_off(imp_xxh, DBIcf_Executed);
+
+ if (DBIc_has(imp_xxh, DBIcf_BegunWork)) {
+ DBIc_off(imp_xxh, DBIcf_BegunWork);
+ if (!DBIc_has(imp_xxh, DBIcf_AutoCommit)) {
+ /* We only get here if the driver hasn't implemented their own code */
+ /* for begin_work, or has but hasn't correctly turned AutoCommit */
+ /* back on in their commit or rollback code. So we have to do it. */
+ /* This is bad because it'll probably trigger a spurious commit() */
+ /* and may mess up the error handling below for the commit/rollback */
+ PUSHMARK(SP);
+ XPUSHs(h);
+ XPUSHs(sv_2mortal(newSVpv("AutoCommit",0)));
+ XPUSHs(&PL_sv_yes);
+ PUTBACK;
+ call_method("STORE", G_DISCARD);
+ SPAGAIN;
+ }
+ }
+ }
+
+ if (PL_tainting
+ && DBIc_is(imp_xxh, DBIcf_TaintOut) /* taint checks requested */
+ /* XXX this would taint *everything* being returned from *any* */
+ /* method that doesn't have IMA_NO_TAINT_OUT set. */
+ /* DISABLED: just tainting fetched data in get_fbav seems ok */
+ && 0/* XXX disabled*/ /* !(ima_flags & IMA_NO_TAINT_OUT) */
+ ) {
+ dTHR;
+ TAINT; /* affects sv_setsv()'s within same perl statement */
+ for(i=0; i < outitems; ++i) {
+ I32 avi;
+ char *p;
+ SV *s;
+ SV *agg = ST(i);
+ if ( !SvROK(agg) )
+ continue;
+ agg = SvRV(agg);
+#define DBI_OUT_TAINTABLE(s) (!SvREADONLY(s) && !SvTAINTED(s))
+ switch (SvTYPE(agg)) {
+ case SVt_PVAV:
+ for(avi=0; avi <= AvFILL((AV*)agg); ++avi) {
+ s = AvARRAY((AV*)agg)[avi];
+ if (DBI_OUT_TAINTABLE(s))
+ SvTAINTED_on(s);
+ }
+ break;
+ case SVt_PVHV:
+ hv_iterinit((HV*)agg);
+ while( (s = hv_iternextsv((HV*)agg, &p, &avi)) ) {
+ if (DBI_OUT_TAINTABLE(s))
+ SvTAINTED_on(s);
+ }
+ break;
+ default:
+ if (DBIc_WARN(imp_xxh)) {
+ PerlIO_printf(DBILOGFP,"Don't know how to taint contents of returned %s (type %d)\n",
+ neatsvpv(agg,0), (int)SvTYPE(agg));
+ }
+ }
+ }
+ }
+
+ /* if method returned a new handle, and that handle has an error on it
+ * then copy the error up into the parent handle
+ */
+ if (ima_flags & IMA_IS_FACTORY && SvROK(ST(0))) {
+ SV *h_new = ST(0);
+ D_impdata(imp_xxh_new, imp_xxh_t, h_new);
+ if (SvOK(DBIc_ERR(imp_xxh_new))) {
+ set_err_sv(h, imp_xxh, DBIc_ERR(imp_xxh_new), DBIc_ERRSTR(imp_xxh_new), DBIc_STATE(imp_xxh_new), &PL_sv_no);
+ }
+ }
+
+ if ( !keep_error /* is a new err/warn/info */
+ && !is_nested_call /* skip nested (internal) calls */
+ && (
+ /* is an error and has RaiseError|PrintError|HandleError set */
+ (SvTRUE(err_sv) && DBIc_has(imp_xxh, DBIcf_RaiseError|DBIcf_PrintError|DBIcf_HandleError))
+ /* is a warn (not info) and has PrintWarn set */
+ || ( SvOK(err_sv) && strlen(SvPV_nolen(err_sv)) && DBIc_has(imp_xxh, DBIcf_PrintWarn))
+ )
+ ) {
+ SV *msg;
+ SV **statement_svp = NULL;
+ const int is_warning = (!SvTRUE(err_sv) && strlen(SvPV_nolen(err_sv))==1);
+ const char *err_meth_name = meth_name;
+ char intro[200];
+
+ if (meth_type == methtype_set_err) {
+ SV **sem_svp = hv_fetch((HV*)SvRV(h), "dbi_set_err_method", 18, GV_ADDWARN);
+ if (SvOK(*sem_svp))
+ err_meth_name = SvPV_nolen(*sem_svp);
+ }
+
+ /* XXX change to vsprintf into sv directly */
+ sprintf(intro,"%s %s %s: ", HvNAME(DBIc_IMP_STASH(imp_xxh)), err_meth_name,
+ SvTRUE(err_sv) ? "failed" : is_warning ? "warning" : "information");
+ msg = sv_2mortal(newSVpv(intro,0));
+ if (SvOK(DBIc_ERRSTR(imp_xxh)))
+ sv_catsv(msg, DBIc_ERRSTR(imp_xxh));
+ else
+ sv_catpvf(msg, "(err=%s, errstr=undef, state=%s)",
+ neatsvpv(DBIc_ERR(imp_xxh),0), neatsvpv(DBIc_STATE(imp_xxh),0) );
+
+ if ( DBIc_has(imp_xxh, DBIcf_ShowErrorStatement)
+ && !is_unrelated_to_Statement
+ && (DBIc_TYPE(imp_xxh) == DBIt_ST || ima_flags & IMA_SHOW_ERR_STMT)
+ && (statement_svp = hv_fetch((HV*)SvRV(h), "Statement", 9, 0))
+ && statement_svp && SvOK(*statement_svp)
+ ) {
+ SV **svp = 0;
+ sv_catpv(msg, " [for Statement \"");
+ sv_catsv(msg, *statement_svp);
+
+ /* fetch from tied outer handle to trigger FETCH magic */
+ /* could add DBIcf_ShowErrorParams (default to on?) */
+ if (!(ima_flags & IMA_HIDE_ERR_PARAMVALUES)) {
+ svp = hv_fetch((HV*)DBIc_MY_H(imp_xxh),"ParamValues",11,FALSE);
+ if (svp && SvMAGICAL(*svp))
+ mg_get(*svp); /* XXX may recurse, may croak. could use eval */
+ }
+ if (svp && SvRV(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVHV && HvKEYS(SvRV(*svp))>0 ) {
+ SV *param_values_sv = sv_2mortal(_join_hash_sorted((HV*)SvRV(*svp), "=",1, ", ",2, 1, -1));
+ sv_catpv(msg, "\" with ParamValues: ");
+ sv_catsv(msg, param_values_sv);
+ sv_catpvn(msg, "]", 1);
+ }
+ else {
+ sv_catpv(msg, "\"]");
+ }
+ }
+
+ if (0) {
+ COP *cop = dbi_caller_cop();
+ if (cop && (CopLINE(cop) != CopLINE(PL_curcop) || CopFILEGV(cop) != CopFILEGV(PL_curcop))) {
+ dbi_caller_string(msg, cop, " called via ", 1, 0);
+ }
+ }
+
+ hook_svp = NULL;
+ if ( SvTRUE(err_sv)
+ && DBIc_has(imp_xxh, DBIcf_HandleError)
+ && (hook_svp = hv_fetch((HV*)SvRV(h),"HandleError",11,0))
+ && hook_svp && SvOK(*hook_svp)
+ ) {
+ dSP;
+ PerlIO *logfp = DBILOGFP;
+ IV items;
+ SV *status;
+ SV *result; /* point to result SV that's pointed to by the stack */
+ if (outitems) {
+ result = *(sp-outitems+1);
+ if (SvREADONLY(result)) {
+ *(sp-outitems+1) = result = sv_2mortal(newSVsv(result));
+ }
+ }
+ else {
+ result = sv_newmortal();
+ }
+ if (trace_level)
+ PerlIO_printf(logfp," -> HandleError on %s via %s%s%s%s\n",
+ neatsvpv(h,0), neatsvpv(*hook_svp,0),
+ (!outitems ? "" : " ("),
+ (!outitems ? "" : neatsvpv(result ,0)),
+ (!outitems ? "" : ")")
+ );
+ PUSHMARK(SP);
+ XPUSHs(msg);
+ XPUSHs(sv_2mortal(newRV_inc((SV*)DBIc_MY_H(imp_xxh))));
+ XPUSHs( result );
+ PUTBACK;
+ items = call_sv(*hook_svp, G_SCALAR);
+ SPAGAIN;
+ status = (items) ? POPs : &PL_sv_undef;
+ PUTBACK;
+ if (trace_level)
+ PerlIO_printf(logfp," <- HandleError= %s%s%s%s\n",
+ neatsvpv(status,0),
+ (!outitems ? "" : " ("),
+ (!outitems ? "" : neatsvpv(result,0)),
+ (!outitems ? "" : ")")
+ );
+ if (!SvTRUE(status)) /* handler says it didn't handle it, so... */
+ hook_svp = 0; /* pretend we didn't have a handler... */
+ }
+
+ if (profile_t1) { /* see also dbi_profile() call a few lines below */
+ SV *statement_sv = (is_unrelated_to_Statement) ? &PL_sv_no : &PL_sv_undef;
+ dbi_profile(h, imp_xxh, statement_sv, imp_msv ? imp_msv : (SV*)cv,
+ profile_t1, dbi_time());
+ }
+ if (is_warning) {
+ if (DBIc_has(imp_xxh, DBIcf_PrintWarn))
+ warn("%s", SvPV_nolen(msg));
+ }
+ else if (!hook_svp && SvTRUE(err_sv)) {
+ if (DBIc_has(imp_xxh, DBIcf_PrintError))
+ warn("%s", SvPV_nolen(msg));
+ if (DBIc_has(imp_xxh, DBIcf_RaiseError))
+ croak("%s", SvPV_nolen(msg));
+ }
+ }
+ else if (profile_t1) { /* see also dbi_profile() call a few lines above */
+ SV *statement_sv = (is_unrelated_to_Statement) ? &PL_sv_no : &PL_sv_undef;
+ dbi_profile(h, imp_xxh, statement_sv, imp_msv ? imp_msv : (SV*)cv,
+ profile_t1, dbi_time());
+ }
+ XSRETURN(outitems);
+}
+
+
+
+/* -------------------------------------------------------------------- */
+
+/* comment and placeholder styles to accept and return */
+
+#define DBIpp_cm_cs 0x000001 /* C style */
+#define DBIpp_cm_hs 0x000002 /* # */
+#define DBIpp_cm_dd 0x000004 /* -- */
+#define DBIpp_cm_br 0x000008 /* {} */
+#define DBIpp_cm_dw 0x000010 /* '-- ' dash dash whitespace */
+#define DBIpp_cm_XX 0x00001F /* any of the above */
+
+#define DBIpp_ph_qm 0x000100 /* ? */
+#define DBIpp_ph_cn 0x000200 /* :1 */
+#define DBIpp_ph_cs 0x000400 /* :name */
+#define DBIpp_ph_sp 0x000800 /* %s (as return only, not accept) */
+#define DBIpp_ph_XX 0x000F00 /* any of the above */
+
+#define DBIpp_st_qq 0x010000 /* '' char escape */
+#define DBIpp_st_bs 0x020000 /* \ char escape */
+#define DBIpp_st_XX 0x030000 /* any of the above */
+
+#define DBIpp_L_BRACE '{'
+#define DBIpp_R_BRACE '}'
+#define PS_accept(flag) DBIbf_has(ps_accept,(flag))
+#define PS_return(flag) DBIbf_has(ps_return,(flag))
+
+SV *
+preparse(SV *dbh, const char *statement, IV ps_return, IV ps_accept, void *foo)
+{
+ dTHX;
+ D_imp_xxh(dbh);
+/*
+ The idea here is that ps_accept defines which constructs to
+ recognize (accept) as valid in the source string (other
+ constructs are ignored), and ps_return defines which
+ constructs are valid to return in the result string.
+
+ If a construct that is valid in the input is also valid in the
+ output then it's simply copied. If it's not valid in the output
+ then it's editied into one of the valid forms (ideally the most
+ 'standard' and/or information preserving one).
+
+ For example, if ps_accept includes '--' style comments but
+ ps_return doesn't, but ps_return does include '#' style
+ comments then any '--' style comments would be rewritten as '#'
+ style comments.
+
+ Similarly for placeholders. DBD::Oracle, for example, would say
+ '?', ':1' and ':name' are all acceptable input, but only
+ ':name' should be returned.
+
+ (There's a tricky issue with the '--' comment style because it can
+ clash with valid syntax, i.e., "... set foo=foo--1 ..." so it
+ would be *bad* to misinterpret that as the start of a comment.
+ Perhaps we need a DBIpp_cm_dw (for dash-dash-whitespace) style
+ to allow for that.)
+
+ Also, we'll only support DBIpp_cm_br as an input style. And
+ even then, only with reluctance. We may (need to) drop it when
+ we add support for odbc escape sequences.
+*/
+ int idx = 1;
+
+ char in_quote = '\0';
+ char in_comment = '\0';
+ char rt_comment = '\0';
+ char *dest, *start;
+ const char *src;
+ const char *style = "", *laststyle = '\0';
+ SV *new_stmt_sv;
+
+ (void)foo;
+
+ if (!(ps_return | DBIpp_ph_XX)) { /* no return ph type specified */
+ ps_return |= ps_accept | DBIpp_ph_XX; /* so copy from ps_accept */
+ }
+
+ /* XXX this allocation strategy won't work when we get to more advanced stuff */
+ new_stmt_sv = newSV(strlen(statement) * 3);
+ sv_setpv(new_stmt_sv,"");
+ src = statement;
+ dest = SvPVX(new_stmt_sv);
+
+ while( *src )
+ {
+ if (*src == '%' && PS_return(DBIpp_ph_sp))
+ *dest++ = '%';
+
+ if (in_comment)
+ {
+ if ( (in_comment == '-' && (*src == '\n' || *(src+1) == '\0'))
+ || (in_comment == '#' && (*src == '\n' || *(src+1) == '\0'))
+ || (in_comment == DBIpp_L_BRACE && *src == DBIpp_R_BRACE) /* XXX nesting? */
+ || (in_comment == '/' && *src == '*' && *(src+1) == '/')
+ ) {
+ switch (rt_comment) {
+ case '/': *dest++ = '*'; *dest++ = '/'; break;
+ case '-': *dest++ = '\n'; break;
+ case '#': *dest++ = '\n'; break;
+ case DBIpp_L_BRACE: *dest++ = DBIpp_R_BRACE; break;
+ case '\0': /* ensure deleting a comment doesn't join two tokens */
+ if (in_comment=='/' || in_comment==DBIpp_L_BRACE)
+ *dest++ = ' '; /* ('-' and '#' styles use the newline) */
+ break;
+ }
+ if (in_comment == '/')
+ src++;
+ src += (*src != '\n' || *(dest-1)=='\n') ? 1 : 0;
+ in_comment = '\0';
+ rt_comment = '\0';
+ }
+ else
+ if (rt_comment)
+ *dest++ = *src++;
+ else
+ src++; /* delete (don't copy) the comment */
+ continue;
+ }
+
+ if (in_quote)
+ {
+ if (*src == in_quote) {
+ in_quote = 0;
+ }
+ *dest++ = *src++;
+ continue;
+ }
+
+ /* Look for comments */
+ if (*src == '-' && *(src+1) == '-' &&
+ (PS_accept(DBIpp_cm_dd) || (*(src+2) == ' ' && PS_accept(DBIpp_cm_dw)))
+ )
+ {
+ in_comment = *src;
+ src += 2; /* skip past 2nd char of double char delimiters */
+ if (PS_return(DBIpp_cm_dd) || PS_return(DBIpp_cm_dw)) {
+ *dest++ = rt_comment = '-';
+ *dest++ = '-';
+ if (PS_return(DBIpp_cm_dw) && *src!=' ')
+ *dest++ = ' '; /* insert needed white space */
+ }
+ else if (PS_return(DBIpp_cm_cs)) {
+ *dest++ = rt_comment = '/';
+ *dest++ = '*';
+ }
+ else if (PS_return(DBIpp_cm_hs)) {
+ *dest++ = rt_comment = '#';
+ }
+ else if (PS_return(DBIpp_cm_br)) {
+ *dest++ = rt_comment = DBIpp_L_BRACE;
+ }
+ continue;
+ }
+ else if (*src == '/' && *(src+1) == '*' && PS_accept(DBIpp_cm_cs))
+ {
+ in_comment = *src;
+ src += 2; /* skip past 2nd char of double char delimiters */
+ if (PS_return(DBIpp_cm_cs)) {
+ *dest++ = rt_comment = '/';
+ *dest++ = '*';
+ }
+ else if (PS_return(DBIpp_cm_dd) || PS_return(DBIpp_cm_dw)) {
+ *dest++ = rt_comment = '-';
+ *dest++ = '-';
+ if (PS_return(DBIpp_cm_dw)) *dest++ = ' ';
+ }
+ else if (PS_return(DBIpp_cm_hs)) {
+ *dest++ = rt_comment = '#';
+ }
+ else if (PS_return(DBIpp_cm_br)) {
+ *dest++ = rt_comment = DBIpp_L_BRACE;
+ }
+ continue;
+ }
+ else if (*src == '#' && PS_accept(DBIpp_cm_hs))
+ {
+ in_comment = *src;
+ src++;
+ if (PS_return(DBIpp_cm_hs)) {
+ *dest++ = rt_comment = '#';
+ }
+ else if (PS_return(DBIpp_cm_dd) || PS_return(DBIpp_cm_dw)) {
+ *dest++ = rt_comment = '-';
+ *dest++ = '-';
+ if (PS_return(DBIpp_cm_dw)) *dest++ = ' ';
+ }
+ else if (PS_return(DBIpp_cm_cs)) {
+ *dest++ = rt_comment = '/';
+ *dest++ = '*';
+ }
+ else if (PS_return(DBIpp_cm_br)) {
+ *dest++ = rt_comment = DBIpp_L_BRACE;
+ }
+ continue;
+ }
+ else if (*src == DBIpp_L_BRACE && PS_accept(DBIpp_cm_br))
+ {
+ in_comment = *src;
+ src++;
+ if (PS_return(DBIpp_cm_br)) {
+ *dest++ = rt_comment = DBIpp_L_BRACE;
+ }
+ else if (PS_return(DBIpp_cm_dd) || PS_return(DBIpp_cm_dw)) {
+ *dest++ = rt_comment = '-';
+ *dest++ = '-';
+ if (PS_return(DBIpp_cm_dw)) *dest++ = ' ';
+ }
+ else if (PS_return(DBIpp_cm_cs)) {
+ *dest++ = rt_comment = '/';
+ *dest++ = '*';
+ }
+ else if (PS_return(DBIpp_cm_hs)) {
+ *dest++ = rt_comment = '#';
+ }
+ continue;
+ }
+
+ if ( !(*src==':' && (PS_accept(DBIpp_ph_cn) || PS_accept(DBIpp_ph_cs)))
+ && !(*src=='?' && PS_accept(DBIpp_ph_qm))
+ ){
+ if (*src == '\'' || *src == '"')
+ in_quote = *src;
+ *dest++ = *src++;
+ continue;
+ }
+
+ /* only here for : or ? outside of a comment or literal */
+
+ start = dest; /* save name inc colon */
+ *dest++ = *src++; /* copy and move past first char */
+
+ if (*start == '?') /* X/Open Standard */
+ {
+ style = "?";
+
+ if (PS_return(DBIpp_ph_qm))
+ ;
+ else if (PS_return(DBIpp_ph_cn)) { /* '?' -> ':p1' (etc) */
+ sprintf(start,":p%d", idx++);
+ dest = start+strlen(start);
+ }
+ else if (PS_return(DBIpp_ph_sp)) { /* '?' -> '%s' */
+ *start = '%';
+ *dest++ = 's';
+ }
+ }
+ else if (isDIGIT(*src)) { /* :1 */
+ const int pln = atoi(src);
+ style = ":1";
+
+ if (PS_return(DBIpp_ph_cn)) { /* ':1'->':p1' */
+ idx = pln;
+ *dest++ = 'p';
+ while(isDIGIT(*src))
+ *dest++ = *src++;
+ }
+ else if (PS_return(DBIpp_ph_qm) /* ':1' -> '?' */
+ || PS_return(DBIpp_ph_sp) /* ':1' -> '%s' */
+ ) {
+ PS_return(DBIpp_ph_qm) ? sprintf(start,"?") : sprintf(start,"%%s");
+ dest = start + strlen(start);
+ if (pln != idx) {
+ char buf[99];
+ sprintf(buf, "preparse found placeholder :%d out of sequence, expected :%d", pln, idx);
+ set_err_char(dbh, imp_xxh, "1", 1, buf, 0, "preparse");
+ return &PL_sv_undef;
+ }
+ while(isDIGIT(*src)) src++;
+ idx++;
+ }
+ }
+ else if (isALNUM(*src)) /* :name */
+ {
+ style = ":name";
+
+ if (PS_return(DBIpp_ph_cs)) {
+ ;
+ }
+ else if (PS_return(DBIpp_ph_qm) /* ':name' -> '?' */
+ || PS_return(DBIpp_ph_sp) /* ':name' -> '%s' */
+ ) {
+ PS_return(DBIpp_ph_qm) ? sprintf(start,"?") : sprintf(start,"%%s");
+ dest = start + strlen(start);
+ while (isALNUM(*src)) /* consume name, includes '_' */
+ src++;
+ }
+ }
+ /* perhaps ':=' PL/SQL construct */
+ else { continue; }
+
+ *dest = '\0'; /* handy for debugging */
+
+ if (laststyle && style != laststyle) {
+ char buf[99];
+ sprintf(buf, "preparse found mixed placeholder styles (%s / %s)", style, laststyle);
+ set_err_char(dbh, imp_xxh, "1", 1, buf, 0, "preparse");
+ return &PL_sv_undef;
+ }
+ laststyle = style;
+ }
+ *dest = '\0';
+
+ /* warn about probable parsing errors, but continue anyway (returning processed string) */
+ switch (in_quote)
+ {
+ case '\'':
+ set_err_char(dbh, imp_xxh, "1", 1, "preparse found unterminated single-quoted string", 0, "preparse");
+ break;
+ case '\"':
+ set_err_char(dbh, imp_xxh, "1", 1, "preparse found unterminated double-quoted string", 0, "preparse");
+ break;
+ }
+ switch (in_comment)
+ {
+ case DBIpp_L_BRACE:
+ set_err_char(dbh, imp_xxh, "1", 1, "preparse found unterminated bracketed {...} comment", 0, "preparse");
+ break;
+ case '/':
+ set_err_char(dbh, imp_xxh, "1", 1, "preparse found unterminated bracketed C-style comment", 0, "preparse");
+ break;
+ }
+
+ SvCUR_set(new_stmt_sv, strlen(SvPVX(new_stmt_sv)));
+ *SvEND(new_stmt_sv) = '\0';
+ return new_stmt_sv;
+}
+
+
+/* -------------------------------------------------------------------- */
+/* The DBI Perl interface (via XS) starts here. Currently these are */
+/* all internal support functions. Note install_method and see DBI.pm */
+
+MODULE = DBI PACKAGE = DBI
+
+REQUIRE: 1.929
+PROTOTYPES: DISABLE
+
+
+BOOT:
+ {
+ MY_CXT_INIT;
+ (void)MY_CXT; /* avoid 'unused variable' warning */
+ }
+ (void)cv;
+ (void)items; /* avoid 'unused variable' warning */
+ dbi_bootinit(NULL);
+ /* make this sub into a fake XS so it can bee seen by DBD::* modules;
+ * never actually call it as an XS sub, or it will crash and burn! */
+ (void) newXS("DBI::_dbi_state_lval", (XSUBADDR_t)_dbi_state_lval, __FILE__);
+
+
+I32
+constant()
+ PROTOTYPE:
+ ALIAS:
+ SQL_ALL_TYPES = SQL_ALL_TYPES
+ SQL_ARRAY = SQL_ARRAY
+ SQL_ARRAY_LOCATOR = SQL_ARRAY_LOCATOR
+ SQL_BIGINT = SQL_BIGINT
+ SQL_BINARY = SQL_BINARY
+ SQL_BIT = SQL_BIT
+ SQL_BLOB = SQL_BLOB
+ SQL_BLOB_LOCATOR = SQL_BLOB_LOCATOR
+ SQL_BOOLEAN = SQL_BOOLEAN
+ SQL_CHAR = SQL_CHAR
+ SQL_CLOB = SQL_CLOB
+ SQL_CLOB_LOCATOR = SQL_CLOB_LOCATOR
+ SQL_DATE = SQL_DATE
+ SQL_DATETIME = SQL_DATETIME
+ SQL_DECIMAL = SQL_DECIMAL
+ SQL_DOUBLE = SQL_DOUBLE
+ SQL_FLOAT = SQL_FLOAT
+ SQL_GUID = SQL_GUID
+ SQL_INTEGER = SQL_INTEGER
+ SQL_INTERVAL = SQL_INTERVAL
+ SQL_INTERVAL_DAY = SQL_INTERVAL_DAY
+ SQL_INTERVAL_DAY_TO_HOUR = SQL_INTERVAL_DAY_TO_HOUR
+ SQL_INTERVAL_DAY_TO_MINUTE = SQL_INTERVAL_DAY_TO_MINUTE
+ SQL_INTERVAL_DAY_TO_SECOND = SQL_INTERVAL_DAY_TO_SECOND
+ SQL_INTERVAL_HOUR = SQL_INTERVAL_HOUR
+ SQL_INTERVAL_HOUR_TO_MINUTE = SQL_INTERVAL_HOUR_TO_MINUTE
+ SQL_INTERVAL_HOUR_TO_SECOND = SQL_INTERVAL_HOUR_TO_SECOND
+ SQL_INTERVAL_MINUTE = SQL_INTERVAL_MINUTE
+ SQL_INTERVAL_MINUTE_TO_SECOND = SQL_INTERVAL_MINUTE_TO_SECOND
+ SQL_INTERVAL_MONTH = SQL_INTERVAL_MONTH
+ SQL_INTERVAL_SECOND = SQL_INTERVAL_SECOND
+ SQL_INTERVAL_YEAR = SQL_INTERVAL_YEAR
+ SQL_INTERVAL_YEAR_TO_MONTH = SQL_INTERVAL_YEAR_TO_MONTH
+ SQL_LONGVARBINARY = SQL_LONGVARBINARY
+ SQL_LONGVARCHAR = SQL_LONGVARCHAR
+ SQL_MULTISET = SQL_MULTISET
+ SQL_MULTISET_LOCATOR = SQL_MULTISET_LOCATOR
+ SQL_NUMERIC = SQL_NUMERIC
+ SQL_REAL = SQL_REAL
+ SQL_REF = SQL_REF
+ SQL_ROW = SQL_ROW
+ SQL_SMALLINT = SQL_SMALLINT
+ SQL_TIME = SQL_TIME
+ SQL_TIMESTAMP = SQL_TIMESTAMP
+ SQL_TINYINT = SQL_TINYINT
+ SQL_TYPE_DATE = SQL_TYPE_DATE
+ SQL_TYPE_TIME = SQL_TYPE_TIME
+ SQL_TYPE_TIMESTAMP = SQL_TYPE_TIMESTAMP
+ SQL_TYPE_TIMESTAMP_WITH_TIMEZONE = SQL_TYPE_TIMESTAMP_WITH_TIMEZONE
+ SQL_TYPE_TIME_WITH_TIMEZONE = SQL_TYPE_TIME_WITH_TIMEZONE
+ SQL_UDT = SQL_UDT
+ SQL_UDT_LOCATOR = SQL_UDT_LOCATOR
+ SQL_UNKNOWN_TYPE = SQL_UNKNOWN_TYPE
+ SQL_VARBINARY = SQL_VARBINARY
+ SQL_VARCHAR = SQL_VARCHAR
+ SQL_WCHAR = SQL_WCHAR
+ SQL_WLONGVARCHAR = SQL_WLONGVARCHAR
+ SQL_WVARCHAR = SQL_WVARCHAR
+ SQL_CURSOR_FORWARD_ONLY = SQL_CURSOR_FORWARD_ONLY
+ SQL_CURSOR_KEYSET_DRIVEN = SQL_CURSOR_KEYSET_DRIVEN
+ SQL_CURSOR_DYNAMIC = SQL_CURSOR_DYNAMIC
+ SQL_CURSOR_STATIC = SQL_CURSOR_STATIC
+ SQL_CURSOR_TYPE_DEFAULT = SQL_CURSOR_TYPE_DEFAULT
+ DBIpp_cm_cs = DBIpp_cm_cs
+ DBIpp_cm_hs = DBIpp_cm_hs
+ DBIpp_cm_dd = DBIpp_cm_dd
+ DBIpp_cm_dw = DBIpp_cm_dw
+ DBIpp_cm_br = DBIpp_cm_br
+ DBIpp_cm_XX = DBIpp_cm_XX
+ DBIpp_ph_qm = DBIpp_ph_qm
+ DBIpp_ph_cn = DBIpp_ph_cn
+ DBIpp_ph_cs = DBIpp_ph_cs
+ DBIpp_ph_sp = DBIpp_ph_sp
+ DBIpp_ph_XX = DBIpp_ph_XX
+ DBIpp_st_qq = DBIpp_st_qq
+ DBIpp_st_bs = DBIpp_st_bs
+ DBIpp_st_XX = DBIpp_st_XX
+ DBIstcf_DISCARD_STRING = DBIstcf_DISCARD_STRING
+ DBIstcf_STRICT = DBIstcf_STRICT
+ DBIf_TRACE_SQL = DBIf_TRACE_SQL
+ DBIf_TRACE_CON = DBIf_TRACE_CON
+ DBIf_TRACE_ENC = DBIf_TRACE_ENC
+ DBIf_TRACE_DBD = DBIf_TRACE_DBD
+ DBIf_TRACE_TXN = DBIf_TRACE_TXN
+ CODE:
+ RETVAL = ix;
+ OUTPUT:
+ RETVAL
+
+
+void
+_clone_dbis()
+ CODE:
+ dMY_CXT;
+ dbistate_t * parent_dbis = DBIS;
+
+ (void)cv;
+ {
+ MY_CXT_CLONE;
+ }
+ dbi_bootinit(parent_dbis);
+
+
+void
+_new_handle(class, parent, attr_ref, imp_datasv, imp_class)
+ SV * class
+ SV * parent
+ SV * attr_ref
+ SV * imp_datasv
+ SV * imp_class
+ PPCODE:
+ dMY_CXT;
+ HV *outer;
+ SV *outer_ref;
+ HV *class_stash = gv_stashsv(class, GV_ADDWARN);
+
+ if (DBIS_TRACE_LEVEL >= 5) {
+ PerlIO_printf(DBILOGFP, " New %s (for %s, parent=%s, id=%s)\n",
+ neatsvpv(class,0), SvPV_nolen(imp_class), neatsvpv(parent,0), neatsvpv(imp_datasv,0));
+ (void)cv; /* avoid unused warning */
+ }
+
+ (void)hv_store((HV*)SvRV(attr_ref), "ImplementorClass", 16, SvREFCNT_inc(imp_class), 0);
+
+ /* make attr into inner handle by blessing it into class */
+ sv_bless(attr_ref, class_stash);
+ /* tie new outer hash to inner handle */
+ outer = newHV(); /* create new hash to be outer handle */
+ outer_ref = newRV_noinc((SV*)outer);
+ /* make outer hash into a handle by blessing it into class */
+ sv_bless(outer_ref, class_stash);
+ /* tie outer handle to inner handle */
+ sv_magic((SV*)outer, attr_ref, PERL_MAGIC_tied, Nullch, 0);
+
+ dbih_setup_handle(aTHX_ outer_ref, SvPV_nolen(imp_class), parent, SvOK(imp_datasv) ? imp_datasv : Nullsv);
+
+ /* return outer handle, plus inner handle if not in scalar context */
+ sv_2mortal(outer_ref);
+ EXTEND(SP, 2);
+ PUSHs(outer_ref);
+ if (GIMME != G_SCALAR) {
+ PUSHs(attr_ref);
+ }
+
+
+void
+_setup_handle(sv, imp_class, parent, imp_datasv)
+ SV * sv
+ char * imp_class
+ SV * parent
+ SV * imp_datasv
+ CODE:
+ (void)cv;
+ dbih_setup_handle(aTHX_ sv, imp_class, parent, SvOK(imp_datasv) ? imp_datasv : Nullsv);
+ ST(0) = &PL_sv_undef;
+
+
+void
+_get_imp_data(sv)
+ SV * sv
+ CODE:
+ D_imp_xxh(sv);
+ (void)cv;
+ ST(0) = sv_mortalcopy(DBIc_IMP_DATA(imp_xxh)); /* okay if NULL */
+
+
+void
+_handles(sv)
+ SV * sv
+ PPCODE:
+ /* return the outer and inner handle for any given handle */
+ D_imp_xxh(sv);
+ SV *ih = sv_mortalcopy( dbih_inner(aTHX_ sv, "_handles") );
+ SV *oh = sv_2mortal(newRV_inc((SV*)DBIc_MY_H(imp_xxh))); /* XXX dangerous */
+ (void)cv;
+ EXTEND(SP, 2);
+ PUSHs(oh); /* returns outer handle then inner */
+ PUSHs(ih);
+
+
+void
+neat(sv, maxlen=0)
+ SV * sv
+ U32 maxlen
+ CODE:
+ ST(0) = sv_2mortal(newSVpv(neatsvpv(sv, maxlen), 0));
+ (void)cv;
+
+
+I32
+hash(key, type=0)
+ const char *key
+ long type
+ CODE:
+ (void)cv;
+ RETVAL = dbi_hash(key, type);
+ OUTPUT:
+ RETVAL
+
+void
+looks_like_number(...)
+ PPCODE:
+ int i;
+ EXTEND(SP, items);
+ (void)cv;
+ for(i=0; i < items ; ++i) {
+ SV *sv = ST(i);
+ if (!SvOK(sv) || (SvPOK(sv) && SvCUR(sv)==0))
+ PUSHs(&PL_sv_undef);
+ else if ( looks_like_number(sv) )
+ PUSHs(&PL_sv_yes);
+ else
+ PUSHs(&PL_sv_no);
+ }
+
+
+void
+_install_method(dbi_class, meth_name, file, attribs=Nullsv)
+ const char * dbi_class
+ char * meth_name
+ char * file
+ SV * attribs
+ CODE:
+ {
+ dMY_CXT;
+ /* install another method name/interface for the DBI dispatcher */
+ SV *trace_msg = (DBIS_TRACE_LEVEL >= 10) ? sv_2mortal(newSVpv("",0)) : Nullsv;
+ CV *cv;
+ SV **svp;
+ dbi_ima_t *ima;
+ MAGIC *mg;
+ (void)dbi_class;
+
+ if (strnNE(meth_name, "DBI::", 5)) /* XXX m/^DBI::\w+::\w+$/ */
+ croak("install_method %s: invalid class", meth_name);
+
+ if (trace_msg)
+ sv_catpvf(trace_msg, "install_method %-21s", meth_name);
+
+ Newxz(ima, 1, dbi_ima_t);
+
+ if (attribs && SvOK(attribs)) {
+ /* convert and store method attributes in a fast access form */
+ if (SvTYPE(SvRV(attribs)) != SVt_PVHV)
+ croak("install_method %s: bad attribs", meth_name);
+
+ DBD_ATTRIB_GET_IV(attribs, "O",1, svp, ima->flags);
+ DBD_ATTRIB_GET_UV(attribs, "T",1, svp, ima->method_trace);
+ DBD_ATTRIB_GET_IV(attribs, "H",1, svp, ima->hidearg);
+
+ if (trace_msg) {
+ if (ima->flags) sv_catpvf(trace_msg, ", flags 0x%04x", (unsigned)ima->flags);
+ if (ima->method_trace)sv_catpvf(trace_msg, ", T 0x%08lx", (unsigned long)ima->method_trace);
+ if (ima->hidearg) sv_catpvf(trace_msg, ", H %u", (unsigned)ima->hidearg);
+ }
+ if ( (svp=DBD_ATTRIB_GET_SVP(attribs, "U",1)) != NULL) {
+ AV *av = (AV*)SvRV(*svp);
+ ima->minargs = (U8)SvIV(*av_fetch(av, 0, 1));
+ ima->maxargs = (U8)SvIV(*av_fetch(av, 1, 1));
+ svp = av_fetch(av, 2, 0);
+ ima->usage_msg = (svp) ? savepv_using_sv(SvPV_nolen(*svp)) : "";
+ ima->flags |= IMA_HAS_USAGE;
+ if (trace_msg && DBIS_TRACE_LEVEL >= 11)
+ sv_catpvf(trace_msg, ",\n usage: min %d, max %d, '%s'",
+ ima->minargs, ima->maxargs, ima->usage_msg);
+ }
+ }
+ if (trace_msg)
+ PerlIO_printf(DBILOGFP,"%s\n", SvPV_nolen(trace_msg));
+ file = savepv(file);
+ cv = newXS(meth_name, XS_DBI_dispatch, file);
+ SvPVX((SV *)cv) = file;
+ SvLEN((SV *)cv) = 1;
+ CvXSUBANY(cv).any_ptr = ima;
+ ima->meth_type = get_meth_type(GvNAME(CvGV(cv)));
+
+ /* Attach magic to handle duping and freeing of the dbi_ima_t struct.
+ * Due to the poor interface of the mg dup function, sneak a pointer
+ * to the original CV in the mg_ptr field (we get called with a
+ * pointer to the mg, but not the SV) */
+ mg = sv_magicext((SV*)cv, NULL, DBI_MAGIC, &dbi_ima_vtbl,
+ (char *)cv, 0);
+#ifdef BROKEN_DUP_ANY_PTR
+ ima->my_perl = my_perl; /* who owns this struct */
+#else
+ mg->mg_flags |= MGf_DUP;
+#endif
+ ST(0) = &PL_sv_yes;
+ }
+
+
+int
+trace(class, level_sv=&PL_sv_undef, file=Nullsv)
+ SV * class
+ SV * level_sv
+ SV * file
+ ALIAS:
+ _debug_dispatch = 1
+ CODE:
+ {
+ dMY_CXT;
+ IV level;
+ if (!DBIS) {
+ ix=ix; /* avoid 'unused variable' warnings */
+ croak("DBI not initialised");
+ }
+ /* Return old/current value. No change if new value not given. */
+ RETVAL = (DBIS) ? DBIS->debug : 0;
+ level = parse_trace_flags(class, level_sv, RETVAL);
+ if (level) /* call before or after altering DBI trace level */
+ set_trace_file(file);
+ if (level != RETVAL) {
+ if ((level & DBIc_TRACE_LEVEL_MASK) > 0) {
+ PerlIO_printf(DBILOGFP," DBI %s%s default trace level set to 0x%lx/%ld (pid %d pi %p) at %s\n",
+ XS_VERSION, dbi_build_opt,
+ (long)(level & DBIc_TRACE_FLAGS_MASK),
+ (long)(level & DBIc_TRACE_LEVEL_MASK),
+ (int)PerlProc_getpid(),
+#ifdef MULTIPLICITY
+ (void *)my_perl,
+#else
+ (void*)NULL,
+#endif
+ log_where(Nullsv, 0, "", "", 1, 1, 0)
+ );
+ if (!PL_dowarn)
+ PerlIO_printf(DBILOGFP," Note: perl is running without the recommended perl -w option\n");
+ PerlIO_flush(DBILOGFP);
+ }
+ DBIS->debug = level;
+ sv_setiv(get_sv("DBI::dbi_debug",0x5), level);
+ }
+ if (!level) /* call before or after altering DBI trace level */
+ set_trace_file(file);
+ }
+ OUTPUT:
+ RETVAL
+
+
+
+void
+dump_handle(sv, msg="DBI::dump_handle", level=0)
+ SV * sv
+ const char *msg
+ int level
+ CODE:
+ (void)cv;
+ dbih_dumphandle(aTHX_ sv, msg, level);
+
+
+
+void
+_svdump(sv)
+ SV * sv
+ CODE:
+ {
+ dMY_CXT;
+ (void)cv;
+ PerlIO_printf(DBILOGFP, "DBI::_svdump(%s)", neatsvpv(sv,0));
+#ifdef DEBUGGING
+ sv_dump(sv);
+#endif
+ }
+
+
+NV
+dbi_time()
+
+
+void
+dbi_profile(h, statement, method, t1, t2)
+ SV *h
+ SV *statement
+ SV *method
+ NV t1
+ NV t2
+ CODE:
+ SV *leaf = &PL_sv_undef;
+ (void)cv; /* avoid unused var warnings */
+ if (SvROK(method))
+ method = SvRV(method);
+ if (dbih_inner(aTHX_ h, NULL)) { /* is a DBI handle */
+ D_imp_xxh(h);
+ leaf = dbi_profile(h, imp_xxh, statement, method, t1, t2);
+ }
+ else if (SvROK(h) && SvTYPE(SvRV(h)) == SVt_PVHV) {
+ /* iterate over values %$h */
+ HV *hv = (HV*)SvRV(h);
+ SV *tmp;
+ char *key;
+ I32 keylen = 0;
+ hv_iterinit(hv);
+ while ( (tmp = hv_iternextsv(hv, &key, &keylen)) != NULL ) {
+ if (SvOK(tmp)) {
+ D_imp_xxh(tmp);
+ leaf = dbi_profile(tmp, imp_xxh, statement, method, t1, t2);
+ }
+ };
+ }
+ else {
+ croak("dbi_profile(%s,...) invalid handle argument", neatsvpv(h,0));
+ }
+ if (GIMME_V == G_VOID)
+ ST(0) = &PL_sv_undef; /* skip sv_mortalcopy if not needed */
+ else
+ ST(0) = sv_mortalcopy(leaf);
+
+
+
+SV *
+dbi_profile_merge_nodes(dest, ...)
+ SV * dest
+ ALIAS:
+ dbi_profile_merge = 1
+ CODE:
+ {
+ if (!SvROK(dest) || SvTYPE(SvRV(dest)) != SVt_PVAV)
+ croak("dbi_profile_merge_nodes(%s,...) destination is not an array reference", neatsvpv(dest,0));
+ if (items <= 1) {
+ (void)cv; /* avoid unused var warnings */
+ (void)ix;
+ RETVAL = 0;
+ }
+ else {
+ /* items==2 for dest + 1 arg, ST(0) is dest, ST(1) is first arg */
+ while (--items >= 1) {
+ SV *thingy = ST(items);
+ dbi_profile_merge_nodes(dest, thingy);
+ }
+ RETVAL = newSVsv(*av_fetch((AV*)SvRV(dest), DBIprof_TOTAL_TIME, 1));
+ }
+ }
+ OUTPUT:
+ RETVAL
+
+
+SV *
+_concat_hash_sorted(hash_sv, kv_sep_sv, pair_sep_sv, use_neat_sv, num_sort_sv)
+ SV *hash_sv
+ SV *kv_sep_sv
+ SV *pair_sep_sv
+ SV *use_neat_sv
+ SV *num_sort_sv
+ PREINIT:
+ char *kv_sep, *pair_sep;
+ STRLEN kv_sep_len, pair_sep_len;
+ CODE:
+ if (!SvOK(hash_sv))
+ XSRETURN_UNDEF;
+ if (!SvROK(hash_sv) || SvTYPE(SvRV(hash_sv))!=SVt_PVHV)
+ croak("hash is not a hash reference");
+
+ kv_sep = SvPV(kv_sep_sv, kv_sep_len);
+ pair_sep = SvPV(pair_sep_sv, pair_sep_len);
+
+ RETVAL = _join_hash_sorted( (HV*)SvRV(hash_sv),
+ kv_sep, kv_sep_len,
+ pair_sep, pair_sep_len,
+ /* use_neat should be undef, 0 or 1, may allow sprintf format strings later */
+ (SvOK(use_neat_sv)) ? SvIV(use_neat_sv) : 0,
+ (SvOK(num_sort_sv)) ? SvIV(num_sort_sv) : -1
+ );
+ OUTPUT:
+ RETVAL
+
+
+int
+sql_type_cast(sv, sql_type, flags=0)
+ SV * sv
+ int sql_type
+ U32 flags
+ CODE:
+ RETVAL = sql_type_cast_svpv(aTHX_ sv, sql_type, flags, 0);
+ OUTPUT:
+ RETVAL
+
+
+
+MODULE = DBI PACKAGE = DBI::var
+
+void
+FETCH(sv)
+ SV * sv
+ CODE:
+ dMY_CXT;
+ /* Note that we do not come through the dispatcher to get here. */
+ char *meth = SvPV_nolen(SvRV(sv)); /* what should this tie do ? */
+ char type = *meth++; /* is this a $ or & style */
+ imp_xxh_t *imp_xxh = (DBI_LAST_HANDLE_OK) ? DBIh_COM(DBI_LAST_HANDLE) : NULL;
+ int trace_level = (imp_xxh ? DBIc_TRACE_LEVEL(imp_xxh) : DBIS_TRACE_LEVEL);
+ NV profile_t1 = 0.0;
+
+ if (imp_xxh && DBIc_has(imp_xxh,DBIcf_Profile))
+ profile_t1 = dbi_time();
+
+ if (trace_level >= 2) {
+ PerlIO_printf(DBILOGFP," -> $DBI::%s (%c) FETCH from lasth=%s\n", meth, type,
+ (imp_xxh) ? neatsvpv(DBI_LAST_HANDLE,0): "none");
+ }
+
+ if (type == '!') { /* special case for $DBI::lasth */
+ /* Currently we can only return the INNER handle. */
+ /* This handle should only be used for true/false tests */
+ ST(0) = (imp_xxh) ? sv_2mortal(newRV_inc(DBI_LAST_HANDLE)) : &PL_sv_undef;
+ }
+ else if ( !imp_xxh ) {
+ if (trace_level)
+ warn("Can't read $DBI::%s, last handle unknown or destroyed", meth);
+ ST(0) = &PL_sv_undef;
+ }
+ else if (type == '*') { /* special case for $DBI::err, see also err method */
+ SV *errsv = DBIc_ERR(imp_xxh);
+ ST(0) = sv_mortalcopy(errsv);
+ }
+ else if (type == '"') { /* special case for $DBI::state */
+ SV *state = DBIc_STATE(imp_xxh);
+ ST(0) = DBIc_STATE_adjust(imp_xxh, state);
+ }
+ else if (type == '$') { /* lookup scalar variable in implementors stash */
+ const char *vname = mkvname(aTHX_ DBIc_IMP_STASH(imp_xxh), meth, 0);
+ SV *vsv = get_sv(vname, 1);
+ ST(0) = sv_mortalcopy(vsv);
+ }
+ else {
+ /* default to method call via stash of implementor of DBI_LAST_HANDLE */
+ GV *imp_gv;
+ HV *imp_stash = DBIc_IMP_STASH(imp_xxh);
+#ifdef DBI_save_hv_fetch_ent
+ HE save_mh = PL_hv_fetch_ent_mh; /* XXX nested tied FETCH bug17575 workaround */
+#endif
+ profile_t1 = 0.0; /* profile this via dispatch only (else we'll double count) */
+ if (trace_level >= 3)
+ PerlIO_printf(DBILOGFP," >> %s::%s\n", HvNAME(imp_stash), meth);
+ ST(0) = sv_2mortal(newRV_inc(DBI_LAST_HANDLE));
+ if ((imp_gv = gv_fetchmethod(imp_stash,meth)) == NULL) {
+ croak("Can't locate $DBI::%s object method \"%s\" via package \"%s\"",
+ meth, meth, HvNAME(imp_stash));
+ }
+ PUSHMARK(mark); /* reset mark (implies one arg as we were called with one arg?) */
+ call_sv((SV*)GvCV(imp_gv), GIMME);
+ SPAGAIN;
+#ifdef DBI_save_hv_fetch_ent
+ PL_hv_fetch_ent_mh = save_mh;
+#endif
+ }
+ if (trace_level)
+ PerlIO_printf(DBILOGFP," <- $DBI::%s= %s\n", meth, neatsvpv(ST(0),0));
+ if (profile_t1) {
+ SV *h = sv_2mortal(newRV_inc(DBI_LAST_HANDLE));
+ dbi_profile(h, imp_xxh, &PL_sv_undef, (SV*)cv, profile_t1, dbi_time());
+ }
+
+
+MODULE = DBI PACKAGE = DBD::_::dr
+
+void
+dbixs_revision(h)
+ SV * h
+ CODE:
+ PERL_UNUSED_VAR(h);
+ ST(0) = sv_2mortal(newSViv(DBIXS_REVISION));
+
+
+MODULE = DBI PACKAGE = DBD::_::db
+
+void
+connected(...)
+ CODE:
+ /* defined here just to avoid AUTOLOAD */
+ (void)cv;
+ (void)items;
+ ST(0) = &PL_sv_undef;
+
+
+SV *
+preparse(dbh, statement, ps_accept, ps_return, foo=Nullch)
+ SV * dbh
+ char * statement
+ IV ps_accept
+ IV ps_return
+ void *foo
+
+
+void
+take_imp_data(h)
+ SV * h
+ PREINIT:
+ /* take_imp_data currently in DBD::_::db not DBD::_::common, so for dbh's only */
+ D_imp_xxh(h);
+ MAGIC *mg;
+ SV *imp_xxh_sv;
+ SV **tmp_svp;
+ CODE:
+ (void)cv; /* unused */
+ /*
+ * Remove and return the imp_xxh_t structure that's attached to the inner
+ * hash of the handle. Effectively this removes the 'brain' of the handle
+ * leaving it as an empty shell - brain dead. All method calls on it fail.
+ *
+ * The imp_xxh_t structure that's removed and returned is a plain scalar
+ * (containing binary data). It can be passed to a new DBI->connect call
+ * in order to have the new $dbh use the same 'connection' as the original
+ * handle. In this way a multi-threaded connection pool can be implemented.
+ *
+ * If the drivers imp_xxh_t structure contains SV*'s, or other interpreter
+ * specific items, they should be freed by the drivers own take_imp_data()
+ * method before it then calls SUPER::take_imp_data() to finalize removal
+ * of the imp_xxh_t structure.
+ *
+ * The driver needs to view the take_imp_data method as being nearly the
+ * same as disconnect+DESTROY only not actually calling the database API to
+ * disconnect. All that needs to remain valid in the imp_xxh_t structure
+ * is the underlying database API connection data. Everything else should
+ * in a 'clean' state such that if the drivers own DESTROY method was
+ * called it would be able to properly handle the contents of the
+ * structure. This is important in case a new handle created using this
+ * imp_data, possibly in a new thread, might end up being DESTROY'd before
+ * the driver has had a chance to 're-setup' the data. See dbih_setup_handle()
+ *
+ * All the above relates to the 'typical use case' for a compiled driver.
+ * For a pure-perl driver using a socket pair, for example, the drivers
+ * take_imp_data method might just return a string containing the fileno()
+ * values of the sockets (without calling this SUPER::take_imp_data() code).
+ * The key point is that the take_imp_data() method returns an opaque buffer
+ * containing whatever the driver would need to reuse the same underlying
+ * 'connection to the database' in a new handle.
+ *
+ * In all cases, care should be taken that driver attributes (such as
+ * AutoCommit) match the state of the underlying connection.
+ */
+
+ if (!DBIc_ACTIVE(imp_xxh)) {/* sanity check, may be relaxed later */
+ set_err_char(h, imp_xxh, "1", 1, "Can't take_imp_data from handle that's not Active", 0, "take_imp_data");
+ XSRETURN(0);
+ }
+
+ /* Ideally there should be no child statement handles existing when
+ * take_imp_data is called because when those statement handles are
+ * destroyed they may need to interact with the 'zombie' parent dbh.
+ * So we do our best to neautralize them (finish & rebless)
+ */
+ if ((tmp_svp = hv_fetch((HV*)SvRV(h), "ChildHandles", 12, FALSE)) && SvROK(*tmp_svp)) {
+ AV *av = (AV*)SvRV(*tmp_svp);
+ HV *zombie_stash = gv_stashpv("DBI::zombie", GV_ADDWARN);
+ I32 kidslots;
+ for (kidslots = AvFILL(av); kidslots >= 0; --kidslots) {
+ SV **hp = av_fetch(av, kidslots, FALSE);
+ if (hp && SvROK(*hp) && SvMAGICAL(SvRV(*hp))) {
+ PUSHMARK(sp);
+ XPUSHs(*hp);
+ PUTBACK;
+ call_method("finish", G_SCALAR|G_DISCARD);
+ SPAGAIN;
+ PUTBACK;
+ sv_unmagic(SvRV(*hp), 'P'); /* untie */
+ sv_bless(*hp, zombie_stash); /* neutralise */
+ }
+ }
+ }
+ /* The above measures may not be sufficient if weakrefs aren't available
+ * or something has a reference to the inner-handle of an sth.
+ * We'll require no Active kids, but just warn about others.
+ */
+ if (DBIc_ACTIVE_KIDS(imp_xxh)) {
+ set_err_char(h, imp_xxh, "1", 1, "Can't take_imp_data from handle while it still has Active kids", 0, "take_imp_data");
+ XSRETURN(0);
+ }
+ if (DBIc_KIDS(imp_xxh))
+ warn("take_imp_data from handle while it still has kids");
+
+ /* it may be better here to return a copy and poison the original
+ * rather than detatching and returning the original
+ */
+
+ /* --- perform the surgery */
+ dbih_getcom2(aTHX_ h, &mg); /* get the MAGIC so we can change it */
+ imp_xxh_sv = mg->mg_obj; /* take local copy of the imp_data pointer */
+ mg->mg_obj = Nullsv; /* sever the link from handle to imp_xxh */
+ mg->mg_ptr = NULL; /* and sever the shortcut too */
+ if (DBIc_TRACE_LEVEL(imp_xxh) >= 9)
+ sv_dump(imp_xxh_sv);
+ /* --- housekeeping */
+ DBIc_ACTIVE_off(imp_xxh); /* silence warning from dbih_clearcom */
+ DBIc_IMPSET_off(imp_xxh); /* silence warning from dbih_clearcom */
+ dbih_clearcom(imp_xxh); /* free SVs like DBD::_mem::common::DESTROY */
+ SvOBJECT_off(imp_xxh_sv); /* no longer needs DESTROY via dbih_clearcom */
+ /* restore flags to mark fact imp data holds active connection */
+ /* (don't use magical DBIc_ACTIVE_on here) */
+ DBIc_FLAGS(imp_xxh) |= DBIcf_IMPSET | DBIcf_ACTIVE;
+ /* --- tidy up the raw PV for life as a more normal string */
+ SvPOK_on(imp_xxh_sv); /* SvCUR & SvEND were set at creation */
+ /* --- return the actual imp_xxh_sv on the stack */
+ ST(0) = imp_xxh_sv;
+
+
+
+MODULE = DBI PACKAGE = DBD::_::st
+
+void
+_get_fbav(sth)
+ SV * sth
+ CODE:
+ D_imp_sth(sth);
+ AV *av = dbih_get_fbav(imp_sth);
+ (void)cv;
+ ST(0) = sv_2mortal(newRV_inc((SV*)av));
+
+void
+_set_fbav(sth, src_rv)
+ SV * sth
+ SV * src_rv
+ CODE:
+ D_imp_sth(sth);
+ int i;
+ AV *src_av;
+ AV *dst_av = dbih_get_fbav(imp_sth);
+ int dst_fields = AvFILL(dst_av)+1;
+ int src_fields;
+ (void)cv;
+
+ if (!SvROK(src_rv) || SvTYPE(SvRV(src_rv)) != SVt_PVAV)
+ croak("_set_fbav(%s): not an array ref", neatsvpv(src_rv,0));
+ src_av = (AV*)SvRV(src_rv);
+ src_fields = AvFILL(src_av)+1;
+ if (src_fields != dst_fields) {
+ warn("_set_fbav(%s): array has %d elements, the statement handle row buffer has %d (and NUM_OF_FIELDS is %d)",
+ neatsvpv(src_rv,0), src_fields, dst_fields, DBIc_NUM_FIELDS(imp_sth));
+ SvREADONLY_off(dst_av);
+ if (src_fields < dst_fields) {
+ /* shrink the array - sadly this looses column bindings for the lost columns */
+ av_fill(dst_av, src_fields-1);
+ dst_fields = src_fields;
+ }
+ else {
+ av_fill(dst_av, src_fields-1);
+ /* av_fill pads with immutable undefs which we need to change */
+ for(i=dst_fields-1; i < src_fields; ++i) {
+ sv_setsv(AvARRAY(dst_av)[i], newSV(0));
+ }
+ }
+ SvREADONLY_on(dst_av);
+ }
+ for(i=0; i < dst_fields; ++i) { /* copy over the row */
+ /* If we're given the values, then taint them if required */
+ if (DBIc_is(imp_sth, DBIcf_TaintOut))
+ SvTAINT(AvARRAY(src_av)[i]);
+ sv_setsv(AvARRAY(dst_av)[i], AvARRAY(src_av)[i]);
+ }
+ ST(0) = sv_2mortal(newRV_inc((SV*)dst_av));
+
+
+void
+bind_col(sth, col, ref, attribs=Nullsv)
+ SV * sth
+ SV * col
+ SV * ref
+ SV * attribs
+ CODE:
+ DBD_ATTRIBS_CHECK("bind_col", sth, attribs);
+ ST(0) = boolSV(dbih_sth_bind_col(sth, col, ref, attribs));
+ (void)cv;
+
+
+void
+fetchrow_array(sth)
+ SV * sth
+ ALIAS:
+ fetchrow = 1
+ PPCODE:
+ SV *retsv;
+ if (CvDEPTH(cv) == 99) {
+ ix = ix; /* avoid 'unused variable' warning' */
+ croak("Deep recursion, probably fetchrow-fetch-fetchrow loop");
+ }
+ PUSHMARK(sp);
+ XPUSHs(sth);
+ PUTBACK;
+ if (call_method("fetch", G_SCALAR) != 1)
+ croak("panic: DBI fetch"); /* should never happen */
+ SPAGAIN;
+ retsv = POPs;
+ PUTBACK;
+ if (SvROK(retsv) && SvTYPE(SvRV(retsv)) == SVt_PVAV) {
+ D_imp_sth(sth);
+ int num_fields, i;
+ AV *bound_av;
+ AV *av = (AV*)SvRV(retsv);
+ num_fields = AvFILL(av)+1;
+ EXTEND(sp, num_fields+1);
+
+ /* We now check for bind_col() having been called but fetch */
+ /* not returning the fields_svav array. Probably because the */
+ /* driver is implemented in perl. XXX This logic may change later. */
+ bound_av = DBIc_FIELDS_AV(imp_sth); /* bind_col() called ? */
+ if (bound_av && av != bound_av) {
+ /* let dbih_get_fbav know what's going on */
+ bound_av = dbih_get_fbav(imp_sth);
+ if (DBIc_TRACE_LEVEL(imp_sth) >= 3) {
+ PerlIO_printf(DBIc_LOGPIO(imp_sth),
+ "fetchrow: updating fbav 0x%lx from 0x%lx\n",
+ (long)bound_av, (long)av);
+ }
+ for(i=0; i < num_fields; ++i) { /* copy over the row */
+ sv_setsv(AvARRAY(bound_av)[i], AvARRAY(av)[i]);
+ }
+ }
+ for(i=0; i < num_fields; ++i) {
+ PUSHs(AvARRAY(av)[i]);
+ }
+ }
+
+
+SV *
+fetchrow_hashref(sth, keyattrib=Nullch)
+ SV * sth
+ const char *keyattrib
+ PREINIT:
+ SV *rowavr;
+ SV *ka_rv;
+ D_imp_sth(sth);
+ CODE:
+ (void)cv;
+ PUSHMARK(sp);
+ XPUSHs(sth);
+ PUTBACK;
+ if (!keyattrib || !*keyattrib) {
+ SV *kn = DBIc_FetchHashKeyName(imp_sth);
+ if (kn && SvOK(kn))
+ keyattrib = SvPVX(kn);
+ else
+ keyattrib = "NAME";
+ }
+ ka_rv = *hv_fetch((HV*)DBIc_MY_H(imp_sth), keyattrib,strlen(keyattrib), TRUE);
+ /* we copy to invoke FETCH magic, and we do that before fetch() so if tainting */
+ /* then the taint triggered by the fetch won't then apply to the fetched name */
+ ka_rv = newSVsv(ka_rv);
+ if (call_method("fetch", G_SCALAR) != 1)
+ croak("panic: DBI fetch"); /* should never happen */
+ SPAGAIN;
+ rowavr = POPs;
+ PUTBACK;
+ /* have we got an array ref in rowavr */
+ if (SvROK(rowavr) && SvTYPE(SvRV(rowavr)) == SVt_PVAV) {
+ int i;
+ AV *rowav = (AV*)SvRV(rowavr);
+ const int num_fields = AvFILL(rowav)+1;
+ HV *hv;
+ AV *ka_av;
+ if (!(SvROK(ka_rv) && SvTYPE(SvRV(ka_rv))==SVt_PVAV)) {
+ sv_setiv(DBIc_ERR(imp_sth), 1);
+ sv_setpvf(DBIc_ERRSTR(imp_sth),
+ "Can't use attribute '%s' because it doesn't contain a reference to an array (%s)",
+ keyattrib, neatsvpv(ka_rv,0));
+ XSRETURN_UNDEF;
+ }
+ ka_av = (AV*)SvRV(ka_rv);
+ hv = newHV();
+ for (i=0; i < num_fields; ++i) { /* honor the original order as sent by the database */
+ SV **field_name_svp = av_fetch(ka_av, i, 1);
+ (void)hv_store_ent(hv, *field_name_svp, newSVsv((SV*)(AvARRAY(rowav)[i])), 0);
+ }
+ RETVAL = newRV_inc((SV*)hv);
+ SvREFCNT_dec(hv); /* since newRV incremented it */
+ }
+ else {
+ RETVAL = &PL_sv_undef;
+#if (PERL_VERSION < 4) || ((PERL_VERSION == 4) && (PERL_SUBVERSION <= 4))
+ RETVAL = newSV(0); /* mutable undef for 5.004_04 */
+#endif
+ }
+ SvREFCNT_dec(ka_rv); /* since we created it */
+ OUTPUT:
+ RETVAL
+
+
+void
+fetch(sth)
+ SV * sth
+ ALIAS:
+ fetchrow_arrayref = 1
+ CODE:
+ int num_fields;
+ if (CvDEPTH(cv) == 99) {
+ (void)ix; /* avoid 'unused variable' warning' */
+ croak("Deep recursion. Probably fetch-fetchrow-fetch loop.");
+ }
+ PUSHMARK(sp);
+ XPUSHs(sth);
+ PUTBACK;
+ num_fields = call_method("fetchrow", G_ARRAY); /* XXX change the name later */
+ SPAGAIN;
+ if (num_fields == 0) {
+ ST(0) = &PL_sv_undef;
+ } else {
+ D_imp_sth(sth);
+ AV *av = dbih_get_fbav(imp_sth);
+ if (num_fields != AvFILL(av)+1)
+ croak("fetchrow returned %d fields, expected %d",
+ num_fields, (int)AvFILL(av)+1);
+ SPAGAIN;
+ while(--num_fields >= 0)
+ sv_setsv(AvARRAY(av)[num_fields], POPs);
+ PUTBACK;
+ ST(0) = sv_2mortal(newRV_inc((SV*)av));
+ }
+
+
+void
+rows(sth)
+ SV * sth
+ CODE:
+ D_imp_sth(sth);
+ const IV rows = DBIc_ROW_COUNT(imp_sth);
+ ST(0) = sv_2mortal(newSViv(rows));
+ (void)cv;
+
+
+void
+finish(sth)
+ SV * sth
+ CODE:
+ D_imp_sth(sth);
+ DBIc_ACTIVE_off(imp_sth);
+ ST(0) = &PL_sv_yes;
+ (void)cv;
+
+
+void
+DESTROY(sth)
+ SV * sth
+ PPCODE:
+ /* keep in sync with DESTROY in Driver.xst */
+ D_imp_sth(sth);
+ ST(0) = &PL_sv_yes;
+ /* we don't test IMPSET here because this code applies to pure-perl drivers */
+ if (DBIc_IADESTROY(imp_sth)) { /* want's ineffective destroy */
+ DBIc_ACTIVE_off(imp_sth);
+ if (DBIc_TRACE_LEVEL(imp_sth))
+ PerlIO_printf(DBIc_LOGPIO(imp_sth), " DESTROY %s skipped due to InactiveDestroy\n", SvPV_nolen(sth));
+ }
+ if (DBIc_ACTIVE(imp_sth)) {
+ D_imp_dbh_from_sth;
+ if (!PL_dirty && DBIc_ACTIVE(imp_dbh)) {
+ dSP;
+ PUSHMARK(sp);
+ XPUSHs(sth);
+ PUTBACK;
+ call_method("finish", G_SCALAR);
+ SPAGAIN;
+ PUTBACK;
+ }
+ else {
+ DBIc_ACTIVE_off(imp_sth);
+ }
+ }
+
+
+MODULE = DBI PACKAGE = DBI::st
+
+void
+TIEHASH(class, inner_ref)
+ SV * class
+ SV * inner_ref
+ CODE:
+ HV *stash = gv_stashsv(class, GV_ADDWARN); /* a new hash is supplied to us, we just need to bless and apply tie magic */
+ sv_bless(inner_ref, stash);
+ ST(0) = inner_ref;
+
+MODULE = DBI PACKAGE = DBD::_::common
+
+
+void
+DESTROY(h)
+ SV * h
+ CODE:
+ /* DESTROY defined here just to avoid AUTOLOAD */
+ (void)cv;
+ (void)h;
+ ST(0) = &PL_sv_undef;
+
+
+void
+STORE(h, keysv, valuesv)
+ SV * h
+ SV * keysv
+ SV * valuesv
+ CODE:
+ ST(0) = &PL_sv_yes;
+ if (!dbih_set_attr_k(h, keysv, 0, valuesv))
+ ST(0) = &PL_sv_no;
+ (void)cv;
+
+
+void
+FETCH(h, keysv)
+ SV * h
+ SV * keysv
+ CODE:
+ ST(0) = dbih_get_attr_k(h, keysv, 0);
+ (void)cv;
+
+
+void
+private_data(h)
+ SV * h
+ CODE:
+ D_imp_xxh(h);
+ (void)cv;
+ ST(0) = sv_mortalcopy(DBIc_IMP_DATA(imp_xxh));
+
+
+void
+err(h)
+ SV * h
+ CODE:
+ D_imp_xxh(h);
+ SV *errsv = DBIc_ERR(imp_xxh);
+ (void)cv;
+ ST(0) = sv_mortalcopy(errsv);
+
+void
+state(h)
+ SV * h
+ CODE:
+ D_imp_xxh(h);
+ SV *state = DBIc_STATE(imp_xxh);
+ (void)cv;
+ ST(0) = DBIc_STATE_adjust(imp_xxh, state);
+
+void
+errstr(h)
+ SV * h
+ CODE:
+ D_imp_xxh(h);
+ SV *errstr = DBIc_ERRSTR(imp_xxh);
+ SV *err;
+ /* If there's no errstr but there is an err then use err */
+ (void)cv;
+ if (!SvTRUE(errstr) && (err=DBIc_ERR(imp_xxh)) && SvTRUE(err))
+ errstr = err;
+ ST(0) = sv_mortalcopy(errstr);
+
+
+void
+set_err(h, err, errstr=&PL_sv_no, state=&PL_sv_undef, method=&PL_sv_undef, result=Nullsv)
+ SV * h
+ SV * err
+ SV * errstr
+ SV * state
+ SV * method
+ SV * result
+ PPCODE:
+ {
+ D_imp_xxh(h);
+ SV **sem_svp;
+ (void)cv;
+
+ if (DBIc_has(imp_xxh, DBIcf_HandleSetErr) && SvREADONLY(method))
+ method = sv_mortalcopy(method); /* HandleSetErr may want to change it */
+
+ if (!set_err_sv(h, imp_xxh, err, errstr, state, method)) {
+ /* set_err was canceled by HandleSetErr, */
+ /* don't set "dbi_set_err_method", return an empty list */
+ }
+ else {
+ /* store provided method name so handler code can find it */
+ sem_svp = hv_fetch((HV*)SvRV(h), "dbi_set_err_method", 18, 1);
+ if (SvOK(method)) {
+ sv_setpv(*sem_svp, SvPV_nolen(method));
+ }
+ else
+ (void)SvOK_off(*sem_svp);
+ EXTEND(SP, 1);
+ PUSHs( result ? result : &PL_sv_undef );
+ }
+ /* We don't check RaiseError and call die here because that must be */
+ /* done by returning through dispatch and letting the DBI handle it */
+ }
+
+
+int
+trace(h, level=&PL_sv_undef, file=Nullsv)
+ SV *h
+ SV *level
+ SV *file
+ ALIAS:
+ debug = 1
+ CODE:
+ RETVAL = set_trace(h, level, file);
+ (void)cv; /* Unused variables */
+ (void)ix;
+ OUTPUT:
+ RETVAL
+
+
+void
+trace_msg(sv, msg, this_trace=1)
+ SV *sv
+ const char *msg
+ int this_trace
+ PREINIT:
+ int current_trace;
+ PerlIO *pio;
+ CODE:
+ {
+ dMY_CXT;
+ (void)cv;
+ if (SvROK(sv)) {
+ D_imp_xxh(sv);
+ current_trace = DBIc_TRACE_LEVEL(imp_xxh);
+ pio = DBIc_LOGPIO(imp_xxh);
+ }
+ else { /* called as a static method */
+ current_trace = DBIS_TRACE_FLAGS;
+ pio = DBILOGFP;
+ }
+ if (DBIc_TRACE_MATCHES(this_trace, current_trace)) {
+ PerlIO_puts(pio, msg);
+ ST(0) = &PL_sv_yes;
+ }
+ else {
+ ST(0) = &PL_sv_no;
+ }
+ }
+
+
+void
+rows(h)
+ SV * h
+ CODE:
+ /* fallback esp for $DBI::rows after $drh was last used */
+ ST(0) = sv_2mortal(newSViv(-1));
+ (void)h;
+ (void)cv;
+
+
+void
+swap_inner_handle(rh1, rh2, allow_reparent=0)
+ SV * rh1
+ SV * rh2
+ IV allow_reparent
+ CODE:
+ {
+ D_impdata(imp_xxh1, imp_xxh_t, rh1);
+ D_impdata(imp_xxh2, imp_xxh_t, rh2);
+ SV *h1i = dbih_inner(aTHX_ rh1, "swap_inner_handle");
+ SV *h2i = dbih_inner(aTHX_ rh2, "swap_inner_handle");
+ SV *h1 = (rh1 == h1i) ? (SV*)DBIc_MY_H(imp_xxh1) : SvRV(rh1);
+ SV *h2 = (rh2 == h2i) ? (SV*)DBIc_MY_H(imp_xxh2) : SvRV(rh2);
+ (void)cv;
+
+ if (DBIc_TYPE(imp_xxh1) != DBIc_TYPE(imp_xxh2)) {
+ char buf[99];
+ sprintf(buf, "Can't swap_inner_handle between %sh and %sh",
+ dbih_htype_name(DBIc_TYPE(imp_xxh1)), dbih_htype_name(DBIc_TYPE(imp_xxh2)));
+ DBIh_SET_ERR_CHAR(rh1, imp_xxh1, "1", 1, buf, Nullch, Nullch);
+ XSRETURN_NO;
+ }
+ if (!allow_reparent && DBIc_PARENT_COM(imp_xxh1) != DBIc_PARENT_COM(imp_xxh2)) {
+ DBIh_SET_ERR_CHAR(rh1, imp_xxh1, "1", 1,
+ "Can't swap_inner_handle with handle from different parent",
+ Nullch, Nullch);
+ XSRETURN_NO;
+ }
+
+ SvREFCNT_inc(h1i);
+ SvREFCNT_inc(h2i);
+
+ sv_unmagic(h1, 'P'); /* untie(%$h1) */
+ sv_unmagic(h2, 'P'); /* untie(%$h2) */
+
+ sv_magic(h1, h2i, 'P', Nullch, 0); /* tie %$h1, $h2i */
+ DBIc_MY_H(imp_xxh2) = (HV*)h1;
+
+ sv_magic(h2, h1i, 'P', Nullch, 0); /* tie %$h2, $h1i */
+ DBIc_MY_H(imp_xxh1) = (HV*)h2;
+
+ SvREFCNT_dec(h1i);
+ SvREFCNT_dec(h2i);
+
+ ST(0) = &PL_sv_yes;
+ }
+
+
+MODULE = DBI PACKAGE = DBD::_mem::common
+
+void
+DESTROY(imp_xxh_rv)
+ SV * imp_xxh_rv
+ CODE:
+ /* ignore 'cast increases required alignment' warning */
+ imp_xxh_t *imp_xxh = (imp_xxh_t*)SvPVX(SvRV(imp_xxh_rv));
+ DBIc_DBISTATE(imp_xxh)->clearcom(imp_xxh);
+ (void)cv;
+
+# end
diff --git a/DBIXS.h b/DBIXS.h
new file mode 100644
index 0000000..f1a3963
--- /dev/null
+++ b/DBIXS.h
@@ -0,0 +1,573 @@
+/* vim: ts=8:sw=4:expandtab
+ *
+ * $Id: DBIXS.h 15268 2012-04-18 11:34:59Z timbo $
+ *
+ * Copyright (c) 1994-2010 Tim Bunce Ireland
+ *
+ * See COPYRIGHT section in DBI.pm for usage and distribution rights.
+ */
+
+/* DBI Interface Definitions for DBD Modules */
+
+#ifndef DBIXS_VERSION /* prevent multiple inclusion */
+
+#ifndef DBIS
+#define DBIS dbis /* default name for dbistate_t variable */
+#endif
+
+/* Here for backwards compat. PERL_POLLUTE was removed in perl 5.13.3 */
+#define PERL_POLLUTE
+
+/* first pull in the standard Perl header files for extensions */
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+
+#ifdef debug /* causes problems with DBIS->debug */
+#undef debug
+#endif
+
+#ifdef std /* causes problems with STLport <tscheresky@micron.com> */
+#undef std
+#endif
+
+/* define DBIXS_REVISION */
+#include "dbixs_rev.h"
+
+/* Perl backwards compatibility definitions */
+#include "dbipport.h"
+
+/* DBI SQL_* type definitions */
+#include "dbi_sql.h"
+
+
+#define DBIXS_VERSION 93 /* superceeded by DBIXS_REVISION */
+
+#ifdef NEED_DBIXS_VERSION
+#if NEED_DBIXS_VERSION > DBIXS_VERSION
+error You_need_to_upgrade_your_DBI_module_before_building_this_driver
+#endif
+#else
+#define NEED_DBIXS_VERSION DBIXS_VERSION
+#endif
+
+
+#define DBI_LOCK
+#define DBI_UNLOCK
+
+#ifndef DBI_NO_THREADS
+#ifdef USE_ITHREADS
+#define DBI_USE_THREADS
+#endif /* USE_ITHREADS */
+#endif /* DBI_NO_THREADS */
+
+
+/* forward struct declarations */
+
+typedef struct dbistate_st dbistate_t;
+/* implementor needs to define actual struct { dbih_??c_t com; ... }*/
+typedef struct imp_drh_st imp_drh_t; /* driver */
+typedef struct imp_dbh_st imp_dbh_t; /* database */
+typedef struct imp_sth_st imp_sth_t; /* statement */
+typedef struct imp_fdh_st imp_fdh_t; /* field descriptor */
+typedef struct imp_xxh_st imp_xxh_t; /* any (defined below) */
+#define DBI_imp_data_ imp_xxh_t /* friendly for take_imp_data */
+
+
+
+/* --- DBI Handle Common Data Structure (all handles have one) --- */
+
+/* Handle types. Code currently assumes child = parent + 1. */
+#define DBIt_DR 1
+#define DBIt_DB 2
+#define DBIt_ST 3
+#define DBIt_FD 4
+
+/* component structures */
+
+typedef struct dbih_com_std_st {
+ U32 flags;
+ int call_depth; /* used by DBI to track nested calls (int) */
+ U16 type; /* DBIt_DR, DBIt_DB, DBIt_ST */
+ HV *my_h; /* copy of outer handle HV (not refcounted) */
+ SV *parent_h; /* parent inner handle (ref to hv) (r.c.inc) */
+ imp_xxh_t *parent_com; /* parent com struct shortcut */
+ PerlInterpreter * thr_user; /* thread that owns the handle */
+
+ HV *imp_stash; /* who is the implementor for this handle */
+ SV *imp_data; /* optional implementors data (for perl imp's) */
+
+ I32 kids; /* count of db's for dr's, st's for db's etc */
+ I32 active_kids; /* kids which are currently DBIc_ACTIVE */
+ U32 pid; /* pid of process that created handle */
+ dbistate_t *dbistate;
+} dbih_com_std_t;
+
+typedef struct dbih_com_attr_st {
+ /* These are copies of the Hash values (ref.cnt.inc'd) */
+ /* Many of the hash values are themselves references */
+ SV *TraceLevel;
+ SV *State; /* Standard SQLSTATE, 5 char string */
+ SV *Err; /* Native engine error code */
+ SV *Errstr; /* Native engine error message */
+ UV ErrCount;
+ U32 LongReadLen; /* auto read length for long/blob types */
+ SV *FetchHashKeyName; /* for fetchrow_hashref */
+ /* (NEW FIELDS?... DON'T FORGET TO UPDATE dbih_clearcom()!) */
+} dbih_com_attr_t;
+
+
+struct dbih_com_st { /* complete core structure (typedef'd above) */
+ dbih_com_std_t std;
+ dbih_com_attr_t attr;
+};
+
+/* This 'implementors' type the DBI defines by default as a way to */
+/* refer to the imp_??h data of a handle without considering its type. */
+struct imp_xxh_st { struct dbih_com_st com; };
+
+/* Define handle-type specific structures for implementors to include */
+/* at the start of their private structures. */
+
+typedef struct { /* -- DRIVER -- */
+ dbih_com_std_t std;
+ dbih_com_attr_t attr;
+ HV *_old_cached_kids; /* not used, here for binary compat */
+} dbih_drc_t;
+
+typedef struct { /* -- DATABASE -- */
+ dbih_com_std_t std; /* \__ standard structure */
+ dbih_com_attr_t attr; /* / plus... (nothing else right now) */
+ HV *_old_cached_kids; /* not used, here for binary compat */
+} dbih_dbc_t;
+
+typedef struct { /* -- STATEMENT -- */
+ dbih_com_std_t std; /* \__ standard structure */
+ dbih_com_attr_t attr; /* / plus ... */
+
+ int num_params; /* number of placeholders */
+ int num_fields; /* NUM_OF_FIELDS, must be set */
+ AV *fields_svav; /* special row buffer (inc bind_cols) */
+ IV row_count; /* incremented by get_fbav() */
+
+ AV *fields_fdav; /* not used yet, may change */
+
+ I32 spare1;
+ void *spare2;
+} dbih_stc_t;
+
+
+/* XXX THIS STRUCTURE SHOULD NOT BE USED */
+typedef struct { /* -- FIELD DESCRIPTOR -- */
+ dbih_com_std_t std; /* standard structure (not fully setup) */
+
+ /* core attributes (from DescribeCol in ODBC) */
+ char *col_name; /* see dbih_make_fdsv */
+ I16 col_name_len;
+ I16 col_sql_type;
+ I16 col_precision;
+ I16 col_scale;
+ I16 col_nullable;
+
+ /* additional attributes (from ColAttributes in ODBC) */
+ I32 col_length;
+ I32 col_disp_size;
+
+ I32 spare1;
+ void *spare2;
+} dbih_fdc_t;
+
+
+#define _imp2com(p,f) ((p)->com.f) /* private */
+
+#define DBIc_FLAGS(imp) _imp2com(imp, std.flags)
+#define DBIc_TYPE(imp) _imp2com(imp, std.type)
+#define DBIc_CALL_DEPTH(imp) _imp2com(imp, std.call_depth)
+#define DBIc_MY_H(imp) _imp2com(imp, std.my_h)
+#define DBIc_PARENT_H(imp) _imp2com(imp, std.parent_h)
+#define DBIc_PARENT_COM(imp) _imp2com(imp, std.parent_com)
+#define DBIc_THR_COND(imp) _imp2com(imp, std.thr_cond)
+#define DBIc_THR_USER(imp) _imp2com(imp, std.thr_user)
+#define DBIc_THR_USER_NONE (0xFFFF)
+#define DBIc_IMP_STASH(imp) _imp2com(imp, std.imp_stash)
+#define DBIc_IMP_DATA(imp) _imp2com(imp, std.imp_data)
+#define DBIc_DBISTATE(imp) _imp2com(imp, std.dbistate)
+#define DBIc_LOGPIO(imp) DBIc_DBISTATE(imp)->logfp
+#define DBIc_KIDS(imp) _imp2com(imp, std.kids)
+#define DBIc_ACTIVE_KIDS(imp) _imp2com(imp, std.active_kids)
+#define DBIc_LAST_METHOD(imp) _imp2com(imp, std.last_method)
+
+/* d = DBD flags, l = DBD level (needs to be shifted down)
+ * D - DBI flags, r = reserved, L = DBI trace level
+ * Trace level bit allocation: 0xddlDDDrL */
+#define DBIc_TRACE_LEVEL_MASK 0x0000000F
+#define DBIc_TRACE_FLAGS_MASK 0xFF0FFF00 /* includes DBD flag bits for DBIc_TRACE */
+#define DBIc_TRACE_SETTINGS(imp) (DBIc_DBISTATE(imp)->debug)
+#define DBIc_TRACE_LEVEL(imp) (DBIc_TRACE_SETTINGS(imp) & DBIc_TRACE_LEVEL_MASK)
+#define DBIc_TRACE_FLAGS(imp) (DBIc_TRACE_SETTINGS(imp) & DBIc_TRACE_FLAGS_MASK)
+/* DBI defined trace flags */
+#define DBIf_TRACE_SQL 0x00000100
+#define DBIf_TRACE_CON 0x00000200
+#define DBIf_TRACE_ENC 0x00000400
+#define DBIf_TRACE_DBD 0x00000800
+#define DBIf_TRACE_TXN 0x00001000
+
+#define DBDc_TRACE_LEVEL_MASK 0x00F00000
+#define DBDc_TRACE_LEVEL_SHIFT 20
+#define DBDc_TRACE_LEVEL(imp) ( (DBIc_TRACE_SETTINGS(imp) & DBDc_TRACE_LEVEL_MASK) >> DBDc_TRACE_LEVEL_SHIFT )
+#define DBDc_TRACE_LEVEL_set(imp, l) ( DBIc_TRACE_SETTINGS(imp) |= (((l) << DBDc_TRACE_LEVEL_SHIFT) & DBDc_TRACE_LEVEL_MASK ))
+
+/* DBIc_TRACE_MATCHES(this, crnt): true if this 'matches' (is within) crnt
+ DBIc_TRACE_MATCHES(foo, DBIc_TRACE_SETTINGS(imp))
+*/
+#define DBIc_TRACE_MATCHES(this, crnt) \
+ ( ((crnt & DBIc_TRACE_LEVEL_MASK) >= (this & DBIc_TRACE_LEVEL_MASK)) \
+ || ((crnt & DBIc_TRACE_FLAGS_MASK) & (this & DBIc_TRACE_FLAGS_MASK)) )
+
+/* DBIc_TRACE(imp, flags, flag_level, fallback_level)
+ True if flags match the handle trace flags & handle trace level >= flag_level,
+ OR if handle trace_level > fallback_level (typically > flag_level).
+ This is the main trace testing macro to be used by drivers.
+ (Drivers should define their own DBDf_TRACE_* macros for the top 8 bits: 0xFF000000)
+ DBIc_TRACE(imp, 0, 0, 4) = if trace level >= 4
+ DBIc_TRACE(imp, DBDf_TRACE_FOO, 2, 4) = if tracing DBDf_FOO & level>=2 or level>=4
+ DBIc_TRACE(imp, DBDf_TRACE_FOO, 2, 0) = as above but never trace just due to level
+ e.g.
+ if (DBIc_TRACE(imp_xxh, DBIf_TRACE_SQL|DBIf_TRACE_xxx, 2, 0)) {
+ PerlIO_printf(DBIc_LOGPIO(imp_sth), "\tThe %s wibbled the %s\n", ...);
+ }
+*/
+#define DBIc_TRACE(imp, flags, flaglevel, level) \
+ ( (flags && (DBIc_TRACE_FLAGS(imp) & flags) && (DBIc_TRACE_LEVEL(imp) >= flaglevel)) \
+ || (level && DBIc_TRACE_LEVEL(imp) >= level) )
+
+#define DBIc_DEBUG(imp) (_imp2com(imp, attr.TraceLevel)) /* deprecated */
+#define DBIc_DEBUGIV(imp) SvIV(DBIc_DEBUG(imp)) /* deprecated */
+#define DBIc_STATE(imp) SvRV(_imp2com(imp, attr.State))
+#define DBIc_ERR(imp) SvRV(_imp2com(imp, attr.Err))
+#define DBIc_ERRSTR(imp) SvRV(_imp2com(imp, attr.Errstr))
+#define DBIc_ErrCount(imp) _imp2com(imp, attr.ErrCount)
+#define DBIc_LongReadLen(imp) _imp2com(imp, attr.LongReadLen)
+#define DBIc_LongReadLen_init 80 /* may change */
+#define DBIc_FetchHashKeyName(imp) (_imp2com(imp, attr.FetchHashKeyName))
+
+/* handle sub-type specific fields */
+/* dbh & drh */
+#define DBIc_CACHED_KIDS(imp) Nullhv /* no longer used, here for src compat */
+/* sth */
+#define DBIc_NUM_FIELDS(imp) _imp2com(imp, num_fields)
+#define DBIc_NUM_PARAMS(imp) _imp2com(imp, num_params)
+#define DBIc_NUM_PARAMS_AT_EXECUTE -9 /* see Driver.xst */
+#define DBIc_ROW_COUNT(imp) _imp2com(imp, row_count)
+#define DBIc_FIELDS_AV(imp) _imp2com(imp, fields_svav)
+#define DBIc_FDESC_AV(imp) _imp2com(imp, fields_fdav)
+#define DBIc_FDESC(imp, i) ((imp_fdh_t*)(void*)SvPVX(AvARRAY(DBIc_FDESC_AV(imp))[i]))
+
+/* XXX --- DO NOT CHANGE THESE VALUES AS THEY ARE COMPILED INTO DRIVERS --- XXX */
+#define DBIcf_COMSET 0x000001 /* needs to be clear'd before free'd */
+#define DBIcf_IMPSET 0x000002 /* has implementor data to be clear'd */
+#define DBIcf_ACTIVE 0x000004 /* needs finish/disconnect before clear */
+#define DBIcf_IADESTROY 0x000008 /* do DBIc_ACTIVE_off before DESTROY */
+#define DBIcf_WARN 0x000010 /* warn about poor practice etc */
+#define DBIcf_COMPAT 0x000020 /* compat/emulation mode (eg oraperl) */
+#define DBIcf_ChopBlanks 0x000040 /* rtrim spaces from fetch char columns */
+#define DBIcf_RaiseError 0x000080 /* throw exception (croak) on error */
+#define DBIcf_PrintError 0x000100 /* warn() on error */
+#define DBIcf_AutoCommit 0x000200 /* dbh only. used by drivers */
+#define DBIcf_LongTruncOk 0x000400 /* truncation to LongReadLen is okay */
+#define DBIcf_MultiThread 0x000800 /* allow multiple threads to enter */
+#define DBIcf_HandleSetErr 0x001000 /* has coderef HandleSetErr attribute */
+#define DBIcf_ShowErrorStatement 0x002000 /* include Statement in error */
+#define DBIcf_BegunWork 0x004000 /* between begin_work & commit/rollback */
+#define DBIcf_HandleError 0x008000 /* has coderef in HandleError attribute */
+#define DBIcf_Profile 0x010000 /* profile activity on this handle */
+#define DBIcf_TaintIn 0x020000 /* check inputs for taintedness */
+#define DBIcf_TaintOut 0x040000 /* taint outgoing data */
+#define DBIcf_Executed 0x080000 /* do/execute called since commit/rollb */
+#define DBIcf_PrintWarn 0x100000 /* warn() on warning (err="0") */
+#define DBIcf_Callbacks 0x200000 /* has Callbacks attribute hash */
+#define DBIcf_AIADESTROY 0x400000 /* auto DBIcf_IADESTROY if pid changes */
+/* NOTE: new flags may require clone() to be updated */
+
+#define DBIcf_INHERITMASK /* what NOT to pass on to children */ \
+ (U32)( DBIcf_COMSET | DBIcf_IMPSET | DBIcf_ACTIVE | DBIcf_IADESTROY \
+ | DBIcf_AutoCommit | DBIcf_BegunWork | DBIcf_Executed | DBIcf_Callbacks )
+
+/* general purpose bit setting and testing macros */
+#define DBIbf_is( bitset,flag) ((bitset) & (flag))
+#define DBIbf_has(bitset,flag) DBIbf_is(bitset, flag) /* alias for _is */
+#define DBIbf_on( bitset,flag) ((bitset) |= (flag))
+#define DBIbf_off(bitset,flag) ((bitset) &= ~(flag))
+#define DBIbf_set(bitset,flag,on) ((on) ? DBIbf_on(bitset, flag) : DBIbf_off(bitset,flag))
+
+/* as above, but specifically for DBIc_FLAGS imp flags (except ACTIVE) */
+#define DBIc_is(imp, flag) DBIbf_is( DBIc_FLAGS(imp), flag)
+#define DBIc_has(imp,flag) DBIc_is(imp, flag) /* alias for DBIc_is */
+#define DBIc_on(imp, flag) DBIbf_on( DBIc_FLAGS(imp), flag)
+#define DBIc_off(imp,flag) DBIbf_off(DBIc_FLAGS(imp), flag)
+#define DBIc_set(imp,flag,on) DBIbf_set(DBIc_FLAGS(imp), flag, on)
+
+#define DBIc_COMSET(imp) DBIc_is(imp, DBIcf_COMSET)
+#define DBIc_COMSET_on(imp) DBIc_on(imp, DBIcf_COMSET)
+#define DBIc_COMSET_off(imp) DBIc_off(imp,DBIcf_COMSET)
+
+#define DBIc_IMPSET(imp) DBIc_is(imp, DBIcf_IMPSET)
+#define DBIc_IMPSET_on(imp) DBIc_on(imp, DBIcf_IMPSET)
+#define DBIc_IMPSET_off(imp) DBIc_off(imp,DBIcf_IMPSET)
+
+#define DBIc_ACTIVE(imp) (DBIc_FLAGS(imp) & DBIcf_ACTIVE)
+#define DBIc_ACTIVE_on(imp) /* adjust parent's active kid count */ \
+ do { \
+ imp_xxh_t *ph_com = DBIc_PARENT_COM(imp); \
+ if (!DBIc_ACTIVE(imp) && ph_com && !PL_dirty \
+ && ++DBIc_ACTIVE_KIDS(ph_com) > DBIc_KIDS(ph_com)) \
+ croak("panic: DBI active kids (%ld) > kids (%ld)", \
+ (long)DBIc_ACTIVE_KIDS(ph_com), \
+ (long)DBIc_KIDS(ph_com)); \
+ DBIc_FLAGS(imp) |= DBIcf_ACTIVE; \
+ } while(0)
+#define DBIc_ACTIVE_off(imp) /* adjust parent's active kid count */ \
+ do { \
+ imp_xxh_t *ph_com = DBIc_PARENT_COM(imp); \
+ if (DBIc_ACTIVE(imp) && ph_com && !PL_dirty \
+ && (--DBIc_ACTIVE_KIDS(ph_com) > DBIc_KIDS(ph_com) \
+ || DBIc_ACTIVE_KIDS(ph_com) < 0) ) \
+ croak("panic: DBI active kids (%ld) < 0 or > kids (%ld)", \
+ (long)DBIc_ACTIVE_KIDS(ph_com), \
+ (long)DBIc_KIDS(ph_com)); \
+ DBIc_FLAGS(imp) &= ~DBIcf_ACTIVE; \
+ } while(0)
+
+#define DBIc_IADESTROY(imp) (DBIc_FLAGS(imp) & DBIcf_IADESTROY)
+#define DBIc_IADESTROY_on(imp) (DBIc_FLAGS(imp) |= DBIcf_IADESTROY)
+#define DBIc_IADESTROY_off(imp) (DBIc_FLAGS(imp) &= ~DBIcf_IADESTROY)
+
+#define DBIc_AIADESTROY(imp) (DBIc_FLAGS(imp) & DBIcf_AIADESTROY)
+#define DBIc_AIADESTROY_on(imp) (DBIc_FLAGS(imp) |= DBIcf_AIADESTROY)
+#define DBIc_AIADESTROY_off(imp) (DBIc_FLAGS(imp) &= ~DBIcf_AIADESTROY)
+
+#define DBIc_WARN(imp) (DBIc_FLAGS(imp) & DBIcf_WARN)
+#define DBIc_WARN_on(imp) (DBIc_FLAGS(imp) |= DBIcf_WARN)
+#define DBIc_WARN_off(imp) (DBIc_FLAGS(imp) &= ~DBIcf_WARN)
+
+#define DBIc_COMPAT(imp) (DBIc_FLAGS(imp) & DBIcf_COMPAT)
+#define DBIc_COMPAT_on(imp) (DBIc_FLAGS(imp) |= DBIcf_COMPAT)
+#define DBIc_COMPAT_off(imp) (DBIc_FLAGS(imp) &= ~DBIcf_COMPAT)
+
+
+#ifdef IN_DBI_XS /* get Handle Common Data Structure */
+#define DBIh_COM(h) (dbih_getcom2(aTHX_ h, 0))
+#else
+#define DBIh_COM(h) (DBIS->getcom(h))
+#define neatsvpv(sv,len) (DBIS->neat_svpv(sv,len))
+#endif
+
+/* --- For sql_type_cast_svpv() --- */
+
+#define DBIstcf_DISCARD_STRING 0x0001
+#define DBIstcf_STRICT 0x0002
+
+/* --- Implementors Private Data Support --- */
+
+#define D_impdata(name,type,h) type *name = (type*)(DBIh_COM(h))
+#define D_imp_drh(h) D_impdata(imp_drh, imp_drh_t, h)
+#define D_imp_dbh(h) D_impdata(imp_dbh, imp_dbh_t, h)
+#define D_imp_sth(h) D_impdata(imp_sth, imp_sth_t, h)
+#define D_imp_xxh(h) D_impdata(imp_xxh, imp_xxh_t, h)
+
+#define D_imp_from_child(name,type,child) \
+ type *name = (type*)(DBIc_PARENT_COM(child))
+#define D_imp_drh_from_dbh D_imp_from_child(imp_drh, imp_drh_t, imp_dbh)
+#define D_imp_dbh_from_sth D_imp_from_child(imp_dbh, imp_dbh_t, imp_sth)
+
+#define DBI_IMP_SIZE(n,s) sv_setiv(get_sv((n), GV_ADDMULTI), (s)) /* XXX */
+
+
+
+/* --- Event Support (VERY LIABLE TO CHANGE) --- */
+
+#define DBIh_EVENTx(h,t,a1,a2) /* deprecated XXX */ &PL_sv_no
+#define DBIh_EVENT0(h,t) DBIh_EVENTx((h), (t), &PL_sv_undef, &PL_sv_undef)
+#define DBIh_EVENT1(h,t, a1) DBIh_EVENTx((h), (t), (a1), &PL_sv_undef)
+#define DBIh_EVENT2(h,t, a1,a2) DBIh_EVENTx((h), (t), (a1), (a2))
+
+#define ERROR_event "ERROR"
+#define WARN_event "WARN"
+#define MSG_event "MESSAGE"
+#define DBEVENT_event "DBEVENT"
+#define UNKNOWN_event "UNKNOWN"
+
+#define DBIh_SET_ERR_SV(h,i, err, errstr, state, method) \
+ (DBIc_DBISTATE(i)->set_err_sv(h,i, err, errstr, state, method))
+#define DBIh_SET_ERR_CHAR(h,i, err_c, err_i, errstr, state, method) \
+ (DBIc_DBISTATE(i)->set_err_char(h,i, err_c, err_i, errstr, state, method))
+
+
+/* --- Handy Macros --- */
+
+#define DBIh_CLEAR_ERROR(imp_xxh) (void)( \
+ (void)SvOK_off(DBIc_ERR(imp_xxh)), \
+ (void)SvOK_off(DBIc_ERRSTR(imp_xxh)), \
+ (void)SvOK_off(DBIc_STATE(imp_xxh)) \
+ )
+
+
+/* --- DBI State Structure --- */
+
+struct dbistate_st {
+
+/* DBISTATE_VERSION is checked at runtime via DBISTATE_INIT and check_version.
+ * It should be incremented on incompatible changes to dbistate_t structure.
+ * Additional function pointers being assigned from spare padding, where the
+ * size of the structure doesn't change, doesn't require an increment.
+ * Incrementing forces all XS drivers to need to be recompiled.
+ * (See also DBIXS_REVISION as a driver source compatibility tool.)
+ */
+#define DBISTATE_VERSION 94 /* ++ on incompatible dbistate_t changes */
+
+ /* this must be the first member in structure */
+ void (*check_version) _((const char *name,
+ int dbis_cv, int dbis_cs, int need_dbixs_cv,
+ int drc_s, int dbc_s, int stc_s, int fdc_s));
+
+ /* version and size are used to check for DBI/DBD version mis-match */
+ U16 version; /* version of this structure */
+ U16 size;
+ U16 xs_version; /* version of the overall DBIXS / DBD interface */
+ U16 spare_pad;
+
+ I32 debug;
+ PerlIO *logfp;
+
+ /* pointers to DBI functions which the DBD's will want to use */
+ char * (*neat_svpv) _((SV *sv, STRLEN maxlen));
+ imp_xxh_t * (*getcom) _((SV *h)); /* see DBIh_COM macro */
+ void (*clearcom) _((imp_xxh_t *imp_xxh));
+ SV * (*event) _((SV *h, const char *name, SV*, SV*));
+ int (*set_attr_k) _((SV *h, SV *keysv, int dbikey, SV *valuesv));
+ SV * (*get_attr_k) _((SV *h, SV *keysv, int dbikey));
+ AV * (*get_fbav) _((imp_sth_t *imp_sth));
+ SV * (*make_fdsv) _((SV *sth, const char *imp_class, STRLEN imp_size, const char *col_name));
+ int (*bind_as_num) _((int sql_type, int p, int s, int *t, void *v)); /* XXX deprecated */
+ I32 (*hash) _((const char *string, long i));
+ SV * (*preparse) _((SV *sth, char *statement, IV ps_return, IV ps_accept, void *foo));
+
+ SV *neatsvpvlen; /* only show dbgpvlen chars when debugging pv's */
+
+ PerlInterpreter * thr_owner; /* thread that owns this dbistate */
+
+ int (*logmsg) _((imp_xxh_t *imp_xxh, const char *fmt, ...));
+ int (*set_err_sv) _((SV *h, imp_xxh_t *imp_xxh, SV *err, SV *errstr, SV *state, SV *method));
+ int (*set_err_char) _((SV *h, imp_xxh_t *imp_xxh, const char *err, IV err_i, const char *errstr, const char *state, const char *method));
+ int (*bind_col) _((SV *sth, SV *col, SV *ref, SV *attribs));
+
+ IO *logfp_ref; /* keep ptr to filehandle for refcounting */
+
+ int (*sql_type_cast_svpv) _((pTHX_ SV *sv, int sql_type, U32 flags, void *v));
+
+ /* WARNING: Only add new structure members here, and reduce pad2 to keep */
+ /* the memory footprint exactly the same */
+ void *pad2[3];
+};
+
+/* macros for backwards compatibility */
+#define set_attr(h, k, v) set_attr_k(h, k, 0, v)
+#define get_attr(h, k) get_attr_k(h, k, 0)
+
+#define DBILOGFP (DBIS->logfp)
+#ifdef IN_DBI_XS
+#define DBILOGMSG (dbih_logmsg)
+#else
+#define DBILOGMSG (DBIS->logmsg)
+#endif
+
+/* --- perl object (ActiveState) / multiplicity hooks and hoops --- */
+/* note that USE_ITHREADS implies MULTIPLICITY */
+
+typedef dbistate_t** (*_dbi_state_lval_t)(pTHX);
+
+# define _DBISTATE_DECLARE_COMMON \
+ static _dbi_state_lval_t dbi_state_lval_p = 0; \
+ static dbistate_t** dbi_get_state(pTHX) { \
+ if (!dbi_state_lval_p) { \
+ CV *cv = get_cv("DBI::_dbi_state_lval", 0); \
+ if (!cv) \
+ croak("Unable to get DBI state function. DBI not loaded."); \
+ dbi_state_lval_p = (_dbi_state_lval_t)CvXSUB(cv); \
+ } \
+ return dbi_state_lval_p(aTHX); \
+ } \
+ typedef int dummy_dbistate /* keep semicolon from feeling lonely */
+
+#if defined(MULTIPLICITY) || defined(PERL_OBJECT) || defined(PERL_CAPI)
+
+# define DBISTATE_DECLARE _DBISTATE_DECLARE_COMMON
+# define _DBISTATE_INIT_DBIS
+# undef DBIS
+# define DBIS (*dbi_get_state(aTHX))
+# define dbis DBIS /* temp for old drivers using 'dbis' instead of 'DBIS' */
+
+#else /* plain and simple non perl object / multiplicity case */
+
+# define DBISTATE_DECLARE \
+ static dbistate_t *DBIS; \
+ _DBISTATE_DECLARE_COMMON
+
+# define _DBISTATE_INIT_DBIS DBIS = *dbi_get_state(aTHX);
+#endif
+
+# define DBISTATE_INIT { /* typically use in BOOT: of XS file */ \
+ _DBISTATE_INIT_DBIS \
+ if (DBIS == NULL) \
+ croak("Unable to get DBI state. DBI not loaded."); \
+ DBIS->check_version(__FILE__, DBISTATE_VERSION, sizeof(*DBIS), NEED_DBIXS_VERSION, \
+ sizeof(dbih_drc_t), sizeof(dbih_dbc_t), sizeof(dbih_stc_t), sizeof(dbih_fdc_t) \
+ ); \
+}
+
+
+/* --- Assorted Utility Macros --- */
+
+#define DBD_ATTRIB_OK(attribs) /* is this a usable attrib value */ \
+ (attribs && SvROK(attribs) && SvTYPE(SvRV(attribs))==SVt_PVHV)
+
+/* If attribs value supplied then croak if it's not a hash ref. */
+/* Also map undef to Null. Should always be called to pre-process the */
+/* attribs value. One day we may add some extra magic in here. */
+#define DBD_ATTRIBS_CHECK(func, h, attribs) \
+ if ((attribs) && SvOK(attribs)) { \
+ if (!SvROK(attribs) || SvTYPE(SvRV(attribs))!=SVt_PVHV) \
+ croak("%s->%s(...): attribute parameter '%s' is not a hash ref", \
+ SvPV_nolen(h), func, SvPV_nolen(attribs)); \
+ } else (attribs) = Nullsv
+
+#define DBD_ATTRIB_GET_SVP(attribs, key,klen) \
+ (DBD_ATTRIB_OK(attribs) \
+ ? hv_fetch((HV*)SvRV(attribs), key,klen, 0) \
+ : (SV **)Nullsv)
+
+#define DBD_ATTRIB_GET_IV(attribs, key,klen, svp, var) \
+ if ((svp=DBD_ATTRIB_GET_SVP(attribs, key,klen)) != NULL) \
+ var = SvIV(*svp)
+
+#define DBD_ATTRIB_GET_UV(attribs, key,klen, svp, var) \
+ if ((svp=DBD_ATTRIB_GET_SVP(attribs, key,klen)) != NULL) \
+ var = SvUV(*svp)
+
+#define DBD_ATTRIB_GET_BOOL(attribs, key,klen, svp, var) \
+ if ((svp=DBD_ATTRIB_GET_SVP(attribs, key,klen)) != NULL) \
+ var = SvTRUE(*svp)
+
+#define DBD_ATTRIB_TRUE(attribs, key,klen, svp) \
+ ( ((svp=DBD_ATTRIB_GET_SVP(attribs, key,klen)) != NULL) \
+ ? SvTRUE(*svp) : 0 )
+
+#define DBD_ATTRIB_GET_PV(attribs, key,klen, svp, dflt) \
+ (((svp=DBD_ATTRIB_GET_SVP(attribs, key,klen)) != NULL) \
+ ? SvPV_nolen(*svp) : (dflt))
+
+#define DBD_ATTRIB_DELETE(attribs, key, klen) \
+ hv_delete((HV*)SvRV(attribs), key, klen, G_DISCARD)
+
+#endif /* DBIXS_VERSION */
+/* end of DBIXS.h */
diff --git a/Driver.xst b/Driver.xst
new file mode 100644
index 0000000..455549d
--- /dev/null
+++ b/Driver.xst
@@ -0,0 +1,778 @@
+# $Id: Driver.xst 14772 2011-03-25 21:45:26Z mjevans $
+# Copyright (c) 1997-2002 Tim Bunce Ireland
+# Copyright (c) 2002 Jonathan Leffler
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+
+#include "Driver_xst.h"
+
+
+MODULE = DBD::~DRIVER~ PACKAGE = DBD::~DRIVER~
+
+REQUIRE: 1.929
+PROTOTYPES: DISABLE
+
+BOOT:
+ items = 0; /* avoid 'unused variable' warning */
+ DBISTATE_INIT;
+ /* XXX this interface will change: */
+ DBI_IMP_SIZE("DBD::~DRIVER~::dr::imp_data_size", sizeof(imp_drh_t));
+ DBI_IMP_SIZE("DBD::~DRIVER~::db::imp_data_size", sizeof(imp_dbh_t));
+ DBI_IMP_SIZE("DBD::~DRIVER~::st::imp_data_size", sizeof(imp_sth_t));
+ dbd_init(DBIS);
+
+
+# ------------------------------------------------------------
+# driver level interface
+# ------------------------------------------------------------
+MODULE = DBD::~DRIVER~ PACKAGE = DBD::~DRIVER~::dr
+
+
+void
+dbixs_revision(...)
+ PPCODE:
+ ST(0) = sv_2mortal(newSViv(DBIXS_REVISION));
+
+
+#ifdef dbd_discon_all
+
+# disconnect_all renamed and ALIAS'd to avoid length clash on VMS :-(
+void
+discon_all_(drh)
+ SV * drh
+ ALIAS:
+ disconnect_all = 1
+ CODE:
+ D_imp_drh(drh);
+ if (0) ix = ix; /* avoid unused variable warning */
+ ST(0) = dbd_discon_all(drh, imp_drh) ? &PL_sv_yes : &PL_sv_no;
+
+#endif /* dbd_discon_all */
+
+
+#ifdef dbd_dr_data_sources
+
+void
+data_sources(drh, attr = Nullsv)
+ SV *drh
+ SV *attr
+ PPCODE:
+ {
+ D_imp_drh(drh);
+ AV *av = dbd_dr_data_sources(drh, imp_drh, attr);
+ if (av) {
+ int i;
+ int n = AvFILL(av)+1;
+ EXTEND(sp, n);
+ for (i = 0; i < n; ++i) {
+ PUSHs(AvARRAY(av)[i]);
+ }
+ }
+ }
+
+#endif
+
+
+# ------------------------------------------------------------
+# database level interface
+# ------------------------------------------------------------
+MODULE = DBD::~DRIVER~ PACKAGE = DBD::~DRIVER~::db
+
+
+void
+_login(dbh, dbname, username, password, attribs=Nullsv)
+ SV * dbh
+ SV * dbname
+ SV * username
+ SV * password
+ SV * attribs
+ CODE:
+ {
+ D_imp_dbh(dbh);
+#if !defined(dbd_db_login6_sv)
+ STRLEN lna;
+ char *u = (SvOK(username)) ? SvPV(username,lna) : "";
+ char *p = (SvOK(password)) ? SvPV(password,lna) : "";
+#endif
+#ifdef dbd_db_login6_sv
+ ST(0) = dbd_db_login6_sv(dbh, imp_dbh, dbname, username, password, attribs) ? &PL_sv_yes : &PL_sv_no;
+#elif defined(dbd_db_login6)
+ ST(0) = dbd_db_login6(dbh, imp_dbh, SvPV_nolen(dbname), u, p, attribs) ? &PL_sv_yes : &PL_sv_no;
+#else
+ ST(0) = dbd_db_login( dbh, imp_dbh, SvPV_nolen(dbname), u, p) ? &PL_sv_yes : &PL_sv_no;
+#endif
+ }
+
+
+void
+selectall_arrayref(...)
+ PREINIT:
+ SV *sth;
+ SV **maxrows_svp;
+ SV **tmp_svp;
+ SV *attr = &PL_sv_undef;
+ imp_sth_t *imp_sth;
+ CODE:
+ if (items > 2) {
+ attr = ST(2);
+ if (SvROK(attr) &&
+ (DBD_ATTRIB_TRUE(attr,"Slice",5,tmp_svp) || DBD_ATTRIB_TRUE(attr,"Columns",7,tmp_svp))
+ ) {
+ /* fallback to perl implementation */
+ SV *tmp =dbixst_bounce_method("DBD::~DRIVER~::db::SUPER::selectall_arrayref", items);
+ SPAGAIN;
+ ST(0) = tmp;
+ XSRETURN(1);
+ }
+ }
+ /* --- prepare --- */
+ if (SvROK(ST(1))) {
+ MAGIC *mg;
+ sth = ST(1);
+ /* switch to inner handle if not already */
+ if ( (mg = mg_find(SvRV(sth),'P')) )
+ sth = mg->mg_obj;
+ }
+ else {
+ sth = dbixst_bounce_method("prepare", 3);
+ SPAGAIN; SP -= items; /* because stack might have been realloc'd */
+ if (!SvROK(sth))
+ XSRETURN_UNDEF;
+ /* switch to inner handle */
+ sth = mg_find(SvRV(sth),'P')->mg_obj;
+ }
+ imp_sth = (imp_sth_t*)(DBIh_COM(sth));
+ /* --- bind_param --- */
+ if (items > 3) { /* need to bind params before execute */
+ if (!dbdxst_bind_params(sth, imp_sth, items-2, ax+2) ) {
+ XSRETURN_UNDEF;
+ }
+ }
+ /* --- execute --- */
+ DBIc_ROW_COUNT(imp_sth) = 0;
+ if ( dbd_st_execute(sth, imp_sth) <= -2 ) { /* -2 == error */
+ XSRETURN_UNDEF;
+ }
+ /* --- fetchall --- */
+ maxrows_svp = DBD_ATTRIB_GET_SVP(attr, "MaxRows", 7);
+ ST(0) = dbdxst_fetchall_arrayref(sth, &PL_sv_undef, (maxrows_svp) ? *maxrows_svp : &PL_sv_undef);
+
+
+void
+selectrow_arrayref(...)
+ ALIAS:
+ selectrow_array = 1
+ PREINIT:
+ int is_selectrow_array = (ix == 1);
+ imp_sth_t *imp_sth;
+ SV *sth;
+ AV *row_av;
+ PPCODE:
+ if (SvROK(ST(1))) {
+ MAGIC *mg;
+ sth = ST(1);
+ /* switch to inner handle if not already */
+ if ( (mg = mg_find(SvRV(sth),'P')) )
+ sth = mg->mg_obj;
+ }
+ else {
+ /* --- prepare --- */
+ sth = dbixst_bounce_method("prepare", 3);
+ SPAGAIN; SP -= items; /* because stack might have been realloc'd */
+ if (!SvROK(sth)) {
+ if (is_selectrow_array) { XSRETURN_EMPTY; } else { XSRETURN_UNDEF; }
+ }
+ /* switch to inner handle */
+ sth = mg_find(SvRV(sth),'P')->mg_obj;
+ }
+ imp_sth = (imp_sth_t*)(DBIh_COM(sth));
+ /* --- bind_param --- */
+ if (items > 3) { /* need to bind params before execute */
+ if (!dbdxst_bind_params(sth, imp_sth, items-2, ax+2) ) {
+ if (is_selectrow_array) { XSRETURN_EMPTY; } else { XSRETURN_UNDEF; }
+ }
+ }
+ /* --- execute --- */
+ DBIc_ROW_COUNT(imp_sth) = 0;
+ if ( dbd_st_execute(sth, imp_sth) <= -2 ) { /* -2 == error */
+ if (is_selectrow_array) { XSRETURN_EMPTY; } else { XSRETURN_UNDEF; }
+ }
+ /* --- fetchrow_arrayref --- */
+ row_av = dbd_st_fetch(sth, imp_sth);
+ if (!row_av) {
+ if (GIMME == G_SCALAR)
+ PUSHs(&PL_sv_undef);
+ }
+ else if (is_selectrow_array) {
+ int i;
+ int num_fields = AvFILL(row_av)+1;
+ if (GIMME == G_SCALAR)
+ num_fields = 1; /* return just first field */
+ EXTEND(sp, num_fields);
+ for(i=0; i < num_fields; ++i) {
+ PUSHs(AvARRAY(row_av)[i]);
+ }
+ }
+ else {
+ PUSHs( sv_2mortal(newRV((SV *)row_av)) );
+ }
+ /* --- finish --- */
+#ifdef dbd_st_finish3
+ dbd_st_finish3(sth, imp_sth, 0);
+#else
+ dbd_st_finish(sth, imp_sth);
+#endif
+
+
+#ifdef dbd_db_do4 /* deebeedee-deebee-doo, deebee-doobee-dah? */
+
+void
+do(dbh, statement, params = Nullsv)
+ SV * dbh
+ char * statement
+ SV * params
+ CODE:
+ {
+ D_imp_dbh(dbh);
+ IV retval;
+ retval = dbd_db_do4(dbh, imp_dbh, statement, params);
+ /* remember that dbd_db_do4 must return <= -2 for error */
+ if (retval == 0) /* ok with no rows affected */
+ XST_mPV(0, "0E0"); /* (true but zero) */
+ else if (retval < -1) /* -1 == unknown number of rows */
+ XST_mUNDEF(0); /* <= -2 means error */
+ else
+ XST_mIV(0, retval); /* typically 1, rowcount or -1 */
+ }
+
+#endif
+
+
+#ifdef dbd_db_last_insert_id
+
+void
+last_insert_id(dbh, catalog, schema, table, field, attr=Nullsv)
+ SV * dbh
+ SV * catalog
+ SV * schema
+ SV * table
+ SV * field
+ SV * attr
+ CODE:
+ {
+ D_imp_dbh(dbh);
+ ST(0) = dbd_db_last_insert_id(dbh, imp_dbh, catalog, schema, table, field, attr);
+ }
+
+#endif
+
+
+void
+commit(dbh)
+ SV * dbh
+ CODE:
+ D_imp_dbh(dbh);
+ if (DBIc_has(imp_dbh,DBIcf_AutoCommit) && DBIc_WARN(imp_dbh))
+ warn("commit ineffective with AutoCommit enabled");
+ ST(0) = dbd_db_commit(dbh, imp_dbh) ? &PL_sv_yes : &PL_sv_no;
+
+
+void
+rollback(dbh)
+ SV * dbh
+ CODE:
+ D_imp_dbh(dbh);
+ if (DBIc_has(imp_dbh,DBIcf_AutoCommit) && DBIc_WARN(imp_dbh))
+ warn("rollback ineffective with AutoCommit enabled");
+ ST(0) = dbd_db_rollback(dbh, imp_dbh) ? &PL_sv_yes : &PL_sv_no;
+
+
+void
+disconnect(dbh)
+ SV * dbh
+ CODE:
+ D_imp_dbh(dbh);
+ if ( !DBIc_ACTIVE(imp_dbh) ) {
+ XSRETURN_YES;
+ }
+ /* Check for disconnect() being called whilst refs to cursors */
+ /* still exists. This possibly needs some more thought. */
+ if (DBIc_ACTIVE_KIDS(imp_dbh) && DBIc_WARN(imp_dbh) && !PL_dirty) {
+ STRLEN lna;
+ char *plural = (DBIc_ACTIVE_KIDS(imp_dbh)==1) ? "" : "s";
+ warn("%s->disconnect invalidates %d active statement handle%s %s",
+ SvPV(dbh,lna), (int)DBIc_ACTIVE_KIDS(imp_dbh), plural,
+ "(either destroy statement handles or call finish on them before disconnecting)");
+ }
+ ST(0) = dbd_db_disconnect(dbh, imp_dbh) ? &PL_sv_yes : &PL_sv_no;
+ DBIc_ACTIVE_off(imp_dbh); /* ensure it's off, regardless */
+
+
+void
+STORE(dbh, keysv, valuesv)
+ SV * dbh
+ SV * keysv
+ SV * valuesv
+ CODE:
+ D_imp_dbh(dbh);
+ if (SvGMAGICAL(valuesv))
+ mg_get(valuesv);
+ ST(0) = &PL_sv_yes;
+ if (!dbd_db_STORE_attrib(dbh, imp_dbh, keysv, valuesv))
+ if (!DBIc_DBISTATE(imp_dbh)->set_attr(dbh, keysv, valuesv))
+ ST(0) = &PL_sv_no;
+
+
+void
+FETCH(dbh, keysv)
+ SV * dbh
+ SV * keysv
+ CODE:
+ D_imp_dbh(dbh);
+ SV *valuesv = dbd_db_FETCH_attrib(dbh, imp_dbh, keysv);
+ if (!valuesv)
+ valuesv = DBIc_DBISTATE(imp_dbh)->get_attr(dbh, keysv);
+ ST(0) = valuesv; /* dbd_db_FETCH_attrib did sv_2mortal */
+
+
+void
+DESTROY(dbh)
+ SV * dbh
+ PPCODE:
+ /* keep in sync with default DESTROY in DBI.xs */
+ D_imp_dbh(dbh);
+ ST(0) = &PL_sv_yes;
+ if (!DBIc_IMPSET(imp_dbh)) { /* was never fully set up */
+ STRLEN lna;
+ if (DBIc_WARN(imp_dbh) && !PL_dirty && DBIc_DBISTATE(imp_dbh)->debug >= 2)
+ PerlIO_printf(DBIc_LOGPIO(imp_dbh),
+ " DESTROY for %s ignored - handle not initialised\n",
+ SvPV(dbh,lna));
+ }
+ else {
+ if (DBIc_IADESTROY(imp_dbh)) { /* wants ineffective destroy */
+ DBIc_ACTIVE_off(imp_dbh);
+ if (DBIc_DBISTATE(imp_dbh)->debug)
+ PerlIO_printf(DBIc_LOGPIO(imp_dbh), " DESTROY %s skipped due to InactiveDestroy\n", SvPV_nolen(dbh));
+ }
+ if (DBIc_ACTIVE(imp_dbh)) {
+ if (!DBIc_has(imp_dbh,DBIcf_AutoCommit)) {
+ /* Application is using transactions and hasn't explicitly disconnected.
+ Some databases will automatically commit on graceful disconnect.
+ Since we're about to gracefully disconnect as part of the DESTROY
+ we want to be sure we're not about to implicitly commit changes
+ that are incomplete and should be rolled back. (The DESTROY may
+ be due to a RaiseError, for example.) So we rollback here.
+ This will be harmless if the application has issued a commit,
+ XXX Could add an attribute flag to indicate that the driver
+ doesn't have this problem. Patches welcome.
+ */
+ if (DBIc_WARN(imp_dbh) /* only warn if likely to be useful... */
+ && DBIc_is(imp_dbh, DBIcf_Executed) /* has not just called commit/rollback */
+ /* && !DBIc_is(imp_dbh, DBIcf_ReadOnly) -- is not read only */
+ && (!PL_dirty || DBIc_DBISTATE(imp_dbh)->debug >= 3)
+ ) {
+ warn("Issuing rollback() due to DESTROY without explicit disconnect() of %s handle %s",
+ SvPV_nolen(*hv_fetch((HV*)SvRV(dbh), "ImplementorClass", 16, 1)),
+ SvPV_nolen(*hv_fetch((HV*)SvRV(dbh), "Name", 4, 1))
+ );
+ }
+ dbd_db_rollback(dbh, imp_dbh); /* ROLLBACK! */
+ }
+ dbd_db_disconnect(dbh, imp_dbh);
+ DBIc_ACTIVE_off(imp_dbh); /* ensure it's off, regardless */
+ }
+ dbd_db_destroy(dbh, imp_dbh);
+ }
+
+
+#ifdef dbd_take_imp_data
+
+void
+take_imp_data(h)
+ SV * h
+ CODE:
+ D_imp_xxh(h);
+ /* dbd_take_imp_data() returns &sv_no (or other defined but false value)
+ * to indicate "preparations complete, now call SUPER::take_imp_data" for me.
+ * Anything else is returned to the caller via sv_2mortal(sv), typically that
+ * would be &sv_undef for error or an SV holding the imp_data.
+ */
+ SV *sv = dbd_take_imp_data(h, imp_xxh, NULL);
+ if (SvOK(sv) && !SvTRUE(sv)) {
+ SV *tmp = dbixst_bounce_method("DBD::~DRIVER~::db::SUPER::take_imp_data", items);
+ SPAGAIN;
+ ST(0) = tmp;
+ } else {
+ ST(0) = sv_2mortal(sv);
+ }
+
+#endif
+
+#ifdef dbd_db_data_sources
+
+void
+data_sources(dbh, attr = Nullsv)
+ SV *dbh
+ SV *attr
+ PPCODE:
+ {
+ D_imp_dbh(dbh);
+ AV *av = dbd_db_data_sources(dbh, imp_dbh, attr);
+ if (av) {
+ int i;
+ int n = AvFILL(av)+1;
+ EXTEND(sp, n);
+ for (i = 0; i < n; ++i) {
+ PUSHs(AvARRAY(av)[i]);
+ }
+ }
+ }
+
+#endif
+
+# -- end of DBD::~DRIVER~::db
+
+# ------------------------------------------------------------
+# statement interface
+# ------------------------------------------------------------
+MODULE = DBD::~DRIVER~ PACKAGE = DBD::~DRIVER~::st
+
+
+void
+_prepare(sth, statement, attribs=Nullsv)
+ SV * sth
+ SV * statement
+ SV * attribs
+ CODE:
+ {
+ D_imp_sth(sth);
+ DBD_ATTRIBS_CHECK("_prepare", sth, attribs);
+#ifdef dbd_st_prepare_sv
+ ST(0) = dbd_st_prepare_sv(sth, imp_sth, statement, attribs) ? &PL_sv_yes : &PL_sv_no;
+#else
+ ST(0) = dbd_st_prepare(sth, imp_sth, SvPV_nolen(statement), attribs) ? &PL_sv_yes : &PL_sv_no;
+#endif
+ }
+
+
+#ifdef dbd_st_rows
+
+void
+rows(sth)
+ SV * sth
+ CODE:
+ D_imp_sth(sth);
+ XST_mIV(0, dbd_st_rows(sth, imp_sth));
+
+#endif /* dbd_st_rows */
+
+
+#ifdef dbd_st_bind_col
+
+void
+bind_col(sth, col, ref, attribs=Nullsv)
+ SV * sth
+ SV * col
+ SV * ref
+ SV * attribs
+ CODE:
+ {
+ IV sql_type = 0;
+ D_imp_sth(sth);
+ if (SvGMAGICAL(ref))
+ mg_get(ref);
+ if (attribs) {
+ if (SvNIOK(attribs)) {
+ sql_type = SvIV(attribs);
+ attribs = Nullsv;
+ }
+ else {
+ SV **svp;
+ DBD_ATTRIBS_CHECK("bind_col", sth, attribs);
+ /* XXX we should perhaps complain if TYPE is not SvNIOK */
+ DBD_ATTRIB_GET_IV(attribs, "TYPE",4, svp, sql_type);
+ }
+ }
+ switch(dbd_st_bind_col(sth, imp_sth, col, ref, sql_type, attribs)) {
+ case 2: ST(0) = &PL_sv_yes; /* job done completely */
+ break;
+ case 1: /* fallback to DBI default */
+ ST(0) = (DBIc_DBISTATE(imp_sth)->bind_col(sth, col, ref, attribs))
+ ? &PL_sv_yes : &PL_sv_no;
+ break;
+ default: ST(0) = &PL_sv_no; /* dbd_st_bind_col has called set_err */
+ break;
+ }
+ }
+
+#endif /* dbd_st_bind_col */
+
+void
+bind_param(sth, param, value, attribs=Nullsv)
+ SV * sth
+ SV * param
+ SV * value
+ SV * attribs
+ CODE:
+ {
+ IV sql_type = 0;
+ D_imp_sth(sth);
+ if (SvGMAGICAL(value))
+ mg_get(value);
+ if (attribs) {
+ if (SvNIOK(attribs)) {
+ sql_type = SvIV(attribs);
+ attribs = Nullsv;
+ }
+ else {
+ SV **svp;
+ DBD_ATTRIBS_CHECK("bind_param", sth, attribs);
+ /* XXX we should perhaps complain if TYPE is not SvNIOK */
+ DBD_ATTRIB_GET_IV(attribs, "TYPE",4, svp, sql_type);
+ }
+ }
+ ST(0) = dbd_bind_ph(sth, imp_sth, param, value, sql_type, attribs, FALSE, 0)
+ ? &PL_sv_yes : &PL_sv_no;
+ }
+
+
+void
+bind_param_inout(sth, param, value_ref, maxlen, attribs=Nullsv)
+ SV * sth
+ SV * param
+ SV * value_ref
+ IV maxlen
+ SV * attribs
+ CODE:
+ {
+ IV sql_type = 0;
+ D_imp_sth(sth);
+ SV *value;
+ if (!SvROK(value_ref) || SvTYPE(SvRV(value_ref)) > SVt_PVMG)
+ croak("bind_param_inout needs a reference to a scalar value");
+ value = SvRV(value_ref);
+ if (SvREADONLY(value))
+ croak("Modification of a read-only value attempted");
+ if (SvGMAGICAL(value))
+ mg_get(value);
+ if (attribs) {
+ if (SvNIOK(attribs)) {
+ sql_type = SvIV(attribs);
+ attribs = Nullsv;
+ }
+ else {
+ SV **svp;
+ DBD_ATTRIBS_CHECK("bind_param", sth, attribs);
+ DBD_ATTRIB_GET_IV(attribs, "TYPE",4, svp, sql_type);
+ }
+ }
+ ST(0) = dbd_bind_ph(sth, imp_sth, param, value, sql_type, attribs, TRUE, maxlen)
+ ? &PL_sv_yes : &PL_sv_no;
+ }
+
+
+void
+execute(sth, ...)
+ SV * sth
+ CODE:
+ D_imp_sth(sth);
+ int retval;
+ if (items > 1) { /* need to bind params */
+ if (!dbdxst_bind_params(sth, imp_sth, items, ax) ) {
+ XSRETURN_UNDEF;
+ }
+ }
+ /* XXX this code is duplicated in selectrow_arrayref above */
+ if (DBIc_ROW_COUNT(imp_sth) > 0) /* reset for re-execute */
+ DBIc_ROW_COUNT(imp_sth) = 0;
+ retval = dbd_st_execute(sth, imp_sth);
+ /* remember that dbd_st_execute must return <= -2 for error */
+ if (retval == 0) /* ok with no rows affected */
+ XST_mPV(0, "0E0"); /* (true but zero) */
+ else if (retval < -1) /* -1 == unknown number of rows */
+ XST_mUNDEF(0); /* <= -2 means error */
+ else
+ XST_mIV(0, retval); /* typically 1, rowcount or -1 */
+
+
+#ifdef dbd_st_execute_for_fetch
+
+void
+execute_for_fetch(sth, fetch_tuple_sub, tuple_status = Nullsv)
+ SV * sth
+ SV * fetch_tuple_sub
+ SV * tuple_status
+ CODE:
+ {
+ D_imp_sth(sth);
+ ST(0) = dbd_st_execute_for_fetch(sth, imp_sth, fetch_tuple_sub, tuple_status);
+ }
+
+#endif
+
+
+
+void
+fetchrow_arrayref(sth)
+ SV * sth
+ ALIAS:
+ fetch = 1
+ CODE:
+ D_imp_sth(sth);
+ AV *av;
+ if (0) ix = ix; /* avoid unused variable warning */
+ av = dbd_st_fetch(sth, imp_sth);
+ ST(0) = (av) ? sv_2mortal(newRV((SV *)av)) : &PL_sv_undef;
+
+
+void
+fetchrow_array(sth)
+ SV * sth
+ ALIAS:
+ fetchrow = 1
+ PPCODE:
+ D_imp_sth(sth);
+ AV *av;
+ av = dbd_st_fetch(sth, imp_sth);
+ if (av) {
+ int i;
+ int num_fields = AvFILL(av)+1;
+ EXTEND(sp, num_fields);
+ for(i=0; i < num_fields; ++i) {
+ PUSHs(AvARRAY(av)[i]);
+ }
+ if (0) ix = ix; /* avoid unused variable warning */
+ }
+
+
+void
+fetchall_arrayref(sth, slice=&PL_sv_undef, batch_row_count=&PL_sv_undef)
+ SV * sth
+ SV * slice
+ SV * batch_row_count
+ CODE:
+ if (SvOK(slice)) { /* fallback to perl implementation */
+ SV *tmp = dbixst_bounce_method("DBD::~DRIVER~::st::SUPER::fetchall_arrayref", 3);
+ SPAGAIN;
+ ST(0) = tmp;
+ }
+ else {
+ ST(0) = dbdxst_fetchall_arrayref(sth, slice, batch_row_count);
+ }
+
+
+void
+finish(sth)
+ SV * sth
+ CODE:
+ D_imp_sth(sth);
+ D_imp_dbh_from_sth;
+ if (!DBIc_ACTIVE(imp_sth)) {
+ /* No active statement to finish */
+ XSRETURN_YES;
+ }
+ if (!DBIc_ACTIVE(imp_dbh)) {
+ /* Either an explicit disconnect() or global destruction */
+ /* has disconnected us from the database. Finish is meaningless */
+ DBIc_ACTIVE_off(imp_sth);
+ XSRETURN_YES;
+ }
+#ifdef dbd_st_finish3
+ ST(0) = dbd_st_finish3(sth, imp_sth, 0) ? &PL_sv_yes : &PL_sv_no;
+#else
+ ST(0) = dbd_st_finish(sth, imp_sth) ? &PL_sv_yes : &PL_sv_no;
+#endif
+
+
+void
+blob_read(sth, field, offset, len, destrv=Nullsv, destoffset=0)
+ SV * sth
+ int field
+ long offset
+ long len
+ SV * destrv
+ long destoffset
+ CODE:
+ {
+ D_imp_sth(sth);
+ if (!destrv)
+ destrv = sv_2mortal(newRV(sv_2mortal(newSV(0))));
+ if (dbd_st_blob_read(sth, imp_sth, field, offset, len, destrv, destoffset))
+ ST(0) = SvRV(destrv);
+ else ST(0) = &PL_sv_undef;
+ }
+
+
+void
+STORE(sth, keysv, valuesv)
+ SV * sth
+ SV * keysv
+ SV * valuesv
+ CODE:
+ D_imp_sth(sth);
+ if (SvGMAGICAL(valuesv))
+ mg_get(valuesv);
+ ST(0) = &PL_sv_yes;
+ if (!dbd_st_STORE_attrib(sth, imp_sth, keysv, valuesv))
+ if (!DBIc_DBISTATE(imp_sth)->set_attr(sth, keysv, valuesv))
+ ST(0) = &PL_sv_no;
+
+
+# FETCH renamed and ALIAS'd to avoid case clash on VMS :-(
+void
+FETCH_attrib(sth, keysv)
+ SV * sth
+ SV * keysv
+ ALIAS:
+ FETCH = 1
+ CODE:
+ D_imp_sth(sth);
+ SV *valuesv;
+ if (0) ix = ix; /* avoid unused variable warning */
+ valuesv = dbd_st_FETCH_attrib(sth, imp_sth, keysv);
+ if (!valuesv)
+ valuesv = DBIc_DBISTATE(imp_sth)->get_attr(sth, keysv);
+ ST(0) = valuesv; /* dbd_st_FETCH_attrib did sv_2mortal */
+
+
+void
+DESTROY(sth)
+ SV * sth
+ PPCODE:
+ /* keep in sync with default DESTROY in DBI.xs */
+ D_imp_sth(sth);
+ ST(0) = &PL_sv_yes;
+ if (!DBIc_IMPSET(imp_sth)) { /* was never fully set up */
+ STRLEN lna;
+ if (DBIc_WARN(imp_sth) && !PL_dirty && DBIc_DBISTATE(imp_sth)->debug >= 2)
+ PerlIO_printf(DBIc_LOGPIO(imp_sth),
+ " DESTROY for %s ignored - handle not initialised\n",
+ SvPV(sth,lna));
+ }
+ else {
+ if (DBIc_IADESTROY(imp_sth)) { /* wants ineffective destroy */
+ DBIc_ACTIVE_off(imp_sth);
+ if (DBIc_DBISTATE(imp_sth)->debug)
+ PerlIO_printf(DBIc_LOGPIO(imp_sth), " DESTROY %s skipped due to InactiveDestroy\n", SvPV_nolen(sth));
+ }
+ if (DBIc_ACTIVE(imp_sth)) {
+ D_imp_dbh_from_sth;
+ if (!PL_dirty && DBIc_ACTIVE(imp_dbh)) {
+#ifdef dbd_st_finish3
+ dbd_st_finish3(sth, imp_sth, 1);
+#else
+ dbd_st_finish(sth, imp_sth);
+#endif
+ }
+ else {
+ DBIc_ACTIVE_off(imp_sth);
+ }
+ }
+ dbd_st_destroy(sth, imp_sth);
+ }
+
+# end of ~DRIVER~.xst
+# vim:ts=8:sw=4:et
diff --git a/Driver_xst.h b/Driver_xst.h
new file mode 100644
index 0000000..0cc79d3
--- /dev/null
+++ b/Driver_xst.h
@@ -0,0 +1,122 @@
+/*
+# $Id: Driver_xst.h 15124 2012-02-03 15:13:41Z timbo $
+# Copyright (c) 2002 Tim Bunce Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+*/
+
+
+/* This is really just a workaround for SUPER:: not working right for XS code.
+ * It would be better if we setup perl's context so SUPER:: did the right thing
+ * (borrowing the relevant magic from pp_entersub in perl pp_hot.c).
+ * Then we could just use call_method("SUPER::foo") instead.
+ * XXX remember to call SPAGAIN in the calling code after calling this!
+ */
+static SV *
+dbixst_bounce_method(char *methname, int params)
+{
+ dTHX;
+ /* XXX this 'magic' undoes the dMARK embedded in the dXSARGS of our caller */
+ /* so that the dXSARGS below can set things up as they were for our caller */
+ void *xxx = PL_markstack_ptr++;
+ dXSARGS; /* declares sp, ax, mark, items */
+ int i;
+ SV *sv;
+ int debug = 0;
+ D_imp_xxh(ST(0));
+ if (debug >= 3) {
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh),
+ " -> %s (trampoline call with %d (%ld) params)\n", methname, params, (long)items);
+ xxx = xxx; /* avoid unused var warning */
+ }
+ EXTEND(SP, params);
+ PUSHMARK(SP);
+ for (i=0; i < params; ++i) {
+ sv = (i >= items) ? &PL_sv_undef : ST(i);
+ PUSHs(sv);
+ }
+ PUTBACK;
+ i = call_method(methname, G_SCALAR);
+ SPAGAIN;
+ sv = (i) ? POPs : &PL_sv_undef;
+ PUTBACK;
+ if (debug >= 3)
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh),
+ " <- %s= %s (trampoline call return)\n", methname, neatsvpv(sv,0));
+ return sv;
+}
+
+
+static int
+dbdxst_bind_params(SV *sth, imp_sth_t *imp_sth, I32 items, I32 ax)
+{
+ /* Handle binding supplied values to placeholders. */
+ /* items = one greater than the number of params */
+ /* ax = ax from calling sub, maybe adjusted to match items */
+ dTHX;
+ int i;
+ SV *idx;
+ if (items-1 != DBIc_NUM_PARAMS(imp_sth)
+ && DBIc_NUM_PARAMS(imp_sth) != DBIc_NUM_PARAMS_AT_EXECUTE
+ ) {
+ char errmsg[99];
+ /* clear any previous ParamValues before error is generated */
+ SV **svp = hv_fetch((HV*)DBIc_MY_H(imp_sth),"ParamValues",11,FALSE);
+ if (svp && SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVHV) {
+ HV *hv = (HV*)SvRV(*svp);
+ hv_clear(hv);
+ }
+ sprintf(errmsg,"called with %d bind variables when %d are needed",
+ (int)items-1, DBIc_NUM_PARAMS(imp_sth));
+ DBIh_SET_ERR_CHAR(sth, (imp_xxh_t*)imp_sth, "-1", -1, errmsg, Nullch, Nullch);
+ return 0;
+ }
+ idx = sv_2mortal(newSViv(0));
+ for(i=1; i < items ; ++i) {
+ SV* value = ST(i);
+ if (SvGMAGICAL(value))
+ mg_get(value); /* trigger magic to FETCH the value */
+ sv_setiv(idx, i);
+ if (!dbd_bind_ph(sth, imp_sth, idx, value, 0, Nullsv, FALSE, 0)) {
+ return 0; /* dbd_bind_ph already registered error */
+ }
+ }
+ return 1;
+}
+
+#ifndef dbd_fetchall_arrayref
+static SV *
+dbdxst_fetchall_arrayref(SV *sth, SV *slice, SV *batch_row_count)
+{
+ dTHX;
+ D_imp_sth(sth);
+ SV *rows_rvav;
+ if (SvOK(slice)) { /* should never get here */
+ char errmsg[99];
+ sprintf(errmsg,"slice param not supported by XS version of fetchall_arrayref");
+ DBIh_SET_ERR_CHAR(sth, (imp_xxh_t*)imp_sth, "-1", -1, errmsg, Nullch, Nullch);
+ return &PL_sv_undef;
+ }
+ else {
+ IV maxrows = SvOK(batch_row_count) ? SvIV(batch_row_count) : -1;
+ AV *fetched_av;
+ AV *rows_av = newAV();
+ if ( !DBIc_ACTIVE(imp_sth) && maxrows>0 ) {
+ /* to simplify application logic we return undef without an error */
+ /* if we've fetched all the rows and called with a batch_row_count */
+ return &PL_sv_undef;
+ }
+ av_extend(rows_av, (maxrows>0) ? maxrows : 31);
+ while ( (maxrows < 0 || maxrows-- > 0)
+ && (fetched_av = dbd_st_fetch(sth, imp_sth))
+ ) {
+ AV *copy_row_av = av_make(AvFILL(fetched_av)+1, AvARRAY(fetched_av));
+ av_push(rows_av, newRV_noinc((SV*)copy_row_av));
+ }
+ rows_rvav = sv_2mortal(newRV_noinc((SV *)rows_av));
+ }
+ return rows_rvav;
+}
+#endif
+
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..37b4bcf
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,121 @@
+Changes History of significant changes to the DBI
+DBI.pm The Database Interface Module Perl code
+DBI.xs The Database Interface Module XS code
+DBIXS.h The DBI XS public interface for Drivers (DBD::...)
+Driver.xst Template driver xs file
+Driver_xst.h Template driver xs support code
+MANIFEST
+Makefile.PL The Makefile generator
+Perl.xs Test harness (currently) for Driver.xst
+README
+TODO_2005.txt Old (but still mostly relevant) occasional random notes about what's missing
+TODO_gofer.txt To-do notes related to gofer
+dbd_xsh.h Prototypes for standard Driver.xst interface
+dbi_sql.h Definitions based on SQL CLI / ODBC (#inc'd by DBIXS.h)
+dbipport.h Perl portability macros (from Devel::PPort)
+dbilogstrip.PL Utility to normalise DBI logs so they can be compared with diff
+dbiprof.PL
+dbiproxy.PL Frontend for DBI::ProxyServer
+dbivport.h DBI version portability macros (for drivers to copy)
+dbixs_rev.h Defines DBIXS_REVISION macro holding DBIXS.h subversion revision number
+dbixs_rev.pl Utility to write dbixs_rev.h
+ex/perl_dbi_nulls_test.pl A test script for forms of IS NULL qualification in SQL
+ex/profile.pl A test script for DBI::Profile
+ex/corogofer.pl A test script for DBD::Gofer::Transport::corostream
+lib/Bundle/DBI.pm A bundle for automatic installation via CPAN.
+lib/DBD/DBM.pm A driver for DBM files (uses DBD::File)
+lib/DBD/ExampleP.pm A very simple example Driver module
+lib/DBD/File.pm A driver base class for simple drivers
+lib/DBD/File/Developers.pod Developer documentation for DBD::File
+lib/DBD/File/Roadmap.pod Roadmap for DBD::File and other Pure Perl DBD's
+lib/DBD/File/HowTo.pod Guide to write a DBD::File based DBI driver
+lib/DBD/Gofer.pm DBD::Gofer 'stateless proxy' driver
+lib/DBD/Gofer/Policy/Base.pm
+lib/DBD/Gofer/Policy/pedantic.pm Safest and most transparent, but also slowest
+lib/DBD/Gofer/Policy/classic.pm Reasonable policy for typical usage
+lib/DBD/Gofer/Policy/rush.pm Raw speed, fewest round trips, least transparent
+lib/DBD/Gofer/Transport/Base.pm Base class for DBD::Gofer driver transport classes
+lib/DBD/Gofer/Transport/corostream.pm Async Gofer transport using Coro and AnyEvent
+lib/DBD/Gofer/Transport/null.pm DBD::Gofer transport that executes in same process (for testing)
+lib/DBD/Gofer/Transport/pipeone.pm DBD::Gofer transport to new subprocess for each request
+lib/DBD/Gofer/Transport/stream.pm DBD::Gofer transport for ssh etc
+lib/DBD/NullP.pm An empty example Driver module
+lib/DBD/Proxy.pm Proxy driver
+lib/DBD/Sponge.pm A driver for fake cursors (precached data)
+lib/DBI/Const/GetInfo/ANSI.pm GetInfo data based on ANSI standard
+lib/DBI/Const/GetInfo/ODBC.pm GetInfo data based on ODBC standard
+lib/DBI/Const/GetInfoReturn.pm GetInfo return values plus tools based on standards
+lib/DBI/Const/GetInfoType.pm GetInfo type code data based on standards
+lib/DBI/DBD.pm Some basic help for people writing DBI drivers
+lib/DBI/DBD/Metadata.pm Metadata tools for people writing DBI drivers
+lib/DBI/DBD/SqlEngine.pm SQL Engine for drivers without an own
+lib/DBI/DBD/SqlEngine/Developers.pod DBI::DBD::SqlEngine API Documentation
+lib/DBI/DBD/SqlEngine/HowTo.pod HowTo ... write a DBI::DBD::SqlEngine based driver
+lib/DBI/FAQ.pm The DBI FAQ in module form for perldoc
+lib/DBI/Gofer/Execute.pm Execution logic for DBD::Gofer server
+lib/DBI/Gofer/Request.pm Request object from DBD::Gofer
+lib/DBI/Gofer/Response.pm Response object for DBD::Gofer
+lib/DBI/Gofer/Serializer/Base.pm
+lib/DBI/Gofer/Serializer/DataDumper.pm
+lib/DBI/Gofer/Serializer/Storable.pm
+lib/DBI/Gofer/Transport/Base.pm Base class for DBD::Gofer server transport classes
+lib/DBI/Gofer/Transport/pipeone.pm DBD::Gofer transport for single requests
+lib/DBI/Gofer/Transport/stream.pm DBI::Gofer transport for ssh etc
+lib/DBI/Profile.pm Manage DBI usage profile data
+lib/DBI/ProfileData.pm
+lib/DBI/ProfileDumper.pm
+lib/DBI/ProfileDumper/Apache.pm
+lib/DBI/ProfileSubs.pm
+lib/DBI/ProxyServer.pm The proxy drivers server
+lib/DBI/PurePerl.pm A DBI.xs emulation in Perl
+lib/DBI/SQL/Nano.pm A 'smaller than micro' SQL parser
+lib/DBI/Util/_accessor.pm A very¬cut-down version of Class::Accessor::Fast
+lib/DBI/Util/CacheMemory.pm A very cut-down version of Cache::Memory
+lib/DBI/W32ODBC.pm An experimental DBI emulation layer for Win32::ODBC
+lib/Win32/DBIODBC.pm An experimental Win32::ODBC emulation layer for DBI
+t/01basics.t
+t/02dbidrv.t
+t/03handle.t
+t/04mods.t
+t/05concathash.t
+t/06attrs.t
+t/07kids.t
+t/08keeperr.t
+t/09trace.t
+t/10examp.t
+t/11fetch.t
+t/12quote.t
+t/13taint.t
+t/14utf8.t
+t/15array.t
+t/16destroy.t
+t/19fhtrace.t
+t/20meta.t
+t/30subclass.t
+t/31methcache.t Test caching of inner methods
+t/35thrclone.t
+t/40profile.t
+t/41prof_dump.t
+t/42prof_data.t
+t/43prof_env.t
+t/48dbi_dbd_sqlengine.t Tests for DBI::DBD::SqlEngine
+t/49dbd_file.t DBD::File API and very basic tests
+t/50dbm_simple.t simple DBD::DBM tests
+t/51dbm_file.t extended DBD::File tests (through DBD::DBM)
+t/52dbm_complex.t Complex DBD::DBM tests with SQL::Statement
+t/60preparse.t
+t/65transact.t
+t/70callbacks.t
+t/72childhandles.t
+t/80proxy.t
+t/85gofer.t
+t/86gofer_fail.t
+t/87gofer_cache.t
+t/90sql_type_cast.t
+t/lib.pl Utility functions for test scripts
+t/pod.t
+t/pod-coverage.t
+test.pl Assorted informal tests, including tests for memory leaks
+typemap
+META.yml Module YAML meta-data (added by MakeMaker)
+META.json Module JSON meta-data (added by MakeMaker)
diff --git a/META.json b/META.json
new file mode 100644
index 0000000..4b3b8bb
--- /dev/null
+++ b/META.json
@@ -0,0 +1,67 @@
+{
+ "abstract" : "Database independent interface for Perl",
+ "author" : [
+ "Tim Bunce (dbi-users@perl.org)"
+ ],
+ "dynamic_config" : 1,
+ "generated_by" : "ExtUtils::MakeMaker version 6.6302, CPAN::Meta::Converter version 2.120630",
+ "license" : [
+ "perl_5"
+ ],
+ "meta-spec" : {
+ "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
+ "version" : "2"
+ },
+ "name" : "DBI",
+ "no_index" : {
+ "directory" : [
+ "t",
+ "inc"
+ ]
+ },
+ "prereqs" : {
+ "build" : {
+ "requires" : {
+ "ExtUtils::MakeMaker" : "6.48",
+ "Test::Simple" : "0.90"
+ }
+ },
+ "configure" : {
+ "requires" : {
+ "ExtUtils::MakeMaker" : "0"
+ }
+ },
+ "runtime" : {
+ "conflicts" : {
+ "DBD::AnyData" : "0.09",
+ "DBD::CSV" : "0.29",
+ "DBD::PO" : "2.10",
+ "DBD::RAM" : "0.072",
+ "SQL::Statement" : "1.27"
+ },
+ "recommends" : {
+ "Clone" : "0.31",
+ "DB_File" : "0",
+ "MLDBM" : "0",
+ "Net::Daemon" : "0",
+ "RPC::PlServer" : "0.2001",
+ "SQL::Statement" : "1.28"
+ },
+ "requires" : {
+ "perl" : "5.008"
+ }
+ }
+ },
+ "release_status" : "stable",
+ "resources" : {
+ "homepage" : "http://dbi.perl.org/",
+ "license" : [
+ "http://dev.perl.org/licenses/"
+ ],
+ "repository" : {
+ "url" : "https://svn.perl.org/modules/dbi/trunk/"
+ },
+ "x_MailingList" : "mailto:dbi-dev@perl.org"
+ },
+ "version" : "1.622"
+}
diff --git a/META.yml b/META.yml
new file mode 100644
index 0000000..2b32528
--- /dev/null
+++ b/META.yml
@@ -0,0 +1,41 @@
+---
+abstract: 'Database independent interface for Perl'
+author:
+ - 'Tim Bunce (dbi-users@perl.org)'
+build_requires:
+ ExtUtils::MakeMaker: 6.48
+ Test::Simple: 0.90
+configure_requires:
+ ExtUtils::MakeMaker: 0
+conflicts:
+ DBD::AnyData: 0.09
+ DBD::CSV: 0.29
+ DBD::PO: 2.10
+ DBD::RAM: 0.072
+ SQL::Statement: 1.27
+dynamic_config: 1
+generated_by: 'ExtUtils::MakeMaker version 6.6302, CPAN::Meta::Converter version 2.120630'
+license: perl
+meta-spec:
+ url: http://module-build.sourceforge.net/META-spec-v1.4.html
+ version: 1.4
+name: DBI
+no_index:
+ directory:
+ - t
+ - inc
+recommends:
+ Clone: 0.31
+ DB_File: 0
+ MLDBM: 0
+ Net::Daemon: 0
+ RPC::PlServer: 0.2001
+ SQL::Statement: 1.28
+requires:
+ perl: 5.008
+resources:
+ homepage: http://dbi.perl.org/
+ license: http://dev.perl.org/licenses/
+ repository: https://svn.perl.org/modules/dbi/trunk/
+ x_MailingList: mailto:dbi-dev@perl.org
+version: 1.622
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..9680e45
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,397 @@
+# -*- perl -*-
+#
+# $Id: Makefile.PL 15248 2012-03-26 21:47:22Z timbo $
+#
+# Copyright (c) 1994-2010 Tim Bunce Ireland
+#
+# See COPYRIGHT section in DBI.pm for usage and distribution rights.
+
+use 5.008_001;
+
+use ExtUtils::MakeMaker 5.16, qw(WriteMakefile $Verbose prompt);
+use Getopt::Long;
+use Config;
+use File::Find;
+use File::Spec;
+use strict;
+
+use lib 'lib'; # for use DBI::DBD
+use DBI::DBD;
+
+$| = 1;
+$^W = 1;
+my $os = $^O;
+my $osvers = $Config{osvers};
+$osvers =~ s/^\s*(\d+\.\d+).*/$1/; # drop sub-sub-version: 2.5.1 -> 2.5
+my $ext_pl = $^O eq 'VMS' ? '.pl' : '';
+my $is_developer = ((-d ".svn" || -d ".git") && -f "MANIFEST.SKIP");
+
+$::opt_v = 0;
+$::opt_thread = 1; # thread if we can, use "-nothread" to disable
+$::opt_g = 0;
+$::opt_g = 1 if $is_developer && $ENV{LOGNAME} && $ENV{LOGNAME} eq 'timbo'; # it's me! (probably)
+
+GetOptions(qw(v! g! thread!))
+ or die "Invalid arguments\n";
+
+$::opt_g &&= '-g'; # convert to actual string
+
+
+if (($ENV{LANG}||'') =~ m/utf-?8/i) {
+ print "\n";
+ print "*** Your LANG environment variable is set to '$ENV{LANG}'\n";
+ print "*** This may cause problems for some perl installations.\n";
+ print "*** If you get test failures, please try again with LANG unset.\n";
+ print "*** If that then works, please email dbi-dev\@perl.org with details\n";
+ print "*** including the output of 'perl -V'\n";
+ print "\n";
+ sleep 1;
+}
+
+if ($Config{useithreads}) {
+ if ($] < 5.012) { # recent perls are reasonably fre of thread bugs
+ print "\n";
+ print "*** You are using a perl configured with threading enabled.\n";
+ print "*** You should be aware that using multiple threads is\n";
+ print "*** not recommended for production environments.\n";
+ print "\n";
+ sleep 1;
+ }
+ $::opt_thread = 1;
+}
+else {
+
+ if ($Config{archname} =~ /\bthread/ && $::opt_thread) {
+ # oh dear... tell it like it is:
+ print "\n";
+ print "DBI versions from 1.29 onwards no longer support the old style\n";
+ print "of perl threading (now known as '5005 threads'). It is badly flawed\n";
+ print "and could never be safe to use in production environments.\n\n";
+ print "If you are using multiple threads you are *strongly* encouraged to\n";
+ print "upgrade to perl 5.8.2 or later.\n";
+ print "If you are not using multiple threads you are *strongly* encouraged to\n";
+ print "upgrade to at least 5.6.1 (preferably perl 5.8.2 or later.)\n";
+ print "or at the very least rebuild this version with threading disabled.\n";
+ print "If you have stick with your current build of perl...\n";
+ print "then you also have to stick with DBI 1.28 for safety.\n";
+ print "Or if *desparate* you may be able to build this DBI using 'perl Makefile.PL -nothread' but\n";
+ print "but but, that will have *no* logic to handle threads because the logic\n";
+ print "that was there for 5005 threads has now been removed! You have been warned.\n";
+ die "*** ABORTED.\n";
+ }
+
+ $::opt_thread = 0;
+}
+
+my %opts = (
+ NAME => 'DBI',
+ AUTHOR => 'Tim Bunce (dbi-users@perl.org)',
+ VERSION_FROM => 'DBI.pm',
+ ABSTRACT_FROM => 'DBI.pm',
+ MIN_PERL_VERSION => '5.008',
+ BUILD_REQUIRES => {
+ 'ExtUtils::MakeMaker' => '6.48',
+ 'Test::Simple' => '0.90',
+ },
+ META_MERGE => {
+ resources => {
+ repository => 'https://svn.perl.org/modules/dbi/trunk/',
+ MailingList => 'mailto:dbi-dev@perl.org',
+ license => 'http://dev.perl.org/licenses/',
+ homepage => 'http://dbi.perl.org/',
+ },
+ recommends => {
+ 'RPC::PlServer' => 0.2001,
+ 'Net::Daemon' => 0,
+ 'SQL::Statement' => 1.28,
+ 'Clone' => 0.31,
+ 'MLDBM' => 0,
+ 'DB_File' => 0,
+ },
+ },
+ PREREQ_PM => {
+ ( $^O eq 'MSWin32' ? ( 'File::Spec' => 3.31, ) : () ),
+ },
+ CONFLICTS => {
+ 'SQL::Statement' => '1.27',
+ 'DBD::AnyData' => '0.09',
+ 'DBD::CSV' => '0.29',
+ 'DBD::RAM' => '0.072',
+ 'DBD::PO' => '2.10',
+ },
+ LICENSE => 'perl',
+ EXE_FILES => [ "dbiproxy$ext_pl", "dbiprof$ext_pl", "dbilogstrip$ext_pl" ],
+ DIR => [ ],
+ dynamic_lib => { OTHERLDFLAGS => "$::opt_g" },
+ clean => { FILES=> "\$(DISTVNAME) Perl.xsi t/zv*_*.t dbi__null_test_tmp*"
+ ." dbiproxy$ext_pl dbiprof$ext_pl dbilogstrip$ext_pl dbiproxy.*log dbitrace.log dbi*.prof ndtest.prt" },
+ dist => {
+ DIST_DEFAULT=> 'clean distcheck disttest tardist',
+ PREOP => '$(MAKE) -f Makefile.old distdir',
+ COMPRESS => 'gzip -v9', SUFFIX => 'gz',
+ },
+);
+$opts{CAPI} = 'TRUE' if $Config{archname} =~ /-object\b/i;
+
+if (my $gccversion = $Config{gccversion}) { # ask gcc to be more pedantic
+ warn "WARNING: Your GNU C $gccversion compiler is very old. Please upgrade it and rebuild perl.\n"
+ if $gccversion =~ m/^\D*(1|2\.[1-8])/;
+ print "Your perl was compiled with gcc (version $Config{gccversion}), okay.\n";
+ $gccversion =~ s/[^\d\.]//g; # just a number please
+ $opts{DEFINE} .= ' -W -Wall -Wpointer-arith -Wbad-function-cast';
+ $opts{DEFINE} .= ' -Wno-comment -Wno-sign-compare -Wno-cast-qual';
+ $opts{DEFINE} .= ' -Wmissing-noreturn -Wno-unused-parameter' if $gccversion ge "3.0";
+ if ($is_developer && $::opt_g) {
+ $opts{DEFINE} .= ' -DPERL_GCC_PEDANTIC -ansi -pedantic' if $gccversion ge "3.0";
+ $opts{DEFINE} .= ' -Wdisabled-optimization -Wformat' if $gccversion ge "3.0";
+ $opts{DEFINE} .= ' -Wmissing-prototypes';
+ }
+}
+
+$opts{DEFINE} .= ' -DDBI_NO_THREADS' unless $::opt_thread;
+
+# HP-UX 9 cannot link a non-PIC object file into a shared library.
+# Since the # .a libs that Oracle supplies contain non-PIC object
+# files, we sadly have to build static on HP-UX 9 :(
+if ($os eq 'hpux' and $osvers < 10) {
+ $opts{LINKTYPE} = 'static';
+ print "Warning: Forced to build static not dynamic on $os $osvers.\a\n";
+ print "** Note: DBI will be built *into* a NEW perl binary. You MUST use that new perl.\n";
+ print " See README and Makefile.PL for more information.\a\n";
+}
+
+if ($os eq 'MSWin32' && $Config{libs} =~ /\bPerlCRT.lib\b/
+ && -f "$Config{archlib}/CORE/PerlCRT.lib") {
+ # ActiveState Perl needs this; should better be done in MakeMaker, but
+ # as a temporary workaround it seems ok.
+ $opts{LIBS} = "-L$Config{archlib}/CORE";
+}
+
+# Set aside some values for post_initialize() in package MY
+my ( $cfg_privlibexp, $cfg_archlibexp, $cfg_sitelibexp, $cfg_sitearchexp,
+ $cfg_man3direxp ) =
+ @Config{qw( privlibexp archlibexp sitelibexp sitearchexp man3direxp ) };
+for ( $cfg_privlibexp, $cfg_archlibexp, $cfg_sitelibexp, $cfg_sitearchexp,
+ $cfg_man3direxp ) {
+ $_ = '' unless defined $_;
+}
+
+# If working from git-svn, the $Id: Makefile.PL 15248 2012-03-26 21:47:22Z timbo $'s in the mod's should be completed
+# before any other action is taken
+$is_developer && -d ".git" && -f "git-svn-vsn.pl" and system $^X, "git-svn-vsn.pl";
+
+my $conflictMsg = <<EOCM;
+***
+ This version of DBI conflicts with the version of
+ module %s (%s) you have installed.
+
+ It's strongly recommended that you update it after
+ installing this version of DBI.
+***
+EOCM
+
+sub CheckConflicts {
+ my %params = @_;
+ my %conflicts = %{ $params{CONFLICTS} };
+ my $found = 0;
+
+ while ( my ( $module, $version ) = each(%conflicts) ) {
+ undef $@;
+ eval "require $module";
+ next if $@;
+ my $installed = eval "\$" . $module . "::VERSION";
+ if ( $installed le $version ) {
+ ++$found;
+ my $msg = $conflictMsg;
+ my $warning = sprintf( $msg, $module, $installed );
+ warn $warning;
+ }
+ }
+
+ return !$found;
+}
+
+sub WriteMakefile1 {
+ #Written by Alexandr Ciornii, version 0.21. Added by eumm-upgrade.
+ my %params = @_;
+ my $eumm_version = $ExtUtils::MakeMaker::VERSION;
+ $eumm_version = eval $eumm_version;
+ die "EXTRA_META is deprecated" if ( exists( $params{EXTRA_META} ) );
+ die "License not specified" if ( !exists( $params{LICENSE} ) );
+ if ( $params{BUILD_REQUIRES} and ( $eumm_version < 6.5503 ) ) {
+ #EUMM 6.5502 has problems with BUILD_REQUIRES
+ $params{PREREQ_PM} = { %{ $params{PREREQ_PM} || {} }, %{ $params{BUILD_REQUIRES} } };
+ delete $params{BUILD_REQUIRES};
+ }
+
+ # more or less taken from Moose' Makefile.PL
+ if ( $params{CONFLICTS} ) {
+ my $ok = CheckConflicts(%params);
+ exit(0) if ( $params{PREREQ_FATAL} and not $ok );
+ my $cpan_smoker = grep { $_ =~ m/(?:CR_SMOKER|CPAN_REPORTER|AUTOMATED_TESTING)/ } keys %ENV;
+ unless ( $cpan_smoker || $ENV{PERL_MM_USE_DEFAULT} ) {
+ sleep 4 unless ($ok);
+ }
+ %{$params{META_MERGE}{conflicts}} = %{$params{CONFLICTS}};
+ delete $params{CONFLICTS};
+ }
+
+ delete $params{CONFIGURE_REQUIRES} if ( $eumm_version < 6.52 );
+ delete $params{MIN_PERL_VERSION} if ( $eumm_version < 6.48 );
+ delete $params{META_MERGE} if ( $eumm_version < 6.46 );
+ delete $params{META_ADD} if ( $eumm_version < 6.46 );
+ delete $params{LICENSE} if ( $eumm_version < 6.31 );
+
+ WriteMakefile(%params);
+}
+
+$Verbose = $::opt_v;
+WriteMakefile1(
+ dbd_edit_mm_attribs(\%opts, {
+ create_pp_tests => 1,
+ create_nano_tests => 1,
+ create_gap_tests => 1,
+ })
+);
+# WriteMakefile call is last thing executed
+# so return value is propagated
+
+
+# =====================================================================
+
+package MY;
+
+sub postamble {
+warn <<EOT;
+
+ I see you're using perl $] on $Config::Config{archname}, okay.
+ Remember to actually *read* the README file!
+ Use 'make' to build the software (dmake or nmake on Windows).
+ Then 'make test' to execute self tests.
+ Then 'make install' to install the DBI and then delete this working
+ directory before unpacking and building any DBD::* drivers.
+
+EOT
+warn <<EOT if $os eq 'MSWin32';
+ Windows users need to use the correct make command.
+ That may be nmake or dmake depending on which Perl you are using.
+ If using the Win32 ActiveState build then it is recommended that you
+ use the ppm utility to fetch and install a prebuilt DBI instead.
+
+EOT
+ return "";
+}
+
+sub libscan {
+ my($self, $path) = @_;
+ ($path =~ /\~$|\B\.(svn|git)\b/) ? undef : $path;
+}
+
+sub const_cccmd {
+ my $self = shift;
+ local($_) = $self->SUPER::const_cccmd(@_);
+ # If perl Makefile.PL *-g* then switch on debugging
+ if ($::opt_g) {
+ s/\s-O\d?\b//; # delete optimise option
+ s/\s-/ -g -/; # add -g option
+ }
+ $_;
+}
+
+
+sub post_initialize {
+ my($self) = shift;
+
+ if ($cfg_privlibexp ne $cfg_sitelibexp) {
+ # this block could probably be removed now
+ my %old;
+ File::Find::find( sub {
+ local $_ = $File::Find::name;
+ s:\\:/:g if $os eq 'MSWin32';
+ $File::Find::prune = 1, return
+ if -d $_ && ( $_ eq $cfg_sitelibexp ||
+ $_ eq $cfg_sitearchexp ||
+ $_ eq $cfg_man3direxp );
+ ++$old{$_} if m:\bDB(I|D$):; # DBI files, but just DBD dirs
+ }, $cfg_privlibexp, $cfg_archlibexp );
+ if ( %old ) {
+ warn "
+Warning: By default new modules are installed into your 'site_lib'
+ directories. Since site_lib directories come after the normal library
+ directories you must delete old DBI files and directories from your
+ 'privlib' and 'archlib' directories and their auto subdirectories.
+
+Reinstall DBI and your DBD::* drivers after deleting the old directories.
+
+Here's a list of probable old files and directories:
+
+ " . join( "\n ", ( sort keys %old ), "\n" );
+ }
+ }
+
+ # install files that DBD's may need
+ File::Find::find( sub {
+
+ # may be '.' or '[]' depending on File::Find version
+ $_ = '.' if $^O eq 'VMS' && $_ eq File::Spec->curdir;
+
+ $File::Find::prune = 1, return if -d $_ && '.' ne $_;
+ $self->{PM}->{$_} = File::Spec->catfile($self->{INST_ARCHAUTODIR}, $_)
+ if '.h' eq substr( $_, -2 ) || '.xst' eq substr( $_, -4 );
+ }, '.' );
+
+ delete $self->{$_}{"git-svn-vsn.pl"} for qw( PM MAN3PODS );
+
+ return '';
+}
+
+
+sub post_constants {
+ my($self) = shift;
+
+ # ensure that Driver.xst and related code gets tested
+ my $xst = main::dbd_postamble();
+ $xst =~ s/\$\(BASEEXT\)/Perl/g;
+ $xst .= '
+dbixs_rev.h: DBIXS.h Driver_xst.h dbipport.h dbivport.h dbixs_rev.pl
+ $(PERL) dbixs_rev.pl
+
+DBI.c: Perl$(OBJ_EXT)
+
+# make Changes file available as installed pod docs "perldoc DBI::Changes"
+inst_libdbi = ' . File::Spec->catdir($self->{INST_LIB}, 'DBI') . '
+changes_pm = ' . File::Spec->catfile($self->{INST_LIB}, 'DBI', 'Changes.pm') . '
+'.q{
+
+config :: $(changes_pm)
+ $(NOECHO) $(NOOP)
+
+$(changes_pm): Changes
+ $(MKPATH) $(inst_libdbi)
+ $(RM_F) $(changes_pm)
+ $(CP) Changes $(changes_pm)
+
+ptest: all
+ prove --blib --jobs 4 --shuffle
+
+faq:
+ : checkin any local changes not already checked in before overwriting
+ svn commit --message "dbi.tiddlyspot.com FAQ update" dbi.tiddlyspot.com.html
+ wget --ignore-length --output-document=dbi.tiddlyspot.com.html --timestamping http://dbi.tiddlyspot.com/download
+ svn commit --message "dbi.tiddlyspot.com FAQ update" dbi.tiddlyspot.com.html
+
+checkkeywords:
+ $(RM_RF) blib
+ find . -type f \( -name .svn -prune -o -name \*.pm -o -name \*.PL -o -name \*.pl \) \
+ -exec bash -c '[ -z "$$(svn pg svn:keywords {})" ] && echo svn propset svn:keywords \"Id Revision\" {}' \;
+
+checkpod:
+ $(RM_RF) blib
+ find . -type f \( -name .svn -prune -o -name \*.pm -o -name \*.PL -o -name \*.pl \) \
+ -exec podchecker {} \; 2>&1 | grep -v 'pod syntax OK'
+};
+
+ return $xst;
+}
+
+# end.
diff --git a/Perl.xs b/Perl.xs
new file mode 100644
index 0000000..048e9d9
--- /dev/null
+++ b/Perl.xs
@@ -0,0 +1,54 @@
+/* This is a skeleton driver that only serves as a basic sanity check
+ that the Driver.xst mechansim doesn't have compile-time errors in it.
+ vim: ts=8:sw=4:expandtab
+*/
+
+#define PERL_NO_GET_CONTEXT
+#include "DBIXS.h"
+#include "dbd_xsh.h"
+
+#undef DBIh_SET_ERR_CHAR /* to syntax check emulation */
+#include "dbivport.h"
+
+DBISTATE_DECLARE;
+
+
+struct imp_drh_st {
+ dbih_drc_t com; /* MUST be first element in structure */
+};
+struct imp_dbh_st {
+ dbih_dbc_t com; /* MUST be first element in structure */
+};
+struct imp_sth_st {
+ dbih_stc_t com; /* MUST be first element in structure */
+};
+
+
+
+#define dbd_discon_all(drh, imp_drh) (drh=drh,imp_drh=imp_drh,1)
+#define dbd_dr_data_sources(drh, imp_drh, attr) (drh=drh,imp_drh=imp_drh,Nullav)
+#define dbd_db_do4(dbh,imp_dbh,p3,p4) (dbh=dbh,imp_dbh=imp_dbh,p3=p3,p4=p4,-2)
+#define dbd_db_last_insert_id(dbh, imp_dbh, p3,p4,p5,p6, attr) \
+ (dbh=dbh,imp_dbh=imp_dbh,p3=p3,p4=p4,p5=p5,p6=p6,&PL_sv_undef)
+#define dbd_take_imp_data(h, imp_xxh, p3) (h=h,imp_xxh=imp_xxh,&PL_sv_undef)
+#define dbd_st_execute_for_fetch(sth, imp_sth, p3, p4) \
+ (sth=sth,imp_sth=imp_sth,p3=p3,p4=p4,&PL_sv_undef)
+
+#define dbd_st_bind_col(sth, imp_sth, param, ref, sql_type, attribs) \
+ (sth=sth,imp_sth=imp_sth,param=param,ref=ref,sql_type=sql_type,attribs=attribs,1)
+
+int /* just to test syntax of macros etc */
+dbd_st_rows(SV *h, imp_sth_t *imp_sth)
+{
+ dTHX;
+ h = h; /* silence unused var warning */
+ DBIh_SET_ERR_CHAR(h, imp_sth, 0, 1, "err msg", "12345", Nullch);
+ return -1;
+}
+
+
+MODULE = DBD::Perl PACKAGE = DBD::Perl
+
+INCLUDE: Perl.xsi
+
+# vim:sw=4:ts=8
diff --git a/README b/README
new file mode 100644
index 0000000..d6fb465
--- /dev/null
+++ b/README
@@ -0,0 +1,145 @@
+DBI - The Perl Database Interface by Tim Bunce.
+
+Copyright (c) 1994-2010 Tim Bunce Ireland.
+
+See COPYRIGHT section in DBI.pm for usage and distribution rights.
+
+See GETTING HELP section in DBI.pm for how to get help.
+
+QUICK START GUIDE:
+
+ The DBI requires one or more 'driver' modules to talk to databases,
+ but they are not needed to build or install the DBI.
+
+ Check that a DBD::* module exists for the database you wish to use.
+
+ Read the DBI README then Build/test/install the DBI by doing
+ perl Makefile.PL
+ make
+ make test
+ make install
+ Then delete the source directory tree since it's no longer needed.
+ Or else use an installer like cpanm, cpanplus, or cpan commands.
+
+ Use the 'perldoc DBI' command to read the DBI documentation.
+ See GETTING HELP section in DBI.pm for how to get help.
+
+ Fetch the DBD::* driver module you wish to use and unpack it.
+ http://search.cpan.org/ (or www.activestate.com if on Windows)
+ It is often important to read the driver README file carefully.
+ Generally the build/test/install/delete sequence is the same
+ as for the DBI module.
+
+
+The DBI.pm file contains the DBI specification and other documentation.
+PLEASE READ IT. It'll save you asking questions on the mailing list
+which you will be told are already answered in the documentation.
+
+For more information and to keep informed about progress you can join
+the a mailing list via mailto:dbi-users-help@perl.org
+
+To help you make the best use of the dbi-users mailing list,
+and any other lists or forums you may use, I strongly
+recommend that you read "How To Ask Questions The Smart Way"
+by Eric Raymond:
+
+ http://www.catb.org/~esr/faqs/smart-questions.html
+
+Much useful information and online archives of the mailing lists can be
+found at http://dbi.perl.org/
+
+See also http://search.cpan.org/
+
+
+BEFORE BUILDING, TESTING AND INSTALLING this you will need to:
+
+ Build, test and install a recent version of Perl 5
+ It is very important to test it and actually install it!
+ (You can use "Configure -Dprefix=..." to build a private copy.)
+
+BUILDING
+
+ perl Makefile.PL
+ make
+ make test
+ make test TEST_VERBOSE=1 (if any of the t/* tests fail)
+ make install (if the tests look okay)
+
+The perl you use to execute Makefile.PL should be the first one in your PATH.
+If you want to use some installed perl then modify your PATH to match.
+
+IF YOU HAVE PROBLEMS:
+
+First, carefully read the notes at the bottom of this file.
+
+If you can't fix it your self please post details to dbi-users@perl.org.
+Please do _not_ send them just to me. Please include:
+
+1. A complete log of a complete build, e.g.:
+
+ perl Makefile.PL (do a make realclean first)
+ make
+ make test
+ make test TEST_VERBOSE=1 (if any of the t/* tests fail)
+
+2. The output of perl -V
+
+3. If you get a core dump, try to include a stack trace from it.
+ (Try installing the Devel::CoreStack module to get a stack trace.)
+ If the stack trace mentions XS_DynaLoader_dl_load_file then rerun
+ make test after setting the environment variable PERL_DL_DEBUG to 2.
+
+4. If your installation succeeds, but your script does not behave
+ as you expect, the problem is possibly in your script. Before
+ sending to dbi-users, try writing a small, easy to use test case
+ to reproduce your problem. Also, use the DBI->trace method to
+ trace your database calls.
+
+Please don't post problems to comp.lang.perl.* or perl5-porters.
+This software is supported via the dbi-users mailing list. For more
+information and to keep informed about progress you can join the
+mailing list via mailto:dbi-users-help@perl.org
+(please note that I do not run or manage the mailing list).
+
+It is important to check that you are using the latest version before
+posting. If you're not then I'm very likely to simply say "upgrade to
+the latest". You would do yourself a favour by upgrading beforehand.
+
+Please remember that I'm _very_ busy. Try to help yourself first,
+then try to help me help you by following these guidelines carefully.
+(Note specifically that I'm unlikely to answer a question that's
+answered clearly in the on-line documentation.)
+
+Regards,
+Tim Bunce.
+
+=======================================================================
+
+---
+If you get an error like "gcc: command not found" or "cc: command not found"
+you need to either install a compiler, or you may be able to install a
+precompiled binary of DBI using a package manager (e.g., ppm for ActiveState,
+Synaptic for Ubuntu, port for FreeBSD etc)
+
+---
+If you get compiler errors refering to Perl's own header files
+(.../CORE/*.h) or the compiler complains about bad options etc then
+there is something wrong with your perl installation. If the compiler complains
+of missing files (.../perl.h: error: sys/types.h: No such file) then you may
+need to install extra packages for your operating system.
+
+Generally it's best to use a Perl that was built on the system you are trying
+to use and it's also important to use the same compiler that was used to build
+the Perl you are using.
+
+If you installed Perl using a binary distribution, such as ActiveState Perl,
+or if Perl came installed with the operating system you use, such as Debian or
+Ubuntu, then you may be able to install a precompiled binary of DBI using a
+package manager. Check the package manager for your distribution of Perl (e.g.
+ppm for ActiveState) or for your operating system (e.g Synaptic for Ubuntu).
+
+---
+If you get compiler warnings like "value computed is not used" and
+"unused variable" you can ignore them.
+
+End.
diff --git a/TODO_2005.txt b/TODO_2005.txt
new file mode 100644
index 0000000..8020ae7
--- /dev/null
+++ b/TODO_2005.txt
@@ -0,0 +1,579 @@
+Change ideas for DBI
+====================
+
+--- Changes that may impact applications:
+
+Turning AutoCommit on, such as when { local $dbh->{AutoCommit} = 0; ... }
+goes out of scope, should trigger rollback not commit. (ODBC does a commit)
+RISK: This will break code that assumes a commit.
+REMEDY: Explicitly $dbh->commit where required.
+MJE: I may misunderstand this but ODBC commits in this case because
+ AutoCommit is turned back on again when the block completes and that
+ causes any outstanding txn to be committed. Neither DBD::ODBC or ODBC
+ turned AutoCommit back on themselves.
+
+Always taint check the $sql for do() and prepare()
+if perl is in taint mode (can't be disabled).
+RISK: May impact code running with taint enabled but not DBI TaintIn/Out
+Also consider other changes to TaintIn/TaintOut attribute semantics.
+
+Alter tables() to default $schema to $dbh->current_schema.
+So tables() will default to returning tables in the current schema.
+(Should include public synonyms)
+RISK: This will impact code requiring tables from multiple schema.
+REMEDY: specify $schema parameter ("%" for all?)
+
+Add $dbh->current_schema (default to $dbh->{Username})
+
+Remove old informix fudge in tables() (would only impact people
+using very old DBD::Informix versions as it now has it's own).
+
+Remove "old-style" connect syntax (where driver name is 4th parameter).
+
+Change undocumented DBI->err and DBI->errstr methods to warn.
+
+Bundle enhanced DBD::Multiplex
+RISK: may break apps using old DBD::Multiplex
+
+disconnect() implies rollback() unless AutoCommit (Driver.xst + drivers)
+
+--- Internal Changes
+
+Move DBI::xx classes to DBI::xx_base and sanction use of DBI::xx
+classes for extensions via mixins.
+
+Increase size of DBIS (dbistate) structure and imp_xxh.com structures
+and improve size/version sanity checks.
+
+Make ShowErrorStatement=>1 the default when handle is created
+
+Mandate use of dbivport.h and related macros.
+
+Drivers to alter trace level behaviour (no output at low levels
+and use named trace topics).
+
+Mandate that NUM_OF_FIELDS must be set by execute() and
+can't be deferred till $sth->{NUM_OF_FIELDS} or fetch*_*() called.
+
+Add PERL_NO_GET_CONTEXT for multiplicity/threads?
+
+Remove DBIS global and related macros.
+Add dDBIS to be used in functions (eg like dTHR) that can't access it via a imp_xxh
+
+Remove PERL_POLLUTE (so some names will require PL_ or Perl_ prefixes)
+ - MJE I believe this is effectively done now as PERL_POLLUTE was removed
+ in 5.13.3
+
+Update dbipport.h from latest Devel::PPPort.
+
+Add function pointers for setting fetched field values into DBIS.
+IV, UV, NV, PV and SV?
+Drivers to use this instead of calling sv_setpv (etc) themselves.
+Use internally for set_fbav().
+
+Add function pointer to indicate 'all fields set'.
+Use for both per-field and per-row OnFetch hooks.
+
+New reset() method:
+$dbh->reset - disconnects + discards all state related to the particular connection
+$sth->reset - finish + discards all state related to the particular statement
+Effectively think of a handle as having two parts:
+attributes related to a particular connection/statement (CachedKids/NUM_OF_PARAMS)
+and attribute not-related (AutoCommit/RaiseError).
+The reset method resets the first set but not the second.
+The reset method would call uncache().
+
+Rework handle creation to use methods:
+Maybe $h->new_child(\%handle_attr)
+ dr::connect =>
+ $dbh = $drh->new_child(\%attr);
+ $dbh->connect(...) - calls $dbh->reset()
+& db::prepare =>
+ sub ...::db::prepare {
+ my ($dbh, $sql, $attr) = @_;
+ $sth = $dbh->new_child($attr)
+ my @statements = $dbh->preparse($sql);
+ $sth->{PendingStatements} = \@statements if @statements > 1;
+ $sth->prepare( shift @statements ) or return;
+ return $sth;
+ }
+ sub prepare_cached - no change, calls $dbh->prepare.
+ sub ...::st::prepare {
+ $sth->reset;
+ ...
+ }
+Also need to consider $sth->more_results and its need for reset()-like behaviour.
+
+Need to enable drivers to work with old and new approaches,
+which means having both ::db::prepare and ::st::prepare
+When a driver is loaded the ::db::prepare() method
+will be deleted if a ::st::reset method exists.
+
+Make $DBI::err etc plain (untied) variables.
+Set them in set_err() and when returning from dispatch.
+Clear them, if appropriate, when entering dispatch dispatch().
+
+Enable drivers to provide a hash to map err codes into state values.
+
+Unified test suite infrastructure to be reused by all drivers.
+A big project.
+
+-- others --
+
+Add (auto-maintained) #define macro giving the version number of the DBI
+as an integer in a form that can be used by #if statements (eg 1043000)
+e.g. Have Makefile.PL write a .h file that contains the value and have
+that #included by DBIXS.h
+
+Fixup @DBD::Foo::ISA and ?->setup_driver issues
+
+Add "imp_xxh_t* imp_xxh;" element to com struct that points back at
+itself so macros can be written to work with imp_??h without needing casts.
+ALso make it cheap to get h from imp_xxh so only imp_xxh needs
+to be passed around.
+
+Add utility function that does SvUTF8_on(sv) if the sv contains
+valid-looking utf8. To be used (perhaps via OnFetch hook) where
+utf8 data is being stored in a non-utf8 aware database.
+
+Add DBIS->carp(varargs) to simplify access to Carp::carp so warnings
+like "execute called with 1 bind variables when 0 are needed" fr do()
+get reported against caller's file and line number and not a line in DBI.pm
+
+pre and post call hooks via ima structure?
+
+Remove _not_impl. Alias debug to trace in DBI::(dr/db/st) and remove
+debug() method from internals.
+
+DBD::Multiplex enhancements (Thomas Kishel <tom@kishel.net>):
+Enable DBIx::HA (http://search.cpan.org/~hasseily/DBIx-HA/HA.pm) features.
+SQL translation hooks:
+mx_translate_sql_parent - called by prepare() to translate sql from app
+mx_translate_sql_child - called for each child handle so each can have different dialect
+(note that mx_translate_sql_parent could parse into internal tree
+from which mx_translate_sql_child then 'regenerates' custom sql for the child handle)
+See also http://c-jdbc.objectweb.org/
+
+Use subversion mechanism for $VERSION in source files.
+
+====== LATER ======
+
+Define expected uft8 behaviour. Basically drivers need to set the
+uft8 flag on returned strings themselves when appropriate.
+The DBI I<may> define a way for an application to indicate that
+a particular column should be flagged as uft8 to help drivers
+that are not able to determine that themselves.
+The DBI won't support automatic character set conversions.
+
+Define "topic bits" for TraceLevel.
+%DBI::TraceTopics & %DBD::Foo::TraceTopics
+"Lint" topic for extra checking, eg warn on $sth DESTROY if still Active
+"Verbose" topic adds verbosity to any other enabled topics
+"Connect" topic to log connect/disconnect/reconnect/failed-ping
+Add topic flags to ima struct and log when bits match?
+Use one bit for logging just the SQL statement executed
+(with no extra text) ideally in a way that lets the text
+file be parsed again later. Perhaps append ";\n\n\n" to each.
+Add parameter values and row count as comments afterwards?
+Use one bit for logging just Errors.
+
+Ability to remove a handle from the parents cache:
+ $sth->uncache;
+and $dbh->uncache; for connect_cached
+
+Add discard_pending_rows() as an alias
+for finish() - which will be deprecated.
+
+$sth->{ParamAttr} eg { "1" => SQL_VARCHAR, "2" => { TYPE=>SQL_VARCHAR, ora_type=>99 }};
+
+$h->{KidsHandles} = ref to cache (array or hash?)
+of weakrefs to child handles (bugs pre 5.8.5 with CLONE and weakrefs,
+see Perl changes 21936 and 22106)
+DESTROY could automatically disconnect/finish children
+
+Document DbTypeSubclass (ala DBIx::AnyDBD)
+Polish up and document _dbtype_names with an external interface and using get_info.
+
+FetchHashReuse attrib (=1 or ={}) copy from dbh to sth
+and use to optimise fetchrow_hash
+
+--- Changes that may affect driver authors
+
+Add PERL_NO_GET_CONTEXT for multiplicity/threads?
+force it for drivers?
+And enable xsbypass in dispatch if possible.
+
+Add log_where() to "trace level set to" log message.
+
+Add bind_col($n, \$foo, { OnFetch => sub { ... } });
+
+Add way to specify default bind_col attributes for each TYPE
+e.g. $dbh->{DefaultBindTypeArgs} = {
+ SQL_DATE => { TYPE => SQL_DATE },
+ SQL_DATETIME => { TYPE => SQL_DATETIME, OnFetch => \&foo },
+ };
+ # effectively automatically adds these as defaults:
+ $sth->bind_col(1, \$foo, {
+ %{ $dbh->{DefaultBindTypeArgs}{$sth->{TYPE}->[1]}, # <==
+ OnFetch => sub { ... }
+ }); # YYYY-MM-DD
+
+Method call for drivers to get (or indicate they've got) the sth metadata
+which can then be used to trigger default bind_cols.
+
+Add a handle flag to say that the driver has a hash that maps error
+codes into SQLSTATE values. The error event mechanism could check for
+the flag and lookup the SQLSTATE value for the error from the hash.
+Allow code hook as well. Maybe $dbh->{SQLSTATE_map} = code or hash ref
+
+Add minimum subset of ODBC3 SQLSTATE values that should be supported
+(and corresponding ODBC2 values?)
+
+Add more macro hooks to Driver.xst: ping, quote etc.
+
+Add dbh active checks to some more sth methods where reasonable.
+
+Define consise DBI<>DBD interface with view towards parrot.
+ note that parrot will use more method calls instead of
+ 'sideways' hooks into DBIS and the driver C code.
+DBI::DBD::Base module?
+Update DBI::DBD with overview and (at least) recommend Driver.xst strongly.
+Find XS drivers that don't use it and talk to authors.
+
+#define a large negative number to mean 'error' from st_execute and
+change *.xst to treat either that or -2 as an error. (The -2 is
+a transition for old drivers.)
+
+--- Other changes
+
+Simplify layering/subclassing of DBD's
+
+Reconsider clone() API
+
+See comment under $drh->$connect_meth in DBI.pm about $drh->errstr
+
+Ensure child $h has err reset after connect_cached() or prepare_cached()
+or else document that $DBI:err may be true after those methods even
+though they haven't failed. Umm. Fixed if $DBI::err isn't tied.
+
+Change t/zz_*_pp.t to be t/zXX_*.t where XX is a combination of:
+ - 'pp' (for DBI_PUREPERL=2)
+ - 'mx' (for DBI_AUTOPROXY=dbi:Multiplex:)
+ - 'pr' (for DBI_AUTOPROXY=dbi:Proxy:)
+mx and pr wouldn't both apply to the same test
+
+Add data structure describing attributes
+Use the data structure to replace similar data in Proxy, Multiplex,
+PurePerl and other places.
+
+Add OnConnect attribute to connect() esp. for connect_cached()
+
+Macro to get new statement handle for XS code
+
+Trace to tied file handle.
+
+Add method to try to make the connection (session) read-only.
+
+preparse() - incl ability to split statements on semicolon
+
+Hooks for method entry and exit.
+
+$dbh->{Statement} can be wrong because fetch doesn't update value
+maybe imp_dbh holds imp_sth (or inner handle) of last sth method
+called (if not DESTROY) and sth outer DESTROY clears it (to reduce ref count)
+Then $dbh->{LastSth} would work (returning outer handle if valid).
+Then $dbh->{Statement} would be the same as $dbh->{LastSth}->{Statement}
+Also $dbh->{ParamValues} would be the same as $dbh->{LastSth}->{ParamValues}.
+
+Remove dummy 'Switch' driver.
+
+Sponge behave_like - generalize into new_child()
+ copy RaiseError, PrintError, HandleError etc from the specified handle
+ but which attributes? LongReadLen, LongTruncOk etc? Presumably all
+ as we're acting as a proxy behind the scenes.
+ Should behave_like handle be dbh or sth or either or same as parent?
+
+Add per-handle debug file pointer:
+ NULL default => h->dbis->tracefp
+ if not NULL then dup() via PerlIO for child handles
+ close(h->tracefp) at end of DESTROY
+ macro to do (h->tracefp || h->dbis->tracefp)
+ $h->{TraceFileHandle} ? (enable "local $h->{TraceFileHandle} = ..."?)
+
+Move TIEHASH etc to XS (and to PurePerl)
+
+Change CachedKids to be a simple attribute cached in the handle hash
+to remove FETCH method call overhead in prepare_cached().
+
+--- Other things to consider
+
+Add $h->err_errstr_state method that returns all three in one go.
+
+Support async (non-blocking) mode
+
+Add $sql = $dbh->show_create($schema_object_name) to return statement
+that would create that schema object, where possible.
+
+Add $id = $dbh->get_session_id() and $dbh->kill_session_id($id).
+
+Study alternate DBI's:
+ ruby
+ python
+ php
+ others?
+ ADO object model
+identify any features we could usefully support and any incompatibilities etc
+
+Add DB version (major.minor ISA major) to DbSubType ISA tree.
+
+Add API to get table create statement (ala SHOW CREATE TABLE foo in MySQL).
+
+Consider closer mapping to SQL3 CLI API for driver API.
+
+Phalanx - test coverage
+
+=cut
+
+*** Small/quick/simple changes/checks ***
+
+fetchall_hashref for multiple keys - pending
+ my $hash_key_name = $sth->{FetchHashKeyName} || 'NAME';
+ my $names_hash = $sth->FETCH("${hash_key_name}_hash");
+
+ my @key_fields = (ref $key_field) ? @$key_field : ($key_field);
+ my @key_values;
+ foreach (@key_fields) {
+
+ my $index = $names_hash->{$_}; # perl index not column
+ ++$index if defined $index; # convert to column number
+ $index ||= $key_field if DBI::looks_like_number($key_field) && $key_field>=1;
+
+ push @key_values, undef;
+ $sth->bind_col($index, \$key_value[-1]) or return;
+ }
+
+ my $rows = {};
+ my $NAME = $sth->{$hash_key_name};
+ while (my $row = $sth->fetchrow_arrayref($hash_key_name)) {
+ my $ref = $rows;
+ $ref = $ref->{$_} ||= {} for @key_values;
+ @{$ref}{@$NAME} = @$row;
+ }
+ return \%rows;
+
+
+
+*** Assorted to-do items and random thoughts *** IN NO PARTICULAR ORDER ***
+
+DBIx::DWIW
+
+make lasth return outer handle?
+
+update lasth on return from method so handles used by the implementation
+of the called method don't affect it?
+
+document dbi_fetchall_arrayref_attr attr of selectall_arrayref().
+
+ODBC 3.5 date and intervals types and subtypes (from unixODBC?)
+http://www.vpservices.com/jeff/programs/SQL/docs/odbc-getinfo-msdn.html
+
+Proxy: allow config to specify SQL to allow/deny via regexen
+Docs for connect_cached and test with proxy.
+
+Attribute to prepare() to prefer lazy-prepare,
+e.g., don't talk to server till first execute
+or a statement handle attribute is accessed.
+
+How to report error from attribute FETCH as fetch method is marked
+keep_error? Perhaps some way to make the current keep_error value
+in the dispatch code available to change (via pointer in DBIS?) so
+a method can change the value of keep_error that's used when the
+method returns. Fixed since 1.43?
+
+BINDING:
+
+Add to docs & tutorial re wrong bind type on a param may cause
+index to not be used! (Find real examples first)
+check using EXPLAIN SELECT * WHERE int_indexed_col='42' vs =42.
+also WHERE int_column = '01' relies on db to convert '01' to an int
+rather than convert int_colum values to strings (which wouldn't match).
+
+> And note that if you are using bind_param_inout as 'bind_param_by_ref',
+> then the $maxlen parameter is redundant. I suspect all drivers could
+> implement bind_param_by_ref; most drivers, and specifically the Informix
+> driver, has no need for bind_param_inout as a mechanism for getting data
+> back from the database as there are no methods in the database which
+> work like that. With Informix, values are passed to the database for
+> placeholders, and values are returned through a cursor, and that's all.
+Okay. I'll take that as a vote for bind_param_by_ref as an alias for
+bind_param_inout. >>todo.
+
+bind_param_by_ref (or bind_param_byref) could be provided as a fallback
+method using a BeforeExecute hook to call bind_param with the 'current value'
+from the reference.
+
+Should ParamValues hold the value or the ref?
+Use ParamAttr to indicate byref?
+
+------
+
+OTHERS:
+
+Add method like
+ sub perform_transaction {
+ my ($dbh, $attr, $coderef, @args) = @_;
+ my $wantarray = wantarray;
+ my $use_transaction = 1;
+ my $orig_AutoCommit = $dbh->{AutoCommit};
+ if ($orig_AutoCommit) {
+ unless (eval { $dbh->{AutoCommit} = 0; 1 }) {
+ die unless $allow_non_transaction;
+ $use_transaction = 0;
+ }
+ }
+ local $dbh->{RaiseError} = 1;
+ eval {
+ @result = ($wantarray) ? $coderef->(@args) : scalar $coderef->(@args);
+ $dbh->commit if $use_transaction;
+ $attr->{OnCommit}->() if $attr->{OnCommit}->();
+ };
+ if ($@) {
+ local $@; protect original error
+ my $rv = eval { ($use_transaction) ? $dbh->rollback : 0 };
+ $attr->{OnRollback}->($rv) if $attr->{OnRollback};
+ }
+ die if $@; # propagate original error
+ $dbh->{AutoCommit} = 1 if $orig_AutoCommit;
+ return $result[0] unless $wantarray;
+ return @result;
+ }
+
+Change bind_column to save the info for get_fbav to use when
+first called. Thus making bind before execute work for all drivers.
+
+ODBC attribute defining if transactions are supported
+http://www.vpservices.com/jeff/programs/SQL/docs/odbc-getinfo-msdn.html
+
+Informix inspired changes?
+
+Add hook to DBI::DBD to write a myconfig.txt file into the
+source directory containing key driver and config info.
+
+dbish - state AutoCommit status clearly at connect time.
+(And try to set AutoCommit off in eval?)
+test shell "/connect user pass" etc
+
+check out http://tegan.deltanet.com/~phlip/DBUIframe.html
+
+Check DBD::Proxy connect&fetch latency (e.g. CGI use).
+
+****** Less urgent changes ******
+
+$dbh->ping($skip_seconds) - skip the ping if ping'd less than $skip_seconds ago
+and $h->err is false
+Change connect_cached() to use ping($skip_seconds || 1);
+
+
+$dbh->get_inner_handle / set_inner_handle
+ use to make $dbh->connect return same handle
+Hook to call code ref on each fetch, pass fbav ref
+datarow_array(), datarow_arrayref(), datarow_hashref()
+remove sth from prepare_cached cache.
+
+
+Give handles names: $h->{Id} ?
+Useful for reporting, Multiplex, DBD::AnyData etc etc
+May become useful for weakrefs etc
+
+--- Fetch scroll and row set
+
+fetch_scroll() handling via get_fbav.
+Also add:
+ row_array(offset)
+ row_arrayref(offset)
+ row_hashref(offset)
+get_fbav has three modes:
+ single row - return cached RV to same cached AV
+ alternate rows - return RV to AV[row % 2]
+ row set - return RV to AV[++row]
+
+Enable fetchall_arrayref() to reuse a cached rowset so the overhead
+of allocating and freeing the individual row arrays and the rowset
+array can be avoided. fetchall_arrayref would then return the same
+arrayref each time. Most useful when combined with $maxrows.
+
+Bless row into DBI::Row ?
+Bless row set into DBI::Rowset ?
+Give get/set access to entire rowset via method calls?
+ want to be able to plug in pre-loaded data row cache to new sth
+ so it'll return the same data.
+
+Add 'break handling' when field values change?
+Use two fbav's so 'previous record' is available.
+Define break fields and handlers.
+Call them via an alternate fetch_with_break method.
+Jan 2002: Also now see DBIx::FetchLoop (Brendan Fagan)
+Alternatively, and perferably, add sufficient hooks for this to be
+done efficiently externally.
+
+Devel::Leak integration?
+
+XA transaction interface. References:
+http://xapool.experlog.com/
+http://www.opengroup.org/publications/catalog/s423.htm
+http://www-106.ibm.com/developerworks/websphere/library/techarticles/0407_woolf/0407_woolf.html?ca=dnp-327
+
+Consider issues affecting OSMM score. Add relevant notes to docs.
+
+--- DBI::Profile
+
+Add %time to per-node DBI::Profile dump
+
+Add 'executer' and 'fetcher' method attributes and increment
+corresponding counters in DBIS when method with those attributes
+are called. When profiling record in the profile data the amount
+they have incremented.
+Add DBI_PROFILE option so count is executions and avg time can be
+totaltime/executions not totaltime/methodcalls.
+
+DBI::Profile: add simple way to normalise the sql (convert constants
+to placeholders) so profiling is more effective for drivers/applications
+which don't use placeholders. Requires preparse()?
+
+DBI::Profile: Add calc of approx XS method call and timing overhead
+by calling perl_call("DBI::dbi_time") x100 at boot time for profile,
+and add 1/100 (x2) to each sample. Beware Win32 where resolution
+is too small and overhead will be 0 normally but may be eg 100ms
+if overhead probe is on cusp of time unit.
+
+Add mechanism so "call path" can be included in the Path of the
+profile data. Something like "<basename>@<linenum>;..." or
+optionally just the basename part. (See log_where())
+
+Allow code ref in Path and use result as string for that element of the Path.
+
+Fix dbi_time for Windows by using or linking-to Time::HiRes code.
+
+---
+
+Add a C call to return boolean for is a number' for a given SV.
+Needs to do the right thing for a non-numeric string SV that's been
+tested in a numeric context (eg $a='S23'; foo() if $a==-1; $sth->execute($a))
+So if SvNVOK is true but the value is 0 then should also do looks_like_number()
+to be sure. [Does perl's looks_like_number() do this already, if not what code do
+callers of looks_like_number() use?]
+
+Record attrib STOREs so can be replayed/copied to new or cloned handle.
+
+--- Test suite (random thoughts beyond the basic architecture in my head)
+
+one test file = one scenario setup (fixture)
+cleanup (destroy all data, disconnect etc)
+repeat tests with different data types (CHAR vs NCHAR) (implies changing fixtures?)
+repeat tests with contextual changes (pureperl/proxy/multiplex etc)
+test with overloaded and other kinds of 'magical' values
+Good to have 'behavior' tests were the outcome is noted but doesn't
+ trigger failure e.g. limitation tests: data values out of range,
+ eg truncation, may or may not cause an error depending on the database.
+random order of subtests
+leak detection after cleanup
diff --git a/TODO_gofer.txt b/TODO_gofer.txt
new file mode 100644
index 0000000..d33116b
--- /dev/null
+++ b/TODO_gofer.txt
@@ -0,0 +1,56 @@
+Gofer TODOs:
+
+DBD::Gofer and http transport changes
+add comparisons with other proxies to gofer docs (see notes)
+ http://code.google.com/p/mod-ndb/
+ http://code.nytimes.com/projects/dbslayer
+update gofer pdf in distribution
+talk about multiple statements in single sql for gofer
+inbalance between two calls to _store_response_in_cache
+ - the call in transmit_request doesn't have the response_needs_retransmit logic
+
+Add server-side caching.
+ combine these:
+ my $request = $transport->thaw_request( $frozen_request, $serializer );
+ my $response = $executor->execute_request( $request );
+ my $frozen_response = $transport->freeze_response($response, $serializer);
+ into single method that first checks the cache and updates it if appropriate.
+ Different serializations will have different caches
+
+Add DBI::Gofer::Serialiser::MIME / Base64
+Add DBI::Gofer::Serialiser::JSON
+
+Gofer - allow dbh attrib changes after connect?
+ note them and pass in request as STORE method calls
+ but then gofer server need to reset them to restore dbh to original state
+ Or, change the attr in the connect() call, but that risks
+ bloating the number of cache dbh in the server.
+Gofer request flags for:
+ - return current executor stats as an attribute - handy for tests
+ - will accept streamed resultsets
+Add attr-passthru to prepare()? ie for gofer cache control & ReadOnly
+Define and document termind that first checks the cache and updates it if appropriate.
+ Different serializations will have different caches
+
+Add DBI::Gofer::Serialiser::MIME / Base64
+Add DBI::Gofer::Serialiser::JSON
+
+Gofer - allow dbh attrib changes after connect?
+ note them and pass in request as STORE method calls
+ but then gofer server need to reset them to restore dbh to original state
+ Or, change the attr in the connect() call, but that risks
+ bloating the number of cache dbh in the server.
+Gofer request flags for:
+ - return current executor stats as an attribute - handy for tests
+ - will accept streamed resultsets
+Add attr-passthru to prepare()? ie for gofer cache control & ReadOnly
+Define and document terminology for client and server ends
+Document user/passwd issues at the various levels of the gofer stack
+ Remove "Password" from connect attr if the same as $password arg
+Extract policy settings by parsing the pod
+Policy for dbh attr FETCH (ie example_driver_path)
+ or piggyback on skip_connect_check
+ could also remember which attr have been returned to us
+ so not bother FETCHing them (unless pedantic)
+Call method on transport failure so transport can cleanup/reset if it wants
+Gofer: gearman - need to disable coallesing for non-idempotent requests
diff --git a/dbd_xsh.h b/dbd_xsh.h
new file mode 100644
index 0000000..f238bb5
--- /dev/null
+++ b/dbd_xsh.h
@@ -0,0 +1,58 @@
+/* @(#)$Id: dbd_xsh.h 11724 2008-09-02 13:34:31Z mjevans $
+ *
+ * Copyright 2000-2002 Tim Bunce
+ * Copyright 2002 Jonathan Leffler
+ *
+ * These prototypes are for dbdimp.c funcs used in the XS file.
+ * These names are #defined to driver specific names by the
+ * dbdimp.h file in the driver source.
+ */
+
+#ifndef DBI_DBD_XSH_H
+#define DBI_DBD_XSH_H
+
+void dbd_init _((dbistate_t *dbistate));
+
+int dbd_discon_all _((SV *drh, imp_drh_t *imp_drh));
+SV *dbd_take_imp_data _((SV *h, imp_xxh_t *imp_xxh, void *foo));
+
+/* Support for dbd_dr_data_sources and dbd_db_do added to Driver.xst in DBI v1.33 */
+/* dbd_dr_data_sources: optional: defined by a driver that calls a C */
+/* function to get the list of data sources */
+AV *dbd_dr_data_sources(SV *drh, imp_drh_t *imp_drh, SV *attrs);
+
+int dbd_db_login6_sv _((SV *dbh, imp_dbh_t *imp_dbh, SV *dbname, SV *uid, SV *pwd, SV*attribs));
+int dbd_db_login6 _((SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *uid, char *pwd, SV*attribs));
+int dbd_db_login _((SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *uid, char *pwd)); /* deprecated */
+/* Note: interface of dbd_db_do changed in v1.33 */
+/* Old prototype: dbd_db_do _((SV *sv, char *statement)); */
+/* dbd_db_do: optional: defined by a driver if the DBI default version is too slow */
+int dbd_db_do4 _((SV *dbh, imp_dbh_t *imp_dbh, char *statement, SV *params));
+int dbd_db_commit _((SV *dbh, imp_dbh_t *imp_dbh));
+int dbd_db_rollback _((SV *dbh, imp_dbh_t *imp_dbh));
+int dbd_db_disconnect _((SV *dbh, imp_dbh_t *imp_dbh));
+void dbd_db_destroy _((SV *dbh, imp_dbh_t *imp_dbh));
+int dbd_db_STORE_attrib _((SV *dbh, imp_dbh_t *imp_dbh, SV *keysv, SV *valuesv));
+SV *dbd_db_FETCH_attrib _((SV *dbh, imp_dbh_t *imp_dbh, SV *keysv));
+SV *dbd_db_last_insert_id _((SV *dbh, imp_dbh_t *imp_dbh, SV *catalog, SV *schema, SV *table, SV *field, SV *attr));
+AV *dbd_db_data_sources _((SV *dbh, imp_dbh_t *imp_dbh, SV *attr));
+
+int dbd_st_prepare _((SV *sth, imp_sth_t *imp_sth, char *statement, SV *attribs));
+int dbd_st_prepare_sv _((SV *sth, imp_sth_t *imp_sth, SV *statement, SV *attribs));
+int dbd_st_rows _((SV *sth, imp_sth_t *imp_sth));
+int dbd_st_execute _((SV *sth, imp_sth_t *imp_sth));
+AV *dbd_st_fetch _((SV *sth, imp_sth_t *imp_sth));
+int dbd_st_finish3 _((SV *sth, imp_sth_t *imp_sth, int from_destroy));
+int dbd_st_finish _((SV *sth, imp_sth_t *imp_sth)); /* deprecated */
+void dbd_st_destroy _((SV *sth, imp_sth_t *imp_sth));
+int dbd_st_blob_read _((SV *sth, imp_sth_t *imp_sth,
+ int field, long offset, long len, SV *destrv, long destoffset));
+int dbd_st_STORE_attrib _((SV *sth, imp_sth_t *imp_sth, SV *keysv, SV *valuesv));
+SV *dbd_st_FETCH_attrib _((SV *sth, imp_sth_t *imp_sth, SV *keysv));
+SV *dbd_st_execute_for_fetch _((SV *sth, imp_sth_t *imp_sth, SV *fetch_tuple_sub, SV *tuple_status));
+
+int dbd_bind_ph _((SV *sth, imp_sth_t *imp_sth,
+ SV *param, SV *value, IV sql_type, SV *attribs,
+ int is_inout, IV maxlen));
+
+#endif /* end of dbd_xsh.h */
diff --git a/dbi_sql.h b/dbi_sql.h
new file mode 100644
index 0000000..1d7869d
--- /dev/null
+++ b/dbi_sql.h
@@ -0,0 +1,96 @@
+/* $Id: dbi_sql.h 2488 2006-02-07 22:24:43Z timbo $
+ *
+ * Copyright (c) 1997,1998,1999 Tim Bunce England
+ *
+ * See COPYRIGHT section in DBI.pm for usage and distribution rights.
+ */
+
+
+/* Some core SQL CLI standard (ODBC) declarations */
+#ifndef SQL_SUCCESS /* don't clash with ODBC based drivers */
+
+/* SQL datatype codes */
+#define SQL_GUID (-11)
+#define SQL_WLONGVARCHAR (-10)
+#define SQL_WVARCHAR (-9)
+#define SQL_WCHAR (-8)
+#define SQL_BIT (-7)
+#define SQL_TINYINT (-6)
+#define SQL_BIGINT (-5)
+#define SQL_LONGVARBINARY (-4)
+#define SQL_VARBINARY (-3)
+#define SQL_BINARY (-2)
+#define SQL_LONGVARCHAR (-1)
+#define SQL_UNKNOWN_TYPE 0
+#define SQL_ALL_TYPES 0
+#define SQL_CHAR 1
+#define SQL_NUMERIC 2
+#define SQL_DECIMAL 3
+#define SQL_INTEGER 4
+#define SQL_SMALLINT 5
+#define SQL_FLOAT 6
+#define SQL_REAL 7
+#define SQL_DOUBLE 8
+#define SQL_DATETIME 9
+#define SQL_DATE 9
+#define SQL_INTERVAL 10
+#define SQL_TIME 10
+#define SQL_TIMESTAMP 11
+#define SQL_VARCHAR 12
+#define SQL_BOOLEAN 16
+#define SQL_UDT 17
+#define SQL_UDT_LOCATOR 18
+#define SQL_ROW 19
+#define SQL_REF 20
+#define SQL_BLOB 30
+#define SQL_BLOB_LOCATOR 31
+#define SQL_CLOB 40
+#define SQL_CLOB_LOCATOR 41
+#define SQL_ARRAY 50
+#define SQL_ARRAY_LOCATOR 51
+#define SQL_MULTISET 55
+#define SQL_MULTISET_LOCATOR 56
+#define SQL_TYPE_DATE 91
+#define SQL_TYPE_TIME 92
+#define SQL_TYPE_TIMESTAMP 93
+#define SQL_TYPE_TIME_WITH_TIMEZONE 94
+#define SQL_TYPE_TIMESTAMP_WITH_TIMEZONE 95
+#define SQL_INTERVAL_YEAR 101
+#define SQL_INTERVAL_MONTH 102
+#define SQL_INTERVAL_DAY 103
+#define SQL_INTERVAL_HOUR 104
+#define SQL_INTERVAL_MINUTE 105
+#define SQL_INTERVAL_SECOND 106
+#define SQL_INTERVAL_YEAR_TO_MONTH 107
+#define SQL_INTERVAL_DAY_TO_HOUR 108
+#define SQL_INTERVAL_DAY_TO_MINUTE 109
+#define SQL_INTERVAL_DAY_TO_SECOND 110
+#define SQL_INTERVAL_HOUR_TO_MINUTE 111
+#define SQL_INTERVAL_HOUR_TO_SECOND 112
+#define SQL_INTERVAL_MINUTE_TO_SECOND 113
+
+
+/* Main return codes */
+#define SQL_ERROR (-1)
+#define SQL_SUCCESS 0
+#define SQL_SUCCESS_WITH_INFO 1
+#define SQL_NO_DATA_FOUND 100
+
+/*
+ * for ODBC SQL Cursor Types
+ */
+#define SQL_CURSOR_FORWARD_ONLY 0UL
+#define SQL_CURSOR_KEYSET_DRIVEN 1UL
+#define SQL_CURSOR_DYNAMIC 2UL
+#define SQL_CURSOR_STATIC 3UL
+#define SQL_CURSOR_TYPE_DEFAULT SQL_CURSOR_FORWARD_ONLY
+
+#endif /* SQL_SUCCESS */
+
+/* Handy macro for testing for success and success with info. */
+/* BEWARE that this macro can have side effects since rc appears twice! */
+/* So DONT use it as if(SQL_ok(func(...))) { ... } */
+#define SQL_ok(rc) ((rc)==SQL_SUCCESS || (rc)==SQL_SUCCESS_WITH_INFO)
+
+
+/* end of dbi_sql.h */
diff --git a/dbilogstrip.PL b/dbilogstrip.PL
new file mode 100644
index 0000000..3bad633
--- /dev/null
+++ b/dbilogstrip.PL
@@ -0,0 +1,71 @@
+# -*- perl -*-
+my $file = $ARGV[0] || 'dbilogstrip';
+
+my $script = <<'SCRIPT';
+~startperl~
+
+=head1 NAME
+
+dbilogstrip - filter to normalize DBI trace logs for diff'ing
+
+=head1 SYNOPSIS
+
+Read DBI trace file C<dbitrace.log> and write out a stripped version to C<dbitrace_stripped.log>
+
+ dbilogstrip dbitrace.log > dbitrace_stripped.log
+
+Run C<yourscript.pl> twice, each with different sets of arguments, with
+DBI_TRACE enabled. Filter the output and trace through C<dbilogstrip> into a
+separate file for each run. Then compare using diff. (This example assumes
+you're using a standard shell.)
+
+ DBI_TRACE=2 perl yourscript.pl ...args1... 2>&1 | dbilogstrip > dbitrace1.log
+ DBI_TRACE=2 perl yourscript.pl ...args2... 2>&1 | dbilogstrip > dbitrace2.log
+ diff -u dbitrace1.log dbitrace2.log
+
+=head1 DESCRIPTION
+
+Replaces any hex addresses, e.g, C<0x128f72ce> with C<0xN>.
+
+Replaces any references to process id or thread id, like C<pid#6254> with C<pidN>.
+
+So a DBI trace line like this:
+
+ -> STORE for DBD::DBM::st (DBI::st=HASH(0x19162a0)~0x191f9c8 'f_params' ARRAY(0x1922018)) thr#1800400
+
+will look like this:
+
+ -> STORE for DBD::DBM::st (DBI::st=HASH(0xN)~0xN 'f_params' ARRAY(0xN)) thrN
+
+=cut
+
+use strict;
+
+while (<>) {
+ # normalize hex addresses: 0xDEADHEAD => 0xN
+ s/ \b 0x [0-9a-f]+ /0xN/gx;
+ # normalize process and thread id number
+ s/ \b (pid|tid|thr) \W? \d+ /${1}N/gx;
+
+} continue {
+ print or die "-p destination: $!\n";
+}
+
+
+SCRIPT
+
+require Config;
+my $config = {};
+$config->{'startperl'} = $Config::Config{'startperl'};
+
+$script =~ s/\~(\w+)\~/$config->{$1}/eg;
+if (!(open(FILE, ">$file")) ||
+ !(print FILE $script) ||
+ !(close(FILE))) {
+ die "Error while writing $file: $!\n";
+}
+chmod 0755, $file;
+print "Extracted $file from ",__FILE__," with variable substitutions.\n";
+# syntax check resulting file, but only for developers
+exit 1 if -d ".svn" and system($^X, '-wc', '-Mblib', $file) != 0;
+
diff --git a/dbipport.h b/dbipport.h
new file mode 100644
index 0000000..b3de803
--- /dev/null
+++ b/dbipport.h
@@ -0,0 +1,7258 @@
+#if 0
+<<'SKIP';
+#endif
+/*
+----------------------------------------------------------------------
+
+ ppport.h -- Perl/Pollution/Portability Version 3.20
+
+ Automatically created by Devel::PPPort running under perl 5.010001.
+
+ Do NOT edit this file directly! -- Edit PPPort_pm.PL and the
+ includes in parts/inc/ instead.
+
+ Use 'perldoc ppport.h' to view the documentation below.
+
+----------------------------------------------------------------------
+
+SKIP
+
+=pod
+
+=head1 NAME
+
+ppport.h - Perl/Pollution/Portability version 3.20
+
+=head1 SYNOPSIS
+
+ perl ppport.h [options] [source files]
+
+ Searches current directory for files if no [source files] are given
+
+ --help show short help
+
+ --version show version
+
+ --patch=file write one patch file with changes
+ --copy=suffix write changed copies with suffix
+ --diff=program use diff program and options
+
+ --compat-version=version provide compatibility with Perl version
+ --cplusplus accept C++ comments
+
+ --quiet don't output anything except fatal errors
+ --nodiag don't show diagnostics
+ --nohints don't show hints
+ --nochanges don't suggest changes
+ --nofilter don't filter input files
+
+ --strip strip all script and doc functionality from
+ ppport.h
+
+ --list-provided list provided API
+ --list-unsupported list unsupported API
+ --api-info=name show Perl API portability information
+
+=head1 COMPATIBILITY
+
+This version of F<ppport.h> is designed to support operation with Perl
+installations back to 5.003, and has been tested up to 5.11.5.
+
+=head1 OPTIONS
+
+=head2 --help
+
+Display a brief usage summary.
+
+=head2 --version
+
+Display the version of F<ppport.h>.
+
+=head2 --patch=I<file>
+
+If this option is given, a single patch file will be created if
+any changes are suggested. This requires a working diff program
+to be installed on your system.
+
+=head2 --copy=I<suffix>
+
+If this option is given, a copy of each file will be saved with
+the given suffix that contains the suggested changes. This does
+not require any external programs. Note that this does not
+automagially add a dot between the original filename and the
+suffix. If you want the dot, you have to include it in the option
+argument.
+
+If neither C<--patch> or C<--copy> are given, the default is to
+simply print the diffs for each file. This requires either
+C<Text::Diff> or a C<diff> program to be installed.
+
+=head2 --diff=I<program>
+
+Manually set the diff program and options to use. The default
+is to use C<Text::Diff>, when installed, and output unified
+context diffs.
+
+=head2 --compat-version=I<version>
+
+Tell F<ppport.h> to check for compatibility with the given
+Perl version. The default is to check for compatibility with Perl
+version 5.003. You can use this option to reduce the output
+of F<ppport.h> if you intend to be backward compatible only
+down to a certain Perl version.
+
+=head2 --cplusplus
+
+Usually, F<ppport.h> will detect C++ style comments and
+replace them with C style comments for portability reasons.
+Using this option instructs F<ppport.h> to leave C++
+comments untouched.
+
+=head2 --quiet
+
+Be quiet. Don't print anything except fatal errors.
+
+=head2 --nodiag
+
+Don't output any diagnostic messages. Only portability
+alerts will be printed.
+
+=head2 --nohints
+
+Don't output any hints. Hints often contain useful portability
+notes. Warnings will still be displayed.
+
+=head2 --nochanges
+
+Don't suggest any changes. Only give diagnostic output and hints
+unless these are also deactivated.
+
+=head2 --nofilter
+
+Don't filter the list of input files. By default, files not looking
+like source code (i.e. not *.xs, *.c, *.cc, *.cpp or *.h) are skipped.
+
+=head2 --strip
+
+Strip all script and documentation functionality from F<ppport.h>.
+This reduces the size of F<ppport.h> dramatically and may be useful
+if you want to include F<ppport.h> in smaller modules without
+increasing their distribution size too much.
+
+The stripped F<ppport.h> will have a C<--unstrip> option that allows
+you to undo the stripping, but only if an appropriate C<Devel::PPPort>
+module is installed.
+
+=head2 --list-provided
+
+Lists the API elements for which compatibility is provided by
+F<ppport.h>. Also lists if it must be explicitly requested,
+if it has dependencies, and if there are hints or warnings for it.
+
+=head2 --list-unsupported
+
+Lists the API elements that are known not to be supported by
+F<ppport.h> and below which version of Perl they probably
+won't be available or work.
+
+=head2 --api-info=I<name>
+
+Show portability information for API elements matching I<name>.
+If I<name> is surrounded by slashes, it is interpreted as a regular
+expression.
+
+=head1 DESCRIPTION
+
+In order for a Perl extension (XS) module to be as portable as possible
+across differing versions of Perl itself, certain steps need to be taken.
+
+=over 4
+
+=item *
+
+Including this header is the first major one. This alone will give you
+access to a large part of the Perl API that hasn't been available in
+earlier Perl releases. Use
+
+ perl ppport.h --list-provided
+
+to see which API elements are provided by ppport.h.
+
+=item *
+
+You should avoid using deprecated parts of the API. For example, using
+global Perl variables without the C<PL_> prefix is deprecated. Also,
+some API functions used to have a C<perl_> prefix. Using this form is
+also deprecated. You can safely use the supported API, as F<ppport.h>
+will provide wrappers for older Perl versions.
+
+=item *
+
+If you use one of a few functions or variables that were not present in
+earlier versions of Perl, and that can't be provided using a macro, you
+have to explicitly request support for these functions by adding one or
+more C<#define>s in your source code before the inclusion of F<ppport.h>.
+
+These functions or variables will be marked C<explicit> in the list shown
+by C<--list-provided>.
+
+Depending on whether you module has a single or multiple files that
+use such functions or variables, you want either C<static> or global
+variants.
+
+For a C<static> function or variable (used only in a single source
+file), use:
+
+ #define NEED_function
+ #define NEED_variable
+
+For a global function or variable (used in multiple source files),
+use:
+
+ #define NEED_function_GLOBAL
+ #define NEED_variable_GLOBAL
+
+Note that you mustn't have more than one global request for the
+same function or variable in your project.
+
+ Function / Variable Static Request Global Request
+ -----------------------------------------------------------------------------------------
+ PL_parser NEED_PL_parser NEED_PL_parser_GLOBAL
+ PL_signals NEED_PL_signals NEED_PL_signals_GLOBAL
+ eval_pv() NEED_eval_pv NEED_eval_pv_GLOBAL
+ grok_bin() NEED_grok_bin NEED_grok_bin_GLOBAL
+ grok_hex() NEED_grok_hex NEED_grok_hex_GLOBAL
+ grok_number() NEED_grok_number NEED_grok_number_GLOBAL
+ grok_numeric_radix() NEED_grok_numeric_radix NEED_grok_numeric_radix_GLOBAL
+ grok_oct() NEED_grok_oct NEED_grok_oct_GLOBAL
+ load_module() NEED_load_module NEED_load_module_GLOBAL
+ my_snprintf() NEED_my_snprintf NEED_my_snprintf_GLOBAL
+ my_sprintf() NEED_my_sprintf NEED_my_sprintf_GLOBAL
+ my_strlcat() NEED_my_strlcat NEED_my_strlcat_GLOBAL
+ my_strlcpy() NEED_my_strlcpy NEED_my_strlcpy_GLOBAL
+ newCONSTSUB() NEED_newCONSTSUB NEED_newCONSTSUB_GLOBAL
+ newRV_noinc() NEED_newRV_noinc NEED_newRV_noinc_GLOBAL
+ newSV_type() NEED_newSV_type NEED_newSV_type_GLOBAL
+ newSVpvn_flags() NEED_newSVpvn_flags NEED_newSVpvn_flags_GLOBAL
+ newSVpvn_share() NEED_newSVpvn_share NEED_newSVpvn_share_GLOBAL
+ pv_display() NEED_pv_display NEED_pv_display_GLOBAL
+ pv_escape() NEED_pv_escape NEED_pv_escape_GLOBAL
+ pv_pretty() NEED_pv_pretty NEED_pv_pretty_GLOBAL
+ sv_2pv_flags() NEED_sv_2pv_flags NEED_sv_2pv_flags_GLOBAL
+ sv_2pvbyte() NEED_sv_2pvbyte NEED_sv_2pvbyte_GLOBAL
+ sv_catpvf_mg() NEED_sv_catpvf_mg NEED_sv_catpvf_mg_GLOBAL
+ sv_catpvf_mg_nocontext() NEED_sv_catpvf_mg_nocontext NEED_sv_catpvf_mg_nocontext_GLOBAL
+ sv_pvn_force_flags() NEED_sv_pvn_force_flags NEED_sv_pvn_force_flags_GLOBAL
+ sv_setpvf_mg() NEED_sv_setpvf_mg NEED_sv_setpvf_mg_GLOBAL
+ sv_setpvf_mg_nocontext() NEED_sv_setpvf_mg_nocontext NEED_sv_setpvf_mg_nocontext_GLOBAL
+ vload_module() NEED_vload_module NEED_vload_module_GLOBAL
+ vnewSVpvf() NEED_vnewSVpvf NEED_vnewSVpvf_GLOBAL
+ warner() NEED_warner NEED_warner_GLOBAL
+
+To avoid namespace conflicts, you can change the namespace of the
+explicitly exported functions / variables using the C<DPPP_NAMESPACE>
+macro. Just C<#define> the macro before including C<ppport.h>:
+
+ #define DPPP_NAMESPACE MyOwnNamespace_
+ #include "ppport.h"
+
+The default namespace is C<DPPP_>.
+
+=back
+
+The good thing is that most of the above can be checked by running
+F<ppport.h> on your source code. See the next section for
+details.
+
+=head1 EXAMPLES
+
+To verify whether F<ppport.h> is needed for your module, whether you
+should make any changes to your code, and whether any special defines
+should be used, F<ppport.h> can be run as a Perl script to check your
+source code. Simply say:
+
+ perl ppport.h
+
+The result will usually be a list of patches suggesting changes
+that should at least be acceptable, if not necessarily the most
+efficient solution, or a fix for all possible problems.
+
+If you know that your XS module uses features only available in
+newer Perl releases, if you're aware that it uses C++ comments,
+and if you want all suggestions as a single patch file, you could
+use something like this:
+
+ perl ppport.h --compat-version=5.6.0 --cplusplus --patch=test.diff
+
+If you only want your code to be scanned without any suggestions
+for changes, use:
+
+ perl ppport.h --nochanges
+
+You can specify a different C<diff> program or options, using
+the C<--diff> option:
+
+ perl ppport.h --diff='diff -C 10'
+
+This would output context diffs with 10 lines of context.
+
+If you want to create patched copies of your files instead, use:
+
+ perl ppport.h --copy=.new
+
+To display portability information for the C<newSVpvn> function,
+use:
+
+ perl ppport.h --api-info=newSVpvn
+
+Since the argument to C<--api-info> can be a regular expression,
+you can use
+
+ perl ppport.h --api-info=/_nomg$/
+
+to display portability information for all C<_nomg> functions or
+
+ perl ppport.h --api-info=/./
+
+to display information for all known API elements.
+
+=head1 BUGS
+
+If this version of F<ppport.h> is causing failure during
+the compilation of this module, please check if newer versions
+of either this module or C<Devel::PPPort> are available on CPAN
+before sending a bug report.
+
+If F<ppport.h> was generated using the latest version of
+C<Devel::PPPort> and is causing failure of this module, please
+file a bug report using the CPAN Request Tracker at L<http://rt.cpan.org/>.
+
+Please include the following information:
+
+=over 4
+
+=item 1.
+
+The complete output from running "perl -V"
+
+=item 2.
+
+This file.
+
+=item 3.
+
+The name and version of the module you were trying to build.
+
+=item 4.
+
+A full log of the build that failed.
+
+=item 5.
+
+Any other information that you think could be relevant.
+
+=back
+
+For the latest version of this code, please get the C<Devel::PPPort>
+module from CPAN.
+
+=head1 COPYRIGHT
+
+Version 3.x, Copyright (c) 2004-2010, Marcus Holland-Moritz.
+
+Version 2.x, Copyright (C) 2001, Paul Marquess.
+
+Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+See L<Devel::PPPort>.
+
+=cut
+
+use strict;
+
+# Disable broken TRIE-optimization
+BEGIN { eval '${^RE_TRIE_MAXBUF} = -1' if $] >= 5.009004 && $] <= 5.009005 }
+
+my $VERSION = 3.20;
+
+my %opt = (
+ quiet => 0,
+ diag => 1,
+ hints => 1,
+ changes => 1,
+ cplusplus => 0,
+ filter => 1,
+ strip => 0,
+ version => 0,
+);
+
+my($ppport) = $0 =~ /([\w.]+)$/;
+my $LF = '(?:\r\n|[\r\n])'; # line feed
+my $HS = "[ \t]"; # horizontal whitespace
+
+# Never use C comments in this file!
+my $ccs = '/'.'*';
+my $cce = '*'.'/';
+my $rccs = quotemeta $ccs;
+my $rcce = quotemeta $cce;
+
+eval {
+ require Getopt::Long;
+ Getopt::Long::GetOptions(\%opt, qw(
+ help quiet diag! filter! hints! changes! cplusplus strip version
+ patch=s copy=s diff=s compat-version=s
+ list-provided list-unsupported api-info=s
+ )) or usage();
+};
+
+if ($@ and grep /^-/, @ARGV) {
+ usage() if "@ARGV" =~ /^--?h(?:elp)?$/;
+ die "Getopt::Long not found. Please don't use any options.\n";
+}
+
+if ($opt{version}) {
+ print "This is $0 $VERSION.\n";
+ exit 0;
+}
+
+usage() if $opt{help};
+strip() if $opt{strip};
+
+if (exists $opt{'compat-version'}) {
+ my($r,$v,$s) = eval { parse_version($opt{'compat-version'}) };
+ if ($@) {
+ die "Invalid version number format: '$opt{'compat-version'}'\n";
+ }
+ die "Only Perl 5 is supported\n" if $r != 5;
+ die "Invalid version number: $opt{'compat-version'}\n" if $v >= 1000 || $s >= 1000;
+ $opt{'compat-version'} = sprintf "%d.%03d%03d", $r, $v, $s;
+}
+else {
+ $opt{'compat-version'} = 5;
+}
+
+my %API = map { /^(\w+)\|([^|]*)\|([^|]*)\|(\w*)$/
+ ? ( $1 => {
+ ($2 ? ( base => $2 ) : ()),
+ ($3 ? ( todo => $3 ) : ()),
+ (index($4, 'v') >= 0 ? ( varargs => 1 ) : ()),
+ (index($4, 'p') >= 0 ? ( provided => 1 ) : ()),
+ (index($4, 'n') >= 0 ? ( nothxarg => 1 ) : ()),
+ } )
+ : die "invalid spec: $_" } qw(
+AvFILLp|5.004050||p
+AvFILL|||
+BhkDISABLE||5.014000|
+BhkENABLE||5.014000|
+BhkENTRY_set||5.014000|
+BhkENTRY|||
+BhkFLAGS|||
+CALL_BLOCK_HOOKS|||
+CLASS|||n
+CPERLscope|5.005000||p
+CX_CURPAD_SAVE|||
+CX_CURPAD_SV|||
+CopFILEAV|5.006000||p
+CopFILEGV_set|5.006000||p
+CopFILEGV|5.006000||p
+CopFILESV|5.006000||p
+CopFILE_set|5.006000||p
+CopFILE|5.006000||p
+CopSTASHPV_set|5.006000||p
+CopSTASHPV|5.006000||p
+CopSTASH_eq|5.006000||p
+CopSTASH_set|5.006000||p
+CopSTASH|5.006000||p
+CopyD|5.009002||p
+Copy|||
+CvPADLIST|||
+CvSTASH|||
+CvWEAKOUTSIDE|||
+DEFSV_set|5.010001||p
+DEFSV|5.004050||p
+END_EXTERN_C|5.005000||p
+ENTER|||
+ERRSV|5.004050||p
+EXTEND|||
+EXTERN_C|5.005000||p
+F0convert|||n
+FREETMPS|||
+GIMME_V||5.004000|n
+GIMME|||n
+GROK_NUMERIC_RADIX|5.007002||p
+G_ARRAY|||
+G_DISCARD|||
+G_EVAL|||
+G_METHOD|5.006001||p
+G_NOARGS|||
+G_SCALAR|||
+G_VOID||5.004000|
+GetVars|||
+GvSVn|5.009003||p
+GvSV|||
+Gv_AMupdate||5.011000|
+HEf_SVKEY||5.004000|
+HeHASH||5.004000|
+HeKEY||5.004000|
+HeKLEN||5.004000|
+HePV||5.004000|
+HeSVKEY_force||5.004000|
+HeSVKEY_set||5.004000|
+HeSVKEY||5.004000|
+HeUTF8||5.010001|
+HeVAL||5.004000|
+HvENAME||5.013007|
+HvNAMELEN_get|5.009003||p
+HvNAME_get|5.009003||p
+HvNAME|||
+INT2PTR|5.006000||p
+IN_LOCALE_COMPILETIME|5.007002||p
+IN_LOCALE_RUNTIME|5.007002||p
+IN_LOCALE|5.007002||p
+IN_PERL_COMPILETIME|5.008001||p
+IS_NUMBER_GREATER_THAN_UV_MAX|5.007002||p
+IS_NUMBER_INFINITY|5.007002||p
+IS_NUMBER_IN_UV|5.007002||p
+IS_NUMBER_NAN|5.007003||p
+IS_NUMBER_NEG|5.007002||p
+IS_NUMBER_NOT_INT|5.007002||p
+IVSIZE|5.006000||p
+IVTYPE|5.006000||p
+IVdf|5.006000||p
+LEAVE|||
+LINKLIST||5.013006|
+LVRET|||
+MARK|||
+MULTICALL||5.014000|
+MY_CXT_CLONE|5.009002||p
+MY_CXT_INIT|5.007003||p
+MY_CXT|5.007003||p
+MoveD|5.009002||p
+Move|||
+NOOP|5.005000||p
+NUM2PTR|5.006000||p
+NVTYPE|5.006000||p
+NVef|5.006001||p
+NVff|5.006001||p
+NVgf|5.006001||p
+Newxc|5.009003||p
+Newxz|5.009003||p
+Newx|5.009003||p
+Nullav|||
+Nullch|||
+Nullcv|||
+Nullhv|||
+Nullsv|||
+OP_CLASS||5.013007|
+OP_DESC||5.007003|
+OP_NAME||5.007003|
+ORIGMARK|||
+PAD_BASE_SV|||
+PAD_CLONE_VARS|||
+PAD_COMPNAME_FLAGS|||
+PAD_COMPNAME_GEN_set|||
+PAD_COMPNAME_GEN|||
+PAD_COMPNAME_OURSTASH|||
+PAD_COMPNAME_PV|||
+PAD_COMPNAME_TYPE|||
+PAD_DUP|||
+PAD_RESTORE_LOCAL|||
+PAD_SAVE_LOCAL|||
+PAD_SAVE_SETNULLPAD|||
+PAD_SETSV|||
+PAD_SET_CUR_NOSAVE|||
+PAD_SET_CUR|||
+PAD_SVl|||
+PAD_SV|||
+PERLIO_FUNCS_CAST|5.009003||p
+PERLIO_FUNCS_DECL|5.009003||p
+PERL_ABS|5.008001||p
+PERL_BCDVERSION|5.014000||p
+PERL_GCC_BRACE_GROUPS_FORBIDDEN|5.008001||p
+PERL_HASH|5.004000||p
+PERL_INT_MAX|5.004000||p
+PERL_INT_MIN|5.004000||p
+PERL_LONG_MAX|5.004000||p
+PERL_LONG_MIN|5.004000||p
+PERL_MAGIC_arylen|5.007002||p
+PERL_MAGIC_backref|5.007002||p
+PERL_MAGIC_bm|5.007002||p
+PERL_MAGIC_collxfrm|5.007002||p
+PERL_MAGIC_dbfile|5.007002||p
+PERL_MAGIC_dbline|5.007002||p
+PERL_MAGIC_defelem|5.007002||p
+PERL_MAGIC_envelem|5.007002||p
+PERL_MAGIC_env|5.007002||p
+PERL_MAGIC_ext|5.007002||p
+PERL_MAGIC_fm|5.007002||p
+PERL_MAGIC_glob|5.014000||p
+PERL_MAGIC_isaelem|5.007002||p
+PERL_MAGIC_isa|5.007002||p
+PERL_MAGIC_mutex|5.014000||p
+PERL_MAGIC_nkeys|5.007002||p
+PERL_MAGIC_overload_elem|5.007002||p
+PERL_MAGIC_overload_table|5.007002||p
+PERL_MAGIC_overload|5.007002||p
+PERL_MAGIC_pos|5.007002||p
+PERL_MAGIC_qr|5.007002||p
+PERL_MAGIC_regdata|5.007002||p
+PERL_MAGIC_regdatum|5.007002||p
+PERL_MAGIC_regex_global|5.007002||p
+PERL_MAGIC_shared_scalar|5.007003||p
+PERL_MAGIC_shared|5.007003||p
+PERL_MAGIC_sigelem|5.007002||p
+PERL_MAGIC_sig|5.007002||p
+PERL_MAGIC_substr|5.007002||p
+PERL_MAGIC_sv|5.007002||p
+PERL_MAGIC_taint|5.007002||p
+PERL_MAGIC_tiedelem|5.007002||p
+PERL_MAGIC_tiedscalar|5.007002||p
+PERL_MAGIC_tied|5.007002||p
+PERL_MAGIC_utf8|5.008001||p
+PERL_MAGIC_uvar_elem|5.007003||p
+PERL_MAGIC_uvar|5.007002||p
+PERL_MAGIC_vec|5.007002||p
+PERL_MAGIC_vstring|5.008001||p
+PERL_PV_ESCAPE_ALL|5.009004||p
+PERL_PV_ESCAPE_FIRSTCHAR|5.009004||p
+PERL_PV_ESCAPE_NOBACKSLASH|5.009004||p
+PERL_PV_ESCAPE_NOCLEAR|5.009004||p
+PERL_PV_ESCAPE_QUOTE|5.009004||p
+PERL_PV_ESCAPE_RE|5.009005||p
+PERL_PV_ESCAPE_UNI_DETECT|5.009004||p
+PERL_PV_ESCAPE_UNI|5.009004||p
+PERL_PV_PRETTY_DUMP|5.009004||p
+PERL_PV_PRETTY_ELLIPSES|5.010000||p
+PERL_PV_PRETTY_LTGT|5.009004||p
+PERL_PV_PRETTY_NOCLEAR|5.010000||p
+PERL_PV_PRETTY_QUOTE|5.009004||p
+PERL_PV_PRETTY_REGPROP|5.009004||p
+PERL_QUAD_MAX|5.004000||p
+PERL_QUAD_MIN|5.004000||p
+PERL_REVISION|5.006000||p
+PERL_SCAN_ALLOW_UNDERSCORES|5.007003||p
+PERL_SCAN_DISALLOW_PREFIX|5.007003||p
+PERL_SCAN_GREATER_THAN_UV_MAX|5.007003||p
+PERL_SCAN_SILENT_ILLDIGIT|5.008001||p
+PERL_SHORT_MAX|5.004000||p
+PERL_SHORT_MIN|5.004000||p
+PERL_SIGNALS_UNSAFE_FLAG|5.008001||p
+PERL_SUBVERSION|5.006000||p
+PERL_SYS_INIT3||5.006000|
+PERL_SYS_INIT|||
+PERL_SYS_TERM||5.014000|
+PERL_UCHAR_MAX|5.004000||p
+PERL_UCHAR_MIN|5.004000||p
+PERL_UINT_MAX|5.004000||p
+PERL_UINT_MIN|5.004000||p
+PERL_ULONG_MAX|5.004000||p
+PERL_ULONG_MIN|5.004000||p
+PERL_UNUSED_ARG|5.009003||p
+PERL_UNUSED_CONTEXT|5.009004||p
+PERL_UNUSED_DECL|5.007002||p
+PERL_UNUSED_VAR|5.007002||p
+PERL_UQUAD_MAX|5.004000||p
+PERL_UQUAD_MIN|5.004000||p
+PERL_USE_GCC_BRACE_GROUPS|5.009004||p
+PERL_USHORT_MAX|5.004000||p
+PERL_USHORT_MIN|5.004000||p
+PERL_VERSION|5.006000||p
+PL_DBsignal|5.005000||p
+PL_DBsingle|||pn
+PL_DBsub|||pn
+PL_DBtrace|||pn
+PL_Sv|5.005000||p
+PL_bufend|5.014000||p
+PL_bufptr|5.014000||p
+PL_compiling|5.004050||p
+PL_copline|5.014000||p
+PL_curcop|5.004050||p
+PL_curstash|5.004050||p
+PL_debstash|5.004050||p
+PL_defgv|5.004050||p
+PL_diehook|5.004050||p
+PL_dirty|5.004050||p
+PL_dowarn|||pn
+PL_errgv|5.004050||p
+PL_error_count|5.014000||p
+PL_expect|5.014000||p
+PL_hexdigit|5.005000||p
+PL_hints|5.005000||p
+PL_in_my_stash|5.014000||p
+PL_in_my|5.014000||p
+PL_keyword_plugin||5.011002|
+PL_last_in_gv|||n
+PL_laststatval|5.005000||p
+PL_lex_state|5.014000||p
+PL_lex_stuff|5.014000||p
+PL_linestr|5.014000||p
+PL_modglobal||5.005000|n
+PL_na|5.004050||pn
+PL_no_modify|5.006000||p
+PL_ofsgv|||n
+PL_opfreehook||5.011000|n
+PL_parser|5.009005|5.009005|p
+PL_peepp||5.007003|n
+PL_perl_destruct_level|5.004050||p
+PL_perldb|5.004050||p
+PL_ppaddr|5.006000||p
+PL_rpeepp||5.013005|n
+PL_rsfp_filters|5.014000||p
+PL_rsfp|5.014000||p
+PL_rs|||n
+PL_signals|5.008001||p
+PL_stack_base|5.004050||p
+PL_stack_sp|5.004050||p
+PL_statcache|5.005000||p
+PL_stdingv|5.004050||p
+PL_sv_arenaroot|5.004050||p
+PL_sv_no|5.004050||pn
+PL_sv_undef|5.004050||pn
+PL_sv_yes|5.004050||pn
+PL_tainted|5.004050||p
+PL_tainting|5.004050||p
+PL_tokenbuf|5.014000||p
+POP_MULTICALL||5.014000|
+POPi|||n
+POPl|||n
+POPn|||n
+POPpbytex||5.007001|n
+POPpx||5.005030|n
+POPp|||n
+POPs|||n
+PTR2IV|5.006000||p
+PTR2NV|5.006000||p
+PTR2UV|5.006000||p
+PTR2nat|5.009003||p
+PTR2ul|5.007001||p
+PTRV|5.006000||p
+PUSHMARK|||
+PUSH_MULTICALL||5.014000|
+PUSHi|||
+PUSHmortal|5.009002||p
+PUSHn|||
+PUSHp|||
+PUSHs|||
+PUSHu|5.004000||p
+PUTBACK|||
+PerlIO_clearerr||5.007003|
+PerlIO_close||5.007003|
+PerlIO_context_layers||5.009004|
+PerlIO_eof||5.007003|
+PerlIO_error||5.007003|
+PerlIO_fileno||5.007003|
+PerlIO_fill||5.007003|
+PerlIO_flush||5.007003|
+PerlIO_get_base||5.007003|
+PerlIO_get_bufsiz||5.007003|
+PerlIO_get_cnt||5.007003|
+PerlIO_get_ptr||5.007003|
+PerlIO_read||5.007003|
+PerlIO_seek||5.007003|
+PerlIO_set_cnt||5.007003|
+PerlIO_set_ptrcnt||5.007003|
+PerlIO_setlinebuf||5.007003|
+PerlIO_stderr||5.007003|
+PerlIO_stdin||5.007003|
+PerlIO_stdout||5.007003|
+PerlIO_tell||5.007003|
+PerlIO_unread||5.007003|
+PerlIO_write||5.007003|
+Perl_signbit||5.009005|n
+PoisonFree|5.009004||p
+PoisonNew|5.009004||p
+PoisonWith|5.009004||p
+Poison|5.008000||p
+RETVAL|||n
+Renewc|||
+Renew|||
+SAVECLEARSV|||
+SAVECOMPPAD|||
+SAVEPADSV|||
+SAVETMPS|||
+SAVE_DEFSV|5.004050||p
+SPAGAIN|||
+SP|||
+START_EXTERN_C|5.005000||p
+START_MY_CXT|5.007003||p
+STMT_END|||p
+STMT_START|||p
+STR_WITH_LEN|5.009003||p
+ST|||
+SV_CONST_RETURN|5.009003||p
+SV_COW_DROP_PV|5.008001||p
+SV_COW_SHARED_HASH_KEYS|5.009005||p
+SV_GMAGIC|5.007002||p
+SV_HAS_TRAILING_NUL|5.009004||p
+SV_IMMEDIATE_UNREF|5.007001||p
+SV_MUTABLE_RETURN|5.009003||p
+SV_NOSTEAL|5.009002||p
+SV_SMAGIC|5.009003||p
+SV_UTF8_NO_ENCODING|5.008001||p
+SVfARG|5.009005||p
+SVf_UTF8|5.006000||p
+SVf|5.006000||p
+SVt_IV|||
+SVt_NV|||
+SVt_PVAV|||
+SVt_PVCV|||
+SVt_PVHV|||
+SVt_PVMG|||
+SVt_PV|||
+Safefree|||
+Slab_Alloc|||
+Slab_Free|||
+Slab_to_rw|||
+StructCopy|||
+SvCUR_set|||
+SvCUR|||
+SvEND|||
+SvGAMAGIC||5.006001|
+SvGETMAGIC|5.004050||p
+SvGROW|||
+SvIOK_UV||5.006000|
+SvIOK_notUV||5.006000|
+SvIOK_off|||
+SvIOK_only_UV||5.006000|
+SvIOK_only|||
+SvIOK_on|||
+SvIOKp|||
+SvIOK|||
+SvIVX|||
+SvIV_nomg|5.009001||p
+SvIV_set|||
+SvIVx|||
+SvIV|||
+SvIsCOW_shared_hash||5.008003|
+SvIsCOW||5.008003|
+SvLEN_set|||
+SvLEN|||
+SvLOCK||5.007003|
+SvMAGIC_set|5.009003||p
+SvNIOK_off|||
+SvNIOKp|||
+SvNIOK|||
+SvNOK_off|||
+SvNOK_only|||
+SvNOK_on|||
+SvNOKp|||
+SvNOK|||
+SvNVX|||
+SvNV_nomg||5.013002|
+SvNV_set|||
+SvNVx|||
+SvNV|||
+SvOK|||
+SvOOK_offset||5.011000|
+SvOOK|||
+SvPOK_off|||
+SvPOK_only_UTF8||5.006000|
+SvPOK_only|||
+SvPOK_on|||
+SvPOKp|||
+SvPOK|||
+SvPVX_const|5.009003||p
+SvPVX_mutable|5.009003||p
+SvPVX|||
+SvPV_const|5.009003||p
+SvPV_flags_const_nolen|5.009003||p
+SvPV_flags_const|5.009003||p
+SvPV_flags_mutable|5.009003||p
+SvPV_flags|5.007002||p
+SvPV_force_flags_mutable|5.009003||p
+SvPV_force_flags_nolen|5.009003||p
+SvPV_force_flags|5.007002||p
+SvPV_force_mutable|5.009003||p
+SvPV_force_nolen|5.009003||p
+SvPV_force_nomg_nolen|5.009003||p
+SvPV_force_nomg|5.007002||p
+SvPV_force|||p
+SvPV_mutable|5.009003||p
+SvPV_nolen_const|5.009003||p
+SvPV_nolen|5.006000||p
+SvPV_nomg_const_nolen|5.009003||p
+SvPV_nomg_const|5.009003||p
+SvPV_nomg_nolen||5.013007|
+SvPV_nomg|5.007002||p
+SvPV_renew|5.009003||p
+SvPV_set|||
+SvPVbyte_force||5.009002|
+SvPVbyte_nolen||5.006000|
+SvPVbytex_force||5.006000|
+SvPVbytex||5.006000|
+SvPVbyte|5.006000||p
+SvPVutf8_force||5.006000|
+SvPVutf8_nolen||5.006000|
+SvPVutf8x_force||5.006000|
+SvPVutf8x||5.006000|
+SvPVutf8||5.006000|
+SvPVx|||
+SvPV|||
+SvREFCNT_dec|||
+SvREFCNT_inc_NN|5.009004||p
+SvREFCNT_inc_simple_NN|5.009004||p
+SvREFCNT_inc_simple_void_NN|5.009004||p
+SvREFCNT_inc_simple_void|5.009004||p
+SvREFCNT_inc_simple|5.009004||p
+SvREFCNT_inc_void_NN|5.009004||p
+SvREFCNT_inc_void|5.009004||p
+SvREFCNT_inc|||p
+SvREFCNT|||
+SvROK_off|||
+SvROK_on|||
+SvROK|||
+SvRV_set|5.009003||p
+SvRV|||
+SvRXOK||5.009005|
+SvRX||5.009005|
+SvSETMAGIC|||
+SvSHARED_HASH|5.009003||p
+SvSHARE||5.007003|
+SvSTASH_set|5.009003||p
+SvSTASH|||
+SvSetMagicSV_nosteal||5.004000|
+SvSetMagicSV||5.004000|
+SvSetSV_nosteal||5.004000|
+SvSetSV|||
+SvTAINTED_off||5.004000|
+SvTAINTED_on||5.004000|
+SvTAINTED||5.004000|
+SvTAINT|||
+SvTRUE_nomg||5.013006|
+SvTRUE|||
+SvTYPE|||
+SvUNLOCK||5.007003|
+SvUOK|5.007001|5.006000|p
+SvUPGRADE|||
+SvUTF8_off||5.006000|
+SvUTF8_on||5.006000|
+SvUTF8||5.006000|
+SvUVXx|5.004000||p
+SvUVX|5.004000||p
+SvUV_nomg|5.009001||p
+SvUV_set|5.009003||p
+SvUVx|5.004000||p
+SvUV|5.004000||p
+SvVOK||5.008001|
+SvVSTRING_mg|5.009004||p
+THIS|||n
+UNDERBAR|5.009002||p
+UTF8_MAXBYTES|5.009002||p
+UVSIZE|5.006000||p
+UVTYPE|5.006000||p
+UVXf|5.007001||p
+UVof|5.006000||p
+UVuf|5.006000||p
+UVxf|5.006000||p
+WARN_ALL|5.006000||p
+WARN_AMBIGUOUS|5.006000||p
+WARN_ASSERTIONS|5.014000||p
+WARN_BAREWORD|5.006000||p
+WARN_CLOSED|5.006000||p
+WARN_CLOSURE|5.006000||p
+WARN_DEBUGGING|5.006000||p
+WARN_DEPRECATED|5.006000||p
+WARN_DIGIT|5.006000||p
+WARN_EXEC|5.006000||p
+WARN_EXITING|5.006000||p
+WARN_GLOB|5.006000||p
+WARN_INPLACE|5.006000||p
+WARN_INTERNAL|5.006000||p
+WARN_IO|5.006000||p
+WARN_LAYER|5.008000||p
+WARN_MALLOC|5.006000||p
+WARN_MISC|5.006000||p
+WARN_NEWLINE|5.006000||p
+WARN_NUMERIC|5.006000||p
+WARN_ONCE|5.006000||p
+WARN_OVERFLOW|5.006000||p
+WARN_PACK|5.006000||p
+WARN_PARENTHESIS|5.006000||p
+WARN_PIPE|5.006000||p
+WARN_PORTABLE|5.006000||p
+WARN_PRECEDENCE|5.006000||p
+WARN_PRINTF|5.006000||p
+WARN_PROTOTYPE|5.006000||p
+WARN_QW|5.006000||p
+WARN_RECURSION|5.006000||p
+WARN_REDEFINE|5.006000||p
+WARN_REGEXP|5.006000||p
+WARN_RESERVED|5.006000||p
+WARN_SEMICOLON|5.006000||p
+WARN_SEVERE|5.006000||p
+WARN_SIGNAL|5.006000||p
+WARN_SUBSTR|5.006000||p
+WARN_SYNTAX|5.006000||p
+WARN_TAINT|5.006000||p
+WARN_THREADS|5.008000||p
+WARN_UNINITIALIZED|5.006000||p
+WARN_UNOPENED|5.006000||p
+WARN_UNPACK|5.006000||p
+WARN_UNTIE|5.006000||p
+WARN_UTF8|5.006000||p
+WARN_VOID|5.006000||p
+XCPT_CATCH|5.009002||p
+XCPT_RETHROW|5.009002||p
+XCPT_TRY_END|5.009002||p
+XCPT_TRY_START|5.009002||p
+XPUSHi|||
+XPUSHmortal|5.009002||p
+XPUSHn|||
+XPUSHp|||
+XPUSHs|||
+XPUSHu|5.004000||p
+XSPROTO|5.010000||p
+XSRETURN_EMPTY|||
+XSRETURN_IV|||
+XSRETURN_NO|||
+XSRETURN_NV|||
+XSRETURN_PV|||
+XSRETURN_UNDEF|||
+XSRETURN_UV|5.008001||p
+XSRETURN_YES|||
+XSRETURN|||p
+XST_mIV|||
+XST_mNO|||
+XST_mNV|||
+XST_mPV|||
+XST_mUNDEF|||
+XST_mUV|5.008001||p
+XST_mYES|||
+XS_APIVERSION_BOOTCHECK||5.013004|
+XS_VERSION_BOOTCHECK|||
+XS_VERSION|||
+XSprePUSH|5.006000||p
+XS|||
+XopDISABLE||5.014000|
+XopENABLE||5.014000|
+XopENTRY_set||5.014000|
+XopENTRY||5.014000|
+XopFLAGS||5.013007|
+ZeroD|5.009002||p
+Zero|||
+_aMY_CXT|5.007003||p
+_append_range_to_invlist|||
+_new_invlist|||
+_pMY_CXT|5.007003||p
+_swash_inversion_hash|||
+_swash_to_invlist|||
+aMY_CXT_|5.007003||p
+aMY_CXT|5.007003||p
+aTHXR_|5.014000||p
+aTHXR|5.014000||p
+aTHX_|5.006000||p
+aTHX|5.006000||p
+add_alternate|||
+add_cp_to_invlist|||
+add_data|||n
+add_range_to_invlist|||
+add_utf16_textfilter|||
+addmad|||
+allocmy|||
+amagic_call|||
+amagic_cmp_locale|||
+amagic_cmp|||
+amagic_deref_call||5.013007|
+amagic_i_ncmp|||
+amagic_ncmp|||
+anonymise_cv_maybe|||
+any_dup|||
+ao|||
+append_madprops|||
+apply_attrs_my|||
+apply_attrs_string||5.006001|
+apply_attrs|||
+apply|||
+assert_uft8_cache_coherent|||
+atfork_lock||5.007003|n
+atfork_unlock||5.007003|n
+av_arylen_p||5.009003|
+av_clear|||
+av_create_and_push||5.009005|
+av_create_and_unshift_one||5.009005|
+av_delete||5.006000|
+av_exists||5.006000|
+av_extend|||
+av_fetch|||
+av_fill|||
+av_iter_p||5.011000|
+av_len|||
+av_make|||
+av_pop|||
+av_push|||
+av_reify|||
+av_shift|||
+av_store|||
+av_undef|||
+av_unshift|||
+ax|||n
+bad_type|||
+bind_match|||
+block_end|||
+block_gimme||5.004000|
+block_start|||
+blockhook_register||5.013003|
+boolSV|5.004000||p
+boot_core_PerlIO|||
+boot_core_UNIVERSAL|||
+boot_core_mro|||
+bytes_cmp_utf8||5.013007|
+bytes_from_utf8||5.007001|
+bytes_to_uni|||n
+bytes_to_utf8||5.006001|
+call_argv|5.006000||p
+call_atexit||5.006000|
+call_list||5.004000|
+call_method|5.006000||p
+call_pv|5.006000||p
+call_sv|5.006000||p
+caller_cx||5.013005|
+calloc||5.007002|n
+cando|||
+cast_i32||5.006000|
+cast_iv||5.006000|
+cast_ulong||5.006000|
+cast_uv||5.006000|
+check_type_and_open|||
+check_uni|||
+check_utf8_print|||
+checkcomma|||
+checkposixcc|||
+ckWARN|5.006000||p
+ck_entersub_args_list||5.013006|
+ck_entersub_args_proto_or_list||5.013006|
+ck_entersub_args_proto||5.013006|
+ck_warner_d||5.011001|v
+ck_warner||5.011001|v
+ckwarn_common|||
+ckwarn_d||5.009003|
+ckwarn||5.009003|
+cl_and|||n
+cl_anything|||n
+cl_init|||n
+cl_is_anything|||n
+cl_or|||n
+clear_placeholders|||
+clone_params_del|||n
+clone_params_new|||n
+closest_cop|||
+convert|||
+cop_free|||
+cop_hints_2hv||5.013007|
+cop_hints_fetch_pvn||5.013007|
+cop_hints_fetch_pvs||5.013007|
+cop_hints_fetch_pv||5.013007|
+cop_hints_fetch_sv||5.013007|
+cophh_2hv||5.013007|
+cophh_copy||5.013007|
+cophh_delete_pvn||5.013007|
+cophh_delete_pvs||5.013007|
+cophh_delete_pv||5.013007|
+cophh_delete_sv||5.013007|
+cophh_fetch_pvn||5.013007|
+cophh_fetch_pvs||5.013007|
+cophh_fetch_pv||5.013007|
+cophh_fetch_sv||5.013007|
+cophh_free||5.013007|
+cophh_new_empty||5.014000|
+cophh_store_pvn||5.013007|
+cophh_store_pvs||5.013007|
+cophh_store_pv||5.013007|
+cophh_store_sv||5.013007|
+cr_textfilter|||
+create_eval_scope|||
+croak_no_modify||5.013003|
+croak_nocontext|||vn
+croak_sv||5.013001|
+croak_xs_usage||5.010001|
+croak|||v
+csighandler||5.009003|n
+curmad|||
+curse|||
+custom_op_desc||5.007003|
+custom_op_name||5.007003|
+custom_op_register||5.013007|
+custom_op_xop||5.013007|
+cv_ckproto_len|||
+cv_clone|||
+cv_const_sv||5.004000|
+cv_dump|||
+cv_get_call_checker||5.013006|
+cv_set_call_checker||5.013006|
+cv_undef|||
+cvgv_set|||
+cvstash_set|||
+cx_dump||5.005000|
+cx_dup|||
+cxinc|||
+dAXMARK|5.009003||p
+dAX|5.007002||p
+dITEMS|5.007002||p
+dMARK|||
+dMULTICALL||5.009003|
+dMY_CXT_SV|5.007003||p
+dMY_CXT|5.007003||p
+dNOOP|5.006000||p
+dORIGMARK|||
+dSP|||
+dTHR|5.004050||p
+dTHXR|5.014000||p
+dTHXa|5.006000||p
+dTHXoa|5.006000||p
+dTHX|5.006000||p
+dUNDERBAR|5.009002||p
+dVAR|5.009003||p
+dXCPT|5.009002||p
+dXSARGS|||
+dXSI32|||
+dXSTARG|5.006000||p
+deb_curcv|||
+deb_nocontext|||vn
+deb_stack_all|||
+deb_stack_n|||
+debop||5.005000|
+debprofdump||5.005000|
+debprof|||
+debstackptrs||5.007003|
+debstack||5.007003|
+debug_start_match|||
+deb||5.007003|v
+del_sv|||
+delete_eval_scope|||
+delimcpy||5.004000|n
+deprecate_commaless_var_list|||
+despatch_signals||5.007001|
+destroy_matcher|||
+die_nocontext|||vn
+die_sv||5.013001|
+die_unwind|||
+die|||v
+dirp_dup|||
+div128|||
+djSP|||
+do_aexec5|||
+do_aexec|||
+do_aspawn|||
+do_binmode||5.004050|
+do_chomp|||
+do_close|||
+do_delete_local|||
+do_dump_pad|||
+do_eof|||
+do_exec3|||
+do_execfree|||
+do_exec|||
+do_gv_dump||5.006000|
+do_gvgv_dump||5.006000|
+do_hv_dump||5.006000|
+do_ipcctl|||
+do_ipcget|||
+do_join|||
+do_magic_dump||5.006000|
+do_msgrcv|||
+do_msgsnd|||
+do_oddball|||
+do_op_dump||5.006000|
+do_op_xmldump|||
+do_open9||5.006000|
+do_openn||5.007001|
+do_open||5.004000|
+do_pmop_dump||5.006000|
+do_pmop_xmldump|||
+do_print|||
+do_readline|||
+do_seek|||
+do_semop|||
+do_shmio|||
+do_smartmatch|||
+do_spawn_nowait|||
+do_spawn|||
+do_sprintf|||
+do_sv_dump||5.006000|
+do_sysseek|||
+do_tell|||
+do_trans_complex_utf8|||
+do_trans_complex|||
+do_trans_count_utf8|||
+do_trans_count|||
+do_trans_simple_utf8|||
+do_trans_simple|||
+do_trans|||
+do_vecget|||
+do_vecset|||
+do_vop|||
+docatch|||
+doeval|||
+dofile|||
+dofindlabel|||
+doform|||
+doing_taint||5.008001|n
+dooneliner|||
+doopen_pm|||
+doparseform|||
+dopoptoeval|||
+dopoptogiven|||
+dopoptolabel|||
+dopoptoloop|||
+dopoptosub_at|||
+dopoptowhen|||
+doref||5.009003|
+dounwind|||
+dowantarray|||
+dump_all_perl|||
+dump_all||5.006000|
+dump_eval||5.006000|
+dump_exec_pos|||
+dump_fds|||
+dump_form||5.006000|
+dump_indent||5.006000|v
+dump_mstats|||
+dump_packsubs_perl|||
+dump_packsubs||5.006000|
+dump_sub_perl|||
+dump_sub||5.006000|
+dump_sv_child|||
+dump_trie_interim_list|||
+dump_trie_interim_table|||
+dump_trie|||
+dump_vindent||5.006000|
+dumpuntil|||
+dup_attrlist|||
+emulate_cop_io|||
+eval_pv|5.006000||p
+eval_sv|5.006000||p
+exec_failed|||
+expect_number|||
+fbm_compile||5.005000|
+fbm_instr||5.005000|
+feature_is_enabled|||
+fetch_cop_label||5.011000|
+filter_add|||
+filter_del|||
+filter_gets|||
+filter_read|||
+find_and_forget_pmops|||
+find_array_subscript|||
+find_beginning|||
+find_byclass|||
+find_hash_subscript|||
+find_in_my_stash|||
+find_runcv||5.008001|
+find_rundefsvoffset||5.009002|
+find_rundefsv||5.013002|
+find_script|||
+find_uninit_var|||
+first_symbol|||n
+foldEQ_latin1||5.013008|n
+foldEQ_locale||5.013002|n
+foldEQ_utf8_flags||5.013010|
+foldEQ_utf8||5.013002|
+foldEQ||5.013002|n
+fold_constants|||
+forbid_setid|||
+force_ident|||
+force_list|||
+force_next|||
+force_strict_version|||
+force_version|||
+force_word|||
+forget_pmop|||
+form_nocontext|||vn
+form||5.004000|v
+fp_dup|||
+fprintf_nocontext|||vn
+free_global_struct|||
+free_tied_hv_pool|||
+free_tmps|||
+gen_constant_list|||
+get_aux_mg|||
+get_av|5.006000||p
+get_context||5.006000|n
+get_cvn_flags|5.009005||p
+get_cvs|5.011000||p
+get_cv|5.006000||p
+get_db_sub|||
+get_debug_opts|||
+get_hash_seed|||
+get_hv|5.006000||p
+get_mstats|||
+get_no_modify|||
+get_num|||
+get_op_descs||5.005000|
+get_op_names||5.005000|
+get_opargs|||
+get_ppaddr||5.006000|
+get_re_arg|||
+get_sv|5.006000||p
+get_vtbl||5.005030|
+getcwd_sv||5.007002|
+getenv_len|||
+glob_2number|||
+glob_assign_glob|||
+glob_assign_ref|||
+gp_dup|||
+gp_free|||
+gp_ref|||
+grok_bin|5.007003||p
+grok_bslash_c|||
+grok_bslash_o|||
+grok_hex|5.007003||p
+grok_number|5.007002||p
+grok_numeric_radix|5.007002||p
+grok_oct|5.007003||p
+group_end|||
+gv_AVadd|||
+gv_HVadd|||
+gv_IOadd|||
+gv_SVadd|||
+gv_add_by_type||5.011000|
+gv_autoload4||5.004000|
+gv_check|||
+gv_const_sv||5.009003|
+gv_dump||5.006000|
+gv_efullname3||5.004000|
+gv_efullname4||5.006001|
+gv_efullname|||
+gv_ename|||
+gv_fetchfile_flags||5.009005|
+gv_fetchfile|||
+gv_fetchmeth_autoload||5.007003|
+gv_fetchmethod_autoload||5.004000|
+gv_fetchmethod_flags||5.011000|
+gv_fetchmethod|||
+gv_fetchmeth|||
+gv_fetchpvn_flags|5.009002||p
+gv_fetchpvs|5.009004||p
+gv_fetchpv|||
+gv_fetchsv|5.009002||p
+gv_fullname3||5.004000|
+gv_fullname4||5.006001|
+gv_fullname|||
+gv_get_super_pkg|||
+gv_handler||5.007001|
+gv_init_sv|||
+gv_init|||
+gv_magicalize_isa|||
+gv_magicalize_overload|||
+gv_name_set||5.009004|
+gv_stashpvn|5.004000||p
+gv_stashpvs|5.009003||p
+gv_stashpv|||
+gv_stashsv|||
+gv_try_downgrade|||
+he_dup|||
+hek_dup|||
+hfreeentries|||
+hsplit|||
+hv_assert|||
+hv_auxinit|||n
+hv_backreferences_p|||
+hv_clear_placeholders||5.009001|
+hv_clear|||
+hv_common_key_len||5.010000|
+hv_common||5.010000|
+hv_copy_hints_hv||5.009004|
+hv_delayfree_ent||5.004000|
+hv_delete_common|||
+hv_delete_ent||5.004000|
+hv_delete|||
+hv_eiter_p||5.009003|
+hv_eiter_set||5.009003|
+hv_ename_add|||
+hv_ename_delete|||
+hv_exists_ent||5.004000|
+hv_exists|||
+hv_fetch_ent||5.004000|
+hv_fetchs|5.009003||p
+hv_fetch|||
+hv_fill||5.013002|
+hv_free_ent||5.004000|
+hv_iterinit|||
+hv_iterkeysv||5.004000|
+hv_iterkey|||
+hv_iternext_flags||5.008000|
+hv_iternextsv|||
+hv_iternext|||
+hv_iterval|||
+hv_kill_backrefs|||
+hv_ksplit||5.004000|
+hv_magic_check|||n
+hv_magic|||
+hv_name_set||5.009003|
+hv_notallowed|||
+hv_placeholders_get||5.009003|
+hv_placeholders_p||5.009003|
+hv_placeholders_set||5.009003|
+hv_riter_p||5.009003|
+hv_riter_set||5.009003|
+hv_scalar||5.009001|
+hv_store_ent||5.004000|
+hv_store_flags||5.008000|
+hv_stores|5.009004||p
+hv_store|||
+hv_undef_flags|||
+hv_undef|||
+ibcmp_locale||5.004000|
+ibcmp_utf8||5.007003|
+ibcmp|||
+incline|||
+incpush_if_exists|||
+incpush_use_sep|||
+incpush|||
+ingroup|||
+init_argv_symbols|||
+init_dbargs|||
+init_debugger|||
+init_global_struct|||
+init_i18nl10n||5.006000|
+init_i18nl14n||5.006000|
+init_ids|||
+init_interp|||
+init_main_stash|||
+init_perllib|||
+init_postdump_symbols|||
+init_predump_symbols|||
+init_stacks||5.005000|
+init_tm||5.007002|
+instr|||n
+intro_my|||
+intuit_method|||
+intuit_more|||
+invert|||
+invlist_array|||
+invlist_destroy|||
+invlist_extend|||
+invlist_intersection|||
+invlist_len|||
+invlist_max|||
+invlist_set_array|||
+invlist_set_len|||
+invlist_set_max|||
+invlist_trim|||
+invlist_union|||
+invoke_exception_hook|||
+io_close|||
+isALNUMC|5.006000||p
+isALPHA|||
+isASCII|5.006000||p
+isBLANK|5.006001||p
+isCNTRL|5.006000||p
+isDIGIT|||
+isGRAPH|5.006000||p
+isGV_with_GP|5.009004||p
+isLOWER|||
+isOCTAL||5.013005|
+isPRINT|5.004000||p
+isPSXSPC|5.006001||p
+isPUNCT|5.006000||p
+isSPACE|||
+isUPPER|||
+isWORDCHAR||5.013006|
+isXDIGIT|5.006000||p
+is_an_int|||
+is_ascii_string||5.011000|n
+is_gv_magical_sv|||
+is_handle_constructor|||n
+is_inplace_av|||
+is_list_assignment|||
+is_lvalue_sub||5.007001|
+is_uni_alnum_lc||5.006000|
+is_uni_alnum||5.006000|
+is_uni_alpha_lc||5.006000|
+is_uni_alpha||5.006000|
+is_uni_ascii_lc||5.006000|
+is_uni_ascii||5.006000|
+is_uni_cntrl_lc||5.006000|
+is_uni_cntrl||5.006000|
+is_uni_digit_lc||5.006000|
+is_uni_digit||5.006000|
+is_uni_graph_lc||5.006000|
+is_uni_graph||5.006000|
+is_uni_idfirst_lc||5.006000|
+is_uni_idfirst||5.006000|
+is_uni_lower_lc||5.006000|
+is_uni_lower||5.006000|
+is_uni_print_lc||5.006000|
+is_uni_print||5.006000|
+is_uni_punct_lc||5.006000|
+is_uni_punct||5.006000|
+is_uni_space_lc||5.006000|
+is_uni_space||5.006000|
+is_uni_upper_lc||5.006000|
+is_uni_upper||5.006000|
+is_uni_xdigit_lc||5.006000|
+is_uni_xdigit||5.006000|
+is_utf8_X_LVT|||
+is_utf8_X_LV_LVT_V|||
+is_utf8_X_LV|||
+is_utf8_X_L|||
+is_utf8_X_T|||
+is_utf8_X_V|||
+is_utf8_X_begin|||
+is_utf8_X_extend|||
+is_utf8_X_non_hangul|||
+is_utf8_X_prepend|||
+is_utf8_alnum||5.006000|
+is_utf8_alpha||5.006000|
+is_utf8_ascii||5.006000|
+is_utf8_char_slow|||n
+is_utf8_char||5.006000|n
+is_utf8_cntrl||5.006000|
+is_utf8_common|||
+is_utf8_digit||5.006000|
+is_utf8_graph||5.006000|
+is_utf8_idcont||5.008000|
+is_utf8_idfirst||5.006000|
+is_utf8_lower||5.006000|
+is_utf8_mark||5.006000|
+is_utf8_perl_space||5.011001|
+is_utf8_perl_word||5.011001|
+is_utf8_posix_digit||5.011001|
+is_utf8_print||5.006000|
+is_utf8_punct||5.006000|
+is_utf8_space||5.006000|
+is_utf8_string_loclen||5.009003|n
+is_utf8_string_loc||5.008001|n
+is_utf8_string||5.006001|n
+is_utf8_upper||5.006000|
+is_utf8_xdigit||5.006000|
+is_utf8_xidcont||5.013010|
+is_utf8_xidfirst||5.013010|
+isa_lookup|||
+items|||n
+ix|||n
+jmaybe|||
+join_exact|||
+keyword_plugin_standard|||
+keyword|||
+leave_scope|||
+lex_bufutf8||5.011002|
+lex_discard_to||5.011002|
+lex_grow_linestr||5.011002|
+lex_next_chunk||5.011002|
+lex_peek_unichar||5.011002|
+lex_read_space||5.011002|
+lex_read_to||5.011002|
+lex_read_unichar||5.011002|
+lex_start||5.009005|
+lex_stuff_pvn||5.011002|
+lex_stuff_pvs||5.013005|
+lex_stuff_pv||5.013006|
+lex_stuff_sv||5.011002|
+lex_unstuff||5.011002|
+listkids|||
+list|||
+load_module_nocontext|||vn
+load_module|5.006000||pv
+localize|||
+looks_like_bool|||
+looks_like_number|||
+lop|||
+mPUSHi|5.009002||p
+mPUSHn|5.009002||p
+mPUSHp|5.009002||p
+mPUSHs|5.010001||p
+mPUSHu|5.009002||p
+mXPUSHi|5.009002||p
+mXPUSHn|5.009002||p
+mXPUSHp|5.009002||p
+mXPUSHs|5.010001||p
+mXPUSHu|5.009002||p
+mad_free|||
+madlex|||
+madparse|||
+magic_clear_all_env|||
+magic_clearenv|||
+magic_clearhints|||
+magic_clearhint|||
+magic_clearisa|||
+magic_clearpack|||
+magic_clearsig|||
+magic_dump||5.006000|
+magic_existspack|||
+magic_freearylen_p|||
+magic_freeovrld|||
+magic_getarylen|||
+magic_getdefelem|||
+magic_getnkeys|||
+magic_getpack|||
+magic_getpos|||
+magic_getsig|||
+magic_getsubstr|||
+magic_gettaint|||
+magic_getuvar|||
+magic_getvec|||
+magic_get|||
+magic_killbackrefs|||
+magic_len|||
+magic_methcall1|||
+magic_methcall|||v
+magic_methpack|||
+magic_nextpack|||
+magic_regdata_cnt|||
+magic_regdatum_get|||
+magic_regdatum_set|||
+magic_scalarpack|||
+magic_set_all_env|||
+magic_setamagic|||
+magic_setarylen|||
+magic_setcollxfrm|||
+magic_setdbline|||
+magic_setdefelem|||
+magic_setenv|||
+magic_sethint|||
+magic_setisa|||
+magic_setmglob|||
+magic_setnkeys|||
+magic_setpack|||
+magic_setpos|||
+magic_setregexp|||
+magic_setsig|||
+magic_setsubstr|||
+magic_settaint|||
+magic_setutf8|||
+magic_setuvar|||
+magic_setvec|||
+magic_set|||
+magic_sizepack|||
+magic_wipepack|||
+make_matcher|||
+make_trie_failtable|||
+make_trie|||
+malloc_good_size|||n
+malloced_size|||n
+malloc||5.007002|n
+markstack_grow|||
+matcher_matches_sv|||
+measure_struct|||
+memEQs|5.009005||p
+memEQ|5.004000||p
+memNEs|5.009005||p
+memNE|5.004000||p
+mem_collxfrm|||
+mem_log_common|||n
+mess_alloc|||
+mess_nocontext|||vn
+mess_sv||5.013001|
+mess||5.006000|v
+method_common|||
+mfree||5.007002|n
+mg_clear|||
+mg_copy|||
+mg_dup|||
+mg_findext||5.013008|
+mg_find|||
+mg_free_type||5.013006|
+mg_free|||
+mg_get|||
+mg_length||5.005000|
+mg_localize|||
+mg_magical|||
+mg_set|||
+mg_size||5.005000|
+mini_mktime||5.007002|
+missingterm|||
+mode_from_discipline|||
+modkids|||
+mod|||
+more_bodies|||
+more_sv|||
+moreswitches|||
+mro_clean_isarev|||
+mro_gather_and_rename|||
+mro_get_from_name||5.010001|
+mro_get_linear_isa_dfs|||
+mro_get_linear_isa||5.009005|
+mro_get_private_data||5.010001|
+mro_isa_changed_in|||
+mro_meta_dup|||
+mro_meta_init|||
+mro_method_changed_in||5.009005|
+mro_package_moved|||
+mro_register||5.010001|
+mro_set_mro||5.010001|
+mro_set_private_data||5.010001|
+mul128|||
+mulexp10|||n
+munge_qwlist_to_paren_list|||
+my_atof2||5.007002|
+my_atof||5.006000|
+my_attrs|||
+my_bcopy|||n
+my_betoh16|||n
+my_betoh32|||n
+my_betoh64|||n
+my_betohi|||n
+my_betohl|||n
+my_betohs|||n
+my_bzero|||n
+my_chsize|||
+my_clearenv|||
+my_cxt_index|||
+my_cxt_init|||
+my_dirfd||5.009005|
+my_exit_jump|||
+my_exit|||
+my_failure_exit||5.004000|
+my_fflush_all||5.006000|
+my_fork||5.007003|n
+my_htobe16|||n
+my_htobe32|||n
+my_htobe64|||n
+my_htobei|||n
+my_htobel|||n
+my_htobes|||n
+my_htole16|||n
+my_htole32|||n
+my_htole64|||n
+my_htolei|||n
+my_htolel|||n
+my_htoles|||n
+my_htonl|||
+my_kid|||
+my_letoh16|||n
+my_letoh32|||n
+my_letoh64|||n
+my_letohi|||n
+my_letohl|||n
+my_letohs|||n
+my_lstat_flags|||
+my_lstat||5.014000|
+my_memcmp||5.004000|n
+my_memset|||n
+my_ntohl|||
+my_pclose||5.004000|
+my_popen_list||5.007001|
+my_popen||5.004000|
+my_setenv|||
+my_snprintf|5.009004||pvn
+my_socketpair||5.007003|n
+my_sprintf|5.009003||pvn
+my_stat_flags|||
+my_stat||5.014000|
+my_strftime||5.007002|
+my_strlcat|5.009004||pn
+my_strlcpy|5.009004||pn
+my_swabn|||n
+my_swap|||
+my_unexec|||
+my_vsnprintf||5.009004|n
+need_utf8|||n
+newANONATTRSUB||5.006000|
+newANONHASH|||
+newANONLIST|||
+newANONSUB|||
+newASSIGNOP|||
+newATTRSUB||5.006000|
+newAVREF|||
+newAV|||
+newBINOP|||
+newCONDOP|||
+newCONSTSUB|5.004050||p
+newCVREF|||
+newDEFSVOP|||
+newFORM|||
+newFOROP||5.013007|
+newGIVENOP||5.009003|
+newGIVWHENOP|||
+newGP|||
+newGVOP|||
+newGVREF|||
+newGVgen|||
+newHVREF|||
+newHVhv||5.005000|
+newHV|||
+newIO|||
+newLISTOP|||
+newLOGOP|||
+newLOOPEX|||
+newLOOPOP|||
+newMADPROP|||
+newMADsv|||
+newMYSUB|||
+newNULLLIST|||
+newOP|||
+newPADOP|||
+newPMOP|||
+newPROG|||
+newPVOP|||
+newRANGE|||
+newRV_inc|5.004000||p
+newRV_noinc|5.004000||p
+newRV|||
+newSLICEOP|||
+newSTATEOP|||
+newSUB|||
+newSVOP|||
+newSVREF|||
+newSV_type|5.009005||p
+newSVhek||5.009003|
+newSViv|||
+newSVnv|||
+newSVpv_share||5.013006|
+newSVpvf_nocontext|||vn
+newSVpvf||5.004000|v
+newSVpvn_flags|5.010001||p
+newSVpvn_share|5.007001||p
+newSVpvn_utf8|5.010001||p
+newSVpvn|5.004050||p
+newSVpvs_flags|5.010001||p
+newSVpvs_share|5.009003||p
+newSVpvs|5.009003||p
+newSVpv|||
+newSVrv|||
+newSVsv|||
+newSVuv|5.006000||p
+newSV|||
+newTOKEN|||
+newUNOP|||
+newWHENOP||5.009003|
+newWHILEOP||5.013007|
+newXS_flags||5.009004|
+newXSproto||5.006000|
+newXS||5.006000|
+new_collate||5.006000|
+new_constant|||
+new_ctype||5.006000|
+new_he|||
+new_logop|||
+new_numeric||5.006000|
+new_stackinfo||5.005000|
+new_version||5.009000|
+new_warnings_bitfield|||
+next_symbol|||
+nextargv|||
+nextchar|||
+ninstr|||n
+no_bareword_allowed|||
+no_fh_allowed|||
+no_op|||
+not_a_number|||
+nothreadhook||5.008000|
+nuke_stacks|||
+num_overflow|||n
+oopsAV|||
+oopsHV|||
+op_append_elem||5.013006|
+op_append_list||5.013006|
+op_clear|||
+op_const_sv|||
+op_contextualize||5.013006|
+op_dump||5.006000|
+op_free|||
+op_getmad_weak|||
+op_getmad|||
+op_linklist||5.013006|
+op_lvalue||5.013007|
+op_null||5.007002|
+op_prepend_elem||5.013006|
+op_refcnt_dec|||
+op_refcnt_inc|||
+op_refcnt_lock||5.009002|
+op_refcnt_unlock||5.009002|
+op_scope||5.013007|
+op_xmldump|||
+open_script|||
+opt_scalarhv|||
+pMY_CXT_|5.007003||p
+pMY_CXT|5.007003||p
+pTHX_|5.006000||p
+pTHX|5.006000||p
+packWARN|5.007003||p
+pack_cat||5.007003|
+pack_rec|||
+package_version|||
+package|||
+packlist||5.008001|
+pad_add_anon|||
+pad_add_name_sv|||
+pad_add_name|||
+pad_alloc|||
+pad_block_start|||
+pad_check_dup|||
+pad_compname_type|||
+pad_findlex|||
+pad_findmy||5.011002|
+pad_fixup_inner_anons|||
+pad_free|||
+pad_leavemy|||
+pad_new|||
+pad_peg|||n
+pad_push|||
+pad_reset|||
+pad_setsv|||
+pad_sv|||
+pad_swipe|||
+pad_tidy|||
+padlist_dup|||
+parse_arithexpr||5.013008|
+parse_barestmt||5.013007|
+parse_block||5.013007|
+parse_body|||
+parse_fullexpr||5.013008|
+parse_fullstmt||5.013005|
+parse_label||5.013007|
+parse_listexpr||5.013008|
+parse_stmtseq||5.013006|
+parse_termexpr||5.013008|
+parse_unicode_opts|||
+parser_dup|||
+parser_free|||
+path_is_absolute|||n
+peep|||
+pending_Slabs_to_ro|||
+perl_alloc_using|||n
+perl_alloc|||n
+perl_clone_using|||n
+perl_clone|||n
+perl_construct|||n
+perl_destruct||5.007003|n
+perl_free|||n
+perl_parse||5.006000|n
+perl_run|||n
+pidgone|||
+pm_description|||
+pmop_dump||5.006000|
+pmop_xmldump|||
+pmruntime|||
+pmtrans|||
+pop_scope|||
+populate_isa|||v
+pregcomp||5.009005|
+pregexec|||
+pregfree2||5.011000|
+pregfree|||
+prepend_madprops|||
+prescan_version||5.011004|
+printbuf|||
+printf_nocontext|||vn
+process_special_blocks|||
+ptr_table_clear||5.009005|
+ptr_table_fetch||5.009005|
+ptr_table_find|||n
+ptr_table_free||5.009005|
+ptr_table_new||5.009005|
+ptr_table_split||5.009005|
+ptr_table_store||5.009005|
+push_scope|||
+put_byte|||
+pv_display|5.006000||p
+pv_escape|5.009004||p
+pv_pretty|5.009004||p
+pv_uni_display||5.007003|
+qerror|||
+qsortsvu|||
+re_compile||5.009005|
+re_croak2|||
+re_dup_guts|||
+re_intuit_start||5.009005|
+re_intuit_string||5.006000|
+readpipe_override|||
+realloc||5.007002|n
+reentrant_free|||
+reentrant_init|||
+reentrant_retry|||vn
+reentrant_size|||
+ref_array_or_hash|||
+refcounted_he_chain_2hv|||
+refcounted_he_fetch_pvn|||
+refcounted_he_fetch_pvs|||
+refcounted_he_fetch_pv|||
+refcounted_he_fetch_sv|||
+refcounted_he_free|||
+refcounted_he_inc|||
+refcounted_he_new_pvn|||
+refcounted_he_new_pvs|||
+refcounted_he_new_pv|||
+refcounted_he_new_sv|||
+refcounted_he_value|||
+refkids|||
+refto|||
+ref||5.014000|
+reg_check_named_buff_matched|||
+reg_named_buff_all||5.009005|
+reg_named_buff_exists||5.009005|
+reg_named_buff_fetch||5.009005|
+reg_named_buff_firstkey||5.009005|
+reg_named_buff_iter|||
+reg_named_buff_nextkey||5.009005|
+reg_named_buff_scalar||5.009005|
+reg_named_buff|||
+reg_namedseq|||
+reg_node|||
+reg_numbered_buff_fetch|||
+reg_numbered_buff_length|||
+reg_numbered_buff_store|||
+reg_qr_package|||
+reg_recode|||
+reg_scan_name|||
+reg_skipcomment|||
+reg_temp_copy|||
+reganode|||
+regatom|||
+regbranch|||
+regclass_swash||5.009004|
+regclass|||
+regcppop|||
+regcppush|||
+regcurly|||
+regdump_extflags|||
+regdump||5.005000|
+regdupe_internal|||
+regexec_flags||5.005000|
+regfree_internal||5.009005|
+reghop3|||n
+reghop4|||n
+reghopmaybe3|||n
+reginclass|||
+reginitcolors||5.006000|
+reginsert|||
+regmatch|||
+regnext||5.005000|
+regpiece|||
+regpposixcc|||
+regprop|||
+regrepeat|||
+regtail_study|||
+regtail|||
+regtry|||
+reguni|||
+regwhite|||n
+reg|||
+repeatcpy|||n
+report_evil_fh|||
+report_uninit|||
+report_wrongway_fh|||
+require_pv||5.006000|
+require_tie_mod|||
+restore_magic|||
+rninstr|||n
+rpeep|||
+rsignal_restore|||
+rsignal_save|||
+rsignal_state||5.004000|
+rsignal||5.004000|
+run_body|||
+run_user_filter|||
+runops_debug||5.005000|
+runops_standard||5.005000|
+rv2cv_op_cv||5.013006|
+rvpv_dup|||
+rxres_free|||
+rxres_restore|||
+rxres_save|||
+safesyscalloc||5.006000|n
+safesysfree||5.006000|n
+safesysmalloc||5.006000|n
+safesysrealloc||5.006000|n
+same_dirent|||
+save_I16||5.004000|
+save_I32|||
+save_I8||5.006000|
+save_adelete||5.011000|
+save_aelem_flags||5.011000|
+save_aelem||5.004050|
+save_alloc||5.006000|
+save_aptr|||
+save_ary|||
+save_bool||5.008001|
+save_clearsv|||
+save_delete|||
+save_destructor_x||5.006000|
+save_destructor||5.006000|
+save_freeop|||
+save_freepv|||
+save_freesv|||
+save_generic_pvref||5.006001|
+save_generic_svref||5.005030|
+save_gp||5.004000|
+save_hash|||
+save_hdelete||5.011000|
+save_hek_flags|||n
+save_helem_flags||5.011000|
+save_helem||5.004050|
+save_hints||5.010001|
+save_hptr|||
+save_int|||
+save_item|||
+save_iv||5.005000|
+save_lines|||
+save_list|||
+save_long|||
+save_magic|||
+save_mortalizesv||5.007001|
+save_nogv|||
+save_op||5.005000|
+save_padsv_and_mortalize||5.010001|
+save_pptr|||
+save_pushi32ptr||5.010001|
+save_pushptri32ptr|||
+save_pushptrptr||5.010001|
+save_pushptr||5.010001|
+save_re_context||5.006000|
+save_scalar_at|||
+save_scalar|||
+save_set_svflags||5.009000|
+save_shared_pvref||5.007003|
+save_sptr|||
+save_svref|||
+save_vptr||5.006000|
+savepvn|||
+savepvs||5.009003|
+savepv|||
+savesharedpvn||5.009005|
+savesharedpvs||5.013006|
+savesharedpv||5.007003|
+savesharedsvpv||5.013006|
+savestack_grow_cnt||5.008001|
+savestack_grow|||
+savesvpv||5.009002|
+sawparens|||
+scalar_mod_type|||n
+scalarboolean|||
+scalarkids|||
+scalarseq|||
+scalarvoid|||
+scalar|||
+scan_bin||5.006000|
+scan_commit|||
+scan_const|||
+scan_formline|||
+scan_heredoc|||
+scan_hex|||
+scan_ident|||
+scan_inputsymbol|||
+scan_num||5.007001|
+scan_oct|||
+scan_pat|||
+scan_str|||
+scan_subst|||
+scan_trans|||
+scan_version||5.009001|
+scan_vstring||5.009005|
+scan_word|||
+screaminstr||5.005000|
+search_const|||
+seed||5.008001|
+sequence_num|||
+sequence_tail|||
+sequence|||
+set_context||5.006000|n
+set_numeric_local||5.006000|
+set_numeric_radix||5.006000|
+set_numeric_standard||5.006000|
+set_regclass_bit_fold|||
+set_regclass_bit|||
+setdefout|||
+share_hek_flags|||
+share_hek||5.004000|
+si_dup|||
+sighandler|||n
+simplify_sort|||
+skipspace0|||
+skipspace1|||
+skipspace2|||
+skipspace|||
+softref2xv|||
+sortcv_stacked|||
+sortcv_xsub|||
+sortcv|||
+sortsv_flags||5.009003|
+sortsv||5.007003|
+space_join_names_mortal|||
+ss_dup|||
+stack_grow|||
+start_force|||
+start_glob|||
+start_subparse||5.004000|
+stashpv_hvname_match||5.014000|
+stdize_locale|||
+store_cop_label|||
+strEQ|||
+strGE|||
+strGT|||
+strLE|||
+strLT|||
+strNE|||
+str_to_version||5.006000|
+strip_return|||
+strnEQ|||
+strnNE|||
+study_chunk|||
+sub_crush_depth|||
+sublex_done|||
+sublex_push|||
+sublex_start|||
+sv_2bool_flags||5.013006|
+sv_2bool|||
+sv_2cv|||
+sv_2io|||
+sv_2iuv_common|||
+sv_2iuv_non_preserve|||
+sv_2iv_flags||5.009001|
+sv_2iv|||
+sv_2mortal|||
+sv_2num|||
+sv_2nv_flags||5.013001|
+sv_2pv_flags|5.007002||p
+sv_2pv_nolen|5.006000||p
+sv_2pvbyte_nolen|5.006000||p
+sv_2pvbyte|5.006000||p
+sv_2pvutf8_nolen||5.006000|
+sv_2pvutf8||5.006000|
+sv_2pv|||
+sv_2uv_flags||5.009001|
+sv_2uv|5.004000||p
+sv_add_arena|||
+sv_add_backref|||
+sv_backoff|||
+sv_bless|||
+sv_cat_decode||5.008001|
+sv_catpv_flags||5.013006|
+sv_catpv_mg|5.004050||p
+sv_catpv_nomg||5.013006|
+sv_catpvf_mg_nocontext|||pvn
+sv_catpvf_mg|5.006000|5.004000|pv
+sv_catpvf_nocontext|||vn
+sv_catpvf||5.004000|v
+sv_catpvn_flags||5.007002|
+sv_catpvn_mg|5.004050||p
+sv_catpvn_nomg|5.007002||p
+sv_catpvn|||
+sv_catpvs_flags||5.013006|
+sv_catpvs_mg||5.013006|
+sv_catpvs_nomg||5.013006|
+sv_catpvs|5.009003||p
+sv_catpv|||
+sv_catsv_flags||5.007002|
+sv_catsv_mg|5.004050||p
+sv_catsv_nomg|5.007002||p
+sv_catsv|||
+sv_catxmlpvn|||
+sv_catxmlpv|||
+sv_catxmlsv|||
+sv_chop|||
+sv_clean_all|||
+sv_clean_objs|||
+sv_clear|||
+sv_cmp_flags||5.013006|
+sv_cmp_locale_flags||5.013006|
+sv_cmp_locale||5.004000|
+sv_cmp|||
+sv_collxfrm_flags||5.013006|
+sv_collxfrm|||
+sv_compile_2op_is_broken|||
+sv_compile_2op||5.008001|
+sv_copypv||5.007003|
+sv_dec_nomg||5.013002|
+sv_dec|||
+sv_del_backref|||
+sv_derived_from||5.004000|
+sv_destroyable||5.010000|
+sv_does||5.009004|
+sv_dump|||
+sv_dup_common|||
+sv_dup_inc_multiple|||
+sv_dup_inc|||
+sv_dup|||
+sv_eq_flags||5.013006|
+sv_eq|||
+sv_exp_grow|||
+sv_force_normal_flags||5.007001|
+sv_force_normal||5.006000|
+sv_free2|||
+sv_free_arenas|||
+sv_free|||
+sv_gets||5.004000|
+sv_grow|||
+sv_i_ncmp|||
+sv_inc_nomg||5.013002|
+sv_inc|||
+sv_insert_flags||5.010001|
+sv_insert|||
+sv_isa|||
+sv_isobject|||
+sv_iv||5.005000|
+sv_kill_backrefs|||
+sv_len_utf8||5.006000|
+sv_len|||
+sv_magic_portable|5.014000|5.004000|p
+sv_magicext||5.007003|
+sv_magic|||
+sv_mortalcopy|||
+sv_ncmp|||
+sv_newmortal|||
+sv_newref|||
+sv_nolocking||5.007003|
+sv_nosharing||5.007003|
+sv_nounlocking|||
+sv_nv||5.005000|
+sv_peek||5.005000|
+sv_pos_b2u_midway|||
+sv_pos_b2u||5.006000|
+sv_pos_u2b_cached|||
+sv_pos_u2b_flags||5.011005|
+sv_pos_u2b_forwards|||n
+sv_pos_u2b_midway|||n
+sv_pos_u2b||5.006000|
+sv_pvbyten_force||5.006000|
+sv_pvbyten||5.006000|
+sv_pvbyte||5.006000|
+sv_pvn_force_flags|5.007002||p
+sv_pvn_force|||
+sv_pvn_nomg|5.007003|5.005000|p
+sv_pvn||5.005000|
+sv_pvutf8n_force||5.006000|
+sv_pvutf8n||5.006000|
+sv_pvutf8||5.006000|
+sv_pv||5.006000|
+sv_recode_to_utf8||5.007003|
+sv_reftype|||
+sv_release_COW|||
+sv_replace|||
+sv_report_used|||
+sv_reset|||
+sv_rvweaken||5.006000|
+sv_setiv_mg|5.004050||p
+sv_setiv|||
+sv_setnv_mg|5.006000||p
+sv_setnv|||
+sv_setpv_mg|5.004050||p
+sv_setpvf_mg_nocontext|||pvn
+sv_setpvf_mg|5.006000|5.004000|pv
+sv_setpvf_nocontext|||vn
+sv_setpvf||5.004000|v
+sv_setpviv_mg||5.008001|
+sv_setpviv||5.008001|
+sv_setpvn_mg|5.004050||p
+sv_setpvn|||
+sv_setpvs_mg||5.013006|
+sv_setpvs|5.009004||p
+sv_setpv|||
+sv_setref_iv|||
+sv_setref_nv|||
+sv_setref_pvn|||
+sv_setref_pvs||5.013006|
+sv_setref_pv|||
+sv_setref_uv||5.007001|
+sv_setsv_cow|||
+sv_setsv_flags||5.007002|
+sv_setsv_mg|5.004050||p
+sv_setsv_nomg|5.007002||p
+sv_setsv|||
+sv_setuv_mg|5.004050||p
+sv_setuv|5.004000||p
+sv_tainted||5.004000|
+sv_taint||5.004000|
+sv_true||5.005000|
+sv_unglob|||
+sv_uni_display||5.007003|
+sv_unmagicext||5.013008|
+sv_unmagic|||
+sv_unref_flags||5.007001|
+sv_unref|||
+sv_untaint||5.004000|
+sv_upgrade|||
+sv_usepvn_flags||5.009004|
+sv_usepvn_mg|5.004050||p
+sv_usepvn|||
+sv_utf8_decode||5.006000|
+sv_utf8_downgrade||5.006000|
+sv_utf8_encode||5.006000|
+sv_utf8_upgrade_flags_grow||5.011000|
+sv_utf8_upgrade_flags||5.007002|
+sv_utf8_upgrade_nomg||5.007002|
+sv_utf8_upgrade||5.007001|
+sv_uv|5.005000||p
+sv_vcatpvf_mg|5.006000|5.004000|p
+sv_vcatpvfn||5.004000|
+sv_vcatpvf|5.006000|5.004000|p
+sv_vsetpvf_mg|5.006000|5.004000|p
+sv_vsetpvfn||5.004000|
+sv_vsetpvf|5.006000|5.004000|p
+sv_xmlpeek|||
+svtype|||
+swallow_bom|||
+swash_fetch||5.007002|
+swash_get|||
+swash_init||5.006000|
+sys_init3||5.010000|n
+sys_init||5.010000|n
+sys_intern_clear|||
+sys_intern_dup|||
+sys_intern_init|||
+sys_term||5.010000|n
+taint_env|||
+taint_proper|||
+tied_method|||v
+tmps_grow||5.006000|
+toLOWER|||
+toUPPER|||
+to_byte_substr|||
+to_uni_fold||5.007003|
+to_uni_lower_lc||5.006000|
+to_uni_lower||5.007003|
+to_uni_title_lc||5.006000|
+to_uni_title||5.007003|
+to_uni_upper_lc||5.006000|
+to_uni_upper||5.007003|
+to_utf8_case||5.007003|
+to_utf8_fold||5.007003|
+to_utf8_lower||5.007003|
+to_utf8_substr|||
+to_utf8_title||5.007003|
+to_utf8_upper||5.007003|
+token_free|||
+token_getmad|||
+tokenize_use|||
+tokeq|||
+tokereport|||
+too_few_arguments|||
+too_many_arguments|||
+try_amagic_bin|||
+try_amagic_un|||
+uiv_2buf|||n
+unlnk|||
+unpack_rec|||
+unpack_str||5.007003|
+unpackstring||5.008001|
+unreferenced_to_tmp_stack|||
+unshare_hek_or_pvn|||
+unshare_hek|||
+unsharepvn||5.004000|
+unwind_handler_stack|||
+update_debugger_info|||
+upg_version||5.009005|
+usage|||
+utf16_textfilter|||
+utf16_to_utf8_reversed||5.006001|
+utf16_to_utf8||5.006001|
+utf8_distance||5.006000|
+utf8_hop||5.006000|
+utf8_length||5.007001|
+utf8_mg_len_cache_update|||
+utf8_mg_pos_cache_update|||
+utf8_to_bytes||5.006001|
+utf8_to_uvchr||5.007001|
+utf8_to_uvuni||5.007001|
+utf8n_to_uvchr|||
+utf8n_to_uvuni||5.007001|
+utilize|||
+uvchr_to_utf8_flags||5.007003|
+uvchr_to_utf8|||
+uvuni_to_utf8_flags||5.007003|
+uvuni_to_utf8||5.007001|
+validate_suid|||
+varname|||
+vcmp||5.009000|
+vcroak||5.006000|
+vdeb||5.007003|
+vform||5.006000|
+visit|||
+vivify_defelem|||
+vivify_ref|||
+vload_module|5.006000||p
+vmess||5.006000|
+vnewSVpvf|5.006000|5.004000|p
+vnormal||5.009002|
+vnumify||5.009000|
+vstringify||5.009000|
+vverify||5.009003|
+vwarner||5.006000|
+vwarn||5.006000|
+wait4pid|||
+warn_nocontext|||vn
+warn_sv||5.013001|
+warner_nocontext|||vn
+warner|5.006000|5.004000|pv
+warn|||v
+watch|||
+whichsig|||
+with_queued_errors|||
+write_no_mem|||
+write_to_stderr|||
+xmldump_all_perl|||
+xmldump_all|||
+xmldump_attr|||
+xmldump_eval|||
+xmldump_form|||
+xmldump_indent|||v
+xmldump_packsubs_perl|||
+xmldump_packsubs|||
+xmldump_sub_perl|||
+xmldump_sub|||
+xmldump_vindent|||
+xs_apiversion_bootcheck|||
+xs_version_bootcheck|||
+yyerror|||
+yylex|||
+yyparse|||
+yyunlex|||
+yywarn|||
+);
+
+if (exists $opt{'list-unsupported'}) {
+ my $f;
+ for $f (sort { lc $a cmp lc $b } keys %API) {
+ next unless $API{$f}{todo};
+ print "$f ", '.'x(40-length($f)), " ", format_version($API{$f}{todo}), "\n";
+ }
+ exit 0;
+}
+
+# Scan for possible replacement candidates
+
+my(%replace, %need, %hints, %warnings, %depends);
+my $replace = 0;
+my($hint, $define, $function);
+
+sub find_api
+{
+ my $code = shift;
+ $code =~ s{
+ / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*)
+ | "[^"\\]*(?:\\.[^"\\]*)*"
+ | '[^'\\]*(?:\\.[^'\\]*)*' }{}egsx;
+ grep { exists $API{$_} } $code =~ /(\w+)/mg;
+}
+
+while (<DATA>) {
+ if ($hint) {
+ my $h = $hint->[0] eq 'Hint' ? \%hints : \%warnings;
+ if (m{^\s*\*\s(.*?)\s*$}) {
+ for (@{$hint->[1]}) {
+ $h->{$_} ||= ''; # suppress warning with older perls
+ $h->{$_} .= "$1\n";
+ }
+ }
+ else { undef $hint }
+ }
+
+ $hint = [$1, [split /,?\s+/, $2]]
+ if m{^\s*$rccs\s+(Hint|Warning):\s+(\w+(?:,?\s+\w+)*)\s*$};
+
+ if ($define) {
+ if ($define->[1] =~ /\\$/) {
+ $define->[1] .= $_;
+ }
+ else {
+ if (exists $API{$define->[0]} && $define->[1] !~ /^DPPP_\(/) {
+ my @n = find_api($define->[1]);
+ push @{$depends{$define->[0]}}, @n if @n
+ }
+ undef $define;
+ }
+ }
+
+ $define = [$1, $2] if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(.*)};
+
+ if ($function) {
+ if (/^}/) {
+ if (exists $API{$function->[0]}) {
+ my @n = find_api($function->[1]);
+ push @{$depends{$function->[0]}}, @n if @n
+ }
+ undef $function;
+ }
+ else {
+ $function->[1] .= $_;
+ }
+ }
+
+ $function = [$1, ''] if m{^DPPP_\(my_(\w+)\)};
+
+ $replace = $1 if m{^\s*$rccs\s+Replace:\s+(\d+)\s+$rcce\s*$};
+ $replace{$2} = $1 if $replace and m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+)};
+ $replace{$2} = $1 if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+).*$rccs\s+Replace\s+$rcce};
+ $replace{$1} = $2 if m{^\s*$rccs\s+Replace (\w+) with (\w+)\s+$rcce\s*$};
+
+ if (m{^\s*$rccs\s+(\w+(\s*,\s*\w+)*)\s+depends\s+on\s+(\w+(\s*,\s*\w+)*)\s+$rcce\s*$}) {
+ my @deps = map { s/\s+//g; $_ } split /,/, $3;
+ my $d;
+ for $d (map { s/\s+//g; $_ } split /,/, $1) {
+ push @{$depends{$d}}, @deps;
+ }
+ }
+
+ $need{$1} = 1 if m{^#if\s+defined\(NEED_(\w+)(?:_GLOBAL)?\)};
+}
+
+for (values %depends) {
+ my %s;
+ $_ = [sort grep !$s{$_}++, @$_];
+}
+
+if (exists $opt{'api-info'}) {
+ my $f;
+ my $count = 0;
+ my $match = $opt{'api-info'} =~ m!^/(.*)/$! ? $1 : "^\Q$opt{'api-info'}\E\$";
+ for $f (sort { lc $a cmp lc $b } keys %API) {
+ next unless $f =~ /$match/;
+ print "\n=== $f ===\n\n";
+ my $info = 0;
+ if ($API{$f}{base} || $API{$f}{todo}) {
+ my $base = format_version($API{$f}{base} || $API{$f}{todo});
+ print "Supported at least starting from perl-$base.\n";
+ $info++;
+ }
+ if ($API{$f}{provided}) {
+ my $todo = $API{$f}{todo} ? format_version($API{$f}{todo}) : "5.003";
+ print "Support by $ppport provided back to perl-$todo.\n";
+ print "Support needs to be explicitly requested by NEED_$f.\n" if exists $need{$f};
+ print "Depends on: ", join(', ', @{$depends{$f}}), ".\n" if exists $depends{$f};
+ print "\n$hints{$f}" if exists $hints{$f};
+ print "\nWARNING:\n$warnings{$f}" if exists $warnings{$f};
+ $info++;
+ }
+ print "No portability information available.\n" unless $info;
+ $count++;
+ }
+ $count or print "Found no API matching '$opt{'api-info'}'.";
+ print "\n";
+ exit 0;
+}
+
+if (exists $opt{'list-provided'}) {
+ my $f;
+ for $f (sort { lc $a cmp lc $b } keys %API) {
+ next unless $API{$f}{provided};
+ my @flags;
+ push @flags, 'explicit' if exists $need{$f};
+ push @flags, 'depend' if exists $depends{$f};
+ push @flags, 'hint' if exists $hints{$f};
+ push @flags, 'warning' if exists $warnings{$f};
+ my $flags = @flags ? ' ['.join(', ', @flags).']' : '';
+ print "$f$flags\n";
+ }
+ exit 0;
+}
+
+my @files;
+my @srcext = qw( .xs .c .h .cc .cpp -c.inc -xs.inc );
+my $srcext = join '|', map { quotemeta $_ } @srcext;
+
+if (@ARGV) {
+ my %seen;
+ for (@ARGV) {
+ if (-e) {
+ if (-f) {
+ push @files, $_ unless $seen{$_}++;
+ }
+ else { warn "'$_' is not a file.\n" }
+ }
+ else {
+ my @new = grep { -f } glob $_
+ or warn "'$_' does not exist.\n";
+ push @files, grep { !$seen{$_}++ } @new;
+ }
+ }
+}
+else {
+ eval {
+ require File::Find;
+ File::Find::find(sub {
+ $File::Find::name =~ /($srcext)$/i
+ and push @files, $File::Find::name;
+ }, '.');
+ };
+ if ($@) {
+ @files = map { glob "*$_" } @srcext;
+ }
+}
+
+if (!@ARGV || $opt{filter}) {
+ my(@in, @out);
+ my %xsc = map { /(.*)\.xs$/ ? ("$1.c" => 1, "$1.cc" => 1) : () } @files;
+ for (@files) {
+ my $out = exists $xsc{$_} || /\b\Q$ppport\E$/i || !/($srcext)$/i;
+ push @{ $out ? \@out : \@in }, $_;
+ }
+ if (@ARGV && @out) {
+ warning("Skipping the following files (use --nofilter to avoid this):\n| ", join "\n| ", @out);
+ }
+ @files = @in;
+}
+
+die "No input files given!\n" unless @files;
+
+my(%files, %global, %revreplace);
+%revreplace = reverse %replace;
+my $filename;
+my $patch_opened = 0;
+
+for $filename (@files) {
+ unless (open IN, "<$filename") {
+ warn "Unable to read from $filename: $!\n";
+ next;
+ }
+
+ info("Scanning $filename ...");
+
+ my $c = do { local $/; <IN> };
+ close IN;
+
+ my %file = (orig => $c, changes => 0);
+
+ # Temporarily remove C/XS comments and strings from the code
+ my @ccom;
+
+ $c =~ s{
+ ( ^$HS*\#$HS*include\b[^\r\n]+\b(?:\Q$ppport\E|XSUB\.h)\b[^\r\n]*
+ | ^$HS*\#$HS*(?:define|elif|if(?:def)?)\b[^\r\n]* )
+ | ( ^$HS*\#[^\r\n]*
+ | "[^"\\]*(?:\\.[^"\\]*)*"
+ | '[^'\\]*(?:\\.[^'\\]*)*'
+ | / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]* ) )
+ }{ defined $2 and push @ccom, $2;
+ defined $1 ? $1 : "$ccs$#ccom$cce" }mgsex;
+
+ $file{ccom} = \@ccom;
+ $file{code} = $c;
+ $file{has_inc_ppport} = $c =~ /^$HS*#$HS*include[^\r\n]+\b\Q$ppport\E\b/m;
+
+ my $func;
+
+ for $func (keys %API) {
+ my $match = $func;
+ $match .= "|$revreplace{$func}" if exists $revreplace{$func};
+ if ($c =~ /\b(?:Perl_)?($match)\b/) {
+ $file{uses_replace}{$1}++ if exists $revreplace{$func} && $1 eq $revreplace{$func};
+ $file{uses_Perl}{$func}++ if $c =~ /\bPerl_$func\b/;
+ if (exists $API{$func}{provided}) {
+ $file{uses_provided}{$func}++;
+ if (!exists $API{$func}{base} || $API{$func}{base} > $opt{'compat-version'}) {
+ $file{uses}{$func}++;
+ my @deps = rec_depend($func);
+ if (@deps) {
+ $file{uses_deps}{$func} = \@deps;
+ for (@deps) {
+ $file{uses}{$_} = 0 unless exists $file{uses}{$_};
+ }
+ }
+ for ($func, @deps) {
+ $file{needs}{$_} = 'static' if exists $need{$_};
+ }
+ }
+ }
+ if (exists $API{$func}{todo} && $API{$func}{todo} > $opt{'compat-version'}) {
+ if ($c =~ /\b$func\b/) {
+ $file{uses_todo}{$func}++;
+ }
+ }
+ }
+ }
+
+ while ($c =~ /^$HS*#$HS*define$HS+(NEED_(\w+?)(_GLOBAL)?)\b/mg) {
+ if (exists $need{$2}) {
+ $file{defined $3 ? 'needed_global' : 'needed_static'}{$2}++;
+ }
+ else { warning("Possibly wrong #define $1 in $filename") }
+ }
+
+ for (qw(uses needs uses_todo needed_global needed_static)) {
+ for $func (keys %{$file{$_}}) {
+ push @{$global{$_}{$func}}, $filename;
+ }
+ }
+
+ $files{$filename} = \%file;
+}
+
+# Globally resolve NEED_'s
+my $need;
+for $need (keys %{$global{needs}}) {
+ if (@{$global{needs}{$need}} > 1) {
+ my @targets = @{$global{needs}{$need}};
+ my @t = grep $files{$_}{needed_global}{$need}, @targets;
+ @targets = @t if @t;
+ @t = grep /\.xs$/i, @targets;
+ @targets = @t if @t;
+ my $target = shift @targets;
+ $files{$target}{needs}{$need} = 'global';
+ for (@{$global{needs}{$need}}) {
+ $files{$_}{needs}{$need} = 'extern' if $_ ne $target;
+ }
+ }
+}
+
+for $filename (@files) {
+ exists $files{$filename} or next;
+
+ info("=== Analyzing $filename ===");
+
+ my %file = %{$files{$filename}};
+ my $func;
+ my $c = $file{code};
+ my $warnings = 0;
+
+ for $func (sort keys %{$file{uses_Perl}}) {
+ if ($API{$func}{varargs}) {
+ unless ($API{$func}{nothxarg}) {
+ my $changes = ($c =~ s{\b(Perl_$func\s*\(\s*)(?!aTHX_?)(\)|[^\s)]*\))}
+ { $1 . ($2 eq ')' ? 'aTHX' : 'aTHX_ ') . $2 }ge);
+ if ($changes) {
+ warning("Doesn't pass interpreter argument aTHX to Perl_$func");
+ $file{changes} += $changes;
+ }
+ }
+ }
+ else {
+ warning("Uses Perl_$func instead of $func");
+ $file{changes} += ($c =~ s{\bPerl_$func(\s*)\((\s*aTHX_?)?\s*}
+ {$func$1(}g);
+ }
+ }
+
+ for $func (sort keys %{$file{uses_replace}}) {
+ warning("Uses $func instead of $replace{$func}");
+ $file{changes} += ($c =~ s/\b$func\b/$replace{$func}/g);
+ }
+
+ for $func (sort keys %{$file{uses_provided}}) {
+ if ($file{uses}{$func}) {
+ if (exists $file{uses_deps}{$func}) {
+ diag("Uses $func, which depends on ", join(', ', @{$file{uses_deps}{$func}}));
+ }
+ else {
+ diag("Uses $func");
+ }
+ }
+ $warnings += hint($func);
+ }
+
+ unless ($opt{quiet}) {
+ for $func (sort keys %{$file{uses_todo}}) {
+ print "*** WARNING: Uses $func, which may not be portable below perl ",
+ format_version($API{$func}{todo}), ", even with '$ppport'\n";
+ $warnings++;
+ }
+ }
+
+ for $func (sort keys %{$file{needed_static}}) {
+ my $message = '';
+ if (not exists $file{uses}{$func}) {
+ $message = "No need to define NEED_$func if $func is never used";
+ }
+ elsif (exists $file{needs}{$func} && $file{needs}{$func} ne 'static') {
+ $message = "No need to define NEED_$func when already needed globally";
+ }
+ if ($message) {
+ diag($message);
+ $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_$func\b.*$LF//mg);
+ }
+ }
+
+ for $func (sort keys %{$file{needed_global}}) {
+ my $message = '';
+ if (not exists $global{uses}{$func}) {
+ $message = "No need to define NEED_${func}_GLOBAL if $func is never used";
+ }
+ elsif (exists $file{needs}{$func}) {
+ if ($file{needs}{$func} eq 'extern') {
+ $message = "No need to define NEED_${func}_GLOBAL when already needed globally";
+ }
+ elsif ($file{needs}{$func} eq 'static') {
+ $message = "No need to define NEED_${func}_GLOBAL when only used in this file";
+ }
+ }
+ if ($message) {
+ diag($message);
+ $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_${func}_GLOBAL\b.*$LF//mg);
+ }
+ }
+
+ $file{needs_inc_ppport} = keys %{$file{uses}};
+
+ if ($file{needs_inc_ppport}) {
+ my $pp = '';
+
+ for $func (sort keys %{$file{needs}}) {
+ my $type = $file{needs}{$func};
+ next if $type eq 'extern';
+ my $suffix = $type eq 'global' ? '_GLOBAL' : '';
+ unless (exists $file{"needed_$type"}{$func}) {
+ if ($type eq 'global') {
+ diag("Files [@{$global{needs}{$func}}] need $func, adding global request");
+ }
+ else {
+ diag("File needs $func, adding static request");
+ }
+ $pp .= "#define NEED_$func$suffix\n";
+ }
+ }
+
+ if ($pp && ($c =~ s/^(?=$HS*#$HS*define$HS+NEED_\w+)/$pp/m)) {
+ $pp = '';
+ $file{changes}++;
+ }
+
+ unless ($file{has_inc_ppport}) {
+ diag("Needs to include '$ppport'");
+ $pp .= qq(#include "$ppport"\n)
+ }
+
+ if ($pp) {
+ $file{changes} += ($c =~ s/^($HS*#$HS*define$HS+NEED_\w+.*?)^/$1$pp/ms)
+ || ($c =~ s/^(?=$HS*#$HS*include.*\Q$ppport\E)/$pp/m)
+ || ($c =~ s/^($HS*#$HS*include.*XSUB.*\s*?)^/$1$pp/m)
+ || ($c =~ s/^/$pp/);
+ }
+ }
+ else {
+ if ($file{has_inc_ppport}) {
+ diag("No need to include '$ppport'");
+ $file{changes} += ($c =~ s/^$HS*?#$HS*include.*\Q$ppport\E.*?$LF//m);
+ }
+ }
+
+ # put back in our C comments
+ my $ix;
+ my $cppc = 0;
+ my @ccom = @{$file{ccom}};
+ for $ix (0 .. $#ccom) {
+ if (!$opt{cplusplus} && $ccom[$ix] =~ s!^//!!) {
+ $cppc++;
+ $file{changes} += $c =~ s/$rccs$ix$rcce/$ccs$ccom[$ix] $cce/;
+ }
+ else {
+ $c =~ s/$rccs$ix$rcce/$ccom[$ix]/;
+ }
+ }
+
+ if ($cppc) {
+ my $s = $cppc != 1 ? 's' : '';
+ warning("Uses $cppc C++ style comment$s, which is not portable");
+ }
+
+ my $s = $warnings != 1 ? 's' : '';
+ my $warn = $warnings ? " ($warnings warning$s)" : '';
+ info("Analysis completed$warn");
+
+ if ($file{changes}) {
+ if (exists $opt{copy}) {
+ my $newfile = "$filename$opt{copy}";
+ if (-e $newfile) {
+ error("'$newfile' already exists, refusing to write copy of '$filename'");
+ }
+ else {
+ local *F;
+ if (open F, ">$newfile") {
+ info("Writing copy of '$filename' with changes to '$newfile'");
+ print F $c;
+ close F;
+ }
+ else {
+ error("Cannot open '$newfile' for writing: $!");
+ }
+ }
+ }
+ elsif (exists $opt{patch} || $opt{changes}) {
+ if (exists $opt{patch}) {
+ unless ($patch_opened) {
+ if (open PATCH, ">$opt{patch}") {
+ $patch_opened = 1;
+ }
+ else {
+ error("Cannot open '$opt{patch}' for writing: $!");
+ delete $opt{patch};
+ $opt{changes} = 1;
+ goto fallback;
+ }
+ }
+ mydiff(\*PATCH, $filename, $c);
+ }
+ else {
+fallback:
+ info("Suggested changes:");
+ mydiff(\*STDOUT, $filename, $c);
+ }
+ }
+ else {
+ my $s = $file{changes} == 1 ? '' : 's';
+ info("$file{changes} potentially required change$s detected");
+ }
+ }
+ else {
+ info("Looks good");
+ }
+}
+
+close PATCH if $patch_opened;
+
+exit 0;
+
+
+sub try_use { eval "use @_;"; return $@ eq '' }
+
+sub mydiff
+{
+ local *F = shift;
+ my($file, $str) = @_;
+ my $diff;
+
+ if (exists $opt{diff}) {
+ $diff = run_diff($opt{diff}, $file, $str);
+ }
+
+ if (!defined $diff and try_use('Text::Diff')) {
+ $diff = Text::Diff::diff($file, \$str, { STYLE => 'Unified' });
+ $diff = <<HEADER . $diff;
+--- $file
++++ $file.patched
+HEADER
+ }
+
+ if (!defined $diff) {
+ $diff = run_diff('diff -u', $file, $str);
+ }
+
+ if (!defined $diff) {
+ $diff = run_diff('diff', $file, $str);
+ }
+
+ if (!defined $diff) {
+ error("Cannot generate a diff. Please install Text::Diff or use --copy.");
+ return;
+ }
+
+ print F $diff;
+}
+
+sub run_diff
+{
+ my($prog, $file, $str) = @_;
+ my $tmp = 'dppptemp';
+ my $suf = 'aaa';
+ my $diff = '';
+ local *F;
+
+ while (-e "$tmp.$suf") { $suf++ }
+ $tmp = "$tmp.$suf";
+
+ if (open F, ">$tmp") {
+ print F $str;
+ close F;
+
+ if (open F, "$prog $file $tmp |") {
+ while (<F>) {
+ s/\Q$tmp\E/$file.patched/;
+ $diff .= $_;
+ }
+ close F;
+ unlink $tmp;
+ return $diff;
+ }
+
+ unlink $tmp;
+ }
+ else {
+ error("Cannot open '$tmp' for writing: $!");
+ }
+
+ return undef;
+}
+
+sub rec_depend
+{
+ my($func, $seen) = @_;
+ return () unless exists $depends{$func};
+ $seen = {%{$seen||{}}};
+ return () if $seen->{$func}++;
+ my %s;
+ grep !$s{$_}++, map { ($_, rec_depend($_, $seen)) } @{$depends{$func}};
+}
+
+sub parse_version
+{
+ my $ver = shift;
+
+ if ($ver =~ /^(\d+)\.(\d+)\.(\d+)$/) {
+ return ($1, $2, $3);
+ }
+ elsif ($ver !~ /^\d+\.[\d_]+$/) {
+ die "cannot parse version '$ver'\n";
+ }
+
+ $ver =~ s/_//g;
+ $ver =~ s/$/000000/;
+
+ my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/;
+
+ $v = int $v;
+ $s = int $s;
+
+ if ($r < 5 || ($r == 5 && $v < 6)) {
+ if ($s % 10) {
+ die "cannot parse version '$ver'\n";
+ }
+ }
+
+ return ($r, $v, $s);
+}
+
+sub format_version
+{
+ my $ver = shift;
+
+ $ver =~ s/$/000000/;
+ my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/;
+
+ $v = int $v;
+ $s = int $s;
+
+ if ($r < 5 || ($r == 5 && $v < 6)) {
+ if ($s % 10) {
+ die "invalid version '$ver'\n";
+ }
+ $s /= 10;
+
+ $ver = sprintf "%d.%03d", $r, $v;
+ $s > 0 and $ver .= sprintf "_%02d", $s;
+
+ return $ver;
+ }
+
+ return sprintf "%d.%d.%d", $r, $v, $s;
+}
+
+sub info
+{
+ $opt{quiet} and return;
+ print @_, "\n";
+}
+
+sub diag
+{
+ $opt{quiet} and return;
+ $opt{diag} and print @_, "\n";
+}
+
+sub warning
+{
+ $opt{quiet} and return;
+ print "*** ", @_, "\n";
+}
+
+sub error
+{
+ print "*** ERROR: ", @_, "\n";
+}
+
+my %given_hints;
+my %given_warnings;
+sub hint
+{
+ $opt{quiet} and return;
+ my $func = shift;
+ my $rv = 0;
+ if (exists $warnings{$func} && !$given_warnings{$func}++) {
+ my $warn = $warnings{$func};
+ $warn =~ s!^!*** !mg;
+ print "*** WARNING: $func\n", $warn;
+ $rv++;
+ }
+ if ($opt{hints} && exists $hints{$func} && !$given_hints{$func}++) {
+ my $hint = $hints{$func};
+ $hint =~ s/^/ /mg;
+ print " --- hint for $func ---\n", $hint;
+ }
+ $rv;
+}
+
+sub usage
+{
+ my($usage) = do { local(@ARGV,$/)=($0); <> } =~ /^=head\d$HS+SYNOPSIS\s*^(.*?)\s*^=/ms;
+ my %M = ( 'I' => '*' );
+ $usage =~ s/^\s*perl\s+\S+/$^X $0/;
+ $usage =~ s/([A-Z])<([^>]+)>/$M{$1}$2$M{$1}/g;
+
+ print <<ENDUSAGE;
+
+Usage: $usage
+
+See perldoc $0 for details.
+
+ENDUSAGE
+
+ exit 2;
+}
+
+sub strip
+{
+ my $self = do { local(@ARGV,$/)=($0); <> };
+ my($copy) = $self =~ /^=head\d\s+COPYRIGHT\s*^(.*?)^=\w+/ms;
+ $copy =~ s/^(?=\S+)/ /gms;
+ $self =~ s/^$HS+Do NOT edit.*?(?=^-)/$copy/ms;
+ $self =~ s/^SKIP.*(?=^__DATA__)/SKIP
+if (\@ARGV && \$ARGV[0] eq '--unstrip') {
+ eval { require Devel::PPPort };
+ \$@ and die "Cannot require Devel::PPPort, please install.\\n";
+ if (eval \$Devel::PPPort::VERSION < $VERSION) {
+ die "$0 was originally generated with Devel::PPPort $VERSION.\\n"
+ . "Your Devel::PPPort is only version \$Devel::PPPort::VERSION.\\n"
+ . "Please install a newer version, or --unstrip will not work.\\n";
+ }
+ Devel::PPPort::WriteFile(\$0);
+ exit 0;
+}
+print <<END;
+
+Sorry, but this is a stripped version of \$0.
+
+To be able to use its original script and doc functionality,
+please try to regenerate this file using:
+
+ \$^X \$0 --unstrip
+
+END
+/ms;
+ my($pl, $c) = $self =~ /(.*^__DATA__)(.*)/ms;
+ $c =~ s{
+ / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*)
+ | ( "[^"\\]*(?:\\.[^"\\]*)*"
+ | '[^'\\]*(?:\\.[^'\\]*)*' )
+ | ($HS+) }{ defined $2 ? ' ' : ($1 || '') }gsex;
+ $c =~ s!\s+$!!mg;
+ $c =~ s!^$LF!!mg;
+ $c =~ s!^\s*#\s*!#!mg;
+ $c =~ s!^\s+!!mg;
+
+ open OUT, ">$0" or die "cannot strip $0: $!\n";
+ print OUT "$pl$c\n";
+
+ exit 0;
+}
+
+__DATA__
+*/
+
+#ifndef _P_P_PORTABILITY_H_
+#define _P_P_PORTABILITY_H_
+
+#ifndef DPPP_NAMESPACE
+# define DPPP_NAMESPACE DPPP_
+#endif
+
+#define DPPP_CAT2(x,y) CAT2(x,y)
+#define DPPP_(name) DPPP_CAT2(DPPP_NAMESPACE, name)
+
+#ifndef PERL_REVISION
+# if !defined(__PATCHLEVEL_H_INCLUDED__) && !(defined(PATCHLEVEL) && defined(SUBVERSION))
+# define PERL_PATCHLEVEL_H_IMPLICIT
+# include <patchlevel.h>
+# endif
+# if !(defined(PERL_VERSION) || (defined(SUBVERSION) && defined(PATCHLEVEL)))
+# include <could_not_find_Perl_patchlevel.h>
+# endif
+# ifndef PERL_REVISION
+# define PERL_REVISION (5)
+ /* Replace: 1 */
+# define PERL_VERSION PATCHLEVEL
+# define PERL_SUBVERSION SUBVERSION
+ /* Replace PERL_PATCHLEVEL with PERL_VERSION */
+ /* Replace: 0 */
+# endif
+#endif
+
+#define _dpppDEC2BCD(dec) ((((dec)/100)<<8)|((((dec)%100)/10)<<4)|((dec)%10))
+#define PERL_BCDVERSION ((_dpppDEC2BCD(PERL_REVISION)<<24)|(_dpppDEC2BCD(PERL_VERSION)<<12)|_dpppDEC2BCD(PERL_SUBVERSION))
+
+/* It is very unlikely that anyone will try to use this with Perl 6
+ (or greater), but who knows.
+ */
+#if PERL_REVISION != 5
+# error ppport.h only works with Perl version 5
+#endif /* PERL_REVISION != 5 */
+#ifndef dTHR
+# define dTHR dNOOP
+#endif
+#ifndef dTHX
+# define dTHX dNOOP
+#endif
+
+#ifndef dTHXa
+# define dTHXa(x) dNOOP
+#endif
+#ifndef pTHX
+# define pTHX void
+#endif
+
+#ifndef pTHX_
+# define pTHX_
+#endif
+
+#ifndef aTHX
+# define aTHX
+#endif
+
+#ifndef aTHX_
+# define aTHX_
+#endif
+
+#if (PERL_BCDVERSION < 0x5006000)
+# ifdef USE_THREADS
+# define aTHXR thr
+# define aTHXR_ thr,
+# else
+# define aTHXR
+# define aTHXR_
+# endif
+# define dTHXR dTHR
+#else
+# define aTHXR aTHX
+# define aTHXR_ aTHX_
+# define dTHXR dTHX
+#endif
+#ifndef dTHXoa
+# define dTHXoa(x) dTHXa(x)
+#endif
+
+#ifdef I_LIMITS
+# include <limits.h>
+#endif
+
+#ifndef PERL_UCHAR_MIN
+# define PERL_UCHAR_MIN ((unsigned char)0)
+#endif
+
+#ifndef PERL_UCHAR_MAX
+# ifdef UCHAR_MAX
+# define PERL_UCHAR_MAX ((unsigned char)UCHAR_MAX)
+# else
+# ifdef MAXUCHAR
+# define PERL_UCHAR_MAX ((unsigned char)MAXUCHAR)
+# else
+# define PERL_UCHAR_MAX ((unsigned char)~(unsigned)0)
+# endif
+# endif
+#endif
+
+#ifndef PERL_USHORT_MIN
+# define PERL_USHORT_MIN ((unsigned short)0)
+#endif
+
+#ifndef PERL_USHORT_MAX
+# ifdef USHORT_MAX
+# define PERL_USHORT_MAX ((unsigned short)USHORT_MAX)
+# else
+# ifdef MAXUSHORT
+# define PERL_USHORT_MAX ((unsigned short)MAXUSHORT)
+# else
+# ifdef USHRT_MAX
+# define PERL_USHORT_MAX ((unsigned short)USHRT_MAX)
+# else
+# define PERL_USHORT_MAX ((unsigned short)~(unsigned)0)
+# endif
+# endif
+# endif
+#endif
+
+#ifndef PERL_SHORT_MAX
+# ifdef SHORT_MAX
+# define PERL_SHORT_MAX ((short)SHORT_MAX)
+# else
+# ifdef MAXSHORT /* Often used in <values.h> */
+# define PERL_SHORT_MAX ((short)MAXSHORT)
+# else
+# ifdef SHRT_MAX
+# define PERL_SHORT_MAX ((short)SHRT_MAX)
+# else
+# define PERL_SHORT_MAX ((short) (PERL_USHORT_MAX >> 1))
+# endif
+# endif
+# endif
+#endif
+
+#ifndef PERL_SHORT_MIN
+# ifdef SHORT_MIN
+# define PERL_SHORT_MIN ((short)SHORT_MIN)
+# else
+# ifdef MINSHORT
+# define PERL_SHORT_MIN ((short)MINSHORT)
+# else
+# ifdef SHRT_MIN
+# define PERL_SHORT_MIN ((short)SHRT_MIN)
+# else
+# define PERL_SHORT_MIN (-PERL_SHORT_MAX - ((3 & -1) == 3))
+# endif
+# endif
+# endif
+#endif
+
+#ifndef PERL_UINT_MAX
+# ifdef UINT_MAX
+# define PERL_UINT_MAX ((unsigned int)UINT_MAX)
+# else
+# ifdef MAXUINT
+# define PERL_UINT_MAX ((unsigned int)MAXUINT)
+# else
+# define PERL_UINT_MAX (~(unsigned int)0)
+# endif
+# endif
+#endif
+
+#ifndef PERL_UINT_MIN
+# define PERL_UINT_MIN ((unsigned int)0)
+#endif
+
+#ifndef PERL_INT_MAX
+# ifdef INT_MAX
+# define PERL_INT_MAX ((int)INT_MAX)
+# else
+# ifdef MAXINT /* Often used in <values.h> */
+# define PERL_INT_MAX ((int)MAXINT)
+# else
+# define PERL_INT_MAX ((int)(PERL_UINT_MAX >> 1))
+# endif
+# endif
+#endif
+
+#ifndef PERL_INT_MIN
+# ifdef INT_MIN
+# define PERL_INT_MIN ((int)INT_MIN)
+# else
+# ifdef MININT
+# define PERL_INT_MIN ((int)MININT)
+# else
+# define PERL_INT_MIN (-PERL_INT_MAX - ((3 & -1) == 3))
+# endif
+# endif
+#endif
+
+#ifndef PERL_ULONG_MAX
+# ifdef ULONG_MAX
+# define PERL_ULONG_MAX ((unsigned long)ULONG_MAX)
+# else
+# ifdef MAXULONG
+# define PERL_ULONG_MAX ((unsigned long)MAXULONG)
+# else
+# define PERL_ULONG_MAX (~(unsigned long)0)
+# endif
+# endif
+#endif
+
+#ifndef PERL_ULONG_MIN
+# define PERL_ULONG_MIN ((unsigned long)0L)
+#endif
+
+#ifndef PERL_LONG_MAX
+# ifdef LONG_MAX
+# define PERL_LONG_MAX ((long)LONG_MAX)
+# else
+# ifdef MAXLONG
+# define PERL_LONG_MAX ((long)MAXLONG)
+# else
+# define PERL_LONG_MAX ((long) (PERL_ULONG_MAX >> 1))
+# endif
+# endif
+#endif
+
+#ifndef PERL_LONG_MIN
+# ifdef LONG_MIN
+# define PERL_LONG_MIN ((long)LONG_MIN)
+# else
+# ifdef MINLONG
+# define PERL_LONG_MIN ((long)MINLONG)
+# else
+# define PERL_LONG_MIN (-PERL_LONG_MAX - ((3 & -1) == 3))
+# endif
+# endif
+#endif
+
+#if defined(HAS_QUAD) && (defined(convex) || defined(uts))
+# ifndef PERL_UQUAD_MAX
+# ifdef ULONGLONG_MAX
+# define PERL_UQUAD_MAX ((unsigned long long)ULONGLONG_MAX)
+# else
+# ifdef MAXULONGLONG
+# define PERL_UQUAD_MAX ((unsigned long long)MAXULONGLONG)
+# else
+# define PERL_UQUAD_MAX (~(unsigned long long)0)
+# endif
+# endif
+# endif
+
+# ifndef PERL_UQUAD_MIN
+# define PERL_UQUAD_MIN ((unsigned long long)0L)
+# endif
+
+# ifndef PERL_QUAD_MAX
+# ifdef LONGLONG_MAX
+# define PERL_QUAD_MAX ((long long)LONGLONG_MAX)
+# else
+# ifdef MAXLONGLONG
+# define PERL_QUAD_MAX ((long long)MAXLONGLONG)
+# else
+# define PERL_QUAD_MAX ((long long) (PERL_UQUAD_MAX >> 1))
+# endif
+# endif
+# endif
+
+# ifndef PERL_QUAD_MIN
+# ifdef LONGLONG_MIN
+# define PERL_QUAD_MIN ((long long)LONGLONG_MIN)
+# else
+# ifdef MINLONGLONG
+# define PERL_QUAD_MIN ((long long)MINLONGLONG)
+# else
+# define PERL_QUAD_MIN (-PERL_QUAD_MAX - ((3 & -1) == 3))
+# endif
+# endif
+# endif
+#endif
+
+/* This is based on code from 5.003 perl.h */
+#ifdef HAS_QUAD
+# ifdef cray
+#ifndef IVTYPE
+# define IVTYPE int
+#endif
+
+#ifndef IV_MIN
+# define IV_MIN PERL_INT_MIN
+#endif
+
+#ifndef IV_MAX
+# define IV_MAX PERL_INT_MAX
+#endif
+
+#ifndef UV_MIN
+# define UV_MIN PERL_UINT_MIN
+#endif
+
+#ifndef UV_MAX
+# define UV_MAX PERL_UINT_MAX
+#endif
+
+# ifdef INTSIZE
+#ifndef IVSIZE
+# define IVSIZE INTSIZE
+#endif
+
+# endif
+# else
+# if defined(convex) || defined(uts)
+#ifndef IVTYPE
+# define IVTYPE long long
+#endif
+
+#ifndef IV_MIN
+# define IV_MIN PERL_QUAD_MIN
+#endif
+
+#ifndef IV_MAX
+# define IV_MAX PERL_QUAD_MAX
+#endif
+
+#ifndef UV_MIN
+# define UV_MIN PERL_UQUAD_MIN
+#endif
+
+#ifndef UV_MAX
+# define UV_MAX PERL_UQUAD_MAX
+#endif
+
+# ifdef LONGLONGSIZE
+#ifndef IVSIZE
+# define IVSIZE LONGLONGSIZE
+#endif
+
+# endif
+# else
+#ifndef IVTYPE
+# define IVTYPE long
+#endif
+
+#ifndef IV_MIN
+# define IV_MIN PERL_LONG_MIN
+#endif
+
+#ifndef IV_MAX
+# define IV_MAX PERL_LONG_MAX
+#endif
+
+#ifndef UV_MIN
+# define UV_MIN PERL_ULONG_MIN
+#endif
+
+#ifndef UV_MAX
+# define UV_MAX PERL_ULONG_MAX
+#endif
+
+# ifdef LONGSIZE
+#ifndef IVSIZE
+# define IVSIZE LONGSIZE
+#endif
+
+# endif
+# endif
+# endif
+#ifndef IVSIZE
+# define IVSIZE 8
+#endif
+
+#ifndef PERL_QUAD_MIN
+# define PERL_QUAD_MIN IV_MIN
+#endif
+
+#ifndef PERL_QUAD_MAX
+# define PERL_QUAD_MAX IV_MAX
+#endif
+
+#ifndef PERL_UQUAD_MIN
+# define PERL_UQUAD_MIN UV_MIN
+#endif
+
+#ifndef PERL_UQUAD_MAX
+# define PERL_UQUAD_MAX UV_MAX
+#endif
+
+#else
+#ifndef IVTYPE
+# define IVTYPE long
+#endif
+
+#ifndef IV_MIN
+# define IV_MIN PERL_LONG_MIN
+#endif
+
+#ifndef IV_MAX
+# define IV_MAX PERL_LONG_MAX
+#endif
+
+#ifndef UV_MIN
+# define UV_MIN PERL_ULONG_MIN
+#endif
+
+#ifndef UV_MAX
+# define UV_MAX PERL_ULONG_MAX
+#endif
+
+#endif
+
+#ifndef IVSIZE
+# ifdef LONGSIZE
+# define IVSIZE LONGSIZE
+# else
+# define IVSIZE 4 /* A bold guess, but the best we can make. */
+# endif
+#endif
+#ifndef UVTYPE
+# define UVTYPE unsigned IVTYPE
+#endif
+
+#ifndef UVSIZE
+# define UVSIZE IVSIZE
+#endif
+#ifndef sv_setuv
+# define sv_setuv(sv, uv) \
+ STMT_START { \
+ UV TeMpUv = uv; \
+ if (TeMpUv <= IV_MAX) \
+ sv_setiv(sv, TeMpUv); \
+ else \
+ sv_setnv(sv, (double)TeMpUv); \
+ } STMT_END
+#endif
+#ifndef newSVuv
+# define newSVuv(uv) ((uv) <= IV_MAX ? newSViv((IV)uv) : newSVnv((NV)uv))
+#endif
+#ifndef sv_2uv
+# define sv_2uv(sv) ((PL_Sv = (sv)), (UV) (SvNOK(PL_Sv) ? SvNV(PL_Sv) : sv_2nv(PL_Sv)))
+#endif
+
+#ifndef SvUVX
+# define SvUVX(sv) ((UV)SvIVX(sv))
+#endif
+
+#ifndef SvUVXx
+# define SvUVXx(sv) SvUVX(sv)
+#endif
+
+#ifndef SvUV
+# define SvUV(sv) (SvIOK(sv) ? SvUVX(sv) : sv_2uv(sv))
+#endif
+
+#ifndef SvUVx
+# define SvUVx(sv) ((PL_Sv = (sv)), SvUV(PL_Sv))
+#endif
+
+/* Hint: sv_uv
+ * Always use the SvUVx() macro instead of sv_uv().
+ */
+#ifndef sv_uv
+# define sv_uv(sv) SvUVx(sv)
+#endif
+
+#if !defined(SvUOK) && defined(SvIOK_UV)
+# define SvUOK(sv) SvIOK_UV(sv)
+#endif
+#ifndef XST_mUV
+# define XST_mUV(i,v) (ST(i) = sv_2mortal(newSVuv(v)) )
+#endif
+
+#ifndef XSRETURN_UV
+# define XSRETURN_UV(v) STMT_START { XST_mUV(0,v); XSRETURN(1); } STMT_END
+#endif
+#ifndef PUSHu
+# define PUSHu(u) STMT_START { sv_setuv(TARG, (UV)(u)); PUSHTARG; } STMT_END
+#endif
+
+#ifndef XPUSHu
+# define XPUSHu(u) STMT_START { sv_setuv(TARG, (UV)(u)); XPUSHTARG; } STMT_END
+#endif
+
+#ifdef HAS_MEMCMP
+#ifndef memNE
+# define memNE(s1,s2,l) (memcmp(s1,s2,l))
+#endif
+
+#ifndef memEQ
+# define memEQ(s1,s2,l) (!memcmp(s1,s2,l))
+#endif
+
+#else
+#ifndef memNE
+# define memNE(s1,s2,l) (bcmp(s1,s2,l))
+#endif
+
+#ifndef memEQ
+# define memEQ(s1,s2,l) (!bcmp(s1,s2,l))
+#endif
+
+#endif
+#ifndef memEQs
+# define memEQs(s1, l, s2) \
+ (sizeof(s2)-1 == l && memEQ(s1, (s2 ""), (sizeof(s2)-1)))
+#endif
+
+#ifndef memNEs
+# define memNEs(s1, l, s2) !memEQs(s1, l, s2)
+#endif
+#ifndef MoveD
+# define MoveD(s,d,n,t) memmove((char*)(d),(char*)(s), (n) * sizeof(t))
+#endif
+
+#ifndef CopyD
+# define CopyD(s,d,n,t) memcpy((char*)(d),(char*)(s), (n) * sizeof(t))
+#endif
+
+#ifdef HAS_MEMSET
+#ifndef ZeroD
+# define ZeroD(d,n,t) memzero((char*)(d), (n) * sizeof(t))
+#endif
+
+#else
+#ifndef ZeroD
+# define ZeroD(d,n,t) ((void)memzero((char*)(d), (n) * sizeof(t)), d)
+#endif
+
+#endif
+#ifndef PoisonWith
+# define PoisonWith(d,n,t,b) (void)memset((char*)(d), (U8)(b), (n) * sizeof(t))
+#endif
+
+#ifndef PoisonNew
+# define PoisonNew(d,n,t) PoisonWith(d,n,t,0xAB)
+#endif
+
+#ifndef PoisonFree
+# define PoisonFree(d,n,t) PoisonWith(d,n,t,0xEF)
+#endif
+
+#ifndef Poison
+# define Poison(d,n,t) PoisonFree(d,n,t)
+#endif
+#ifndef Newx
+# define Newx(v,n,t) New(0,v,n,t)
+#endif
+
+#ifndef Newxc
+# define Newxc(v,n,t,c) Newc(0,v,n,t,c)
+#endif
+
+#ifndef Newxz
+# define Newxz(v,n,t) Newz(0,v,n,t)
+#endif
+
+#ifndef PERL_UNUSED_DECL
+# ifdef HASATTRIBUTE
+# if (defined(__GNUC__) && defined(__cplusplus)) || defined(__INTEL_COMPILER)
+# define PERL_UNUSED_DECL
+# else
+# define PERL_UNUSED_DECL __attribute__((unused))
+# endif
+# else
+# define PERL_UNUSED_DECL
+# endif
+#endif
+
+#ifndef PERL_UNUSED_ARG
+# if defined(lint) && defined(S_SPLINT_S) /* www.splint.org */
+# include <note.h>
+# define PERL_UNUSED_ARG(x) NOTE(ARGUNUSED(x))
+# else
+# define PERL_UNUSED_ARG(x) ((void)x)
+# endif
+#endif
+
+#ifndef PERL_UNUSED_VAR
+# define PERL_UNUSED_VAR(x) ((void)x)
+#endif
+
+#ifndef PERL_UNUSED_CONTEXT
+# ifdef USE_ITHREADS
+# define PERL_UNUSED_CONTEXT PERL_UNUSED_ARG(my_perl)
+# else
+# define PERL_UNUSED_CONTEXT
+# endif
+#endif
+#ifndef NOOP
+# define NOOP /*EMPTY*/(void)0
+#endif
+
+#ifndef dNOOP
+# define dNOOP extern int /*@unused@*/ Perl___notused PERL_UNUSED_DECL
+#endif
+
+#ifndef NVTYPE
+# if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE)
+# define NVTYPE long double
+# else
+# define NVTYPE double
+# endif
+typedef NVTYPE NV;
+#endif
+
+#ifndef INT2PTR
+# if (IVSIZE == PTRSIZE) && (UVSIZE == PTRSIZE)
+# define PTRV UV
+# define INT2PTR(any,d) (any)(d)
+# else
+# if PTRSIZE == LONGSIZE
+# define PTRV unsigned long
+# else
+# define PTRV unsigned
+# endif
+# define INT2PTR(any,d) (any)(PTRV)(d)
+# endif
+#endif
+
+#ifndef PTR2ul
+# if PTRSIZE == LONGSIZE
+# define PTR2ul(p) (unsigned long)(p)
+# else
+# define PTR2ul(p) INT2PTR(unsigned long,p)
+# endif
+#endif
+#ifndef PTR2nat
+# define PTR2nat(p) (PTRV)(p)
+#endif
+
+#ifndef NUM2PTR
+# define NUM2PTR(any,d) (any)PTR2nat(d)
+#endif
+
+#ifndef PTR2IV
+# define PTR2IV(p) INT2PTR(IV,p)
+#endif
+
+#ifndef PTR2UV
+# define PTR2UV(p) INT2PTR(UV,p)
+#endif
+
+#ifndef PTR2NV
+# define PTR2NV(p) NUM2PTR(NV,p)
+#endif
+
+#undef START_EXTERN_C
+#undef END_EXTERN_C
+#undef EXTERN_C
+#ifdef __cplusplus
+# define START_EXTERN_C extern "C" {
+# define END_EXTERN_C }
+# define EXTERN_C extern "C"
+#else
+# define START_EXTERN_C
+# define END_EXTERN_C
+# define EXTERN_C extern
+#endif
+
+#if defined(PERL_GCC_PEDANTIC)
+# ifndef PERL_GCC_BRACE_GROUPS_FORBIDDEN
+# define PERL_GCC_BRACE_GROUPS_FORBIDDEN
+# endif
+#endif
+
+#if defined(__GNUC__) && !defined(PERL_GCC_BRACE_GROUPS_FORBIDDEN) && !defined(__cplusplus)
+# ifndef PERL_USE_GCC_BRACE_GROUPS
+# define PERL_USE_GCC_BRACE_GROUPS
+# endif
+#endif
+
+#undef STMT_START
+#undef STMT_END
+#ifdef PERL_USE_GCC_BRACE_GROUPS
+# define STMT_START (void)( /* gcc supports ``({ STATEMENTS; })'' */
+# define STMT_END )
+#else
+# if defined(VOIDFLAGS) && (VOIDFLAGS) && (defined(sun) || defined(__sun__)) && !defined(__GNUC__)
+# define STMT_START if (1)
+# define STMT_END else (void)0
+# else
+# define STMT_START do
+# define STMT_END while (0)
+# endif
+#endif
+#ifndef boolSV
+# define boolSV(b) ((b) ? &PL_sv_yes : &PL_sv_no)
+#endif
+
+/* DEFSV appears first in 5.004_56 */
+#ifndef DEFSV
+# define DEFSV GvSV(PL_defgv)
+#endif
+
+#ifndef SAVE_DEFSV
+# define SAVE_DEFSV SAVESPTR(GvSV(PL_defgv))
+#endif
+
+#ifndef DEFSV_set
+# define DEFSV_set(sv) (DEFSV = (sv))
+#endif
+
+/* Older perls (<=5.003) lack AvFILLp */
+#ifndef AvFILLp
+# define AvFILLp AvFILL
+#endif
+#ifndef ERRSV
+# define ERRSV get_sv("@",FALSE)
+#endif
+
+/* Hint: gv_stashpvn
+ * This function's backport doesn't support the length parameter, but
+ * rather ignores it. Portability can only be ensured if the length
+ * parameter is used for speed reasons, but the length can always be
+ * correctly computed from the string argument.
+ */
+#ifndef gv_stashpvn
+# define gv_stashpvn(str,len,create) gv_stashpv(str,create)
+#endif
+
+/* Replace: 1 */
+#ifndef get_cv
+# define get_cv perl_get_cv
+#endif
+
+#ifndef get_sv
+# define get_sv perl_get_sv
+#endif
+
+#ifndef get_av
+# define get_av perl_get_av
+#endif
+
+#ifndef get_hv
+# define get_hv perl_get_hv
+#endif
+
+/* Replace: 0 */
+#ifndef dUNDERBAR
+# define dUNDERBAR dNOOP
+#endif
+
+#ifndef UNDERBAR
+# define UNDERBAR DEFSV
+#endif
+#ifndef dAX
+# define dAX I32 ax = MARK - PL_stack_base + 1
+#endif
+
+#ifndef dITEMS
+# define dITEMS I32 items = SP - MARK
+#endif
+#ifndef dXSTARG
+# define dXSTARG SV * targ = sv_newmortal()
+#endif
+#ifndef dAXMARK
+# define dAXMARK I32 ax = POPMARK; \
+ register SV ** const mark = PL_stack_base + ax++
+#endif
+#ifndef XSprePUSH
+# define XSprePUSH (sp = PL_stack_base + ax - 1)
+#endif
+
+#if (PERL_BCDVERSION < 0x5005000)
+# undef XSRETURN
+# define XSRETURN(off) \
+ STMT_START { \
+ PL_stack_sp = PL_stack_base + ax + ((off) - 1); \
+ return; \
+ } STMT_END
+#endif
+#ifndef XSPROTO
+# define XSPROTO(name) void name(pTHX_ CV* cv)
+#endif
+
+#ifndef SVfARG
+# define SVfARG(p) ((void*)(p))
+#endif
+#ifndef PERL_ABS
+# define PERL_ABS(x) ((x) < 0 ? -(x) : (x))
+#endif
+#ifndef dVAR
+# define dVAR dNOOP
+#endif
+#ifndef SVf
+# define SVf "_"
+#endif
+#ifndef UTF8_MAXBYTES
+# define UTF8_MAXBYTES UTF8_MAXLEN
+#endif
+#ifndef CPERLscope
+# define CPERLscope(x) x
+#endif
+#ifndef PERL_HASH
+# define PERL_HASH(hash,str,len) \
+ STMT_START { \
+ const char *s_PeRlHaSh = str; \
+ I32 i_PeRlHaSh = len; \
+ U32 hash_PeRlHaSh = 0; \
+ while (i_PeRlHaSh--) \
+ hash_PeRlHaSh = hash_PeRlHaSh * 33 + *s_PeRlHaSh++; \
+ (hash) = hash_PeRlHaSh; \
+ } STMT_END
+#endif
+
+#ifndef PERLIO_FUNCS_DECL
+# ifdef PERLIO_FUNCS_CONST
+# define PERLIO_FUNCS_DECL(funcs) const PerlIO_funcs funcs
+# define PERLIO_FUNCS_CAST(funcs) (PerlIO_funcs*)(funcs)
+# else
+# define PERLIO_FUNCS_DECL(funcs) PerlIO_funcs funcs
+# define PERLIO_FUNCS_CAST(funcs) (funcs)
+# endif
+#endif
+
+/* provide these typedefs for older perls */
+#if (PERL_BCDVERSION < 0x5009003)
+
+# ifdef ARGSproto
+typedef OP* (CPERLscope(*Perl_ppaddr_t))(ARGSproto);
+# else
+typedef OP* (CPERLscope(*Perl_ppaddr_t))(pTHX);
+# endif
+
+typedef OP* (CPERLscope(*Perl_check_t)) (pTHX_ OP*);
+
+#endif
+#ifndef isPSXSPC
+# define isPSXSPC(c) (isSPACE(c) || (c) == '\v')
+#endif
+
+#ifndef isBLANK
+# define isBLANK(c) ((c) == ' ' || (c) == '\t')
+#endif
+
+#ifdef EBCDIC
+#ifndef isALNUMC
+# define isALNUMC(c) isalnum(c)
+#endif
+
+#ifndef isASCII
+# define isASCII(c) isascii(c)
+#endif
+
+#ifndef isCNTRL
+# define isCNTRL(c) iscntrl(c)
+#endif
+
+#ifndef isGRAPH
+# define isGRAPH(c) isgraph(c)
+#endif
+
+#ifndef isPRINT
+# define isPRINT(c) isprint(c)
+#endif
+
+#ifndef isPUNCT
+# define isPUNCT(c) ispunct(c)
+#endif
+
+#ifndef isXDIGIT
+# define isXDIGIT(c) isxdigit(c)
+#endif
+
+#else
+# if (PERL_BCDVERSION < 0x5010000)
+/* Hint: isPRINT
+ * The implementation in older perl versions includes all of the
+ * isSPACE() characters, which is wrong. The version provided by
+ * Devel::PPPort always overrides a present buggy version.
+ */
+# undef isPRINT
+# endif
+#ifndef isALNUMC
+# define isALNUMC(c) (isALPHA(c) || isDIGIT(c))
+#endif
+
+#ifndef isASCII
+# define isASCII(c) ((U8) (c) <= 127)
+#endif
+
+#ifndef isCNTRL
+# define isCNTRL(c) ((U8) (c) < ' ' || (c) == 127)
+#endif
+
+#ifndef isGRAPH
+# define isGRAPH(c) (isALNUM(c) || isPUNCT(c))
+#endif
+
+#ifndef isPRINT
+# define isPRINT(c) (((c) >= 32 && (c) < 127))
+#endif
+
+#ifndef isPUNCT
+# define isPUNCT(c) (((c) >= 33 && (c) <= 47) || ((c) >= 58 && (c) <= 64) || ((c) >= 91 && (c) <= 96) || ((c) >= 123 && (c) <= 126))
+#endif
+
+#ifndef isXDIGIT
+# define isXDIGIT(c) (isDIGIT(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
+#endif
+
+#endif
+
+#ifndef PERL_SIGNALS_UNSAFE_FLAG
+
+#define PERL_SIGNALS_UNSAFE_FLAG 0x0001
+
+#if (PERL_BCDVERSION < 0x5008000)
+# define D_PPP_PERL_SIGNALS_INIT PERL_SIGNALS_UNSAFE_FLAG
+#else
+# define D_PPP_PERL_SIGNALS_INIT 0
+#endif
+
+#if defined(NEED_PL_signals)
+static U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT;
+#elif defined(NEED_PL_signals_GLOBAL)
+U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT;
+#else
+extern U32 DPPP_(my_PL_signals);
+#endif
+#define PL_signals DPPP_(my_PL_signals)
+
+#endif
+
+/* Hint: PL_ppaddr
+ * Calling an op via PL_ppaddr requires passing a context argument
+ * for threaded builds. Since the context argument is different for
+ * 5.005 perls, you can use aTHXR (supplied by ppport.h), which will
+ * automatically be defined as the correct argument.
+ */
+
+#if (PERL_BCDVERSION <= 0x5005005)
+/* Replace: 1 */
+# define PL_ppaddr ppaddr
+# define PL_no_modify no_modify
+/* Replace: 0 */
+#endif
+
+#if (PERL_BCDVERSION <= 0x5004005)
+/* Replace: 1 */
+# define PL_DBsignal DBsignal
+# define PL_DBsingle DBsingle
+# define PL_DBsub DBsub
+# define PL_DBtrace DBtrace
+# define PL_Sv Sv
+# define PL_bufend bufend
+# define PL_bufptr bufptr
+# define PL_compiling compiling
+# define PL_copline copline
+# define PL_curcop curcop
+# define PL_curstash curstash
+# define PL_debstash debstash
+# define PL_defgv defgv
+# define PL_diehook diehook
+# define PL_dirty dirty
+# define PL_dowarn dowarn
+# define PL_errgv errgv
+# define PL_error_count error_count
+# define PL_expect expect
+# define PL_hexdigit hexdigit
+# define PL_hints hints
+# define PL_in_my in_my
+# define PL_laststatval laststatval
+# define PL_lex_state lex_state
+# define PL_lex_stuff lex_stuff
+# define PL_linestr linestr
+# define PL_na na
+# define PL_perl_destruct_level perl_destruct_level
+# define PL_perldb perldb
+# define PL_rsfp_filters rsfp_filters
+# define PL_rsfp rsfp
+# define PL_stack_base stack_base
+# define PL_stack_sp stack_sp
+# define PL_statcache statcache
+# define PL_stdingv stdingv
+# define PL_sv_arenaroot sv_arenaroot
+# define PL_sv_no sv_no
+# define PL_sv_undef sv_undef
+# define PL_sv_yes sv_yes
+# define PL_tainted tainted
+# define PL_tainting tainting
+# define PL_tokenbuf tokenbuf
+/* Replace: 0 */
+#endif
+
+/* Warning: PL_parser
+ * For perl versions earlier than 5.9.5, this is an always
+ * non-NULL dummy. Also, it cannot be dereferenced. Don't
+ * use it if you can avoid is and unless you absolutely know
+ * what you're doing.
+ * If you always check that PL_parser is non-NULL, you can
+ * define DPPP_PL_parser_NO_DUMMY to avoid the creation of
+ * a dummy parser structure.
+ */
+
+#if (PERL_BCDVERSION >= 0x5009005)
+# ifdef DPPP_PL_parser_NO_DUMMY
+# define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \
+ (croak("panic: PL_parser == NULL in %s:%d", \
+ __FILE__, __LINE__), (yy_parser *) NULL))->var)
+# else
+# ifdef DPPP_PL_parser_NO_DUMMY_WARNING
+# define D_PPP_parser_dummy_warning(var)
+# else
+# define D_PPP_parser_dummy_warning(var) \
+ warn("warning: dummy PL_" #var " used in %s:%d", __FILE__, __LINE__),
+# endif
+# define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \
+ (D_PPP_parser_dummy_warning(var) &DPPP_(dummy_PL_parser)))->var)
+#if defined(NEED_PL_parser)
+static yy_parser DPPP_(dummy_PL_parser);
+#elif defined(NEED_PL_parser_GLOBAL)
+yy_parser DPPP_(dummy_PL_parser);
+#else
+extern yy_parser DPPP_(dummy_PL_parser);
+#endif
+
+# endif
+
+/* PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf depends on PL_parser */
+/* Warning: PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf
+ * Do not use this variable unless you know exactly what you're
+ * doint. It is internal to the perl parser and may change or even
+ * be removed in the future. As of perl 5.9.5, you have to check
+ * for (PL_parser != NULL) for this variable to have any effect.
+ * An always non-NULL PL_parser dummy is provided for earlier
+ * perl versions.
+ * If PL_parser is NULL when you try to access this variable, a
+ * dummy is being accessed instead and a warning is issued unless
+ * you define DPPP_PL_parser_NO_DUMMY_WARNING.
+ * If DPPP_PL_parser_NO_DUMMY is defined, the code trying to access
+ * this variable will croak with a panic message.
+ */
+
+# define PL_expect D_PPP_my_PL_parser_var(expect)
+# define PL_copline D_PPP_my_PL_parser_var(copline)
+# define PL_rsfp D_PPP_my_PL_parser_var(rsfp)
+# define PL_rsfp_filters D_PPP_my_PL_parser_var(rsfp_filters)
+# define PL_linestr D_PPP_my_PL_parser_var(linestr)
+# define PL_bufptr D_PPP_my_PL_parser_var(bufptr)
+# define PL_bufend D_PPP_my_PL_parser_var(bufend)
+# define PL_lex_state D_PPP_my_PL_parser_var(lex_state)
+# define PL_lex_stuff D_PPP_my_PL_parser_var(lex_stuff)
+# define PL_tokenbuf D_PPP_my_PL_parser_var(tokenbuf)
+# define PL_in_my D_PPP_my_PL_parser_var(in_my)
+# define PL_in_my_stash D_PPP_my_PL_parser_var(in_my_stash)
+# define PL_error_count D_PPP_my_PL_parser_var(error_count)
+
+
+#else
+
+/* ensure that PL_parser != NULL and cannot be dereferenced */
+# define PL_parser ((void *) 1)
+
+#endif
+#ifndef mPUSHs
+# define mPUSHs(s) PUSHs(sv_2mortal(s))
+#endif
+
+#ifndef PUSHmortal
+# define PUSHmortal PUSHs(sv_newmortal())
+#endif
+
+#ifndef mPUSHp
+# define mPUSHp(p,l) sv_setpvn(PUSHmortal, (p), (l))
+#endif
+
+#ifndef mPUSHn
+# define mPUSHn(n) sv_setnv(PUSHmortal, (NV)(n))
+#endif
+
+#ifndef mPUSHi
+# define mPUSHi(i) sv_setiv(PUSHmortal, (IV)(i))
+#endif
+
+#ifndef mPUSHu
+# define mPUSHu(u) sv_setuv(PUSHmortal, (UV)(u))
+#endif
+#ifndef mXPUSHs
+# define mXPUSHs(s) XPUSHs(sv_2mortal(s))
+#endif
+
+#ifndef XPUSHmortal
+# define XPUSHmortal XPUSHs(sv_newmortal())
+#endif
+
+#ifndef mXPUSHp
+# define mXPUSHp(p,l) STMT_START { EXTEND(sp,1); sv_setpvn(PUSHmortal, (p), (l)); } STMT_END
+#endif
+
+#ifndef mXPUSHn
+# define mXPUSHn(n) STMT_START { EXTEND(sp,1); sv_setnv(PUSHmortal, (NV)(n)); } STMT_END
+#endif
+
+#ifndef mXPUSHi
+# define mXPUSHi(i) STMT_START { EXTEND(sp,1); sv_setiv(PUSHmortal, (IV)(i)); } STMT_END
+#endif
+
+#ifndef mXPUSHu
+# define mXPUSHu(u) STMT_START { EXTEND(sp,1); sv_setuv(PUSHmortal, (UV)(u)); } STMT_END
+#endif
+
+/* Replace: 1 */
+#ifndef call_sv
+# define call_sv perl_call_sv
+#endif
+
+#ifndef call_pv
+# define call_pv perl_call_pv
+#endif
+
+#ifndef call_argv
+# define call_argv perl_call_argv
+#endif
+
+#ifndef call_method
+# define call_method perl_call_method
+#endif
+#ifndef eval_sv
+# define eval_sv perl_eval_sv
+#endif
+
+/* Replace: 0 */
+#ifndef PERL_LOADMOD_DENY
+# define PERL_LOADMOD_DENY 0x1
+#endif
+
+#ifndef PERL_LOADMOD_NOIMPORT
+# define PERL_LOADMOD_NOIMPORT 0x2
+#endif
+
+#ifndef PERL_LOADMOD_IMPORT_OPS
+# define PERL_LOADMOD_IMPORT_OPS 0x4
+#endif
+
+#ifndef G_METHOD
+# define G_METHOD 64
+# ifdef call_sv
+# undef call_sv
+# endif
+# if (PERL_BCDVERSION < 0x5006000)
+# define call_sv(sv, flags) ((flags) & G_METHOD ? perl_call_method((char *) SvPV_nolen_const(sv), \
+ (flags) & ~G_METHOD) : perl_call_sv(sv, flags))
+# else
+# define call_sv(sv, flags) ((flags) & G_METHOD ? Perl_call_method(aTHX_ (char *) SvPV_nolen_const(sv), \
+ (flags) & ~G_METHOD) : Perl_call_sv(aTHX_ sv, flags))
+# endif
+#endif
+
+/* Replace perl_eval_pv with eval_pv */
+
+#ifndef eval_pv
+#if defined(NEED_eval_pv)
+static SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error);
+static
+#else
+extern SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error);
+#endif
+
+#ifdef eval_pv
+# undef eval_pv
+#endif
+#define eval_pv(a,b) DPPP_(my_eval_pv)(aTHX_ a,b)
+#define Perl_eval_pv DPPP_(my_eval_pv)
+
+#if defined(NEED_eval_pv) || defined(NEED_eval_pv_GLOBAL)
+
+SV*
+DPPP_(my_eval_pv)(char *p, I32 croak_on_error)
+{
+ dSP;
+ SV* sv = newSVpv(p, 0);
+
+ PUSHMARK(sp);
+ eval_sv(sv, G_SCALAR);
+ SvREFCNT_dec(sv);
+
+ SPAGAIN;
+ sv = POPs;
+ PUTBACK;
+
+ if (croak_on_error && SvTRUE(GvSV(errgv)))
+ croak(SvPVx(GvSV(errgv), na));
+
+ return sv;
+}
+
+#endif
+#endif
+
+#ifndef vload_module
+#if defined(NEED_vload_module)
+static void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args);
+static
+#else
+extern void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args);
+#endif
+
+#ifdef vload_module
+# undef vload_module
+#endif
+#define vload_module(a,b,c,d) DPPP_(my_vload_module)(aTHX_ a,b,c,d)
+#define Perl_vload_module DPPP_(my_vload_module)
+
+#if defined(NEED_vload_module) || defined(NEED_vload_module_GLOBAL)
+
+void
+DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args)
+{
+ dTHR;
+ dVAR;
+ OP *veop, *imop;
+
+ OP * const modname = newSVOP(OP_CONST, 0, name);
+ /* 5.005 has a somewhat hacky force_normal that doesn't croak on
+ SvREADONLY() if PL_compling is true. Current perls take care in
+ ck_require() to correctly turn off SvREADONLY before calling
+ force_normal_flags(). This seems a better fix than fudging PL_compling
+ */
+ SvREADONLY_off(((SVOP*)modname)->op_sv);
+ modname->op_private |= OPpCONST_BARE;
+ if (ver) {
+ veop = newSVOP(OP_CONST, 0, ver);
+ }
+ else
+ veop = NULL;
+ if (flags & PERL_LOADMOD_NOIMPORT) {
+ imop = sawparens(newNULLLIST());
+ }
+ else if (flags & PERL_LOADMOD_IMPORT_OPS) {
+ imop = va_arg(*args, OP*);
+ }
+ else {
+ SV *sv;
+ imop = NULL;
+ sv = va_arg(*args, SV*);
+ while (sv) {
+ imop = append_elem(OP_LIST, imop, newSVOP(OP_CONST, 0, sv));
+ sv = va_arg(*args, SV*);
+ }
+ }
+ {
+ const line_t ocopline = PL_copline;
+ COP * const ocurcop = PL_curcop;
+ const int oexpect = PL_expect;
+
+#if (PERL_BCDVERSION >= 0x5004000)
+ utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(FALSE, 0),
+ veop, modname, imop);
+#else
+ utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(),
+ modname, imop);
+#endif
+ PL_expect = oexpect;
+ PL_copline = ocopline;
+ PL_curcop = ocurcop;
+ }
+}
+
+#endif
+#endif
+
+#ifndef load_module
+#if defined(NEED_load_module)
+static void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...);
+static
+#else
+extern void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...);
+#endif
+
+#ifdef load_module
+# undef load_module
+#endif
+#define load_module DPPP_(my_load_module)
+#define Perl_load_module DPPP_(my_load_module)
+
+#if defined(NEED_load_module) || defined(NEED_load_module_GLOBAL)
+
+void
+DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...)
+{
+ va_list args;
+ va_start(args, ver);
+ vload_module(flags, name, ver, &args);
+ va_end(args);
+}
+
+#endif
+#endif
+#ifndef newRV_inc
+# define newRV_inc(sv) newRV(sv) /* Replace */
+#endif
+
+#ifndef newRV_noinc
+#if defined(NEED_newRV_noinc)
+static SV * DPPP_(my_newRV_noinc)(SV *sv);
+static
+#else
+extern SV * DPPP_(my_newRV_noinc)(SV *sv);
+#endif
+
+#ifdef newRV_noinc
+# undef newRV_noinc
+#endif
+#define newRV_noinc(a) DPPP_(my_newRV_noinc)(aTHX_ a)
+#define Perl_newRV_noinc DPPP_(my_newRV_noinc)
+
+#if defined(NEED_newRV_noinc) || defined(NEED_newRV_noinc_GLOBAL)
+SV *
+DPPP_(my_newRV_noinc)(SV *sv)
+{
+ SV *rv = (SV *)newRV(sv);
+ SvREFCNT_dec(sv);
+ return rv;
+}
+#endif
+#endif
+
+/* Hint: newCONSTSUB
+ * Returns a CV* as of perl-5.7.1. This return value is not supported
+ * by Devel::PPPort.
+ */
+
+/* newCONSTSUB from IO.xs is in the core starting with 5.004_63 */
+#if (PERL_BCDVERSION < 0x5004063) && (PERL_BCDVERSION != 0x5004005)
+#if defined(NEED_newCONSTSUB)
+static void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv);
+static
+#else
+extern void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv);
+#endif
+
+#ifdef newCONSTSUB
+# undef newCONSTSUB
+#endif
+#define newCONSTSUB(a,b,c) DPPP_(my_newCONSTSUB)(aTHX_ a,b,c)
+#define Perl_newCONSTSUB DPPP_(my_newCONSTSUB)
+
+#if defined(NEED_newCONSTSUB) || defined(NEED_newCONSTSUB_GLOBAL)
+
+/* This is just a trick to avoid a dependency of newCONSTSUB on PL_parser */
+/* (There's no PL_parser in perl < 5.005, so this is completely safe) */
+#define D_PPP_PL_copline PL_copline
+
+void
+DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv)
+{
+ U32 oldhints = PL_hints;
+ HV *old_cop_stash = PL_curcop->cop_stash;
+ HV *old_curstash = PL_curstash;
+ line_t oldline = PL_curcop->cop_line;
+ PL_curcop->cop_line = D_PPP_PL_copline;
+
+ PL_hints &= ~HINT_BLOCK_SCOPE;
+ if (stash)
+ PL_curstash = PL_curcop->cop_stash = stash;
+
+ newSUB(
+
+#if (PERL_BCDVERSION < 0x5003022)
+ start_subparse(),
+#elif (PERL_BCDVERSION == 0x5003022)
+ start_subparse(0),
+#else /* 5.003_23 onwards */
+ start_subparse(FALSE, 0),
+#endif
+
+ newSVOP(OP_CONST, 0, newSVpv((char *) name, 0)),
+ newSVOP(OP_CONST, 0, &PL_sv_no), /* SvPV(&PL_sv_no) == "" -- GMB */
+ newSTATEOP(0, Nullch, newSVOP(OP_CONST, 0, sv))
+ );
+
+ PL_hints = oldhints;
+ PL_curcop->cop_stash = old_cop_stash;
+ PL_curstash = old_curstash;
+ PL_curcop->cop_line = oldline;
+}
+#endif
+#endif
+
+/*
+ * Boilerplate macros for initializing and accessing interpreter-local
+ * data from C. All statics in extensions should be reworked to use
+ * this, if you want to make the extension thread-safe. See ext/re/re.xs
+ * for an example of the use of these macros.
+ *
+ * Code that uses these macros is responsible for the following:
+ * 1. #define MY_CXT_KEY to a unique string, e.g. "DynaLoader_guts"
+ * 2. Declare a typedef named my_cxt_t that is a structure that contains
+ * all the data that needs to be interpreter-local.
+ * 3. Use the START_MY_CXT macro after the declaration of my_cxt_t.
+ * 4. Use the MY_CXT_INIT macro such that it is called exactly once
+ * (typically put in the BOOT: section).
+ * 5. Use the members of the my_cxt_t structure everywhere as
+ * MY_CXT.member.
+ * 6. Use the dMY_CXT macro (a declaration) in all the functions that
+ * access MY_CXT.
+ */
+
+#if defined(MULTIPLICITY) || defined(PERL_OBJECT) || \
+ defined(PERL_CAPI) || defined(PERL_IMPLICIT_CONTEXT)
+
+#ifndef START_MY_CXT
+
+/* This must appear in all extensions that define a my_cxt_t structure,
+ * right after the definition (i.e. at file scope). The non-threads
+ * case below uses it to declare the data as static. */
+#define START_MY_CXT
+
+#if (PERL_BCDVERSION < 0x5004068)
+/* Fetches the SV that keeps the per-interpreter data. */
+#define dMY_CXT_SV \
+ SV *my_cxt_sv = get_sv(MY_CXT_KEY, FALSE)
+#else /* >= perl5.004_68 */
+#define dMY_CXT_SV \
+ SV *my_cxt_sv = *hv_fetch(PL_modglobal, MY_CXT_KEY, \
+ sizeof(MY_CXT_KEY)-1, TRUE)
+#endif /* < perl5.004_68 */
+
+/* This declaration should be used within all functions that use the
+ * interpreter-local data. */
+#define dMY_CXT \
+ dMY_CXT_SV; \
+ my_cxt_t *my_cxtp = INT2PTR(my_cxt_t*,SvUV(my_cxt_sv))
+
+/* Creates and zeroes the per-interpreter data.
+ * (We allocate my_cxtp in a Perl SV so that it will be released when
+ * the interpreter goes away.) */
+#define MY_CXT_INIT \
+ dMY_CXT_SV; \
+ /* newSV() allocates one more than needed */ \
+ my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\
+ Zero(my_cxtp, 1, my_cxt_t); \
+ sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
+
+/* This macro must be used to access members of the my_cxt_t structure.
+ * e.g. MYCXT.some_data */
+#define MY_CXT (*my_cxtp)
+
+/* Judicious use of these macros can reduce the number of times dMY_CXT
+ * is used. Use is similar to pTHX, aTHX etc. */
+#define pMY_CXT my_cxt_t *my_cxtp
+#define pMY_CXT_ pMY_CXT,
+#define _pMY_CXT ,pMY_CXT
+#define aMY_CXT my_cxtp
+#define aMY_CXT_ aMY_CXT,
+#define _aMY_CXT ,aMY_CXT
+
+#endif /* START_MY_CXT */
+
+#ifndef MY_CXT_CLONE
+/* Clones the per-interpreter data. */
+#define MY_CXT_CLONE \
+ dMY_CXT_SV; \
+ my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\
+ Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t);\
+ sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
+#endif
+
+#else /* single interpreter */
+
+#ifndef START_MY_CXT
+
+#define START_MY_CXT static my_cxt_t my_cxt;
+#define dMY_CXT_SV dNOOP
+#define dMY_CXT dNOOP
+#define MY_CXT_INIT NOOP
+#define MY_CXT my_cxt
+
+#define pMY_CXT void
+#define pMY_CXT_
+#define _pMY_CXT
+#define aMY_CXT
+#define aMY_CXT_
+#define _aMY_CXT
+
+#endif /* START_MY_CXT */
+
+#ifndef MY_CXT_CLONE
+#define MY_CXT_CLONE NOOP
+#endif
+
+#endif
+
+#ifndef IVdf
+# if IVSIZE == LONGSIZE
+# define IVdf "ld"
+# define UVuf "lu"
+# define UVof "lo"
+# define UVxf "lx"
+# define UVXf "lX"
+# else
+# if IVSIZE == INTSIZE
+# define IVdf "d"
+# define UVuf "u"
+# define UVof "o"
+# define UVxf "x"
+# define UVXf "X"
+# endif
+# endif
+#endif
+
+#ifndef NVef
+# if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) && \
+ defined(PERL_PRIfldbl) && (PERL_BCDVERSION != 0x5006000)
+ /* Not very likely, but let's try anyway. */
+# define NVef PERL_PRIeldbl
+# define NVff PERL_PRIfldbl
+# define NVgf PERL_PRIgldbl
+# else
+# define NVef "e"
+# define NVff "f"
+# define NVgf "g"
+# endif
+#endif
+
+#ifndef SvREFCNT_inc
+# ifdef PERL_USE_GCC_BRACE_GROUPS
+# define SvREFCNT_inc(sv) \
+ ({ \
+ SV * const _sv = (SV*)(sv); \
+ if (_sv) \
+ (SvREFCNT(_sv))++; \
+ _sv; \
+ })
+# else
+# define SvREFCNT_inc(sv) \
+ ((PL_Sv=(SV*)(sv)) ? (++(SvREFCNT(PL_Sv)),PL_Sv) : NULL)
+# endif
+#endif
+
+#ifndef SvREFCNT_inc_simple
+# ifdef PERL_USE_GCC_BRACE_GROUPS
+# define SvREFCNT_inc_simple(sv) \
+ ({ \
+ if (sv) \
+ (SvREFCNT(sv))++; \
+ (SV *)(sv); \
+ })
+# else
+# define SvREFCNT_inc_simple(sv) \
+ ((sv) ? (SvREFCNT(sv)++,(SV*)(sv)) : NULL)
+# endif
+#endif
+
+#ifndef SvREFCNT_inc_NN
+# ifdef PERL_USE_GCC_BRACE_GROUPS
+# define SvREFCNT_inc_NN(sv) \
+ ({ \
+ SV * const _sv = (SV*)(sv); \
+ SvREFCNT(_sv)++; \
+ _sv; \
+ })
+# else
+# define SvREFCNT_inc_NN(sv) \
+ (PL_Sv=(SV*)(sv),++(SvREFCNT(PL_Sv)),PL_Sv)
+# endif
+#endif
+
+#ifndef SvREFCNT_inc_void
+# ifdef PERL_USE_GCC_BRACE_GROUPS
+# define SvREFCNT_inc_void(sv) \
+ ({ \
+ SV * const _sv = (SV*)(sv); \
+ if (_sv) \
+ (void)(SvREFCNT(_sv)++); \
+ })
+# else
+# define SvREFCNT_inc_void(sv) \
+ (void)((PL_Sv=(SV*)(sv)) ? ++(SvREFCNT(PL_Sv)) : 0)
+# endif
+#endif
+#ifndef SvREFCNT_inc_simple_void
+# define SvREFCNT_inc_simple_void(sv) STMT_START { if (sv) SvREFCNT(sv)++; } STMT_END
+#endif
+
+#ifndef SvREFCNT_inc_simple_NN
+# define SvREFCNT_inc_simple_NN(sv) (++SvREFCNT(sv), (SV*)(sv))
+#endif
+
+#ifndef SvREFCNT_inc_void_NN
+# define SvREFCNT_inc_void_NN(sv) (void)(++SvREFCNT((SV*)(sv)))
+#endif
+
+#ifndef SvREFCNT_inc_simple_void_NN
+# define SvREFCNT_inc_simple_void_NN(sv) (void)(++SvREFCNT((SV*)(sv)))
+#endif
+
+#ifndef newSV_type
+
+#if defined(NEED_newSV_type)
+static SV* DPPP_(my_newSV_type)(pTHX_ svtype const t);
+static
+#else
+extern SV* DPPP_(my_newSV_type)(pTHX_ svtype const t);
+#endif
+
+#ifdef newSV_type
+# undef newSV_type
+#endif
+#define newSV_type(a) DPPP_(my_newSV_type)(aTHX_ a)
+#define Perl_newSV_type DPPP_(my_newSV_type)
+
+#if defined(NEED_newSV_type) || defined(NEED_newSV_type_GLOBAL)
+
+SV*
+DPPP_(my_newSV_type)(pTHX_ svtype const t)
+{
+ SV* const sv = newSV(0);
+ sv_upgrade(sv, t);
+ return sv;
+}
+
+#endif
+
+#endif
+
+#if (PERL_BCDVERSION < 0x5006000)
+# define D_PPP_CONSTPV_ARG(x) ((char *) (x))
+#else
+# define D_PPP_CONSTPV_ARG(x) (x)
+#endif
+#ifndef newSVpvn
+# define newSVpvn(data,len) ((data) \
+ ? ((len) ? newSVpv((data), (len)) : newSVpv("", 0)) \
+ : newSV(0))
+#endif
+#ifndef newSVpvn_utf8
+# define newSVpvn_utf8(s, len, u) newSVpvn_flags((s), (len), (u) ? SVf_UTF8 : 0)
+#endif
+#ifndef SVf_UTF8
+# define SVf_UTF8 0
+#endif
+
+#ifndef newSVpvn_flags
+
+#if defined(NEED_newSVpvn_flags)
+static SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags);
+static
+#else
+extern SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags);
+#endif
+
+#ifdef newSVpvn_flags
+# undef newSVpvn_flags
+#endif
+#define newSVpvn_flags(a,b,c) DPPP_(my_newSVpvn_flags)(aTHX_ a,b,c)
+#define Perl_newSVpvn_flags DPPP_(my_newSVpvn_flags)
+
+#if defined(NEED_newSVpvn_flags) || defined(NEED_newSVpvn_flags_GLOBAL)
+
+SV *
+DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags)
+{
+ SV *sv = newSVpvn(D_PPP_CONSTPV_ARG(s), len);
+ SvFLAGS(sv) |= (flags & SVf_UTF8);
+ return (flags & SVs_TEMP) ? sv_2mortal(sv) : sv;
+}
+
+#endif
+
+#endif
+
+/* Backwards compatibility stuff... :-( */
+#if !defined(NEED_sv_2pv_flags) && defined(NEED_sv_2pv_nolen)
+# define NEED_sv_2pv_flags
+#endif
+#if !defined(NEED_sv_2pv_flags_GLOBAL) && defined(NEED_sv_2pv_nolen_GLOBAL)
+# define NEED_sv_2pv_flags_GLOBAL
+#endif
+
+/* Hint: sv_2pv_nolen
+ * Use the SvPV_nolen() or SvPV_nolen_const() macros instead of sv_2pv_nolen().
+ */
+#ifndef sv_2pv_nolen
+# define sv_2pv_nolen(sv) SvPV_nolen(sv)
+#endif
+
+#ifdef SvPVbyte
+
+/* Hint: SvPVbyte
+ * Does not work in perl-5.6.1, ppport.h implements a version
+ * borrowed from perl-5.7.3.
+ */
+
+#if (PERL_BCDVERSION < 0x5007000)
+
+#if defined(NEED_sv_2pvbyte)
+static char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp);
+static
+#else
+extern char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp);
+#endif
+
+#ifdef sv_2pvbyte
+# undef sv_2pvbyte
+#endif
+#define sv_2pvbyte(a,b) DPPP_(my_sv_2pvbyte)(aTHX_ a,b)
+#define Perl_sv_2pvbyte DPPP_(my_sv_2pvbyte)
+
+#if defined(NEED_sv_2pvbyte) || defined(NEED_sv_2pvbyte_GLOBAL)
+
+char *
+DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp)
+{
+ sv_utf8_downgrade(sv,0);
+ return SvPV(sv,*lp);
+}
+
+#endif
+
+/* Hint: sv_2pvbyte
+ * Use the SvPVbyte() macro instead of sv_2pvbyte().
+ */
+
+#undef SvPVbyte
+
+#define SvPVbyte(sv, lp) \
+ ((SvFLAGS(sv) & (SVf_POK|SVf_UTF8)) == (SVf_POK) \
+ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pvbyte(sv, &lp))
+
+#endif
+
+#else
+
+# define SvPVbyte SvPV
+# define sv_2pvbyte sv_2pv
+
+#endif
+#ifndef sv_2pvbyte_nolen
+# define sv_2pvbyte_nolen(sv) sv_2pv_nolen(sv)
+#endif
+
+/* Hint: sv_pvn
+ * Always use the SvPV() macro instead of sv_pvn().
+ */
+
+/* Hint: sv_pvn_force
+ * Always use the SvPV_force() macro instead of sv_pvn_force().
+ */
+
+/* If these are undefined, they're not handled by the core anyway */
+#ifndef SV_IMMEDIATE_UNREF
+# define SV_IMMEDIATE_UNREF 0
+#endif
+
+#ifndef SV_GMAGIC
+# define SV_GMAGIC 0
+#endif
+
+#ifndef SV_COW_DROP_PV
+# define SV_COW_DROP_PV 0
+#endif
+
+#ifndef SV_UTF8_NO_ENCODING
+# define SV_UTF8_NO_ENCODING 0
+#endif
+
+#ifndef SV_NOSTEAL
+# define SV_NOSTEAL 0
+#endif
+
+#ifndef SV_CONST_RETURN
+# define SV_CONST_RETURN 0
+#endif
+
+#ifndef SV_MUTABLE_RETURN
+# define SV_MUTABLE_RETURN 0
+#endif
+
+#ifndef SV_SMAGIC
+# define SV_SMAGIC 0
+#endif
+
+#ifndef SV_HAS_TRAILING_NUL
+# define SV_HAS_TRAILING_NUL 0
+#endif
+
+#ifndef SV_COW_SHARED_HASH_KEYS
+# define SV_COW_SHARED_HASH_KEYS 0
+#endif
+
+#if (PERL_BCDVERSION < 0x5007002)
+
+#if defined(NEED_sv_2pv_flags)
+static char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
+static
+#else
+extern char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
+#endif
+
+#ifdef sv_2pv_flags
+# undef sv_2pv_flags
+#endif
+#define sv_2pv_flags(a,b,c) DPPP_(my_sv_2pv_flags)(aTHX_ a,b,c)
+#define Perl_sv_2pv_flags DPPP_(my_sv_2pv_flags)
+
+#if defined(NEED_sv_2pv_flags) || defined(NEED_sv_2pv_flags_GLOBAL)
+
+char *
+DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags)
+{
+ STRLEN n_a = (STRLEN) flags;
+ return sv_2pv(sv, lp ? lp : &n_a);
+}
+
+#endif
+
+#if defined(NEED_sv_pvn_force_flags)
+static char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
+static
+#else
+extern char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
+#endif
+
+#ifdef sv_pvn_force_flags
+# undef sv_pvn_force_flags
+#endif
+#define sv_pvn_force_flags(a,b,c) DPPP_(my_sv_pvn_force_flags)(aTHX_ a,b,c)
+#define Perl_sv_pvn_force_flags DPPP_(my_sv_pvn_force_flags)
+
+#if defined(NEED_sv_pvn_force_flags) || defined(NEED_sv_pvn_force_flags_GLOBAL)
+
+char *
+DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags)
+{
+ STRLEN n_a = (STRLEN) flags;
+ return sv_pvn_force(sv, lp ? lp : &n_a);
+}
+
+#endif
+
+#endif
+
+#if (PERL_BCDVERSION < 0x5008008) || ( (PERL_BCDVERSION >= 0x5009000) && (PERL_BCDVERSION < 0x5009003) )
+# define DPPP_SVPV_NOLEN_LP_ARG &PL_na
+#else
+# define DPPP_SVPV_NOLEN_LP_ARG 0
+#endif
+#ifndef SvPV_const
+# define SvPV_const(sv, lp) SvPV_flags_const(sv, lp, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_mutable
+# define SvPV_mutable(sv, lp) SvPV_flags_mutable(sv, lp, SV_GMAGIC)
+#endif
+#ifndef SvPV_flags
+# define SvPV_flags(sv, lp, flags) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pv_flags(sv, &lp, flags))
+#endif
+#ifndef SvPV_flags_const
+# define SvPV_flags_const(sv, lp, flags) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? ((lp = SvCUR(sv)), SvPVX_const(sv)) : \
+ (const char*) sv_2pv_flags(sv, &lp, flags|SV_CONST_RETURN))
+#endif
+#ifndef SvPV_flags_const_nolen
+# define SvPV_flags_const_nolen(sv, flags) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? SvPVX_const(sv) : \
+ (const char*) sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags|SV_CONST_RETURN))
+#endif
+#ifndef SvPV_flags_mutable
+# define SvPV_flags_mutable(sv, lp, flags) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) : \
+ sv_2pv_flags(sv, &lp, flags|SV_MUTABLE_RETURN))
+#endif
+#ifndef SvPV_force
+# define SvPV_force(sv, lp) SvPV_force_flags(sv, lp, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_force_nolen
+# define SvPV_force_nolen(sv) SvPV_force_flags_nolen(sv, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_force_mutable
+# define SvPV_force_mutable(sv, lp) SvPV_force_flags_mutable(sv, lp, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_force_nomg
+# define SvPV_force_nomg(sv, lp) SvPV_force_flags(sv, lp, 0)
+#endif
+
+#ifndef SvPV_force_nomg_nolen
+# define SvPV_force_nomg_nolen(sv) SvPV_force_flags_nolen(sv, 0)
+#endif
+#ifndef SvPV_force_flags
+# define SvPV_force_flags(sv, lp, flags) \
+ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
+ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_pvn_force_flags(sv, &lp, flags))
+#endif
+#ifndef SvPV_force_flags_nolen
+# define SvPV_force_flags_nolen(sv, flags) \
+ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
+ ? SvPVX(sv) : sv_pvn_force_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags))
+#endif
+#ifndef SvPV_force_flags_mutable
+# define SvPV_force_flags_mutable(sv, lp, flags) \
+ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
+ ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) \
+ : sv_pvn_force_flags(sv, &lp, flags|SV_MUTABLE_RETURN))
+#endif
+#ifndef SvPV_nolen
+# define SvPV_nolen(sv) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? SvPVX(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC))
+#endif
+#ifndef SvPV_nolen_const
+# define SvPV_nolen_const(sv) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? SvPVX_const(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC|SV_CONST_RETURN))
+#endif
+#ifndef SvPV_nomg
+# define SvPV_nomg(sv, lp) SvPV_flags(sv, lp, 0)
+#endif
+
+#ifndef SvPV_nomg_const
+# define SvPV_nomg_const(sv, lp) SvPV_flags_const(sv, lp, 0)
+#endif
+
+#ifndef SvPV_nomg_const_nolen
+# define SvPV_nomg_const_nolen(sv) SvPV_flags_const_nolen(sv, 0)
+#endif
+#ifndef SvPV_renew
+# define SvPV_renew(sv,n) STMT_START { SvLEN_set(sv, n); \
+ SvPV_set((sv), (char *) saferealloc( \
+ (Malloc_t)SvPVX(sv), (MEM_SIZE)((n)))); \
+ } STMT_END
+#endif
+#ifndef SvMAGIC_set
+# define SvMAGIC_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \
+ (((XPVMG*) SvANY(sv))->xmg_magic = (val)); } STMT_END
+#endif
+
+#if (PERL_BCDVERSION < 0x5009003)
+#ifndef SvPVX_const
+# define SvPVX_const(sv) ((const char*) (0 + SvPVX(sv)))
+#endif
+
+#ifndef SvPVX_mutable
+# define SvPVX_mutable(sv) (0 + SvPVX(sv))
+#endif
+#ifndef SvRV_set
+# define SvRV_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) >= SVt_RV); \
+ (((XRV*) SvANY(sv))->xrv_rv = (val)); } STMT_END
+#endif
+
+#else
+#ifndef SvPVX_const
+# define SvPVX_const(sv) ((const char*)((sv)->sv_u.svu_pv))
+#endif
+
+#ifndef SvPVX_mutable
+# define SvPVX_mutable(sv) ((sv)->sv_u.svu_pv)
+#endif
+#ifndef SvRV_set
+# define SvRV_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) >= SVt_RV); \
+ ((sv)->sv_u.svu_rv = (val)); } STMT_END
+#endif
+
+#endif
+#ifndef SvSTASH_set
+# define SvSTASH_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \
+ (((XPVMG*) SvANY(sv))->xmg_stash = (val)); } STMT_END
+#endif
+
+#if (PERL_BCDVERSION < 0x5004000)
+#ifndef SvUV_set
+# define SvUV_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \
+ (((XPVIV*) SvANY(sv))->xiv_iv = (IV) (val)); } STMT_END
+#endif
+
+#else
+#ifndef SvUV_set
+# define SvUV_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \
+ (((XPVUV*) SvANY(sv))->xuv_uv = (val)); } STMT_END
+#endif
+
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(vnewSVpvf)
+#if defined(NEED_vnewSVpvf)
+static SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args);
+static
+#else
+extern SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args);
+#endif
+
+#ifdef vnewSVpvf
+# undef vnewSVpvf
+#endif
+#define vnewSVpvf(a,b) DPPP_(my_vnewSVpvf)(aTHX_ a,b)
+#define Perl_vnewSVpvf DPPP_(my_vnewSVpvf)
+
+#if defined(NEED_vnewSVpvf) || defined(NEED_vnewSVpvf_GLOBAL)
+
+SV *
+DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args)
+{
+ register SV *sv = newSV(0);
+ sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*));
+ return sv;
+}
+
+#endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf)
+# define sv_vcatpvf(sv, pat, args) sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*))
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf)
+# define sv_vsetpvf(sv, pat, args) sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*))
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg)
+#if defined(NEED_sv_catpvf_mg)
+static void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
+static
+#else
+extern void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
+#endif
+
+#define Perl_sv_catpvf_mg DPPP_(my_sv_catpvf_mg)
+
+#if defined(NEED_sv_catpvf_mg) || defined(NEED_sv_catpvf_mg_GLOBAL)
+
+void
+DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...)
+{
+ va_list args;
+ va_start(args, pat);
+ sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+ SvSETMAGIC(sv);
+ va_end(args);
+}
+
+#endif
+#endif
+
+#ifdef PERL_IMPLICIT_CONTEXT
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg_nocontext)
+#if defined(NEED_sv_catpvf_mg_nocontext)
+static void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...);
+static
+#else
+extern void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...);
+#endif
+
+#define sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext)
+#define Perl_sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext)
+
+#if defined(NEED_sv_catpvf_mg_nocontext) || defined(NEED_sv_catpvf_mg_nocontext_GLOBAL)
+
+void
+DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...)
+{
+ dTHX;
+ va_list args;
+ va_start(args, pat);
+ sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+ SvSETMAGIC(sv);
+ va_end(args);
+}
+
+#endif
+#endif
+#endif
+
+/* sv_catpvf_mg depends on sv_catpvf_mg_nocontext */
+#ifndef sv_catpvf_mg
+# ifdef PERL_IMPLICIT_CONTEXT
+# define sv_catpvf_mg Perl_sv_catpvf_mg_nocontext
+# else
+# define sv_catpvf_mg Perl_sv_catpvf_mg
+# endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf_mg)
+# define sv_vcatpvf_mg(sv, pat, args) \
+ STMT_START { \
+ sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); \
+ SvSETMAGIC(sv); \
+ } STMT_END
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg)
+#if defined(NEED_sv_setpvf_mg)
+static void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
+static
+#else
+extern void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
+#endif
+
+#define Perl_sv_setpvf_mg DPPP_(my_sv_setpvf_mg)
+
+#if defined(NEED_sv_setpvf_mg) || defined(NEED_sv_setpvf_mg_GLOBAL)
+
+void
+DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...)
+{
+ va_list args;
+ va_start(args, pat);
+ sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+ SvSETMAGIC(sv);
+ va_end(args);
+}
+
+#endif
+#endif
+
+#ifdef PERL_IMPLICIT_CONTEXT
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg_nocontext)
+#if defined(NEED_sv_setpvf_mg_nocontext)
+static void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...);
+static
+#else
+extern void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...);
+#endif
+
+#define sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext)
+#define Perl_sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext)
+
+#if defined(NEED_sv_setpvf_mg_nocontext) || defined(NEED_sv_setpvf_mg_nocontext_GLOBAL)
+
+void
+DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...)
+{
+ dTHX;
+ va_list args;
+ va_start(args, pat);
+ sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+ SvSETMAGIC(sv);
+ va_end(args);
+}
+
+#endif
+#endif
+#endif
+
+/* sv_setpvf_mg depends on sv_setpvf_mg_nocontext */
+#ifndef sv_setpvf_mg
+# ifdef PERL_IMPLICIT_CONTEXT
+# define sv_setpvf_mg Perl_sv_setpvf_mg_nocontext
+# else
+# define sv_setpvf_mg Perl_sv_setpvf_mg
+# endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf_mg)
+# define sv_vsetpvf_mg(sv, pat, args) \
+ STMT_START { \
+ sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); \
+ SvSETMAGIC(sv); \
+ } STMT_END
+#endif
+
+/* Hint: newSVpvn_share
+ * The SVs created by this function only mimic the behaviour of
+ * shared PVs without really being shared. Only use if you know
+ * what you're doing.
+ */
+
+#ifndef newSVpvn_share
+
+#if defined(NEED_newSVpvn_share)
+static SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash);
+static
+#else
+extern SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash);
+#endif
+
+#ifdef newSVpvn_share
+# undef newSVpvn_share
+#endif
+#define newSVpvn_share(a,b,c) DPPP_(my_newSVpvn_share)(aTHX_ a,b,c)
+#define Perl_newSVpvn_share DPPP_(my_newSVpvn_share)
+
+#if defined(NEED_newSVpvn_share) || defined(NEED_newSVpvn_share_GLOBAL)
+
+SV *
+DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash)
+{
+ SV *sv;
+ if (len < 0)
+ len = -len;
+ if (!hash)
+ PERL_HASH(hash, (char*) src, len);
+ sv = newSVpvn((char *) src, len);
+ sv_upgrade(sv, SVt_PVIV);
+ SvIVX(sv) = hash;
+ SvREADONLY_on(sv);
+ SvPOK_on(sv);
+ return sv;
+}
+
+#endif
+
+#endif
+#ifndef SvSHARED_HASH
+# define SvSHARED_HASH(sv) (0 + SvUVX(sv))
+#endif
+#ifndef HvNAME_get
+# define HvNAME_get(hv) HvNAME(hv)
+#endif
+#ifndef HvNAMELEN_get
+# define HvNAMELEN_get(hv) (HvNAME_get(hv) ? (I32)strlen(HvNAME_get(hv)) : 0)
+#endif
+#ifndef GvSVn
+# define GvSVn(gv) GvSV(gv)
+#endif
+
+#ifndef isGV_with_GP
+# define isGV_with_GP(gv) isGV(gv)
+#endif
+
+#ifndef gv_fetchpvn_flags
+# define gv_fetchpvn_flags(name, len, flags, svt) gv_fetchpv(name, flags, svt)
+#endif
+
+#ifndef gv_fetchsv
+# define gv_fetchsv(name, flags, svt) gv_fetchpv(SvPV_nolen_const(name), flags, svt)
+#endif
+#ifndef get_cvn_flags
+# define get_cvn_flags(name, namelen, flags) get_cv(name, flags)
+#endif
+#ifndef WARN_ALL
+# define WARN_ALL 0
+#endif
+
+#ifndef WARN_CLOSURE
+# define WARN_CLOSURE 1
+#endif
+
+#ifndef WARN_DEPRECATED
+# define WARN_DEPRECATED 2
+#endif
+
+#ifndef WARN_EXITING
+# define WARN_EXITING 3
+#endif
+
+#ifndef WARN_GLOB
+# define WARN_GLOB 4
+#endif
+
+#ifndef WARN_IO
+# define WARN_IO 5
+#endif
+
+#ifndef WARN_CLOSED
+# define WARN_CLOSED 6
+#endif
+
+#ifndef WARN_EXEC
+# define WARN_EXEC 7
+#endif
+
+#ifndef WARN_LAYER
+# define WARN_LAYER 8
+#endif
+
+#ifndef WARN_NEWLINE
+# define WARN_NEWLINE 9
+#endif
+
+#ifndef WARN_PIPE
+# define WARN_PIPE 10
+#endif
+
+#ifndef WARN_UNOPENED
+# define WARN_UNOPENED 11
+#endif
+
+#ifndef WARN_MISC
+# define WARN_MISC 12
+#endif
+
+#ifndef WARN_NUMERIC
+# define WARN_NUMERIC 13
+#endif
+
+#ifndef WARN_ONCE
+# define WARN_ONCE 14
+#endif
+
+#ifndef WARN_OVERFLOW
+# define WARN_OVERFLOW 15
+#endif
+
+#ifndef WARN_PACK
+# define WARN_PACK 16
+#endif
+
+#ifndef WARN_PORTABLE
+# define WARN_PORTABLE 17
+#endif
+
+#ifndef WARN_RECURSION
+# define WARN_RECURSION 18
+#endif
+
+#ifndef WARN_REDEFINE
+# define WARN_REDEFINE 19
+#endif
+
+#ifndef WARN_REGEXP
+# define WARN_REGEXP 20
+#endif
+
+#ifndef WARN_SEVERE
+# define WARN_SEVERE 21
+#endif
+
+#ifndef WARN_DEBUGGING
+# define WARN_DEBUGGING 22
+#endif
+
+#ifndef WARN_INPLACE
+# define WARN_INPLACE 23
+#endif
+
+#ifndef WARN_INTERNAL
+# define WARN_INTERNAL 24
+#endif
+
+#ifndef WARN_MALLOC
+# define WARN_MALLOC 25
+#endif
+
+#ifndef WARN_SIGNAL
+# define WARN_SIGNAL 26
+#endif
+
+#ifndef WARN_SUBSTR
+# define WARN_SUBSTR 27
+#endif
+
+#ifndef WARN_SYNTAX
+# define WARN_SYNTAX 28
+#endif
+
+#ifndef WARN_AMBIGUOUS
+# define WARN_AMBIGUOUS 29
+#endif
+
+#ifndef WARN_BAREWORD
+# define WARN_BAREWORD 30
+#endif
+
+#ifndef WARN_DIGIT
+# define WARN_DIGIT 31
+#endif
+
+#ifndef WARN_PARENTHESIS
+# define WARN_PARENTHESIS 32
+#endif
+
+#ifndef WARN_PRECEDENCE
+# define WARN_PRECEDENCE 33
+#endif
+
+#ifndef WARN_PRINTF
+# define WARN_PRINTF 34
+#endif
+
+#ifndef WARN_PROTOTYPE
+# define WARN_PROTOTYPE 35
+#endif
+
+#ifndef WARN_QW
+# define WARN_QW 36
+#endif
+
+#ifndef WARN_RESERVED
+# define WARN_RESERVED 37
+#endif
+
+#ifndef WARN_SEMICOLON
+# define WARN_SEMICOLON 38
+#endif
+
+#ifndef WARN_TAINT
+# define WARN_TAINT 39
+#endif
+
+#ifndef WARN_THREADS
+# define WARN_THREADS 40
+#endif
+
+#ifndef WARN_UNINITIALIZED
+# define WARN_UNINITIALIZED 41
+#endif
+
+#ifndef WARN_UNPACK
+# define WARN_UNPACK 42
+#endif
+
+#ifndef WARN_UNTIE
+# define WARN_UNTIE 43
+#endif
+
+#ifndef WARN_UTF8
+# define WARN_UTF8 44
+#endif
+
+#ifndef WARN_VOID
+# define WARN_VOID 45
+#endif
+
+#ifndef WARN_ASSERTIONS
+# define WARN_ASSERTIONS 46
+#endif
+#ifndef packWARN
+# define packWARN(a) (a)
+#endif
+
+#ifndef ckWARN
+# ifdef G_WARN_ON
+# define ckWARN(a) (PL_dowarn & G_WARN_ON)
+# else
+# define ckWARN(a) PL_dowarn
+# endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(warner)
+#if defined(NEED_warner)
+static void DPPP_(my_warner)(U32 err, const char *pat, ...);
+static
+#else
+extern void DPPP_(my_warner)(U32 err, const char *pat, ...);
+#endif
+
+#define Perl_warner DPPP_(my_warner)
+
+#if defined(NEED_warner) || defined(NEED_warner_GLOBAL)
+
+void
+DPPP_(my_warner)(U32 err, const char *pat, ...)
+{
+ SV *sv;
+ va_list args;
+
+ PERL_UNUSED_ARG(err);
+
+ va_start(args, pat);
+ sv = vnewSVpvf(pat, &args);
+ va_end(args);
+ sv_2mortal(sv);
+ warn("%s", SvPV_nolen(sv));
+}
+
+#define warner Perl_warner
+
+#define Perl_warner_nocontext Perl_warner
+
+#endif
+#endif
+
+/* concatenating with "" ensures that only literal strings are accepted as argument
+ * note that STR_WITH_LEN() can't be used as argument to macros or functions that
+ * under some configurations might be macros
+ */
+#ifndef STR_WITH_LEN
+# define STR_WITH_LEN(s) (s ""), (sizeof(s)-1)
+#endif
+#ifndef newSVpvs
+# define newSVpvs(str) newSVpvn(str "", sizeof(str) - 1)
+#endif
+
+#ifndef newSVpvs_flags
+# define newSVpvs_flags(str, flags) newSVpvn_flags(str "", sizeof(str) - 1, flags)
+#endif
+
+#ifndef newSVpvs_share
+# define newSVpvs_share(str) newSVpvn_share(str "", sizeof(str) - 1, 0)
+#endif
+
+#ifndef sv_catpvs
+# define sv_catpvs(sv, str) sv_catpvn(sv, str "", sizeof(str) - 1)
+#endif
+
+#ifndef sv_setpvs
+# define sv_setpvs(sv, str) sv_setpvn(sv, str "", sizeof(str) - 1)
+#endif
+
+#ifndef hv_fetchs
+# define hv_fetchs(hv, key, lval) hv_fetch(hv, key "", sizeof(key) - 1, lval)
+#endif
+
+#ifndef hv_stores
+# define hv_stores(hv, key, val) hv_store(hv, key "", sizeof(key) - 1, val, 0)
+#endif
+#ifndef gv_fetchpvs
+# define gv_fetchpvs(name, flags, svt) gv_fetchpvn_flags(name "", sizeof(name) - 1, flags, svt)
+#endif
+
+#ifndef gv_stashpvs
+# define gv_stashpvs(name, flags) gv_stashpvn(name "", sizeof(name) - 1, flags)
+#endif
+#ifndef get_cvs
+# define get_cvs(name, flags) get_cvn_flags(name "", sizeof(name)-1, flags)
+#endif
+#ifndef SvGETMAGIC
+# define SvGETMAGIC(x) STMT_START { if (SvGMAGICAL(x)) mg_get(x); } STMT_END
+#endif
+#ifndef PERL_MAGIC_sv
+# define PERL_MAGIC_sv '\0'
+#endif
+
+#ifndef PERL_MAGIC_overload
+# define PERL_MAGIC_overload 'A'
+#endif
+
+#ifndef PERL_MAGIC_overload_elem
+# define PERL_MAGIC_overload_elem 'a'
+#endif
+
+#ifndef PERL_MAGIC_overload_table
+# define PERL_MAGIC_overload_table 'c'
+#endif
+
+#ifndef PERL_MAGIC_bm
+# define PERL_MAGIC_bm 'B'
+#endif
+
+#ifndef PERL_MAGIC_regdata
+# define PERL_MAGIC_regdata 'D'
+#endif
+
+#ifndef PERL_MAGIC_regdatum
+# define PERL_MAGIC_regdatum 'd'
+#endif
+
+#ifndef PERL_MAGIC_env
+# define PERL_MAGIC_env 'E'
+#endif
+
+#ifndef PERL_MAGIC_envelem
+# define PERL_MAGIC_envelem 'e'
+#endif
+
+#ifndef PERL_MAGIC_fm
+# define PERL_MAGIC_fm 'f'
+#endif
+
+#ifndef PERL_MAGIC_regex_global
+# define PERL_MAGIC_regex_global 'g'
+#endif
+
+#ifndef PERL_MAGIC_isa
+# define PERL_MAGIC_isa 'I'
+#endif
+
+#ifndef PERL_MAGIC_isaelem
+# define PERL_MAGIC_isaelem 'i'
+#endif
+
+#ifndef PERL_MAGIC_nkeys
+# define PERL_MAGIC_nkeys 'k'
+#endif
+
+#ifndef PERL_MAGIC_dbfile
+# define PERL_MAGIC_dbfile 'L'
+#endif
+
+#ifndef PERL_MAGIC_dbline
+# define PERL_MAGIC_dbline 'l'
+#endif
+
+#ifndef PERL_MAGIC_mutex
+# define PERL_MAGIC_mutex 'm'
+#endif
+
+#ifndef PERL_MAGIC_shared
+# define PERL_MAGIC_shared 'N'
+#endif
+
+#ifndef PERL_MAGIC_shared_scalar
+# define PERL_MAGIC_shared_scalar 'n'
+#endif
+
+#ifndef PERL_MAGIC_collxfrm
+# define PERL_MAGIC_collxfrm 'o'
+#endif
+
+#ifndef PERL_MAGIC_tied
+# define PERL_MAGIC_tied 'P'
+#endif
+
+#ifndef PERL_MAGIC_tiedelem
+# define PERL_MAGIC_tiedelem 'p'
+#endif
+
+#ifndef PERL_MAGIC_tiedscalar
+# define PERL_MAGIC_tiedscalar 'q'
+#endif
+
+#ifndef PERL_MAGIC_qr
+# define PERL_MAGIC_qr 'r'
+#endif
+
+#ifndef PERL_MAGIC_sig
+# define PERL_MAGIC_sig 'S'
+#endif
+
+#ifndef PERL_MAGIC_sigelem
+# define PERL_MAGIC_sigelem 's'
+#endif
+
+#ifndef PERL_MAGIC_taint
+# define PERL_MAGIC_taint 't'
+#endif
+
+#ifndef PERL_MAGIC_uvar
+# define PERL_MAGIC_uvar 'U'
+#endif
+
+#ifndef PERL_MAGIC_uvar_elem
+# define PERL_MAGIC_uvar_elem 'u'
+#endif
+
+#ifndef PERL_MAGIC_vstring
+# define PERL_MAGIC_vstring 'V'
+#endif
+
+#ifndef PERL_MAGIC_vec
+# define PERL_MAGIC_vec 'v'
+#endif
+
+#ifndef PERL_MAGIC_utf8
+# define PERL_MAGIC_utf8 'w'
+#endif
+
+#ifndef PERL_MAGIC_substr
+# define PERL_MAGIC_substr 'x'
+#endif
+
+#ifndef PERL_MAGIC_defelem
+# define PERL_MAGIC_defelem 'y'
+#endif
+
+#ifndef PERL_MAGIC_glob
+# define PERL_MAGIC_glob '*'
+#endif
+
+#ifndef PERL_MAGIC_arylen
+# define PERL_MAGIC_arylen '#'
+#endif
+
+#ifndef PERL_MAGIC_pos
+# define PERL_MAGIC_pos '.'
+#endif
+
+#ifndef PERL_MAGIC_backref
+# define PERL_MAGIC_backref '<'
+#endif
+
+#ifndef PERL_MAGIC_ext
+# define PERL_MAGIC_ext '~'
+#endif
+
+/* That's the best we can do... */
+#ifndef sv_catpvn_nomg
+# define sv_catpvn_nomg sv_catpvn
+#endif
+
+#ifndef sv_catsv_nomg
+# define sv_catsv_nomg sv_catsv
+#endif
+
+#ifndef sv_setsv_nomg
+# define sv_setsv_nomg sv_setsv
+#endif
+
+#ifndef sv_pvn_nomg
+# define sv_pvn_nomg sv_pvn
+#endif
+
+#ifndef SvIV_nomg
+# define SvIV_nomg SvIV
+#endif
+
+#ifndef SvUV_nomg
+# define SvUV_nomg SvUV
+#endif
+
+#ifndef sv_catpv_mg
+# define sv_catpv_mg(sv, ptr) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_catpv(TeMpSv,ptr); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_catpvn_mg
+# define sv_catpvn_mg(sv, ptr, len) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_catpvn(TeMpSv,ptr,len); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_catsv_mg
+# define sv_catsv_mg(dsv, ssv) \
+ STMT_START { \
+ SV *TeMpSv = dsv; \
+ sv_catsv(TeMpSv,ssv); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setiv_mg
+# define sv_setiv_mg(sv, i) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_setiv(TeMpSv,i); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setnv_mg
+# define sv_setnv_mg(sv, num) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_setnv(TeMpSv,num); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setpv_mg
+# define sv_setpv_mg(sv, ptr) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_setpv(TeMpSv,ptr); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setpvn_mg
+# define sv_setpvn_mg(sv, ptr, len) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_setpvn(TeMpSv,ptr,len); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setsv_mg
+# define sv_setsv_mg(dsv, ssv) \
+ STMT_START { \
+ SV *TeMpSv = dsv; \
+ sv_setsv(TeMpSv,ssv); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setuv_mg
+# define sv_setuv_mg(sv, i) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_setuv(TeMpSv,i); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_usepvn_mg
+# define sv_usepvn_mg(sv, ptr, len) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_usepvn(TeMpSv,ptr,len); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+#ifndef SvVSTRING_mg
+# define SvVSTRING_mg(sv) (SvMAGICAL(sv) ? mg_find(sv, PERL_MAGIC_vstring) : NULL)
+#endif
+
+/* Hint: sv_magic_portable
+ * This is a compatibility function that is only available with
+ * Devel::PPPort. It is NOT in the perl core.
+ * Its purpose is to mimic the 5.8.0 behaviour of sv_magic() when
+ * it is being passed a name pointer with namlen == 0. In that
+ * case, perl 5.8.0 and later store the pointer, not a copy of it.
+ * The compatibility can be provided back to perl 5.004. With
+ * earlier versions, the code will not compile.
+ */
+
+#if (PERL_BCDVERSION < 0x5004000)
+
+ /* code that uses sv_magic_portable will not compile */
+
+#elif (PERL_BCDVERSION < 0x5008000)
+
+# define sv_magic_portable(sv, obj, how, name, namlen) \
+ STMT_START { \
+ SV *SvMp_sv = (sv); \
+ char *SvMp_name = (char *) (name); \
+ I32 SvMp_namlen = (namlen); \
+ if (SvMp_name && SvMp_namlen == 0) \
+ { \
+ MAGIC *mg; \
+ sv_magic(SvMp_sv, obj, how, 0, 0); \
+ mg = SvMAGIC(SvMp_sv); \
+ mg->mg_len = -42; /* XXX: this is the tricky part */ \
+ mg->mg_ptr = SvMp_name; \
+ } \
+ else \
+ { \
+ sv_magic(SvMp_sv, obj, how, SvMp_name, SvMp_namlen); \
+ } \
+ } STMT_END
+
+#else
+
+# define sv_magic_portable(a, b, c, d, e) sv_magic(a, b, c, d, e)
+
+#endif
+
+#ifdef USE_ITHREADS
+#ifndef CopFILE
+# define CopFILE(c) ((c)->cop_file)
+#endif
+
+#ifndef CopFILEGV
+# define CopFILEGV(c) (CopFILE(c) ? gv_fetchfile(CopFILE(c)) : Nullgv)
+#endif
+
+#ifndef CopFILE_set
+# define CopFILE_set(c,pv) ((c)->cop_file = savepv(pv))
+#endif
+
+#ifndef CopFILESV
+# define CopFILESV(c) (CopFILE(c) ? GvSV(gv_fetchfile(CopFILE(c))) : Nullsv)
+#endif
+
+#ifndef CopFILEAV
+# define CopFILEAV(c) (CopFILE(c) ? GvAV(gv_fetchfile(CopFILE(c))) : Nullav)
+#endif
+
+#ifndef CopSTASHPV
+# define CopSTASHPV(c) ((c)->cop_stashpv)
+#endif
+
+#ifndef CopSTASHPV_set
+# define CopSTASHPV_set(c,pv) ((c)->cop_stashpv = ((pv) ? savepv(pv) : Nullch))
+#endif
+
+#ifndef CopSTASH
+# define CopSTASH(c) (CopSTASHPV(c) ? gv_stashpv(CopSTASHPV(c),GV_ADD) : Nullhv)
+#endif
+
+#ifndef CopSTASH_set
+# define CopSTASH_set(c,hv) CopSTASHPV_set(c, (hv) ? HvNAME(hv) : Nullch)
+#endif
+
+#ifndef CopSTASH_eq
+# define CopSTASH_eq(c,hv) ((hv) && (CopSTASHPV(c) == HvNAME(hv) \
+ || (CopSTASHPV(c) && HvNAME(hv) \
+ && strEQ(CopSTASHPV(c), HvNAME(hv)))))
+#endif
+
+#else
+#ifndef CopFILEGV
+# define CopFILEGV(c) ((c)->cop_filegv)
+#endif
+
+#ifndef CopFILEGV_set
+# define CopFILEGV_set(c,gv) ((c)->cop_filegv = (GV*)SvREFCNT_inc(gv))
+#endif
+
+#ifndef CopFILE_set
+# define CopFILE_set(c,pv) CopFILEGV_set((c), gv_fetchfile(pv))
+#endif
+
+#ifndef CopFILESV
+# define CopFILESV(c) (CopFILEGV(c) ? GvSV(CopFILEGV(c)) : Nullsv)
+#endif
+
+#ifndef CopFILEAV
+# define CopFILEAV(c) (CopFILEGV(c) ? GvAV(CopFILEGV(c)) : Nullav)
+#endif
+
+#ifndef CopFILE
+# define CopFILE(c) (CopFILESV(c) ? SvPVX(CopFILESV(c)) : Nullch)
+#endif
+
+#ifndef CopSTASH
+# define CopSTASH(c) ((c)->cop_stash)
+#endif
+
+#ifndef CopSTASH_set
+# define CopSTASH_set(c,hv) ((c)->cop_stash = (hv))
+#endif
+
+#ifndef CopSTASHPV
+# define CopSTASHPV(c) (CopSTASH(c) ? HvNAME(CopSTASH(c)) : Nullch)
+#endif
+
+#ifndef CopSTASHPV_set
+# define CopSTASHPV_set(c,pv) CopSTASH_set((c), gv_stashpv(pv,GV_ADD))
+#endif
+
+#ifndef CopSTASH_eq
+# define CopSTASH_eq(c,hv) (CopSTASH(c) == (hv))
+#endif
+
+#endif /* USE_ITHREADS */
+#ifndef IN_PERL_COMPILETIME
+# define IN_PERL_COMPILETIME (PL_curcop == &PL_compiling)
+#endif
+
+#ifndef IN_LOCALE_RUNTIME
+# define IN_LOCALE_RUNTIME (PL_curcop->op_private & HINT_LOCALE)
+#endif
+
+#ifndef IN_LOCALE_COMPILETIME
+# define IN_LOCALE_COMPILETIME (PL_hints & HINT_LOCALE)
+#endif
+
+#ifndef IN_LOCALE
+# define IN_LOCALE (IN_PERL_COMPILETIME ? IN_LOCALE_COMPILETIME : IN_LOCALE_RUNTIME)
+#endif
+#ifndef IS_NUMBER_IN_UV
+# define IS_NUMBER_IN_UV 0x01
+#endif
+
+#ifndef IS_NUMBER_GREATER_THAN_UV_MAX
+# define IS_NUMBER_GREATER_THAN_UV_MAX 0x02
+#endif
+
+#ifndef IS_NUMBER_NOT_INT
+# define IS_NUMBER_NOT_INT 0x04
+#endif
+
+#ifndef IS_NUMBER_NEG
+# define IS_NUMBER_NEG 0x08
+#endif
+
+#ifndef IS_NUMBER_INFINITY
+# define IS_NUMBER_INFINITY 0x10
+#endif
+
+#ifndef IS_NUMBER_NAN
+# define IS_NUMBER_NAN 0x20
+#endif
+#ifndef GROK_NUMERIC_RADIX
+# define GROK_NUMERIC_RADIX(sp, send) grok_numeric_radix(sp, send)
+#endif
+#ifndef PERL_SCAN_GREATER_THAN_UV_MAX
+# define PERL_SCAN_GREATER_THAN_UV_MAX 0x02
+#endif
+
+#ifndef PERL_SCAN_SILENT_ILLDIGIT
+# define PERL_SCAN_SILENT_ILLDIGIT 0x04
+#endif
+
+#ifndef PERL_SCAN_ALLOW_UNDERSCORES
+# define PERL_SCAN_ALLOW_UNDERSCORES 0x01
+#endif
+
+#ifndef PERL_SCAN_DISALLOW_PREFIX
+# define PERL_SCAN_DISALLOW_PREFIX 0x02
+#endif
+
+#ifndef grok_numeric_radix
+#if defined(NEED_grok_numeric_radix)
+static bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send);
+static
+#else
+extern bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send);
+#endif
+
+#ifdef grok_numeric_radix
+# undef grok_numeric_radix
+#endif
+#define grok_numeric_radix(a,b) DPPP_(my_grok_numeric_radix)(aTHX_ a,b)
+#define Perl_grok_numeric_radix DPPP_(my_grok_numeric_radix)
+
+#if defined(NEED_grok_numeric_radix) || defined(NEED_grok_numeric_radix_GLOBAL)
+bool
+DPPP_(my_grok_numeric_radix)(pTHX_ const char **sp, const char *send)
+{
+#ifdef USE_LOCALE_NUMERIC
+#ifdef PL_numeric_radix_sv
+ if (PL_numeric_radix_sv && IN_LOCALE) {
+ STRLEN len;
+ char* radix = SvPV(PL_numeric_radix_sv, len);
+ if (*sp + len <= send && memEQ(*sp, radix, len)) {
+ *sp += len;
+ return TRUE;
+ }
+ }
+#else
+ /* older perls don't have PL_numeric_radix_sv so the radix
+ * must manually be requested from locale.h
+ */
+#include <locale.h>
+ dTHR; /* needed for older threaded perls */
+ struct lconv *lc = localeconv();
+ char *radix = lc->decimal_point;
+ if (radix && IN_LOCALE) {
+ STRLEN len = strlen(radix);
+ if (*sp + len <= send && memEQ(*sp, radix, len)) {
+ *sp += len;
+ return TRUE;
+ }
+ }
+#endif
+#endif /* USE_LOCALE_NUMERIC */
+ /* always try "." if numeric radix didn't match because
+ * we may have data from different locales mixed */
+ if (*sp < send && **sp == '.') {
+ ++*sp;
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif
+#endif
+
+#ifndef grok_number
+#if defined(NEED_grok_number)
+static int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep);
+static
+#else
+extern int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep);
+#endif
+
+#ifdef grok_number
+# undef grok_number
+#endif
+#define grok_number(a,b,c) DPPP_(my_grok_number)(aTHX_ a,b,c)
+#define Perl_grok_number DPPP_(my_grok_number)
+
+#if defined(NEED_grok_number) || defined(NEED_grok_number_GLOBAL)
+int
+DPPP_(my_grok_number)(pTHX_ const char *pv, STRLEN len, UV *valuep)
+{
+ const char *s = pv;
+ const char *send = pv + len;
+ const UV max_div_10 = UV_MAX / 10;
+ const char max_mod_10 = UV_MAX % 10;
+ int numtype = 0;
+ int sawinf = 0;
+ int sawnan = 0;
+
+ while (s < send && isSPACE(*s))
+ s++;
+ if (s == send) {
+ return 0;
+ } else if (*s == '-') {
+ s++;
+ numtype = IS_NUMBER_NEG;
+ }
+ else if (*s == '+')
+ s++;
+
+ if (s == send)
+ return 0;
+
+ /* next must be digit or the radix separator or beginning of infinity */
+ if (isDIGIT(*s)) {
+ /* UVs are at least 32 bits, so the first 9 decimal digits cannot
+ overflow. */
+ UV value = *s - '0';
+ /* This construction seems to be more optimiser friendly.
+ (without it gcc does the isDIGIT test and the *s - '0' separately)
+ With it gcc on arm is managing 6 instructions (6 cycles) per digit.
+ In theory the optimiser could deduce how far to unroll the loop
+ before checking for overflow. */
+ if (++s < send) {
+ int digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ /* Now got 9 digits, so need to check
+ each time for overflow. */
+ digit = *s - '0';
+ while (digit >= 0 && digit <= 9
+ && (value < max_div_10
+ || (value == max_div_10
+ && digit <= max_mod_10))) {
+ value = value * 10 + digit;
+ if (++s < send)
+ digit = *s - '0';
+ else
+ break;
+ }
+ if (digit >= 0 && digit <= 9
+ && (s < send)) {
+ /* value overflowed.
+ skip the remaining digits, don't
+ worry about setting *valuep. */
+ do {
+ s++;
+ } while (s < send && isDIGIT(*s));
+ numtype |=
+ IS_NUMBER_GREATER_THAN_UV_MAX;
+ goto skip_value;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ numtype |= IS_NUMBER_IN_UV;
+ if (valuep)
+ *valuep = value;
+
+ skip_value:
+ if (GROK_NUMERIC_RADIX(&s, send)) {
+ numtype |= IS_NUMBER_NOT_INT;
+ while (s < send && isDIGIT(*s)) /* optional digits after the radix */
+ s++;
+ }
+ }
+ else if (GROK_NUMERIC_RADIX(&s, send)) {
+ numtype |= IS_NUMBER_NOT_INT | IS_NUMBER_IN_UV; /* valuep assigned below */
+ /* no digits before the radix means we need digits after it */
+ if (s < send && isDIGIT(*s)) {
+ do {
+ s++;
+ } while (s < send && isDIGIT(*s));
+ if (valuep) {
+ /* integer approximation is valid - it's 0. */
+ *valuep = 0;
+ }
+ }
+ else
+ return 0;
+ } else if (*s == 'I' || *s == 'i') {
+ s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
+ s++; if (s == send || (*s != 'F' && *s != 'f')) return 0;
+ s++; if (s < send && (*s == 'I' || *s == 'i')) {
+ s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
+ s++; if (s == send || (*s != 'I' && *s != 'i')) return 0;
+ s++; if (s == send || (*s != 'T' && *s != 't')) return 0;
+ s++; if (s == send || (*s != 'Y' && *s != 'y')) return 0;
+ s++;
+ }
+ sawinf = 1;
+ } else if (*s == 'N' || *s == 'n') {
+ /* XXX TODO: There are signaling NaNs and quiet NaNs. */
+ s++; if (s == send || (*s != 'A' && *s != 'a')) return 0;
+ s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
+ s++;
+ sawnan = 1;
+ } else
+ return 0;
+
+ if (sawinf) {
+ numtype &= IS_NUMBER_NEG; /* Keep track of sign */
+ numtype |= IS_NUMBER_INFINITY | IS_NUMBER_NOT_INT;
+ } else if (sawnan) {
+ numtype &= IS_NUMBER_NEG; /* Keep track of sign */
+ numtype |= IS_NUMBER_NAN | IS_NUMBER_NOT_INT;
+ } else if (s < send) {
+ /* we can have an optional exponent part */
+ if (*s == 'e' || *s == 'E') {
+ /* The only flag we keep is sign. Blow away any "it's UV" */
+ numtype &= IS_NUMBER_NEG;
+ numtype |= IS_NUMBER_NOT_INT;
+ s++;
+ if (s < send && (*s == '-' || *s == '+'))
+ s++;
+ if (s < send && isDIGIT(*s)) {
+ do {
+ s++;
+ } while (s < send && isDIGIT(*s));
+ }
+ else
+ return 0;
+ }
+ }
+ while (s < send && isSPACE(*s))
+ s++;
+ if (s >= send)
+ return numtype;
+ if (len == 10 && memEQ(pv, "0 but true", 10)) {
+ if (valuep)
+ *valuep = 0;
+ return IS_NUMBER_IN_UV;
+ }
+ return 0;
+}
+#endif
+#endif
+
+/*
+ * The grok_* routines have been modified to use warn() instead of
+ * Perl_warner(). Also, 'hexdigit' was the former name of PL_hexdigit,
+ * which is why the stack variable has been renamed to 'xdigit'.
+ */
+
+#ifndef grok_bin
+#if defined(NEED_grok_bin)
+static UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+static
+#else
+extern UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+#endif
+
+#ifdef grok_bin
+# undef grok_bin
+#endif
+#define grok_bin(a,b,c,d) DPPP_(my_grok_bin)(aTHX_ a,b,c,d)
+#define Perl_grok_bin DPPP_(my_grok_bin)
+
+#if defined(NEED_grok_bin) || defined(NEED_grok_bin_GLOBAL)
+UV
+DPPP_(my_grok_bin)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
+{
+ const char *s = start;
+ STRLEN len = *len_p;
+ UV value = 0;
+ NV value_nv = 0;
+
+ const UV max_div_2 = UV_MAX / 2;
+ bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
+ bool overflowed = FALSE;
+
+ if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) {
+ /* strip off leading b or 0b.
+ for compatibility silently suffer "b" and "0b" as valid binary
+ numbers. */
+ if (len >= 1) {
+ if (s[0] == 'b') {
+ s++;
+ len--;
+ }
+ else if (len >= 2 && s[0] == '0' && s[1] == 'b') {
+ s+=2;
+ len-=2;
+ }
+ }
+ }
+
+ for (; len-- && *s; s++) {
+ char bit = *s;
+ if (bit == '0' || bit == '1') {
+ /* Write it in this wonky order with a goto to attempt to get the
+ compiler to make the common case integer-only loop pretty tight.
+ With gcc seems to be much straighter code than old scan_bin. */
+ redo:
+ if (!overflowed) {
+ if (value <= max_div_2) {
+ value = (value << 1) | (bit - '0');
+ continue;
+ }
+ /* Bah. We're just overflowed. */
+ warn("Integer overflow in binary number");
+ overflowed = TRUE;
+ value_nv = (NV) value;
+ }
+ value_nv *= 2.0;
+ /* If an NV has not enough bits in its mantissa to
+ * represent a UV this summing of small low-order numbers
+ * is a waste of time (because the NV cannot preserve
+ * the low-order bits anyway): we could just remember when
+ * did we overflow and in the end just multiply value_nv by the
+ * right amount. */
+ value_nv += (NV)(bit - '0');
+ continue;
+ }
+ if (bit == '_' && len && allow_underscores && (bit = s[1])
+ && (bit == '0' || bit == '1'))
+ {
+ --len;
+ ++s;
+ goto redo;
+ }
+ if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
+ warn("Illegal binary digit '%c' ignored", *s);
+ break;
+ }
+
+ if ( ( overflowed && value_nv > 4294967295.0)
+#if UVSIZE > 4
+ || (!overflowed && value > 0xffffffff )
+#endif
+ ) {
+ warn("Binary number > 0b11111111111111111111111111111111 non-portable");
+ }
+ *len_p = s - start;
+ if (!overflowed) {
+ *flags = 0;
+ return value;
+ }
+ *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
+ if (result)
+ *result = value_nv;
+ return UV_MAX;
+}
+#endif
+#endif
+
+#ifndef grok_hex
+#if defined(NEED_grok_hex)
+static UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+static
+#else
+extern UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+#endif
+
+#ifdef grok_hex
+# undef grok_hex
+#endif
+#define grok_hex(a,b,c,d) DPPP_(my_grok_hex)(aTHX_ a,b,c,d)
+#define Perl_grok_hex DPPP_(my_grok_hex)
+
+#if defined(NEED_grok_hex) || defined(NEED_grok_hex_GLOBAL)
+UV
+DPPP_(my_grok_hex)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
+{
+ const char *s = start;
+ STRLEN len = *len_p;
+ UV value = 0;
+ NV value_nv = 0;
+
+ const UV max_div_16 = UV_MAX / 16;
+ bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
+ bool overflowed = FALSE;
+ const char *xdigit;
+
+ if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) {
+ /* strip off leading x or 0x.
+ for compatibility silently suffer "x" and "0x" as valid hex numbers.
+ */
+ if (len >= 1) {
+ if (s[0] == 'x') {
+ s++;
+ len--;
+ }
+ else if (len >= 2 && s[0] == '0' && s[1] == 'x') {
+ s+=2;
+ len-=2;
+ }
+ }
+ }
+
+ for (; len-- && *s; s++) {
+ xdigit = strchr((char *) PL_hexdigit, *s);
+ if (xdigit) {
+ /* Write it in this wonky order with a goto to attempt to get the
+ compiler to make the common case integer-only loop pretty tight.
+ With gcc seems to be much straighter code than old scan_hex. */
+ redo:
+ if (!overflowed) {
+ if (value <= max_div_16) {
+ value = (value << 4) | ((xdigit - PL_hexdigit) & 15);
+ continue;
+ }
+ warn("Integer overflow in hexadecimal number");
+ overflowed = TRUE;
+ value_nv = (NV) value;
+ }
+ value_nv *= 16.0;
+ /* If an NV has not enough bits in its mantissa to
+ * represent a UV this summing of small low-order numbers
+ * is a waste of time (because the NV cannot preserve
+ * the low-order bits anyway): we could just remember when
+ * did we overflow and in the end just multiply value_nv by the
+ * right amount of 16-tuples. */
+ value_nv += (NV)((xdigit - PL_hexdigit) & 15);
+ continue;
+ }
+ if (*s == '_' && len && allow_underscores && s[1]
+ && (xdigit = strchr((char *) PL_hexdigit, s[1])))
+ {
+ --len;
+ ++s;
+ goto redo;
+ }
+ if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
+ warn("Illegal hexadecimal digit '%c' ignored", *s);
+ break;
+ }
+
+ if ( ( overflowed && value_nv > 4294967295.0)
+#if UVSIZE > 4
+ || (!overflowed && value > 0xffffffff )
+#endif
+ ) {
+ warn("Hexadecimal number > 0xffffffff non-portable");
+ }
+ *len_p = s - start;
+ if (!overflowed) {
+ *flags = 0;
+ return value;
+ }
+ *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
+ if (result)
+ *result = value_nv;
+ return UV_MAX;
+}
+#endif
+#endif
+
+#ifndef grok_oct
+#if defined(NEED_grok_oct)
+static UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+static
+#else
+extern UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+#endif
+
+#ifdef grok_oct
+# undef grok_oct
+#endif
+#define grok_oct(a,b,c,d) DPPP_(my_grok_oct)(aTHX_ a,b,c,d)
+#define Perl_grok_oct DPPP_(my_grok_oct)
+
+#if defined(NEED_grok_oct) || defined(NEED_grok_oct_GLOBAL)
+UV
+DPPP_(my_grok_oct)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
+{
+ const char *s = start;
+ STRLEN len = *len_p;
+ UV value = 0;
+ NV value_nv = 0;
+
+ const UV max_div_8 = UV_MAX / 8;
+ bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
+ bool overflowed = FALSE;
+
+ for (; len-- && *s; s++) {
+ /* gcc 2.95 optimiser not smart enough to figure that this subtraction
+ out front allows slicker code. */
+ int digit = *s - '0';
+ if (digit >= 0 && digit <= 7) {
+ /* Write it in this wonky order with a goto to attempt to get the
+ compiler to make the common case integer-only loop pretty tight.
+ */
+ redo:
+ if (!overflowed) {
+ if (value <= max_div_8) {
+ value = (value << 3) | digit;
+ continue;
+ }
+ /* Bah. We're just overflowed. */
+ warn("Integer overflow in octal number");
+ overflowed = TRUE;
+ value_nv = (NV) value;
+ }
+ value_nv *= 8.0;
+ /* If an NV has not enough bits in its mantissa to
+ * represent a UV this summing of small low-order numbers
+ * is a waste of time (because the NV cannot preserve
+ * the low-order bits anyway): we could just remember when
+ * did we overflow and in the end just multiply value_nv by the
+ * right amount of 8-tuples. */
+ value_nv += (NV)digit;
+ continue;
+ }
+ if (digit == ('_' - '0') && len && allow_underscores
+ && (digit = s[1] - '0') && (digit >= 0 && digit <= 7))
+ {
+ --len;
+ ++s;
+ goto redo;
+ }
+ /* Allow \octal to work the DWIM way (that is, stop scanning
+ * as soon as non-octal characters are seen, complain only iff
+ * someone seems to want to use the digits eight and nine). */
+ if (digit == 8 || digit == 9) {
+ if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
+ warn("Illegal octal digit '%c' ignored", *s);
+ }
+ break;
+ }
+
+ if ( ( overflowed && value_nv > 4294967295.0)
+#if UVSIZE > 4
+ || (!overflowed && value > 0xffffffff )
+#endif
+ ) {
+ warn("Octal number > 037777777777 non-portable");
+ }
+ *len_p = s - start;
+ if (!overflowed) {
+ *flags = 0;
+ return value;
+ }
+ *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
+ if (result)
+ *result = value_nv;
+ return UV_MAX;
+}
+#endif
+#endif
+
+#if !defined(my_snprintf)
+#if defined(NEED_my_snprintf)
+static int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...);
+static
+#else
+extern int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...);
+#endif
+
+#define my_snprintf DPPP_(my_my_snprintf)
+#define Perl_my_snprintf DPPP_(my_my_snprintf)
+
+#if defined(NEED_my_snprintf) || defined(NEED_my_snprintf_GLOBAL)
+
+int
+DPPP_(my_my_snprintf)(char *buffer, const Size_t len, const char *format, ...)
+{
+ dTHX;
+ int retval;
+ va_list ap;
+ va_start(ap, format);
+#ifdef HAS_VSNPRINTF
+ retval = vsnprintf(buffer, len, format, ap);
+#else
+ retval = vsprintf(buffer, format, ap);
+#endif
+ va_end(ap);
+ if (retval < 0 || (len > 0 && (Size_t)retval >= len))
+ Perl_croak(aTHX_ "panic: my_snprintf buffer overflow");
+ return retval;
+}
+
+#endif
+#endif
+
+#if !defined(my_sprintf)
+#if defined(NEED_my_sprintf)
+static int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...);
+static
+#else
+extern int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...);
+#endif
+
+#define my_sprintf DPPP_(my_my_sprintf)
+#define Perl_my_sprintf DPPP_(my_my_sprintf)
+
+#if defined(NEED_my_sprintf) || defined(NEED_my_sprintf_GLOBAL)
+
+int
+DPPP_(my_my_sprintf)(char *buffer, const char* pat, ...)
+{
+ va_list args;
+ va_start(args, pat);
+ vsprintf(buffer, pat, args);
+ va_end(args);
+ return strlen(buffer);
+}
+
+#endif
+#endif
+
+#ifdef NO_XSLOCKS
+# ifdef dJMPENV
+# define dXCPT dJMPENV; int rEtV = 0
+# define XCPT_TRY_START JMPENV_PUSH(rEtV); if (rEtV == 0)
+# define XCPT_TRY_END JMPENV_POP;
+# define XCPT_CATCH if (rEtV != 0)
+# define XCPT_RETHROW JMPENV_JUMP(rEtV)
+# else
+# define dXCPT Sigjmp_buf oldTOP; int rEtV = 0
+# define XCPT_TRY_START Copy(top_env, oldTOP, 1, Sigjmp_buf); rEtV = Sigsetjmp(top_env, 1); if (rEtV == 0)
+# define XCPT_TRY_END Copy(oldTOP, top_env, 1, Sigjmp_buf);
+# define XCPT_CATCH if (rEtV != 0)
+# define XCPT_RETHROW Siglongjmp(top_env, rEtV)
+# endif
+#endif
+
+#if !defined(my_strlcat)
+#if defined(NEED_my_strlcat)
+static Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size);
+static
+#else
+extern Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size);
+#endif
+
+#define my_strlcat DPPP_(my_my_strlcat)
+#define Perl_my_strlcat DPPP_(my_my_strlcat)
+
+#if defined(NEED_my_strlcat) || defined(NEED_my_strlcat_GLOBAL)
+
+Size_t
+DPPP_(my_my_strlcat)(char *dst, const char *src, Size_t size)
+{
+ Size_t used, length, copy;
+
+ used = strlen(dst);
+ length = strlen(src);
+ if (size > 0 && used < size - 1) {
+ copy = (length >= size - used) ? size - used - 1 : length;
+ memcpy(dst + used, src, copy);
+ dst[used + copy] = '\0';
+ }
+ return used + length;
+}
+#endif
+#endif
+
+#if !defined(my_strlcpy)
+#if defined(NEED_my_strlcpy)
+static Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size);
+static
+#else
+extern Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size);
+#endif
+
+#define my_strlcpy DPPP_(my_my_strlcpy)
+#define Perl_my_strlcpy DPPP_(my_my_strlcpy)
+
+#if defined(NEED_my_strlcpy) || defined(NEED_my_strlcpy_GLOBAL)
+
+Size_t
+DPPP_(my_my_strlcpy)(char *dst, const char *src, Size_t size)
+{
+ Size_t length, copy;
+
+ length = strlen(src);
+ if (size > 0) {
+ copy = (length >= size) ? size - 1 : length;
+ memcpy(dst, src, copy);
+ dst[copy] = '\0';
+ }
+ return length;
+}
+
+#endif
+#endif
+#ifndef PERL_PV_ESCAPE_QUOTE
+# define PERL_PV_ESCAPE_QUOTE 0x0001
+#endif
+
+#ifndef PERL_PV_PRETTY_QUOTE
+# define PERL_PV_PRETTY_QUOTE PERL_PV_ESCAPE_QUOTE
+#endif
+
+#ifndef PERL_PV_PRETTY_ELLIPSES
+# define PERL_PV_PRETTY_ELLIPSES 0x0002
+#endif
+
+#ifndef PERL_PV_PRETTY_LTGT
+# define PERL_PV_PRETTY_LTGT 0x0004
+#endif
+
+#ifndef PERL_PV_ESCAPE_FIRSTCHAR
+# define PERL_PV_ESCAPE_FIRSTCHAR 0x0008
+#endif
+
+#ifndef PERL_PV_ESCAPE_UNI
+# define PERL_PV_ESCAPE_UNI 0x0100
+#endif
+
+#ifndef PERL_PV_ESCAPE_UNI_DETECT
+# define PERL_PV_ESCAPE_UNI_DETECT 0x0200
+#endif
+
+#ifndef PERL_PV_ESCAPE_ALL
+# define PERL_PV_ESCAPE_ALL 0x1000
+#endif
+
+#ifndef PERL_PV_ESCAPE_NOBACKSLASH
+# define PERL_PV_ESCAPE_NOBACKSLASH 0x2000
+#endif
+
+#ifndef PERL_PV_ESCAPE_NOCLEAR
+# define PERL_PV_ESCAPE_NOCLEAR 0x4000
+#endif
+
+#ifndef PERL_PV_ESCAPE_RE
+# define PERL_PV_ESCAPE_RE 0x8000
+#endif
+
+#ifndef PERL_PV_PRETTY_NOCLEAR
+# define PERL_PV_PRETTY_NOCLEAR PERL_PV_ESCAPE_NOCLEAR
+#endif
+#ifndef PERL_PV_PRETTY_DUMP
+# define PERL_PV_PRETTY_DUMP PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_QUOTE
+#endif
+
+#ifndef PERL_PV_PRETTY_REGPROP
+# define PERL_PV_PRETTY_REGPROP PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_LTGT|PERL_PV_ESCAPE_RE
+#endif
+
+/* Hint: pv_escape
+ * Note that unicode functionality is only backported to
+ * those perl versions that support it. For older perl
+ * versions, the implementation will fall back to bytes.
+ */
+
+#ifndef pv_escape
+#if defined(NEED_pv_escape)
+static char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags);
+static
+#else
+extern char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags);
+#endif
+
+#ifdef pv_escape
+# undef pv_escape
+#endif
+#define pv_escape(a,b,c,d,e,f) DPPP_(my_pv_escape)(aTHX_ a,b,c,d,e,f)
+#define Perl_pv_escape DPPP_(my_pv_escape)
+
+#if defined(NEED_pv_escape) || defined(NEED_pv_escape_GLOBAL)
+
+char *
+DPPP_(my_pv_escape)(pTHX_ SV *dsv, char const * const str,
+ const STRLEN count, const STRLEN max,
+ STRLEN * const escaped, const U32 flags)
+{
+ const char esc = flags & PERL_PV_ESCAPE_RE ? '%' : '\\';
+ const char dq = flags & PERL_PV_ESCAPE_QUOTE ? '"' : esc;
+ char octbuf[32] = "%123456789ABCDF";
+ STRLEN wrote = 0;
+ STRLEN chsize = 0;
+ STRLEN readsize = 1;
+#if defined(is_utf8_string) && defined(utf8_to_uvchr)
+ bool isuni = flags & PERL_PV_ESCAPE_UNI ? 1 : 0;
+#endif
+ const char *pv = str;
+ const char * const end = pv + count;
+ octbuf[0] = esc;
+
+ if (!(flags & PERL_PV_ESCAPE_NOCLEAR))
+ sv_setpvs(dsv, "");
+
+#if defined(is_utf8_string) && defined(utf8_to_uvchr)
+ if ((flags & PERL_PV_ESCAPE_UNI_DETECT) && is_utf8_string((U8*)pv, count))
+ isuni = 1;
+#endif
+
+ for (; pv < end && (!max || wrote < max) ; pv += readsize) {
+ const UV u =
+#if defined(is_utf8_string) && defined(utf8_to_uvchr)
+ isuni ? utf8_to_uvchr((U8*)pv, &readsize) :
+#endif
+ (U8)*pv;
+ const U8 c = (U8)u & 0xFF;
+
+ if (u > 255 || (flags & PERL_PV_ESCAPE_ALL)) {
+ if (flags & PERL_PV_ESCAPE_FIRSTCHAR)
+ chsize = my_snprintf(octbuf, sizeof octbuf,
+ "%"UVxf, u);
+ else
+ chsize = my_snprintf(octbuf, sizeof octbuf,
+ "%cx{%"UVxf"}", esc, u);
+ } else if (flags & PERL_PV_ESCAPE_NOBACKSLASH) {
+ chsize = 1;
+ } else {
+ if (c == dq || c == esc || !isPRINT(c)) {
+ chsize = 2;
+ switch (c) {
+ case '\\' : /* fallthrough */
+ case '%' : if (c == esc)
+ octbuf[1] = esc;
+ else
+ chsize = 1;
+ break;
+ case '\v' : octbuf[1] = 'v'; break;
+ case '\t' : octbuf[1] = 't'; break;
+ case '\r' : octbuf[1] = 'r'; break;
+ case '\n' : octbuf[1] = 'n'; break;
+ case '\f' : octbuf[1] = 'f'; break;
+ case '"' : if (dq == '"')
+ octbuf[1] = '"';
+ else
+ chsize = 1;
+ break;
+ default: chsize = my_snprintf(octbuf, sizeof octbuf,
+ pv < end && isDIGIT((U8)*(pv+readsize))
+ ? "%c%03o" : "%c%o", esc, c);
+ }
+ } else {
+ chsize = 1;
+ }
+ }
+ if (max && wrote + chsize > max) {
+ break;
+ } else if (chsize > 1) {
+ sv_catpvn(dsv, octbuf, chsize);
+ wrote += chsize;
+ } else {
+ char tmp[2];
+ my_snprintf(tmp, sizeof tmp, "%c", c);
+ sv_catpvn(dsv, tmp, 1);
+ wrote++;
+ }
+ if (flags & PERL_PV_ESCAPE_FIRSTCHAR)
+ break;
+ }
+ if (escaped != NULL)
+ *escaped= pv - str;
+ return SvPVX(dsv);
+}
+
+#endif
+#endif
+
+#ifndef pv_pretty
+#if defined(NEED_pv_pretty)
+static char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags);
+static
+#else
+extern char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags);
+#endif
+
+#ifdef pv_pretty
+# undef pv_pretty
+#endif
+#define pv_pretty(a,b,c,d,e,f,g) DPPP_(my_pv_pretty)(aTHX_ a,b,c,d,e,f,g)
+#define Perl_pv_pretty DPPP_(my_pv_pretty)
+
+#if defined(NEED_pv_pretty) || defined(NEED_pv_pretty_GLOBAL)
+
+char *
+DPPP_(my_pv_pretty)(pTHX_ SV *dsv, char const * const str, const STRLEN count,
+ const STRLEN max, char const * const start_color, char const * const end_color,
+ const U32 flags)
+{
+ const U8 dq = (flags & PERL_PV_PRETTY_QUOTE) ? '"' : '%';
+ STRLEN escaped;
+
+ if (!(flags & PERL_PV_PRETTY_NOCLEAR))
+ sv_setpvs(dsv, "");
+
+ if (dq == '"')
+ sv_catpvs(dsv, "\"");
+ else if (flags & PERL_PV_PRETTY_LTGT)
+ sv_catpvs(dsv, "<");
+
+ if (start_color != NULL)
+ sv_catpv(dsv, D_PPP_CONSTPV_ARG(start_color));
+
+ pv_escape(dsv, str, count, max, &escaped, flags | PERL_PV_ESCAPE_NOCLEAR);
+
+ if (end_color != NULL)
+ sv_catpv(dsv, D_PPP_CONSTPV_ARG(end_color));
+
+ if (dq == '"')
+ sv_catpvs(dsv, "\"");
+ else if (flags & PERL_PV_PRETTY_LTGT)
+ sv_catpvs(dsv, ">");
+
+ if ((flags & PERL_PV_PRETTY_ELLIPSES) && escaped < count)
+ sv_catpvs(dsv, "...");
+
+ return SvPVX(dsv);
+}
+
+#endif
+#endif
+
+#ifndef pv_display
+#if defined(NEED_pv_display)
+static char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim);
+static
+#else
+extern char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim);
+#endif
+
+#ifdef pv_display
+# undef pv_display
+#endif
+#define pv_display(a,b,c,d,e) DPPP_(my_pv_display)(aTHX_ a,b,c,d,e)
+#define Perl_pv_display DPPP_(my_pv_display)
+
+#if defined(NEED_pv_display) || defined(NEED_pv_display_GLOBAL)
+
+char *
+DPPP_(my_pv_display)(pTHX_ SV *dsv, const char *pv, STRLEN cur, STRLEN len, STRLEN pvlim)
+{
+ pv_pretty(dsv, pv, cur, pvlim, NULL, NULL, PERL_PV_PRETTY_DUMP);
+ if (len > cur && pv[cur] == '\0')
+ sv_catpvs(dsv, "\\0");
+ return SvPVX(dsv);
+}
+
+#endif
+#endif
+
+#endif /* _P_P_PORTABILITY_H_ */
+
+/* End of File ppport.h */
diff --git a/dbiprof.PL b/dbiprof.PL
new file mode 100644
index 0000000..d5688e7
--- /dev/null
+++ b/dbiprof.PL
@@ -0,0 +1,287 @@
+# -*- perl -*-
+
+my $file = $ARGV[0] || 'dbiprof';
+
+my $script = <<'SCRIPT';
+~startperl~
+
+use strict;
+
+my $VERSION = sprintf("1.%06d", q$Revision: 13336 $ =~ /(\d+)/o);
+
+use Data::Dumper;
+use DBI::ProfileData;
+use Getopt::Long;
+
+# default options
+my $number = 10;
+my $sort = 'total';
+my $filename = 'dbi.prof';
+my $reverse = 0;
+my $case_sensitive = 0;
+my (%match, %exclude);
+
+# get options from command line
+GetOptions(
+ 'version' => sub { die "dbiprof $VERSION\n" },
+ 'help' => sub { exit usage() },
+ 'number=i' => \$number,
+ 'sort=s' => \$sort,
+ 'dumpnodes!' => \my $dumpnodes,
+ 'reverse' => \$reverse,
+ 'match=s' => \%match,
+ 'exclude=s' => \%exclude,
+ 'case-sensitive' => \$case_sensitive,
+ 'delete!' => \my $opt_delete,
+) or exit usage();
+
+sub usage {
+ print <<EOS;
+dbiprof [options] [files]
+
+Reads and merges DBI profile data from files and prints a summary.
+
+files: defaults to $filename
+
+options:
+
+ -number=N show top N, defaults to $number
+ -sort=S sort by S, defaults to $sort
+ -reverse reverse the sort
+ -match=K=V for filtering, see docs
+ -exclude=K=V for filtering, see docs
+ -case_sensitive for -match and -exclude
+ -delete rename files before reading then delete afterwards
+ -version print version number and exit
+ -help print this help
+
+EOS
+ return 1;
+}
+
+# list of files defaults to dbi.prof
+my @files = @ARGV ? @ARGV : ('dbi.prof');
+
+
+# instantiate ProfileData object
+my $prof = eval {
+ DBI::ProfileData->new(
+ Files => \@files,
+ DeleteFiles => $opt_delete,
+ );
+};
+die "Unable to load profile data: $@\n" if $@;
+
+if (%match) { # handle matches
+ while (my ($key, $val) = each %match) {
+ if ($val =~ m!^/(.+)/$!) {
+ $val = $case_sensitive ? qr/$1/ : qr/$1/i;
+ }
+ $prof->match($key, $val, case_sensitive => $case_sensitive);
+ }
+}
+
+if (%exclude) { # handle excludes
+ while (my ($key, $val) = each %exclude) {
+ if ($val =~ m!^/(.+)/$!) {
+ $val = $case_sensitive ? qr/$1/ : qr/$1/i;
+ }
+ $prof->exclude($key, $val, case_sensitive => $case_sensitive);
+ }
+}
+
+# sort the data
+$prof->sort(field => $sort, reverse => $reverse);
+
+# all done, print it out
+if ($dumpnodes) {
+ $Data::Dumper::Indent = 1;
+ $Data::Dumper::Terse = 1;
+ $Data::Dumper::Useqq = 1;
+ $Data::Dumper::Deparse = 0;
+ print Dumper($prof->nodes);
+}
+else {
+ print $prof->report(number => $number);
+}
+exit 0;
+
+__END__
+
+=head1 NAME
+
+dbiprof - command-line client for DBI::ProfileData
+
+=head1 SYNOPSIS
+
+See a report of the ten queries with the longest total runtime in the
+profile dump file F<prof1.out>:
+
+ dbiprof prof1.out
+
+See the top 10 most frequently run queries in the profile file
+F<dbi.prof> (the default):
+
+ dbiprof --sort count
+
+See the same report with 15 entries:
+
+ dbiprof --sort count --number 15
+
+=head1 DESCRIPTION
+
+This tool is a command-line client for the DBI::ProfileData. It
+allows you to analyze the profile data file produced by
+DBI::ProfileDumper and produce various useful reports.
+
+=head1 OPTIONS
+
+This program accepts the following options:
+
+=over 4
+
+=item --number N
+
+Produce this many items in the report. Defaults to 10. If set to
+"all" then all results are shown.
+
+=item --sort field
+
+Sort results by the given field. Sorting by multiple fields isn't currently
+supported (patches welcome). The available sort fields are:
+
+=over 4
+
+=item total
+
+Sorts by total time run time across all runs. This is the default
+sort.
+
+=item longest
+
+Sorts by the longest single run.
+
+=item count
+
+Sorts by total number of runs.
+
+=item first
+
+Sorts by the time taken in the first run.
+
+=item shortest
+
+Sorts by the shortest single run.
+
+=item key1
+
+Sorts by the value of the first element in the Path, which should be numeric.
+You can also sort by C<key2> and C<key3>.
+
+=back
+
+=item --reverse
+
+Reverses the selected sort. For example, to see a report of the
+shortest overall time:
+
+ dbiprof --sort total --reverse
+
+=item --match keyN=value
+
+Consider only items where the specified key matches the given value.
+Keys are numbered from 1. For example, let's say you used a
+DBI::Profile Path of:
+
+ [ DBIprofile_Statement, DBIprofile_Methodname ]
+
+And called dbiprof as in:
+
+ dbiprof --match key2=execute
+
+Your report would only show execute queries, leaving out prepares,
+fetches, etc.
+
+If the value given starts and ends with slashes (C</>) then it will be
+treated as a regular expression. For example, to only include SELECT
+queries where key1 is the statement:
+
+ dbiprof --match key1=/^SELECT/
+
+By default the match expression is matched case-insensitively, but
+this can be changed with the --case-sensitive option.
+
+=item --exclude keyN=value
+
+Remove items for where the specified key matches the given value. For
+example, to exclude all prepare entries where key2 is the method name:
+
+ dbiprof --exclude key2=prepare
+
+Like C<--match>, If the value given starts and ends with slashes
+(C</>) then it will be treated as a regular expression. For example,
+to exclude UPDATE queries where key1 is the statement:
+
+ dbiprof --match key1=/^UPDATE/
+
+By default the exclude expression is matched case-insensitively, but
+this can be changed with the --case-sensitive option.
+
+=item --case-sensitive
+
+Using this option causes --match and --exclude to work
+case-sensitively. Defaults to off.
+
+=item --delete
+
+Sets the C<DeleteFiles> option to L<DBI::ProfileData> which causes the
+files to be deleted after reading. See L<DBI::ProfileData> for more details.
+
+=item --dumpnodes
+
+Print the list of nodes in the form of a perl data structure.
+Use the C<-sort> option if you want the list sorted.
+
+=item --version
+
+Print the dbiprof version number and exit.
+
+=back
+
+=head1 AUTHOR
+
+Sam Tregar <sam@tregar.com>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2002 Sam Tregar
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl 5 itself.
+
+=head1 SEE ALSO
+
+L<DBI::ProfileDumper|DBI::ProfileDumper>,
+L<DBI::Profile|DBI::Profile>, L<DBI|DBI>.
+
+=cut
+
+SCRIPT
+
+
+require Config;
+my $config = {};
+$config->{'startperl'} = $Config::Config{'startperl'};
+
+$script =~ s/\~(\w+)\~/$config->{$1}/eg;
+if (!(open(FILE, ">$file")) ||
+ !(print FILE $script) ||
+ !(close(FILE))) {
+ die "Error while writing $file: $!\n";
+}
+chmod 0755, $file;
+print "Extracted $file from ",__FILE__," with variable substitutions.\n";
+
+# syntax check resulting file, but only for developers
+exit 1 if -d ".svn"|| -d ".git" and system($^X, '-wc', '-Mblib', $file) != 0;
+
diff --git a/dbiproxy.PL b/dbiproxy.PL
new file mode 100644
index 0000000..1ac3100
--- /dev/null
+++ b/dbiproxy.PL
@@ -0,0 +1,208 @@
+# -*- perl -*-
+
+my $file = $ARGV[0] || 'dbiproxy';
+
+my $script = <<'SCRIPT';
+~startperl~
+
+use strict;
+
+my $VERSION = sprintf("1.%06d", q$Revision: 13336 $ =~ /(\d+)/o);
+
+my $arg_test = shift(@ARGV) if $ARGV[0] eq '--test';
+$ENV{DBI_TRACE} = shift(@ARGV) || 2 if $ARGV[0] =~ s/^--dbitrace=?//;
+
+require DBI::ProxyServer;
+
+# XXX these should probably be moved into DBI::ProxyServer
+delete $ENV{IFS};
+delete $ENV{CDPATH};
+delete $ENV{ENV};
+delete $ENV{BASH_ENV};
+
+if ($arg_test) {
+ require RPC::PlServer::Test;
+ @DBI::ProxyServer::ISA = qw(RPC::PlServer::Test DBI);
+}
+
+DBI::ProxyServer::main(@ARGV);
+
+exit(0);
+
+
+__END__
+
+=head1 NAME
+
+dbiproxy - A proxy server for the DBD::Proxy driver
+
+=head1 SYNOPSIS
+
+ dbiproxy <options> --localport=<port>
+
+
+=head1 DESCRIPTION
+
+This tool is just a front end for the DBI::ProxyServer package. All it
+does is picking options from the command line and calling
+DBI::ProxyServer::main(). See L<DBI::ProxyServer> for details.
+
+Available options include:
+
+=over 4
+
+=item B<--chroot=dir>
+
+(UNIX only) After doing a bind(), change root directory to the given
+directory by doing a chroot(). This is useful for security, but it
+restricts the environment a lot. For example, you need to load DBI
+drivers in the config file or you have to create hard links to Unix
+sockets, if your drivers are using them. For example, with MySQL, a
+config file might contain the following lines:
+
+ my $rootdir = '/var/dbiproxy';
+ my $unixsockdir = '/tmp';
+ my $unixsockfile = 'mysql.sock';
+ foreach $dir ($rootdir, "$rootdir$unixsockdir") {
+ mkdir 0755, $dir;
+ }
+ link("$unixsockdir/$unixsockfile",
+ "$rootdir$unixsockdir/$unixsockfile");
+ require DBD::mysql;
+
+ {
+ 'chroot' => $rootdir,
+ ...
+ }
+
+If you don't know chroot(), think of an FTP server where you can see a
+certain directory tree only after logging in. See also the --group and
+--user options.
+
+=item B<--configfile=file>
+
+Config files are assumed to return a single hash ref that overrides the
+arguments of the new method. However, command line arguments in turn take
+precedence over the config file. See the "CONFIGURATION FILE" section
+in the L<DBI::ProxyServer> documentation for details on the config file.
+
+=item B<--debug>
+
+Turn debugging mode on. Mainly this asserts that logging messages of
+level "debug" are created.
+
+=item B<--facility=mode>
+
+(UNIX only) Facility to use for L<Sys::Syslog>. The default is
+B<daemon>.
+
+=item B<--group=gid>
+
+After doing a bind(), change the real and effective GID to the given.
+This is useful, if you want your server to bind to a privileged port
+(<1024), but don't want the server to execute as root. See also
+the --user option.
+
+GID's can be passed as group names or numeric values.
+
+=item B<--localaddr=ip>
+
+By default a daemon is listening to any IP number that a machine
+has. This attribute allows to restrict the server to the given
+IP number.
+
+=item B<--localport=port>
+
+This attribute sets the port on which the daemon is listening. It
+must be given somehow, as there's no default.
+
+=item B<--logfile=file>
+
+Be default logging messages will be written to the syslog (Unix) or
+to the event log (Windows NT). On other operating systems you need to
+specify a log file. The special value "STDERR" forces logging to
+stderr. See L<Net::Daemon::Log> for details.
+
+=item B<--mode=modename>
+
+The server can run in three different modes, depending on the environment.
+
+If you are running Perl 5.005 and did compile it for threads, then the
+server will create a new thread for each connection. The thread will
+execute the server's Run() method and then terminate. This mode is the
+default, you can force it with "--mode=threads".
+
+If threads are not available, but you have a working fork(), then the
+server will behave similar by creating a new process for each connection.
+This mode will be used automatically in the absence of threads or if
+you use the "--mode=fork" option.
+
+Finally there's a single-connection mode: If the server has accepted a
+connection, he will enter the Run() method. No other connections are
+accepted until the Run() method returns (if the client disconnects).
+This operation mode is useful if you have neither threads nor fork(),
+for example on the Macintosh. For debugging purposes you can force this
+mode with "--mode=single".
+
+=item B<--pidfile=file>
+
+(UNIX only) If this option is present, a PID file will be created at the
+given location. Default is to not create a pidfile.
+
+=item B<--user=uid>
+
+After doing a bind(), change the real and effective UID to the given.
+This is useful, if you want your server to bind to a privileged port
+(<1024), but don't want the server to execute as root. See also
+the --group and the --chroot options.
+
+UID's can be passed as group names or numeric values.
+
+=item B<--version>
+
+Supresses startup of the server; instead the version string will
+be printed and the program exits immediately.
+
+=back
+
+
+=head1 AUTHOR
+
+ Copyright (c) 1997 Jochen Wiedmann
+ Am Eisteich 9
+ 72555 Metzingen
+ Germany
+
+ Email: joe@ispsoft.de
+ Phone: +49 7123 14881
+
+The DBI::ProxyServer module is free software; you can redistribute it
+and/or modify it under the same terms as Perl itself. In particular
+permission is granted to Tim Bunce for distributing this as a part of
+the DBI.
+
+
+=head1 SEE ALSO
+
+L<DBI::ProxyServer>, L<DBD::Proxy>, L<DBI>
+
+=cut
+SCRIPT
+
+
+require Config;
+my $config = {};
+$config->{'startperl'} = $Config::Config{'startperl'};
+
+$script =~ s/\~(\w+)\~/$config->{$1}/eg;
+if (!(open(FILE, ">$file")) ||
+ !(print FILE $script) ||
+ !(close(FILE))) {
+ die "Error while writing $file: $!\n";
+}
+chmod 0755, $file;
+print "Extracted $file from ",__FILE__," with variable substitutions.\n";
+
+# syntax check resulting file, but only for developers
+exit 1 if -d ".svn" || -d ".git" and system($^X, '-wc', '-Mblib', $file) != 0;
+
diff --git a/dbivport.h b/dbivport.h
new file mode 100644
index 0000000..77dd96b
--- /dev/null
+++ b/dbivport.h
@@ -0,0 +1,52 @@
+/* dbivport.h
+
+ Provides macros that enable greater portability between DBI versions.
+
+ This file should be *copied* and included in driver distributions
+ and #included into the source, after #include DBIXS.h
+
+ New driver releases should include an updated copy of dbivport.h
+ from the most recent DBI release.
+*/
+
+#ifndef DBI_VPORT_H
+#define DBI_VPORT_H
+
+#ifndef DBIh_SET_ERR_CHAR
+/* Emulate DBIh_SET_ERR_CHAR
+ Only uses the err_i, errstr and state parameters.
+*/
+#define DBIh_SET_ERR_CHAR(h, imp_xxh, err_c, err_i, errstr, state, method) \
+ sv_setiv(DBIc_ERR(imp_xxh), err_i); \
+ (state) ? (void)sv_setpv(DBIc_STATE(imp_xxh), state) : (void)SvOK_off(DBIc_STATE(imp_xxh)); \
+ sv_setpv(DBIc_ERRSTR(imp_xxh), errstr)
+#endif
+
+#ifndef DBIcf_Executed
+#define DBIcf_Executed 0x080000
+#endif
+
+#ifndef DBIc_TRACE_LEVEL_MASK
+#define DBIc_TRACE_LEVEL_MASK 0x0000000F
+#define DBIc_TRACE_FLAGS_MASK 0xFFFFFF00
+#define DBIc_TRACE_SETTINGS(imp) (DBIc_DBISTATE(imp)->debug)
+#define DBIc_TRACE_LEVEL(imp) (DBIc_TRACE_SETTINGS(imp) & DBIc_TRACE_LEVEL_MASK)
+#define DBIc_TRACE_FLAGS(imp) (DBIc_TRACE_SETTINGS(imp) & DBIc_TRACE_FLAGS_MASK)
+/* DBIc_TRACE_MATCHES - true if s1 'matches' s2 (c.f. trace_msg())
+ DBIc_TRACE_MATCHES(foo, DBIc_TRACE_SETTINGS(imp))
+*/
+#define DBIc_TRACE_MATCHES(s1, s2) \
+ ( ((s1 & DBIc_TRACE_LEVEL_MASK) >= (s2 & DBIc_TRACE_LEVEL_MASK)) \
+ || ((s1 & DBIc_TRACE_FLAGS_MASK) & (s2 & DBIc_TRACE_FLAGS_MASK)) )
+/* DBIc_TRACE - true if flags match & DBI level>=flaglevel, or if DBI level>level
+ DBIc_TRACE(imp, 0, 0, 4) = if level >= 4
+ DBIc_TRACE(imp, DBDtf_FOO, 2, 4) = if tracing DBDtf_FOO & level>=2 or level>=4
+ DBIc_TRACE(imp, DBDtf_FOO, 2, 0) = as above but never trace just due to level
+*/
+#define DBIc_TRACE(imp, flags, flaglevel, level) \
+ ( (flags && (DBIc_TRACE_FLAGS(imp) & flags) && (DBIc_TRACE_LEVEL(imp) >= flaglevel)) \
+ || (level && DBIc_TRACE_LEVEL(imp) >= level) )
+#endif
+
+
+#endif /* !DBI_VPORT_H */
diff --git a/dbixs_rev.h b/dbixs_rev.h
new file mode 100644
index 0000000..335aef0
--- /dev/null
+++ b/dbixs_rev.h
@@ -0,0 +1,4 @@
+/* Wed Apr 18 12:37:44 2012 */
+/* Mixed revision working copy (15267M:15268) */
+/* Code modified since last checkin */
+#define DBIXS_REVISION 15267
diff --git a/dbixs_rev.pl b/dbixs_rev.pl
new file mode 100644
index 0000000..9e83eb1
--- /dev/null
+++ b/dbixs_rev.pl
@@ -0,0 +1,51 @@
+#!perl -w
+use strict;
+
+my $dbixs_rev_file = "dbixs_rev.h";
+
+my $is_make_dist;
+my $svnversion;
+
+if (is_dbi_svn_dir(".")) {
+ $svnversion = `svnversion -n`;
+}
+elsif (is_dbi_svn_dir("..")) {
+ # presumably we're in a subdirectory because the user is doing a 'make dist'
+ $svnversion = `svnversion -n ..`;
+ $is_make_dist = 1;
+}
+else {
+ # presumably we're being run by an end-user because their file timestamps
+ # got messed up
+ print "Skipping regeneration of $dbixs_rev_file\n";
+ utime(time(), time(), $dbixs_rev_file); # update modification time
+ exit 0;
+}
+
+my @warn;
+die "Neither current directory nor parent directory are an svn working copy\n"
+ unless $svnversion and $svnversion =~ m/^\d+/;
+push @warn, "Mixed revision working copy ($svnversion:$1)"
+ if $svnversion =~ s/:(\d+)//;
+push @warn, "Code modified since last checkin"
+ if $svnversion =~ s/[MS]+$//;
+warn "$dbixs_rev_file warning: $_\n" for @warn;
+die "$0 failed\n" if $is_make_dist && @warn;
+
+write_header($dbixs_rev_file, DBIXS_REVISION => $svnversion, \@warn);
+
+sub write_header {
+ my ($file, $macro, $version, $comments_ref) = @_;
+ open my $fh, ">$file" or die "Can't open $file: $!\n";
+ unshift @$comments_ref, scalar localtime(time);
+ print $fh "/* $_ */\n" for @$comments_ref;
+ print $fh "#define $macro $version\n";
+ close $fh or die "Error closing $file: $!\n";
+ print "Wrote $macro $version to $file\n";
+}
+
+sub is_dbi_svn_dir {
+ my ($dir) = @_;
+ return (-d "$dir/.svn" && -f "$dir/MANIFEST.SKIP");
+}
+
diff --git a/ex/corogofer.pl b/ex/corogofer.pl
new file mode 100644
index 0000000..8baa587
--- /dev/null
+++ b/ex/corogofer.pl
@@ -0,0 +1,32 @@
+#!perl
+
+use strict;
+use warnings;
+use Time::HiRes qw(time);
+
+BEGIN { $ENV{PERL_ANYEVENT_STRICT} = 1; $ENV{PERL_ANYEVENT_VERBOSE} = 1; }
+
+use AnyEvent;
+
+BEGIN { $ENV{DBI_TRACE} = 0; $ENV{DBI_PUREPERL} = 0; $ENV{DBI_GOFER_TRACE} = 0; $ENV{DBD_GOFER_TRACE} = 0; };
+
+use DBI;
+
+$ENV{DBI_AUTOPROXY} = 'dbi:Gofer:transport=corostream';
+
+my $ticker = AnyEvent->timer( after => 0, interval => 0.1, cb => sub {
+ warn sprintf "-tick- %.2f\n", time
+} );
+
+warn "connecting...\n";
+my $dbh = DBI->connect("dbi:NullP:");
+warn "...connected\n";
+
+for (1..5) {
+ warn "entering DBI...\n";
+ $dbh->do("sleep 0.3"); # pseudo-sql understood by the DBD::NullP driver
+ warn "...returned\n";
+}
+
+warn "done.";
+
diff --git a/ex/perl_dbi_nulls_test.pl b/ex/perl_dbi_nulls_test.pl
new file mode 100644
index 0000000..fbef238
--- /dev/null
+++ b/ex/perl_dbi_nulls_test.pl
@@ -0,0 +1,176 @@
+#! /usr/bin/perl -w
+
+# This script checks which style of WHERE clause(s) will support both
+# null and non-null values. Refer to the NULL Values sub-section
+# of the "Placeholders and Bind Values" section in the DBI
+# documention for more information on this issue. The clause styles
+# and their numbering (0-6) map directly to the examples in the
+# documentation.
+#
+# To use this script:
+#
+# 1) If you are not using the DBI_DSN env variable, then update the
+# connect method arguments to support your database engine and
+# database, and remove the nearby check for DBI_DSN.
+# 2) Set PrintError to 1 in the connect method if you want see the
+# engine's reason WHY your engine won't support a particular
+# style.
+# 3) If your database does not support NULL columns by default
+# (e.g. Sybase) find and edit the CREATE TABLE statement
+# accordingly.
+# 4) To properly test style #5, you need the capability to create the
+# stored procedure SP_ISNULL that acts as a function: it tests its
+# argument and returns 1 if it is null, 0 otherwise. For example,
+# using Informix IDS engine, a definition would look like:
+#
+# CREATE PROCEDURE SP_ISNULL (arg VARCHAR(32)) RETURNING INTEGER;
+# IF arg IS NULL THEN RETURN 1;
+# ELSE RETURN 0;
+# END IF;
+# END PROCEDURE;
+#
+# Warning: This script will attempt to create a table named by the
+# $tablename variable (default dbi__null_test_tmp) and WILL DESTROY
+# any pre-existing table so named.
+
+use strict;
+use DBI;
+
+# The array represents the values that will be stored in the char column of our table.
+# One array element per row.
+# We expect the non-null test to return row 3 (Marge)
+# and the null test to return rows 2 and 4 (the undefs).
+
+my $homer = "Homer";
+my $marge = "Marge";
+
+my @char_column_values = (
+ $homer, # 1
+ undef, # 2
+ $marge, # 3
+ undef, # 4
+);
+
+# Define the SQL statements with the various WHERE clause styles we want to test
+# and the parameters we'll substitute.
+
+my @select_clauses =
+(
+ {clause=>qq{WHERE mycol = ?}, nonnull=>[$marge], null=>[undef]},
+ {clause=>qq{WHERE NVL(mycol, '-') = NVL(?, '-')}, nonnull=>[$marge], null=>[undef]},
+ {clause=>qq{WHERE ISNULL(mycol, '-') = ISNULL(?, '-')}, nonnull=>[$marge], null=>[undef]},
+ {clause=>qq{WHERE DECODE(mycol, ?, 1, 0) = 1}, nonnull=>[$marge], null=>[undef]},
+ {clause=>qq{WHERE mycol = ? OR (mycol IS NULL AND ? IS NULL)}, nonnull=>[$marge,$marge], null=>[undef,undef]},
+ {clause=>qq{WHERE mycol = ? OR (mycol IS NULL AND SP_ISNULL(?) = 1)}, nonnull=>[$marge,$marge], null=>[undef,undef]},
+ {clause=>qq{WHERE mycol = ? OR (mycol IS NULL AND ? = 1)}, nonnull=>[$marge,0], null=>[undef,1]},
+);
+
+# This is the table we'll create and use for these tests.
+# If it exists, we'll DESTROY it too. So the name must be obscure.
+
+my $tablename = "dbi__null_test_tmp";
+
+# Remove this if you are not using the DBI_DSN env variable,
+# and update the connect statement below.
+
+die "DBI_DSN environment variable not defined"
+ unless $ENV{DBI_DSN};
+
+my $dbh = DBI->connect(undef, undef, undef,
+ {
+ RaiseError => 0,
+ PrintError => 1
+ }
+) || die DBI->errstr;
+
+printf "Using %s, db version: %s\n", $ENV{DBI_DSN} || "connect arguments", $dbh->get_info(18) || "(unknown)";
+
+my $sth;
+my @ok;
+
+print "=> Drop table '$tablename', if it already exists...\n";
+do { local $dbh->{PrintError}=0; $dbh->do("DROP TABLE $tablename"); };
+
+print "=> Create table '$tablename'...\n";
+$dbh->do("CREATE TABLE $tablename (myid int NOT NULL, mycol char(5))");
+# Use this if your database does not support NULL columns by default:
+#$dbh->do("CREATE TABLE $tablename (myid int NOT NULL, mycol char(5) NULL)");
+
+print "=> Insert 4 rows into the table...\n";
+
+$sth = $dbh->prepare("INSERT INTO $tablename (myid, mycol) VALUES (?,?)");
+for my $i (0..$#char_column_values)
+{
+ my $val = $char_column_values[$i];
+ printf " Inserting values (%d, %s)\n", $i+1, $dbh->quote($val);
+ $sth->execute($i+1, $val);
+}
+print "(Driver bug: statement handle should not be Active after an INSERT.)\n"
+ if $sth->{Active};
+
+# Run the tests...
+
+for my $i (0..$#select_clauses)
+{
+ my $sel = $select_clauses[$i];
+ print "\n=> Testing clause style $i: ".$sel->{clause}."...\n";
+
+ $sth = $dbh->prepare("SELECT myid,mycol FROM $tablename ".$sel->{clause})
+ or next;
+
+ print " Selecting row with $marge\n";
+ $sth->execute(@{$sel->{nonnull}})
+ or next;
+ my $r1 = $sth->fetchall_arrayref();
+ my $n1_rows = $sth->rows;
+ my $n1 = @$r1;
+
+ print " Selecting rows with NULL\n";
+ $sth->execute(@{$sel->{null}})
+ or next;
+ my $r2 = $sth->fetchall_arrayref();
+ my $n2_rows = $sth->rows;
+ my $n2 = @$r2;
+
+ # Complain a bit...
+
+ print "\n=>Your DBD driver doesn't support the 'rows' method very well.\n\n"
+ unless ($n1_rows == $n1 && $n2_rows == $n2);
+
+ # Did we get back the expected "n"umber of rows?
+ # Did we get back the specific "r"ows we expected as identifed by the myid column?
+
+ if ( $n1 == 1 # one row for Marge
+ && $n2 == 2 # two rows for nulls
+ && $r1->[0][0] == 3 # Marge is myid 3
+ && $r2->[0][0] == 2 # NULL for myid 2
+ && $r2->[1][0] == 4 # NULL for myid 4
+ ) {
+ print "=> WHERE clause style $i is supported.\n";
+ push @ok, "\tStyle $i: ".$sel->{clause};
+ }
+ else
+ {
+ print "=> WHERE clause style $i returned incorrect results.\n";
+ if ($n1 > 0 || $n2 > 0)
+ {
+ print " Non-NULL test rows returned these row ids: ".
+ join(", ", map { $r1->[$_][0] } (0..$#{$r1}))."\n";
+ print " The NULL test rows returned these row ids: ".
+ join(", ", map { $r2->[$_][0] } (0..$#{$r2}))."\n";
+ }
+ }
+}
+
+$dbh->disconnect();
+print "\n";
+print "-" x 72, "\n";
+printf "%d styles are supported:\n", scalar @ok;
+print "$_\n" for @ok;
+print "-" x 72, "\n";
+print "\n";
+print "If these results don't match what's in the 'Placeholders and Bind Values'\n";
+print "section of the DBI documentation, or are for a database that not already\n";
+print "listed, please email the results to dbi-users\@perl.org. Thank you.\n";
+
+exit 0;
diff --git a/ex/profile.pl b/ex/profile.pl
new file mode 100644
index 0000000..96df9ae
--- /dev/null
+++ b/ex/profile.pl
@@ -0,0 +1,25 @@
+#!/usr/bin/perl -w
+
+use DBI;
+
+$dbh = DBI->connect('dbi:SQLite:dbname=ex_profile.db', '', '', { RaiseError => 1 });
+
+$dbh->do("DROP TABLE IF EXISTS ex_profile");
+$dbh->do("CREATE TABLE ex_profile (a int)");
+
+ $dbh->do("INSERT INTO ex_profile (a) VALUES ($_)", undef) for 1..100;
+#$dbh->do("INSERT INTO ex_profile (a) VALUES (?)", undef, $_) for 1..100;
+
+my $select_sql = "SELECT a FROM ex_profile";
+
+$dbh->selectall_arrayref($select_sql);
+
+$dbh->selectall_hashref($select_sql, 'a');
+
+my $sth = $dbh->prepare($select_sql);
+$sth->execute;
+while ( @row = $sth->fetchrow_array ) {
+}
+
+
+__DATA__
diff --git a/lib/Bundle/DBI.pm b/lib/Bundle/DBI.pm
new file mode 100644
index 0000000..50375a3
--- /dev/null
+++ b/lib/Bundle/DBI.pm
@@ -0,0 +1,51 @@
+# -*- perl -*-
+
+package Bundle::DBI;
+
+our $VERSION = sprintf("12.%06d", q$Revision: 8695 $ =~ /(\d+)/o);
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bundle::DBI - A bundle to install DBI and required modules.
+
+=head1 SYNOPSIS
+
+ perl -MCPAN -e 'install Bundle::DBI'
+
+=head1 CONTENTS
+
+DBI - for to get to know thyself
+
+DBI::Shell 11.91 - the DBI command line shell
+
+Storable 2.06 - for DBD::Proxy, DBI::ProxyServer, DBD::Forward
+
+Net::Daemon 0.37 - for DBD::Proxy and DBI::ProxyServer
+
+RPC::PlServer 0.2016 - for DBD::Proxy and DBI::ProxyServer
+
+DBD::Multiplex 1.19 - treat multiple db handles as one
+
+=head1 DESCRIPTION
+
+This bundle includes all the modules used by the Perl Database
+Interface (DBI) module, created by Tim Bunce.
+
+A I<Bundle> is a module that simply defines a collection of other
+modules. It is used by the L<CPAN> module to automate the fetching,
+building and installing of modules from the CPAN ftp archive sites.
+
+This bundle does not deal with the various database drivers (e.g.
+DBD::Informix, DBD::Oracle etc), most of which require software from
+sources other than CPAN. You'll need to fetch and build those drivers
+yourself.
+
+=head1 AUTHORS
+
+Jonathan Leffler, Jochen Wiedmann and Tim Bunce.
+
+=cut
diff --git a/lib/DBD/DBM.pm b/lib/DBD/DBM.pm
new file mode 100644
index 0000000..3c621a3
--- /dev/null
+++ b/lib/DBD/DBM.pm
@@ -0,0 +1,1461 @@
+#######################################################################
+#
+# DBD::DBM - a DBI driver for DBM files
+#
+# Copyright (c) 2004 by Jeff Zucker < jzucker AT cpan.org >
+# Copyright (c) 2010 by Jens Rehsack & H.Merijn Brand
+#
+# All rights reserved.
+#
+# You may freely distribute and/or modify this module under the terms
+# of either the GNU General Public License (GPL) or the Artistic License,
+# as specified in the Perl README file.
+#
+# USERS - see the pod at the bottom of this file
+#
+# DBD AUTHORS - see the comments in the code
+#
+#######################################################################
+require 5.008;
+use strict;
+
+#################
+package DBD::DBM;
+#################
+use base qw( DBD::File );
+use vars qw($VERSION $ATTRIBUTION $drh $methods_already_installed);
+$VERSION = '0.06';
+$ATTRIBUTION = 'DBD::DBM by Jens Rehsack';
+
+# no need to have driver() unless you need private methods
+#
+sub driver ($;$)
+{
+ my ( $class, $attr ) = @_;
+ return $drh if ($drh);
+
+ # do the real work in DBD::File
+ #
+ $attr->{Attribution} = 'DBD::DBM by Jens Rehsack';
+ $drh = $class->SUPER::driver($attr);
+
+ # install private methods
+ #
+ # this requires that dbm_ (or foo_) be a registered prefix
+ # but you can write private methods before official registration
+ # by hacking the $dbd_prefix_registry in a private copy of DBI.pm
+ #
+ unless ( $methods_already_installed++ )
+ {
+ DBD::DBM::st->install_method('dbm_schema');
+ }
+
+ return $drh;
+}
+
+sub CLONE
+{
+ undef $drh;
+}
+
+#####################
+package DBD::DBM::dr;
+#####################
+$DBD::DBM::dr::imp_data_size = 0;
+@DBD::DBM::dr::ISA = qw(DBD::File::dr);
+
+# you could put some :dr private methods here
+
+# you may need to over-ride some DBD::File::dr methods here
+# but you can probably get away with just letting it do the work
+# in most cases
+
+#####################
+package DBD::DBM::db;
+#####################
+$DBD::DBM::db::imp_data_size = 0;
+@DBD::DBM::db::ISA = qw(DBD::File::db);
+
+sub validate_STORE_attr
+{
+ my ( $dbh, $attrib, $value ) = @_;
+
+ if ( $attrib eq "dbm_ext" or $attrib eq "dbm_lockfile" )
+ {
+ ( my $newattrib = $attrib ) =~ s/^dbm_/f_/g;
+ # carp "Attribute '$attrib' is depreciated, use '$newattrib' instead" if( $^W );
+ $attrib = $newattrib;
+ }
+
+ return $dbh->SUPER::validate_STORE_attr( $attrib, $value );
+}
+
+sub validate_FETCH_attr
+{
+ my ( $dbh, $attrib ) = @_;
+
+ if ( $attrib eq "dbm_ext" or $attrib eq "dbm_lockfile" )
+ {
+ ( my $newattrib = $attrib ) =~ s/^dbm_/f_/g;
+ # carp "Attribute '$attrib' is depreciated, use '$newattrib' instead" if( $^W );
+ $attrib = $newattrib;
+ }
+
+ return $dbh->SUPER::validate_FETCH_attr($attrib);
+}
+
+sub set_versions
+{
+ my $this = $_[0];
+ $this->{dbm_version} = $DBD::DBM::VERSION;
+ return $this->SUPER::set_versions();
+}
+
+sub init_valid_attributes
+{
+ my $dbh = shift;
+
+ # define valid private attributes
+ #
+ # attempts to set non-valid attrs in connect() or
+ # with $dbh->{attr} will throw errors
+ #
+ # the attrs here *must* start with dbm_ or foo_
+ #
+ # see the STORE methods below for how to check these attrs
+ #
+ $dbh->{dbm_valid_attrs} = {
+ dbm_type => 1, # the global DBM type e.g. SDBM_File
+ dbm_mldbm => 1, # the global MLDBM serializer
+ dbm_cols => 1, # the global column names
+ dbm_version => 1, # verbose DBD::DBM version
+ dbm_store_metadata => 1, # column names, etc.
+ dbm_berkeley_flags => 1, # for BerkeleyDB
+ dbm_valid_attrs => 1, # DBD::DBM::db valid attrs
+ dbm_readonly_attrs => 1, # DBD::DBM::db r/o attrs
+ dbm_meta => 1, # DBD::DBM public access for f_meta
+ dbm_tables => 1, # DBD::DBM public access for f_meta
+ };
+ $dbh->{dbm_readonly_attrs} = {
+ dbm_version => 1, # verbose DBD::DBM version
+ dbm_valid_attrs => 1, # DBD::DBM::db valid attrs
+ dbm_readonly_attrs => 1, # DBD::DBM::db r/o attrs
+ dbm_meta => 1, # DBD::DBM public access for f_meta
+ };
+
+ $dbh->{dbm_meta} = "dbm_tables";
+
+ return $dbh->SUPER::init_valid_attributes();
+}
+
+sub init_default_attributes
+{
+ my ( $dbh, $phase ) = @_;
+
+ $dbh->SUPER::init_default_attributes($phase);
+ $dbh->{f_lockfile} = '.lck';
+
+ return $dbh;
+}
+
+sub get_dbm_versions
+{
+ my ( $dbh, $table ) = @_;
+ $table ||= '';
+
+ my $meta;
+ my $class = $dbh->{ImplementorClass};
+ $class =~ s/::db$/::Table/;
+ $table and ( undef, $meta ) = $class->get_table_meta( $dbh, $table, 1 );
+ $meta or ( $meta = {} and $class->bootstrap_table_meta( $dbh, $meta, $table ) );
+
+ my $dver;
+ my $dtype = $meta->{dbm_type};
+ eval {
+ $dver = $meta->{dbm_type}->VERSION();
+
+ # *) when we're still alive here, everthing went ok - no need to check for $@
+ $dtype .= " ($dver)";
+ };
+ if ( $meta->{dbm_mldbm} )
+ {
+ $dtype .= ' + MLDBM';
+ eval {
+ $dver = MLDBM->VERSION();
+ $dtype .= " ($dver)"; # (*)
+ };
+ eval {
+ my $ser_class = "MLDBM::Serializer::" . $meta->{dbm_mldbm};
+ my $ser_mod = $ser_class;
+ $ser_mod =~ s|::|/|g;
+ $ser_mod .= ".pm";
+ require $ser_mod;
+ $dver = $ser_class->VERSION();
+ $dtype .= ' + ' . $ser_class; # (*)
+ $dver and $dtype .= " ($dver)"; # (*)
+ };
+ }
+ return sprintf( "%s using %s", $dbh->{dbm_version}, $dtype );
+}
+
+# you may need to over-ride some DBD::File::db methods here
+# but you can probably get away with just letting it do the work
+# in most cases
+
+#####################
+package DBD::DBM::st;
+#####################
+$DBD::DBM::st::imp_data_size = 0;
+@DBD::DBM::st::ISA = qw(DBD::File::st);
+
+sub FETCH
+{
+ my ( $sth, $attr ) = @_;
+
+ if ( $attr eq "NULLABLE" )
+ {
+ my @colnames = $sth->sql_get_colnames();
+
+ # XXX only BerkeleyDB fails having NULL values for non-MLDBM databases,
+ # none accept it for key - but it requires more knowledge between
+ # queries and tables storage to return fully correct information
+ $attr eq "NULLABLE" and return [ map { 0 } @colnames ];
+ }
+
+ return $sth->SUPER::FETCH($attr);
+} # FETCH
+
+sub dbm_schema
+{
+ my ( $sth, $tname ) = @_;
+ return $sth->set_err( $DBI::stderr, 'No table name supplied!' ) unless $tname;
+ return $sth->set_err( $DBI::stderr, "Unknown table '$tname'!" )
+ unless ( $sth->{Database}->{f_meta}
+ and $sth->{Database}->{f_meta}->{$tname} );
+ return $sth->{Database}->{f_meta}->{$tname}->{schema};
+}
+# you could put some :st private methods here
+
+# you may need to over-ride some DBD::File::st methods here
+# but you can probably get away with just letting it do the work
+# in most cases
+
+############################
+package DBD::DBM::Statement;
+############################
+
+@DBD::DBM::Statement::ISA = qw(DBD::File::Statement);
+
+########################
+package DBD::DBM::Table;
+########################
+use Carp;
+use Fcntl;
+
+@DBD::DBM::Table::ISA = qw(DBD::File::Table);
+
+my $dirfext = $^O eq 'VMS' ? '.sdbm_dir' : '.dir';
+
+sub file2table
+{
+ my ( $self, $meta, $file, $file_is_table, $quoted ) = @_;
+
+ my $tbl = $self->SUPER::file2table( $meta, $file, $file_is_table, $quoted ) or return;
+
+ $meta->{f_dontopen} = 1;
+
+ return $tbl;
+}
+
+my %reset_on_modify = (
+ dbm_type => "dbm_tietype",
+ dbm_mldbm => "dbm_tietype",
+ );
+__PACKAGE__->register_reset_on_modify( \%reset_on_modify );
+
+my %compat_map = (
+ ( map { $_ => "dbm_$_" } qw(type mldbm store_metadata) ),
+ dbm_ext => 'f_ext',
+ dbm_file => 'f_file',
+ dbm_lockfile => ' f_lockfile',
+ );
+__PACKAGE__->register_compat_map (\%compat_map);
+
+sub bootstrap_table_meta
+{
+ my ( $self, $dbh, $meta, $table ) = @_;
+
+ $meta->{dbm_type} ||= $dbh->{dbm_type} || 'SDBM_File';
+ $meta->{dbm_mldbm} ||= $dbh->{dbm_mldbm} if ( $dbh->{dbm_mldbm} );
+ $meta->{dbm_berkeley_flags} ||= $dbh->{dbm_berkeley_flags};
+
+ defined $meta->{f_ext}
+ or $meta->{f_ext} = $dbh->{f_ext};
+ unless ( defined( $meta->{f_ext} ) )
+ {
+ my $ext;
+ if ( $meta->{dbm_type} eq 'SDBM_File' or $meta->{dbm_type} eq 'ODBM_File' )
+ {
+ $ext = '.pag/r';
+ }
+ elsif ( $meta->{dbm_type} eq 'NDBM_File' )
+ {
+ # XXX NDBM_File on FreeBSD (and elsewhere?) may actually be Berkeley
+ # behind the scenes and so create a single .db file.
+ if ( $^O =~ /bsd/i or lc($^O) eq 'darwin' )
+ {
+ $ext = '.db/r';
+ }
+ elsif ( $^O eq 'SunOS' or $^O eq 'Solaris' or $^O eq 'AIX' )
+ {
+ $ext = '.pag/r'; # here it's implemented like dbm - just a bit improved
+ }
+ # else wrapped GDBM
+ }
+ defined($ext) and $meta->{f_ext} = $ext;
+ }
+
+ $self->SUPER::bootstrap_table_meta( $dbh, $meta, $table );
+}
+
+sub init_table_meta
+{
+ my ( $self, $dbh, $meta, $table ) = @_;
+
+ unless ( defined( $meta->{dbm_tietype} ) )
+ {
+ my $tie_type = $meta->{dbm_type};
+ $INC{"$tie_type.pm"} or require "$tie_type.pm";
+ $tie_type eq 'BerkeleyDB' and $tie_type = 'BerkeleyDB::Hash';
+
+ if ( $meta->{dbm_mldbm} )
+ {
+ $INC{"MLDBM.pm"} or require "MLDBM.pm";
+ $meta->{dbm_usedb} = $tie_type;
+ $tie_type = 'MLDBM';
+ }
+
+ $meta->{dbm_tietype} = $tie_type;
+ }
+
+ unless ( defined( $meta->{dbm_store_metadata} ) )
+ {
+ my $store = $dbh->{dbm_store_metadata};
+ defined($store) or $store = 1;
+ $meta->{dbm_store_metadata} = $store;
+ }
+
+ unless ( defined( $meta->{col_names} ) )
+ {
+ defined( $dbh->{dbm_cols} ) and $meta->{col_names} = $dbh->{dbm_cols};
+ }
+
+ $self->SUPER::init_table_meta( $dbh, $meta, $table );
+}
+
+sub open_file
+{
+ my ( $self, $meta, $attrs, $flags ) = @_;
+ $self->SUPER::open_file( $meta, $attrs, $flags );
+ unless ( $flags->{dropMode} )
+ {
+ # TIEING
+ #
+ # XXX allow users to pass in a pre-created tied object
+ #
+ my @tie_args;
+ if ( $meta->{dbm_type} eq 'BerkeleyDB' )
+ {
+ my $DB_CREATE = BerkeleyDB::DB_CREATE();
+ my $DB_RDONLY = BerkeleyDB::DB_RDONLY();
+ my %tie_flags;
+ if ( my $f = $meta->{dbm_berkeley_flags} )
+ {
+ defined( $f->{DB_CREATE} ) and $DB_CREATE = delete $f->{DB_CREATE};
+ defined( $f->{DB_RDONLY} ) and $DB_RDONLY = delete $f->{DB_RDONLY};
+ %tie_flags = %$f;
+ }
+ my $open_mode = $flags->{lockMode} || $flags->{createMode} ? $DB_CREATE : $DB_RDONLY;
+ @tie_args = (
+ -Filename => $meta->{f_fqbn},
+ -Flags => $open_mode,
+ %tie_flags
+ );
+ }
+ else
+ {
+ my $open_mode = O_RDONLY;
+ $flags->{lockMode} and $open_mode = O_RDWR;
+ $flags->{createMode} and $open_mode = O_RDWR | O_CREAT | O_TRUNC;
+
+ @tie_args = ( $meta->{f_fqbn}, $open_mode, 0666 );
+ }
+
+ if ( $meta->{dbm_mldbm} )
+ {
+ $MLDBM::UseDB = $meta->{dbm_usedb};
+ $MLDBM::Serializer = $meta->{dbm_mldbm};
+ }
+
+ $meta->{hash} = {};
+ my $tie_class = $meta->{dbm_tietype};
+ eval { tie %{ $meta->{hash} }, $tie_class, @tie_args };
+ $@ and croak "Cannot tie(\%h $tie_class @tie_args): $@";
+ -f $meta->{f_fqfn} or croak( "No such file: '" . $meta->{f_fqfn} . "'" );
+ }
+
+ unless ( $flags->{createMode} )
+ {
+ my ( $meta_data, $schema, $col_names );
+ if ( $meta->{dbm_store_metadata} )
+ {
+ $meta_data = $col_names = $meta->{hash}->{"_metadata \0"};
+ if ( $meta_data and $meta_data =~ m~<dbd_metadata>(.+)</dbd_metadata>~is )
+ {
+ $schema = $col_names = $1;
+ $schema =~ s~.*<schema>(.+)</schema>.*~$1~is;
+ $col_names =~ s~.*<col_names>(.+)</col_names>.*~$1~is;
+ }
+ }
+ $col_names ||= $meta->{col_names} || [ 'k', 'v' ];
+ $col_names = [ split /,/, $col_names ] if ( ref $col_names ne 'ARRAY' );
+ if ( $meta->{dbm_store_metadata} and not $meta->{hash}->{"_metadata \0"} )
+ {
+ $schema or $schema = '';
+ $meta->{hash}->{"_metadata \0"} =
+ "<dbd_metadata>"
+ . "<schema>$schema</schema>"
+ . "<col_names>"
+ . join( ",", @{$col_names} )
+ . "</col_names>"
+ . "</dbd_metadata>";
+ }
+
+ $meta->{schema} = $schema;
+ $meta->{col_names} = $col_names;
+ }
+}
+
+# you must define drop
+# it is called from execute of a SQL DROP statement
+#
+sub drop ($$)
+{
+ my ( $self, $data ) = @_;
+ my $meta = $self->{meta};
+ $meta->{hash} and untie %{ $meta->{hash} };
+ $self->SUPER::drop($data);
+ # XXX extra_files
+ -f $meta->{f_fqbn} . $dirfext
+ and $meta->{f_ext} eq '.pag/r'
+ and unlink( $meta->{f_fqbn} . $dirfext );
+ return 1;
+}
+
+# you must define fetch_row, it is called on all fetches;
+# it MUST return undef when no rows are left to fetch;
+# checking for $ary[0] is specific to hashes so you'll
+# probably need some other kind of check for nothing-left.
+# as Janis might say: "undef's just another word for
+# nothing left to fetch" :-)
+#
+sub fetch_row ($$)
+{
+ my ( $self, $data ) = @_;
+ my $meta = $self->{meta};
+ # fetch with %each
+ #
+ my @ary = each %{ $meta->{hash} };
+ $meta->{dbm_store_metadata}
+ and $ary[0]
+ and $ary[0] eq "_metadata \0"
+ and @ary = each %{ $meta->{hash} };
+
+ my ( $key, $val ) = @ary;
+ unless ($key)
+ {
+ delete $self->{row};
+ return;
+ }
+ my @row = ( ref($val) eq 'ARRAY' ) ? ( $key, @$val ) : ( $key, $val );
+ $self->{row} = @row ? \@row : undef;
+ return wantarray ? @row : \@row;
+}
+
+# you must define push_row except insert_new_row and update_specific_row is defined
+# it is called on inserts and updates as primitive
+#
+sub insert_new_row ($$$)
+{
+ my ( $self, $data, $row_aryref ) = @_;
+ my $meta = $self->{meta};
+ my $ncols = scalar( @{ $meta->{col_names} } );
+ my $nitems = scalar( @{$row_aryref} );
+ $ncols == $nitems
+ or croak "You tried to insert $nitems, but table is created with $ncols columns";
+
+ my $key = shift @$row_aryref;
+ my $exists;
+ eval { $exists = exists( $meta->{hash}->{$key} ); };
+ $exists and croak "Row with PK '$key' already exists";
+
+ $meta->{hash}->{$key} = $meta->{dbm_mldbm} ? $row_aryref : $row_aryref->[0];
+
+ return 1;
+}
+
+# this is where you grab the column names from a CREATE statement
+# if you don't need to do that, it must be defined but can be empty
+#
+sub push_names ($$$)
+{
+ my ( $self, $data, $row_aryref ) = @_;
+ my $meta = $self->{meta};
+
+ # some sanity checks ...
+ my $ncols = scalar(@$row_aryref);
+ $ncols < 2 and croak "At least 2 columns are required for DBD::DBM tables ...";
+ !$meta->{dbm_mldbm}
+ and $ncols > 2
+ and croak "Without serializing with MLDBM only 2 columns are supported, you give $ncols";
+ $meta->{col_names} = $row_aryref;
+ return unless $meta->{dbm_store_metadata};
+
+ my $stmt = $data->{sql_stmt};
+ my $col_names = join( ',', @{$row_aryref} );
+ my $schema = $data->{Database}->{Statement};
+ $schema =~ s/^[^\(]+\((.+)\)$/$1/s;
+ $schema = $stmt->schema_str() if ( $stmt->can('schema_str') );
+ $meta->{hash}->{"_metadata \0"} =
+ "<dbd_metadata>"
+ . "<schema>$schema</schema>"
+ . "<col_names>$col_names</col_names>"
+ . "</dbd_metadata>";
+}
+
+# fetch_one_row, delete_one_row, update_one_row
+# are optimized for hash-style lookup without looping;
+# if you don't need them, omit them, they're optional
+# but, in that case you may need to define
+# truncate() and seek(), see below
+#
+sub fetch_one_row ($$;$)
+{
+ my ( $self, $key_only, $key ) = @_;
+ my $meta = $self->{meta};
+ $key_only and return $meta->{col_names}->[0];
+ exists $meta->{hash}->{$key} or return;
+ my $val = $meta->{hash}->{$key};
+ $val = ( ref($val) eq 'ARRAY' ) ? $val : [$val];
+ my $row = [ $key, @$val ];
+ return wantarray ? @{$row} : $row;
+}
+
+sub delete_one_row ($$$)
+{
+ my ( $self, $data, $aryref ) = @_;
+ my $meta = $self->{meta};
+ delete $meta->{hash}->{ $aryref->[0] };
+}
+
+sub update_one_row ($$$)
+{
+ my ( $self, $data, $aryref ) = @_;
+ my $meta = $self->{meta};
+ my $key = shift @$aryref;
+ defined $key or return;
+ my $row = ( ref($aryref) eq 'ARRAY' ) ? $aryref : [$aryref];
+ $meta->{hash}->{$key} = $meta->{dbm_mldbm} ? $row : $row->[0];
+}
+
+sub update_specific_row ($$$$)
+{
+ my ( $self, $data, $aryref, $origary ) = @_;
+ my $meta = $self->{meta};
+ my $key = shift @$origary;
+ my $newkey = shift @$aryref;
+ return unless ( defined $key );
+ $key eq $newkey or delete $meta->{hash}->{$key};
+ my $row = ( ref($aryref) eq 'ARRAY' ) ? $aryref : [$aryref];
+ $meta->{hash}->{$newkey} = $meta->{dbm_mldbm} ? $row : $row->[0];
+}
+
+# you may not need to explicitly DESTROY the ::Table
+# put cleanup code to run when the execute is done
+#
+sub DESTROY ($)
+{
+ my $self = shift;
+ my $meta = $self->{meta};
+ $meta->{hash} and untie %{ $meta->{hash} };
+
+ $self->SUPER::DESTROY();
+}
+
+# truncate() and seek() must be defined to satisfy DBI::SQL::Nano
+# *IF* you define the *_one_row methods above, truncate() and
+# seek() can be empty or you can use them without actually
+# truncating or seeking anything but if you don't define the
+# *_one_row methods, you may need to define these
+
+# if you need to do something after a series of
+# deletes or updates, you can put it in truncate()
+# which is called at the end of executing
+#
+sub truncate ($$)
+{
+ # my ( $self, $data ) = @_;
+ return 1;
+}
+
+# seek() is only needed if you use IO::File
+# though it could be used for other non-file operations
+# that you need to do before "writes" or truncate()
+#
+sub seek ($$$$)
+{
+ # my ( $self, $data, $pos, $whence ) = @_;
+ return 1;
+}
+
+# Th, th, th, that's all folks! See DBD::File and DBD::CSV for other
+# examples of creating pure perl DBDs. I hope this helped.
+# Now it's time to go forth and create your own DBD!
+# Remember to check in with dbi-dev@perl.org before you get too far.
+# We may be able to make suggestions or point you to other related
+# projects.
+
+1;
+__END__
+
+=pod
+
+=head1 NAME
+
+DBD::DBM - a DBI driver for DBM & MLDBM files
+
+=head1 SYNOPSIS
+
+ use DBI;
+ $dbh = DBI->connect('dbi:DBM:'); # defaults to SDBM_File
+ $dbh = DBI->connect('DBI:DBM(RaiseError=1):'); # defaults to SDBM_File
+ $dbh = DBI->connect('dbi:DBM:dbm_type=DB_File'); # defaults to DB_File
+ $dbh = DBI->connect('dbi:DBM:dbm_mldbm=Storable'); # MLDBM with SDBM_File
+
+ # or
+ $dbh = DBI->connect('dbi:DBM:', undef, undef);
+ $dbh = DBI->connect('dbi:DBM:', undef, undef, {
+ f_ext => '.db/r',
+ f_dir => '/path/to/dbfiles/',
+ f_lockfile => '.lck',
+ dbm_type => 'BerkeleyDB',
+ dbm_mldbm => 'FreezeThaw',
+ dbm_store_metadata => 1,
+ dbm_berkeley_flags => {
+ '-Cachesize' => 1000, # set a ::Hash flag
+ },
+ });
+
+and other variations on connect() as shown in the L<DBI> docs,
+L<DBD::File/Metadata|DBD::File metadata> and L</Metadata>
+shown below.
+
+Use standard DBI prepare, execute, fetch, placeholders, etc.,
+see L<QUICK START> for an example.
+
+=head1 DESCRIPTION
+
+DBD::DBM is a database management system that works right out of the
+box. If you have a standard installation of Perl and DBI you can
+begin creating, accessing, and modifying simple database tables
+without any further modules. You can add other modules (e.g.,
+SQL::Statement, DB_File etc) for improved functionality.
+
+The module uses a DBM file storage layer. DBM file storage is common on
+many platforms and files can be created with it in many programming
+languages using different APIs. That means, in addition to creating
+files with DBI/SQL, you can also use DBI/SQL to access and modify files
+created by other DBM modules and programs and vice versa. B<Note> that
+in those cases it might be necessary to use a common subset of the
+provided features.
+
+DBM files are stored in binary format optimized for quick retrieval
+when using a key field. That optimization can be used advantageously
+to make DBD::DBM SQL operations that use key fields very fast. There
+are several different "flavors" of DBM which use different storage
+formats supported by perl modules such as SDBM_File and MLDBM. This
+module supports all of the flavors that perl supports and, when used
+with MLDBM, supports tables with any number of columns and insertion
+of Perl objects into tables.
+
+DBD::DBM has been tested with the following DBM types: SDBM_File,
+NDBM_File, ODBM_File, GDBM_File, DB_File, BerkeleyDB. Each type was
+tested both with and without MLDBM and with the Data::Dumper,
+Storable, FreezeThaw, YAML and JSON serializers using the DBI::SQL::Nano
+or the SQL::Statement engines.
+
+=head1 QUICK START
+
+DBD::DBM operates like all other DBD drivers - it's basic syntax and
+operation is specified by DBI. If you're not familiar with DBI, you should
+start by reading L<DBI> and the documents it points to and then come back
+and read this file. If you are familiar with DBI, you already know most of
+what you need to know to operate this module. Just jump in and create a
+test script something like the one shown below.
+
+You should be aware that there are several options for the SQL engine
+underlying DBD::DBM, see L<Supported SQL syntax>. There are also many
+options for DBM support, see especially the section on L<Adding
+multi-column support with MLDBM>.
+
+But here's a sample to get you started.
+
+ use DBI;
+ my $dbh = DBI->connect('dbi:DBM:');
+ $dbh->{RaiseError} = 1;
+ for my $sql( split /;\n+/,"
+ CREATE TABLE user ( user_name TEXT, phone TEXT );
+ INSERT INTO user VALUES ('Fred Bloggs','233-7777');
+ INSERT INTO user VALUES ('Sanjay Patel','777-3333');
+ INSERT INTO user VALUES ('Junk','xxx-xxxx');
+ DELETE FROM user WHERE user_name = 'Junk';
+ UPDATE user SET phone = '999-4444' WHERE user_name = 'Sanjay Patel';
+ SELECT * FROM user
+ "){
+ my $sth = $dbh->prepare($sql);
+ $sth->execute;
+ $sth->dump_results if $sth->{NUM_OF_FIELDS};
+ }
+ $dbh->disconnect;
+
+=head1 USAGE
+
+This section will explain some useage cases in more detail. To get an
+overview about the available attributes, see L</Metadata>.
+
+=head2 Specifying Files and Directories
+
+DBD::DBM will automatically supply an appropriate file extension for the
+type of DBM you are using. For example, if you use SDBM_File, a table
+called "fruit" will be stored in two files called "fruit.pag" and
+"fruit.dir". You should B<never> specify the file extensions in your SQL
+statements.
+
+DBD::DBM recognizes following default extensions for following types:
+
+=over 4
+
+=item .pag/r
+
+Chosen for dbm_type C<< SDBM_File >>, C<< ODBM_File >> and C<< NDBM_File >>
+when an implementation is detected which wraps C<< -ldbm >> for
+C<< NDBM_File >> (e.g. Solaris, AIX, ...).
+
+For those types, the C<< .dir >> extension is recognized, too (for being
+deleted when dropping a table).
+
+=item .db/r
+
+Chosen for dbm_type C<< NDBM_File >> when an implementation is detected
+which wraps BerkeleyDB 1.x for C<< NDBM_File >> (typically BSD's, Darwin).
+
+=back
+
+C<< GDBM_File >>, C<< DB_File >> and C<< BerkeleyDB >> don't usually
+use a file extension.
+
+If your DBM type uses an extension other than one of the recognized
+types of extensions, you should set the I<f_ext> attribute to the
+extension B<and> file a bug report as described in DBI with the name
+of the implementation and extension so we can add it to DBD::DBM.
+Thanks in advance for that :-).
+
+ $dbh = DBI->connect('dbi:DBM:f_ext=.db'); # .db extension is used
+ $dbh = DBI->connect('dbi:DBM:f_ext='); # no extension is used
+
+ # or
+ $dbh->{f_ext}='.db'; # global setting
+ $dbh->{f_meta}->{'qux'}->{f_ext}='.db'; # setting for table 'qux'
+
+By default files are assumed to be in the current working directory.
+To use other directories specify the I<f_dir> attribute in either the
+connect string or by setting the database handle attribute.
+
+For example, this will look for the file /foo/bar/fruit (or
+/foo/bar/fruit.pag for DBM types that use that extension)
+
+ my $dbh = DBI->connect('dbi:DBM:f_dir=/foo/bar');
+ # and this will too:
+ my $dbh = DBI->connect('dbi:DBM:');
+ $dbh->{f_dir} = '/foo/bar';
+ # but this is recommended
+ my $dbh = DBI->connect('dbi:DBM:', undef, undef, { f_dir => '/foo/bar' } );
+
+ # now you can do
+ my $ary = $dbh->selectall_arrayref(q{ SELECT x FROM fruit });
+
+You can also use delimited identifiers to specify paths directly in SQL
+statements. This looks in the same place as the two examples above but
+without setting I<f_dir>:
+
+ my $dbh = DBI->connect('dbi:DBM:');
+ my $ary = $dbh->selectall_arrayref(q{
+ SELECT x FROM "/foo/bar/fruit"
+ });
+
+You can also tell DBD::DBM to use a specified path for a specific table:
+
+ $dbh->{dbm_tables}->{f}->{file} = q(/foo/bar/fruit);
+
+Please be aware that you cannot specify this during connection.
+
+If you have SQL::Statement installed, you can use table aliases:
+
+ my $dbh = DBI->connect('dbi:DBM:');
+ my $ary = $dbh->selectall_arrayref(q{
+ SELECT f.x FROM "/foo/bar/fruit" AS f
+ });
+
+See the L<GOTCHAS AND WARNINGS> for using DROP on tables.
+
+=head2 Table locking and flock()
+
+Table locking is accomplished using a lockfile which has the same
+basename as the table's file but with the file extension '.lck' (or a
+lockfile extension that you supply, see below). This lock file is
+created with the table during a CREATE and removed during a DROP.
+Every time the table itself is opened, the lockfile is flocked(). For
+SELECT, this is a shared lock. For all other operations, it is an
+exclusive lock (except when you specify something different using the
+I<f_lock> attribute).
+
+Since the locking depends on flock(), it only works on operating
+systems that support flock(). In cases where flock() is not
+implemented, DBD::DBM will simply behave as if the flock() had
+occurred although no actual locking will happen. Read the
+documentation for flock() for more information.
+
+Even on those systems that do support flock(), locking is only
+advisory - as is always the case with flock(). This means that if
+another program tries to access the table file while DBD::DBM has the
+table locked, that other program will *succeed* at opening unless
+it is also using flock on the '.lck' file. As a result DBD::DBM's
+locking only really applies to other programs using DBD::DBM or other
+program written to cooperate with DBD::DBM locking.
+
+=head2 Specifying the DBM type
+
+Each "flavor" of DBM stores its files in a different format and has
+different capabilities and limitations. See L<AnyDBM_File> for a
+comparison of DBM types.
+
+By default, DBD::DBM uses the C<< SDBM_File >> type of storage since
+C<< SDBM_File >> comes with Perl itself. If you have other types of
+DBM storage available, you can use any of them with DBD::DBM. It is
+strongly recommended to use at least C<< DB_File >>, because C<<
+SDBM_File >> has quirks and limitations and C<< ODBM_file >>, C<<
+NDBM_File >> and C<< GDBM_File >> are not always available.
+
+You can specify the DBM type using the I<dbm_type> attribute which can
+be set in the connection string or with C<< $dbh->{dbm_type} >> and
+C<< $dbh->{f_meta}->{$table_name}->{type} >> for per-table settings in
+cases where a single script is accessing more than one kind of DBM
+file.
+
+In the connection string, just set C<< dbm_type=TYPENAME >> where
+C<< TYPENAME >> is any DBM type such as GDBM_File, DB_File, etc. Do I<not>
+use MLDBM as your I<dbm_type> as that is set differently, see below.
+
+ my $dbh=DBI->connect('dbi:DBM:'); # uses the default SDBM_File
+ my $dbh=DBI->connect('dbi:DBM:dbm_type=GDBM_File'); # uses the GDBM_File
+
+ # You can also use $dbh->{dbm_type} to set the DBM type for the connection:
+ $dbh->{dbm_type} = 'DB_File'; # set the global DBM type
+ print $dbh->{dbm_type}; # display the global DBM type
+
+If you have several tables in your script that use different DBM
+types, you can use the $dbh->{dbm_tables} hash to store different
+settings for the various tables. You can even use this to perform
+joins on files that have completely different storage mechanisms.
+
+ # sets global default of GDBM_File
+ my $dbh->('dbi:DBM:type=GDBM_File');
+
+ # overrides the global setting, but only for the tables called
+ # I<foo> and I<bar>
+ my $dbh->{f_meta}->{foo}->{dbm_type} = 'DB_File';
+ my $dbh->{f_meta}->{bar}->{dbm_type} = 'BerkeleyDB';
+
+ # prints the dbm_type for the table "foo"
+ print $dbh->{f_meta}->{foo}->{dbm_type};
+
+B<Note> that you must change the I<dbm_type> of a table before you access
+it for first time.
+
+=head2 Adding multi-column support with MLDBM
+
+Most of the DBM types only support two columns and even if it would
+support more, DBD::DBM would only use two. However a CPAN module
+called MLDBM overcomes this limitation by allowing more than two
+columns. MLDBM does this by serializing the data - basically it puts
+a reference to an array into the second column. It can also put almost
+any kind of Perl object or even B<Perl coderefs> into columns.
+
+If you want more than two columns, you B<must> install MLDBM. It's available
+for many platforms and is easy to install.
+
+MLDBM is by default distributed with three serializers - Data::Dumper,
+Storable, and FreezeThaw. Data::Dumper is the default and Storable is the
+fastest. MLDBM can also make use of user-defined serialization methods or
+other serialization modules (e.g. L<YAML::MLDBM> or
+L<MLDBM::Serializer::JSON>. You select the serializer using the
+I<dbm_mldbm> attribute.
+
+Some examples:
+
+ $dbh=DBI->connect('dbi:DBM:dbm_mldbm=Storable'); # use MLDBM with Storable
+ $dbh=DBI->connect(
+ 'dbi:DBM:dbm_mldbm=MySerializer' # use MLDBM with a user defined module
+ );
+ $dbh=DBI->connect('dbi::dbm:', undef,
+ undef, { dbm_mldbm => 'YAML' }); # use 3rd party serializer
+ $dbh->{dbm_mldbm} = 'YAML'; # same as above
+ print $dbh->{dbm_mldbm} # show the MLDBM serializer
+ $dbh->{f_meta}->{foo}->{dbm_mldbm}='Data::Dumper'; # set Data::Dumper for table "foo"
+ print $dbh->{f_meta}->{foo}->{mldbm}; # show serializer for table "foo"
+
+MLDBM works on top of other DBM modules so you can also set a DBM type
+along with setting dbm_mldbm. The examples above would default to using
+SDBM_File with MLDBM. If you wanted GDBM_File instead, here's how:
+
+ # uses DB_File with MLDBM and Storable
+ $dbh = DBI->connect('dbi:DBM:', undef, undef, {
+ dbm_type => 'DB_File',
+ dbm_mldbm => 'Storable',
+ });
+
+SDBM_File, the default I<dbm_type> is quite limited, so if you are going to
+use MLDBM, you should probably use a different type, see L<AnyDBM_File>.
+
+See below for some L<GOTCHAS AND WARNINGS> about MLDBM.
+
+=head2 Support for Berkeley DB
+
+The Berkeley DB storage type is supported through two different Perl
+modules - DB_File (which supports only features in old versions of Berkeley
+DB) and BerkeleyDB (which supports all versions). DBD::DBM supports
+specifying either "DB_File" or "BerkeleyDB" as a I<dbm_type>, with or
+without MLDBM support.
+
+The "BerkeleyDB" dbm_type is experimental and it's interface is likely to
+change. It currently defaults to BerkeleyDB::Hash and does not currently
+support ::Btree or ::Recno.
+
+With BerkeleyDB, you can specify initialization flags by setting them in
+your script like this:
+
+ use BerkeleyDB;
+ my $env = new BerkeleyDB::Env -Home => $dir; # and/or other Env flags
+ $dbh = DBI->connect('dbi:DBM:', undef, undef, {
+ dbm_type => 'BerkeleyDB',
+ dbm_mldbm => 'Storable',
+ dbm_berkeley_flags => {
+ 'DB_CREATE' => DB_CREATE, # pass in constants
+ 'DB_RDONLY' => DB_RDONLY, # pass in constants
+ '-Cachesize' => 1000, # set a ::Hash flag
+ '-Env' => $env, # pass in an environment
+ },
+ });
+
+Do I<not> set the -Flags or -Filename flags as those are determined and
+overwritten by the SQL (e.g. -Flags => DB_RDONLY is set automatically
+when you issue a SELECT statement).
+
+Time has not permitted us to provide support in this release of DBD::DBM
+for further Berkeley DB features such as transactions, concurrency,
+locking, etc. We will be working on these in the future and would value
+suggestions, patches, etc.
+
+See L<DB_File> and L<BerkeleyDB> for further details.
+
+=head2 Optimizing the use of key fields
+
+Most "flavors" of DBM have only two physical columns (but can contain
+multiple logical columns as explained above in
+L<Adding multi-column support with MLDBM>). They work similarly to a
+Perl hash with the first column serving as the key. Like a Perl hash, DBM
+files permit you to do quick lookups by specifying the key and thus avoid
+looping through all records (supported by DBI::SQL::Nano only). Also like
+a Perl hash, the keys must be unique. It is impossible to create two
+records with the same key. To put this more simply and in SQL terms,
+the key column functions as the I<PRIMARY KEY> or UNIQUE INDEX.
+
+In DBD::DBM, you can take advantage of the speed of keyed lookups by using
+DBI::SQL::Nano and a WHERE clause with a single equal comparison on the key
+field. For example, the following SQL statements are optimized for keyed
+lookup:
+
+ CREATE TABLE user ( user_name TEXT, phone TEXT);
+ INSERT INTO user VALUES ('Fred Bloggs','233-7777');
+ # ... many more inserts
+ SELECT phone FROM user WHERE user_name='Fred Bloggs';
+
+The "user_name" column is the key column since it is the first
+column. The SELECT statement uses the key column in a single equal
+comparison - "user_name='Fred Bloggs'" - so the search will find it
+very quickly without having to loop through all the names which were
+inserted into the table.
+
+In contrast, these searches on the same table are not optimized:
+
+ 1. SELECT phone FROM user WHERE user_name < 'Fred';
+ 2. SELECT user_name FROM user WHERE phone = '233-7777';
+
+In #1, the operation uses a less-than (<) comparison rather than an equals
+comparison, so it will not be optimized for key searching. In #2, the key
+field "user_name" is not specified in the WHERE clause, and therefore the
+search will need to loop through all rows to find the requested row(s).
+
+B<Note> that the underlying DBM storage needs to loop over all I<key/value>
+pairs when the optimized fetch is used. SQL::Statement has a massively
+improved where clause evaluation which costs around 15% of the evaluation
+in DBI::SQL::Nano - combined with the loop in the DBM storage the speed
+improvement isn't so impressive.
+
+Even if lookups are faster by around 50%, DBI::SQL::Nano and
+SQL::Statement can benefit from the key field optimizations on
+updating and deleting rows - and here the improved where clause
+evaluation of SQL::Statement might beat DBI::SQL::Nano every time the
+where clause contains not only the key field (or more than one).
+
+=head2 Supported SQL syntax
+
+DBD::DBM uses a subset of SQL. The robustness of that subset depends on
+what other modules you have installed. Both options support basic SQL
+operations including CREATE TABLE, DROP TABLE, INSERT, DELETE, UPDATE, and
+SELECT.
+
+B<Option #1:> By default, this module inherits its SQL support from
+DBI::SQL::Nano that comes with DBI. Nano is, as its name implies, a *very*
+small SQL engine. Although limited in scope, it is faster than option #2
+for some operations (especially single I<primary key> lookups). See
+L<DBI::SQL::Nano> for a description of the SQL it supports and comparisons
+of it with option #2.
+
+B<Option #2:> If you install the pure Perl CPAN module SQL::Statement,
+DBD::DBM will use it instead of Nano. This adds support for table aliases,
+functions, joins, and much more. If you're going to use DBD::DBM
+for anything other than very simple tables and queries, you should install
+SQL::Statement. You don't have to change DBD::DBM or your scripts in any
+way, simply installing SQL::Statement will give you the more robust SQL
+capabilities without breaking scripts written for DBI::SQL::Nano. See
+L<SQL::Statement> for a description of the SQL it supports.
+
+To find out which SQL module is working in a given script, you can use the
+dbm_versions() method or, if you don't need the full output and version
+numbers, just do this:
+
+ print $dbh->{sql_handler}, "\n";
+
+That will print out either "SQL::Statement" or "DBI::SQL::Nano".
+
+Baring the section about optimized access to the DBM storage in mind,
+comparing the benefits of both engines:
+
+ # DBI::SQL::Nano is faster
+ $sth = $dbh->prepare( "update foo set value='new' where key=15" );
+ $sth->execute();
+ $sth = $dbh->prepare( "delete from foo where key=27" );
+ $sth->execute();
+ $sth = $dbh->prepare( "select * from foo where key='abc'" );
+
+ # SQL::Statement might faster (depending on DB size)
+ $sth = $dbh->prepare( "update foo set value='new' where key=?" );
+ $sth->execute(15);
+ $sth = $dbh->prepare( "update foo set value=? where key=15" );
+ $sth->execute('new');
+ $sth = $dbh->prepare( "delete from foo where key=?" );
+ $sth->execute(27);
+
+ # SQL::Statement is faster
+ $sth = $dbh->prepare( "update foo set value='new' where value='old'" );
+ $sth->execute();
+ # must be expressed using "where key = 15 or key = 27 or key = 42 or key = 'abc'"
+ # in DBI::SQL::Nano
+ $sth = $dbh->prepare( "delete from foo where key in (15,27,42,'abc')" );
+ $sth->execute();
+ # must be expressed using "where key > 10 and key < 90" in DBI::SQL::Nano
+ $sth = $dbh->prepare( "select * from foo where key between (10,90)" );
+ $sth->execute();
+
+ # only SQL::Statement can handle
+ $sth->prepare( "select * from foo,bar where foo.name = bar.name" );
+ $sth->execute();
+ $sth->prepare( "insert into foo values ( 1, 'foo' ), ( 2, 'bar' )" );
+ $sth->execute();
+
+=head2 Specifying Column Names
+
+DBM files don't have a standard way to store column names. DBD::DBM gets
+around this issue with a DBD::DBM specific way of storing the column names.
+B<If you are working only with DBD::DBM and not using files created by or
+accessed with other DBM programs, you can ignore this section.>
+
+DBD::DBM stores column names as a row in the file with the key I<_metadata
+\0>. So this code
+
+ my $dbh = DBI->connect('dbi:DBM:');
+ $dbh->do("CREATE TABLE baz (foo CHAR(10), bar INTEGER)");
+ $dbh->do("INSERT INTO baz (foo,bar) VALUES ('zippy',1)");
+
+Will create a file that has a structure something like this:
+
+ _metadata \0 | <dbd_metadata><schema></schema><col_names>foo,bar</col_names></dbd_metadata>
+ zippy | 1
+
+The next time you access this table with DBD::DBM, it will treat the
+I<_metadata \0> row as a header rather than as data and will pull the column
+names from there. However, if you access the file with something other
+than DBD::DBM, the row will be treated as a regular data row.
+
+If you do not want the column names stored as a data row in the table you
+can set the I<dbm_store_metadata> attribute to 0.
+
+ my $dbh = DBI->connect('dbi:DBM:', undef, undef, { dbm_store_metadata => 0 });
+
+ # or
+ $dbh->{dbm_store_metadata} = 0;
+
+ # or for per-table setting
+ $dbh->{f_meta}->{qux}->{dbm_store_metadata} = 0;
+
+By default, DBD::DBM assumes that you have two columns named "k" and "v"
+(short for "key" and "value"). So if you have I<dbm_store_metadata> set to
+1 and you want to use alternate column names, you need to specify the
+column names like this:
+
+ my $dbh = DBI->connect('dbi:DBM:', undef, undef, {
+ dbm_store_metadata => 0,
+ dbm_cols => [ qw(foo bar) ],
+ });
+
+ # or
+ $dbh->{dbm_store_metadata} = 0;
+ $dbh->{dbm_cols} = 'foo,bar';
+
+ # or to set the column names on per-table basis, do this:
+ # sets the column names only for table "qux"
+ $dbh->{f_meta}->{qux}->{dbm_store_metadata} = 0;
+ $dbh->{f_meta}->{qux}->{col_names} = [qw(foo bar)];
+
+If you have a file that was created by another DBM program or created with
+I<dbm_store_metadata> set to zero and you want to convert it to using
+DBD::DBM's column name storage, just use one of the methods above to name
+the columns but *without* specifying I<dbm_store_metadata> as zero. You
+only have to do that once - thereafter you can get by without setting
+either I<dbm_store_metadata> or setting I<dbm_cols> because the names will
+be stored in the file.
+
+=head1 DBI database handle attributes
+
+=head2 Metadata
+
+=head3 Statement handle ($sth) attributes and methods
+
+Most statement handle attributes such as NAME, NUM_OF_FIELDS, etc. are
+available only after an execute. The same is true of $sth->rows which is
+available after the execute but does I<not> require a fetch.
+
+=head3 Driver handle ($dbh) attributes
+
+It is not supported anymore to use dbm-attributes without the dbm_-prefix.
+Currently, if an DBD::DBM private attribute is accessed without an
+underscore in it's name, dbm_ is prepended to that attribute and it's
+processed further. If the resulting attribute name is invalid, an error is
+thrown.
+
+=head4 dbm_cols
+
+Contains a comma separated list of column names or an array reference to
+the column names.
+
+=head4 dbm_type
+
+Contains the DBM storage type. Currently known supported type are
+C<< ODBM_File >>, C<< NDBM_File >>, C<< SDBM_File >>, C<< GDBM_File >>,
+C<< DB_File >> and C<< BerkeleyDB >>. It is not recommended to use one
+of the first three types - even if C<< SDBM_File >> is the most commonly
+available I<dbm_type>.
+
+=head4 dbm_mldbm
+
+Contains the serializer for DBM storage (value column). Requires the
+CPAN module L<MLDBM> installed. Currently known supported serializers
+are:
+
+=over 8
+
+=item Data::Dumper
+
+Default serializer. Deployed with Perl core.
+
+=item Storable
+
+Faster serializer. Deployed with Perl core.
+
+=item FreezeThaw
+
+Pure Perl serializer, requires L<FreezeThaw> to be installed.
+
+=item YAML
+
+Portable serializer (between languages but not architectures).
+Requires L<YAML::MLDBM> installation.
+
+=item JSON
+
+Portable, fast serializer (between languages but not architectures).
+Requires L<MLDBM::Serializer::JSON> installation.
+
+=back
+
+=head4 dbm_store_metadata
+
+Boolean value which determines if the metadata in DBM is stored or not.
+
+=head4 dbm_berkeley_flags
+
+Hash reference with additional flags for BerkeleyDB::Hash instantiation.
+
+=head4 dbm_version
+
+Readonly attribute containing the version of DBD::DBM.
+
+=head4 f_meta
+
+In addition to the attributes L<DBD::File> recognizes, DBD::DBM knows
+about the (public) attributes C<col_names> (B<Note> not I<dbm_cols>
+here!), C<dbm_type>, C<dbm_mldbm>, C<dbm_store_metadata> and
+C<dbm_berkeley_flags>. As in DBD::File, there are undocumented,
+internal attributes in DBD::DBM. Be very careful when modifying
+attributes you do not know; the consequence might a destroyed or
+corrupted table.
+
+=head4 dbm_tables
+
+This attribute provides restricted access to the table meta data. See
+L<f_meta> and L<DBD::File/f_meta> for attribute details.
+
+dbm_tables is a tied hash providing the internal table names as keys
+(accessing unknown tables might create an entry) and their meta
+data as another tied hash. The table meta storage is obtained via
+the C<get_table_meta> method from the table implementation (see
+L<DBD::File::Developers>). Attribute setting and getting within the
+table meta data is handled via the methods C<set_table_meta_attr> and
+C<get_table_meta_attr>.
+
+=head3 Following attributes are no longer handled by DBD::DBM:
+
+=head4 dbm_ext
+
+This attribute is silently mapped to DBD::File's attribute I<f_ext>.
+Later versions of DBI might show a depreciated warning when this attribute
+is used and eventually it will be removed.
+
+=head4 dbm_lockfile
+
+This attribute is silently mapped to DBD::File's attribute I<f_lockfile>.
+Later versions of DBI might show a depreciated warning when this attribute
+is used and eventually it will be removed.
+
+=head1 DBI database handle methods
+
+=head2 The $dbh->dbm_versions() method
+
+The private method dbm_versions() returns a summary of what other modules
+are being used at any given time. DBD::DBM can work with or without many
+other modules - it can use either SQL::Statement or DBI::SQL::Nano as its
+SQL engine, it can be run with DBI or DBI::PurePerl, it can use many kinds
+of DBM modules, and many kinds of serializers when run with MLDBM. The
+dbm_versions() method reports all of that and more.
+
+ print $dbh->dbm_versions; # displays global settings
+ print $dbh->dbm_versions($table_name); # displays per table settings
+
+An important thing to note about this method is that when it called
+with no arguments, it displays the *global* settings. If you override
+these by setting per-table attributes, these will I<not> be shown
+unless you specify a table name as an argument to the method call.
+
+=head2 Storing Objects
+
+If you are using MLDBM, you can use DBD::DBM to take advantage of its
+serializing abilities to serialize any Perl object that MLDBM can handle.
+To store objects in columns, you should (but don't absolutely need to)
+declare it as a column of type BLOB (the type is *currently* ignored by
+the SQL engine, but it's good form).
+
+=head1 EXTENSIBILITY
+
+=over 8
+
+=item C<SQL::Statement>
+
+Improved SQL engine compared to the built-in DBI::SQL::Nano - see
+L<Supported SQL syntax>.
+
+=item C<DB_File>
+
+Berkeley DB version 1. This database library is available on many
+systems without additional installation and most systems are
+supported.
+
+=item C<GDBM_File>
+
+Simple dbm type (comparable to C<DB_File>) under the GNU license.
+Typically not available (or requires extra installation) on non-GNU
+operating systems.
+
+=item C<BerkeleyDB>
+
+Berkeley DB version up to v4 (and maybe higher) - requires additional
+installation but is easier than GDBM_File on non-GNU systems.
+
+db4 comes with a many tools which allow repairing and migrating
+databases. This is the B<recommended> dbm type for production use.
+
+=item C<MLDBM>
+
+Serializer wrapper to support more than one column for the files.
+Comes with serializers using C<Data::Dumper>, C<FreezeThaw> and
+C<Storable>.
+
+=item C<YAML::MLDBM>
+
+Additional serializer for MLDBM. YAML is very portable between languanges.
+
+=item C<MLDBM::Serializer::JSON>
+
+Additional serializer for MLDBM. JSON is very portable between languanges,
+probably more than YAML.
+
+=back
+
+=head1 GOTCHAS AND WARNINGS
+
+Using the SQL DROP command will remove any file that has the name specified
+in the command with either '.pag' and '.dir', '.db' or your {f_ext} appended
+to it. So this be dangerous if you aren't sure what file it refers to:
+
+ $dbh->do(qq{DROP TABLE "/path/to/any/file"});
+
+Each DBM type has limitations. SDBM_File, for example, can only store
+values of less than 1,000 characters. *You* as the script author must
+ensure that you don't exceed those bounds. If you try to insert a value
+that is larger than DBM can store, the results will be unpredictable.
+See the documentation for whatever DBM you are using for details.
+
+Different DBM implementations return records in different orders.
+That means that you I<should not> rely on the order of records unless
+you use an ORDER BY statement.
+
+DBM data files are platform-specific. To move them from one platform to
+another, you'll need to do something along the lines of dumping your data
+to CSV on platform #1 and then dumping from CSV to DBM on platform #2.
+DBD::AnyData and DBD::CSV can help with that. There may also be DBM
+conversion tools for your platforms which would probably be quicker.
+
+When using MLDBM, there is a very powerful serializer - it will allow
+you to store Perl code or objects in database columns. When these get
+de-serialized, they may be eval'ed - in other words MLDBM (or actually
+Data::Dumper when used by MLDBM) may take the values and try to
+execute them in Perl. Obviously, this can present dangers, so if you
+do not know what is in a file, be careful before you access it with
+MLDBM turned on!
+
+See the entire section on L<Table locking and flock()> for gotchas and
+warnings about the use of flock().
+
+=head1 BUGS AND LIMITATIONS
+
+This module uses hash interfaces of two column file databases. While
+none of supported SQL engines have support for indices, the following
+statements really do the same (even if they mean something completely
+different) for each dbm type which lacks C<EXISTS> support:
+
+ $sth->do( "insert into foo values (1, 'hello')" );
+
+ # this statement does ...
+ $sth->do( "update foo set v='world' where k=1" );
+ # ... the same as this statement
+ $sth->do( "insert into foo values (1, 'world')" );
+
+This is considered to be a bug and might change in a future release.
+
+Known affected dbm types are C<ODBM_File> and C<NDBM_File>. We highly
+recommended you use a more modern dbm type such as C<DB_File>.
+
+=head1 GETTING HELP, MAKING SUGGESTIONS, AND REPORTING BUGS
+
+If you need help installing or using DBD::DBM, please write to the DBI
+users mailing list at dbi-users@perl.org or to the
+comp.lang.perl.modules newsgroup on usenet. I cannot always answer
+every question quickly but there are many on the mailing list or in
+the newsgroup who can.
+
+DBD developers for DBD's which rely on DBD::File or DBD::DBM or use
+one of them as an example are suggested to join the DBI developers
+mailing list at dbi-dev@perl.org and strongly encouraged to join our
+IRC channel at L<irc://irc.perl.org/dbi>.
+
+If you have suggestions, ideas for improvements, or bugs to report, please
+report a bug as described in DBI. Do not mail any of the authors directly,
+you might not get an answer.
+
+When reporting bugs, please send the output of $dbh->dbm_versions($table)
+for a table that exhibits the bug and as small a sample as you can make of
+the code that produces the bug. And of course, patches are welcome, too
+:-).
+
+If you need enhancements quickly, you can get commercial support as
+described at L<http://dbi.perl.org/support/> or you can contact Jens Rehsack
+at rehsack@cpan.org for commercial support in Germany.
+
+Please don't bother Jochen Wiedmann or Jeff Zucker for support - they
+handed over further maintenance to H.Merijn Brand and Jens Rehsack.
+
+=head1 ACKNOWLEDGEMENTS
+
+Many, many thanks to Tim Bunce for prodding me to write this, and for
+copious, wise, and patient suggestions all along the way. (Jeff Zucker)
+
+I send my thanks and acknowledgements to H.Merijn Brand for his
+initial refactoring of DBD::File and his strong and ongoing support of
+SQL::Statement. Without him, the current progress would never have
+been made. And I have to name Martin J. Evans for each laugh (and
+correction) of all those funny word creations I (as non-native
+speaker) made to the documentation. And - of course - I have to thank
+all those unnamed contributors and testers from the Perl
+community. (Jens Rehsack)
+
+=head1 AUTHOR AND COPYRIGHT
+
+This module is written by Jeff Zucker < jzucker AT cpan.org >, who also
+maintained it till 2007. After that, in 2010, Jens Rehsack & H.Merijn Brand
+took over maintenance.
+
+ Copyright (c) 2004 by Jeff Zucker, all rights reserved.
+ Copyright (c) 2010 by Jens Rehsack & H.Merijn Brand, all rights reserved.
+
+You may freely distribute and/or modify this module under the terms of
+either the GNU General Public License (GPL) or the Artistic License, as
+specified in the Perl README file.
+
+=head1 SEE ALSO
+
+L<DBI>,
+L<SQL::Statement>, L<DBI::SQL::Nano>,
+L<AnyDBM_File>, L<DB_File>, L<BerkeleyDB>,
+L<MLDBM>, L<YAML::MLDBM>, L<MLDBM::Serializer::JSON>
+
+=cut
diff --git a/lib/DBD/ExampleP.pm b/lib/DBD/ExampleP.pm
new file mode 100644
index 0000000..0bbace0
--- /dev/null
+++ b/lib/DBD/ExampleP.pm
@@ -0,0 +1,428 @@
+{
+ package DBD::ExampleP;
+
+ use Symbol;
+
+ use DBI qw(:sql_types);
+
+ require File::Spec;
+
+ @EXPORT = qw(); # Do NOT @EXPORT anything.
+ $VERSION = sprintf("12.%06d", q$Revision: 14310 $ =~ /(\d+)/o);
+
+
+# $Id: ExampleP.pm 14310 2010-08-02 06:35:25Z REHSACK $
+#
+# Copyright (c) 1994,1997,1998 Tim Bunce
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+ @statnames = qw(dev ino mode nlink
+ uid gid rdev size
+ atime mtime ctime
+ blksize blocks name);
+ @statnames{@statnames} = (0 .. @statnames-1);
+
+ @stattypes = (SQL_INTEGER, SQL_INTEGER, SQL_INTEGER, SQL_INTEGER,
+ SQL_INTEGER, SQL_INTEGER, SQL_INTEGER, SQL_INTEGER,
+ SQL_INTEGER, SQL_INTEGER, SQL_INTEGER,
+ SQL_INTEGER, SQL_INTEGER, SQL_VARCHAR);
+ @stattypes{@statnames} = @stattypes;
+ @statprec = ((10) x (@statnames-1), 1024);
+ @statprec{@statnames} = @statprec;
+ die unless @statnames == @stattypes;
+ die unless @statprec == @stattypes;
+
+ $drh = undef; # holds driver handle once initialised
+ #$gensym = "SYM000"; # used by st::execute() for filehandles
+
+ sub driver{
+ return $drh if $drh;
+ my($class, $attr) = @_;
+ $class .= "::dr";
+ ($drh) = DBI::_new_drh($class, {
+ 'Name' => 'ExampleP',
+ 'Version' => $VERSION,
+ 'Attribution' => 'DBD Example Perl stub by Tim Bunce',
+ }, ['example implementors private data '.__PACKAGE__]);
+ $drh;
+ }
+
+ sub CLONE {
+ undef $drh;
+ }
+}
+
+
+{ package DBD::ExampleP::dr; # ====== DRIVER ======
+ $imp_data_size = 0;
+ use strict;
+
+ sub connect { # normally overridden, but a handy default
+ my($drh, $dbname, $user, $auth)= @_;
+ my ($outer, $dbh) = DBI::_new_dbh($drh, {
+ Name => $dbname,
+ examplep_private_dbh_attrib => 42, # an example, for testing
+ });
+ $dbh->{examplep_get_info} = {
+ 29 => '"', # SQL_IDENTIFIER_QUOTE_CHAR
+ 41 => '.', # SQL_CATALOG_NAME_SEPARATOR
+ 114 => 1, # SQL_CATALOG_LOCATION
+ };
+ #$dbh->{Name} = $dbname;
+ $dbh->STORE('Active', 1);
+ return $outer;
+ }
+
+ sub data_sources {
+ return ("dbi:ExampleP:dir=."); # possibly usefully meaningless
+ }
+
+}
+
+
+{ package DBD::ExampleP::db; # ====== DATABASE ======
+ $imp_data_size = 0;
+ use strict;
+
+ sub prepare {
+ my($dbh, $statement)= @_;
+ my @fields;
+ my($fields, $dir) = $statement =~ m/^\s*select\s+(.*?)\s+from\s+(\S*)/i;
+
+ if (defined $fields and defined $dir) {
+ @fields = ($fields eq '*')
+ ? keys %DBD::ExampleP::statnames
+ : split(/\s*,\s*/, $fields);
+ }
+ else {
+ return $dbh->set_err($DBI::stderr, "Syntax error in select statement (\"$statement\")")
+ unless $statement =~ m/^\s*set\s+/;
+ # the SET syntax is just a hack so the ExampleP driver can
+ # be used to test non-select statements.
+ # Now we have DBI::DBM etc., ExampleP should be deprecated
+ }
+
+ my ($outer, $sth) = DBI::_new_sth($dbh, {
+ 'Statement' => $statement,
+ examplep_private_sth_attrib => 24, # an example, for testing
+ }, ['example implementors private data '.__PACKAGE__]);
+
+ my @bad = map {
+ defined $DBD::ExampleP::statnames{$_} ? () : $_
+ } @fields;
+ return $dbh->set_err($DBI::stderr, "Unknown field names: @bad")
+ if @bad;
+
+ $outer->STORE('NUM_OF_FIELDS' => scalar(@fields));
+
+ $sth->{examplep_ex_dir} = $dir if defined($dir) && $dir !~ /\?/;
+ $outer->STORE('NUM_OF_PARAMS' => ($dir) ? $dir =~ tr/?/?/ : 0);
+
+ if (@fields) {
+ $outer->STORE('NAME' => \@fields);
+ $outer->STORE('NULLABLE' => [ (0) x @fields ]);
+ $outer->STORE('SCALE' => [ (0) x @fields ]);
+ }
+
+ $outer;
+ }
+
+
+ sub table_info {
+ my $dbh = shift;
+ my ($catalog, $schema, $table, $type) = @_;
+
+ my @types = split(/["']*,["']/, $type || 'TABLE');
+ my %types = map { $_=>$_ } @types;
+
+ # Return a list of all subdirectories
+ my $dh = Symbol::gensym(); # "DBD::ExampleP::".++$DBD::ExampleP::gensym;
+ my $dir = $catalog || File::Spec->curdir();
+ my @list;
+ if ($types{VIEW}) { # for use by test harness
+ push @list, [ undef, "schema", "table", 'VIEW', undef ];
+ push @list, [ undef, "sch-ema", "table", 'VIEW', undef ];
+ push @list, [ undef, "schema", "ta-ble", 'VIEW', undef ];
+ push @list, [ undef, "sch ema", "table", 'VIEW', undef ];
+ push @list, [ undef, "schema", "ta ble", 'VIEW', undef ];
+ }
+ if ($types{TABLE}) {
+ no strict 'refs';
+ opendir($dh, $dir)
+ or return $dbh->set_err(int($!), "Failed to open directory $dir: $!");
+ while (defined(my $item = readdir($dh))) {
+ if ($^O eq 'VMS') {
+ # if on VMS then avoid warnings from catdir if you use a file
+ # (not a dir) as the item below
+ next if $item !~ /\.dir$/oi;
+ }
+ my $file = File::Spec->catdir($dir,$item);
+ next unless -d $file;
+ my($dev, $ino, $mode, $nlink, $uid) = lstat($file);
+ my $pwnam = undef; # eval { scalar(getpwnam($uid)) } || $uid;
+ push @list, [ $dir, $pwnam, $item, 'TABLE', undef ];
+ }
+ close($dh);
+ }
+ # We would like to simply do a DBI->connect() here. However,
+ # this is wrong if we are in a subclass like DBI::ProxyServer.
+ $dbh->{'dbd_sponge_dbh'} ||= DBI->connect("DBI:Sponge:", '','')
+ or return $dbh->set_err($DBI::err,
+ "Failed to connect to DBI::Sponge: $DBI::errstr");
+
+ my $attr = {
+ 'rows' => \@list,
+ 'NUM_OF_FIELDS' => 5,
+ 'NAME' => ['TABLE_CAT', 'TABLE_SCHEM', 'TABLE_NAME',
+ 'TABLE_TYPE', 'REMARKS'],
+ 'TYPE' => [DBI::SQL_VARCHAR(), DBI::SQL_VARCHAR(),
+ DBI::SQL_VARCHAR(), DBI::SQL_VARCHAR(), DBI::SQL_VARCHAR() ],
+ 'NULLABLE' => [1, 1, 1, 1, 1]
+ };
+ my $sdbh = $dbh->{'dbd_sponge_dbh'};
+ my $sth = $sdbh->prepare("SHOW TABLES FROM $dir", $attr)
+ or return $dbh->set_err($sdbh->err(), $sdbh->errstr());
+ $sth;
+ }
+
+
+ sub type_info_all {
+ my ($dbh) = @_;
+ my $ti = [
+ { TYPE_NAME => 0,
+ DATA_TYPE => 1,
+ COLUMN_SIZE => 2,
+ LITERAL_PREFIX => 3,
+ LITERAL_SUFFIX => 4,
+ CREATE_PARAMS => 5,
+ NULLABLE => 6,
+ CASE_SENSITIVE => 7,
+ SEARCHABLE => 8,
+ UNSIGNED_ATTRIBUTE=> 9,
+ FIXED_PREC_SCALE=> 10,
+ AUTO_UNIQUE_VALUE => 11,
+ LOCAL_TYPE_NAME => 12,
+ MINIMUM_SCALE => 13,
+ MAXIMUM_SCALE => 14,
+ },
+ [ 'VARCHAR', DBI::SQL_VARCHAR, 1024, "'","'", undef, 0, 1, 1, 0, 0,0,undef,0,0 ],
+ [ 'INTEGER', DBI::SQL_INTEGER, 10, "","", undef, 0, 0, 1, 0, 0,0,undef,0,0 ],
+ ];
+ return $ti;
+ }
+
+
+ sub ping {
+ (shift->FETCH('Active')) ? 2 : 0; # the value 2 is checked for by t/80proxy.t
+ }
+
+
+ sub disconnect {
+ shift->STORE(Active => 0);
+ return 1;
+ }
+
+
+ sub get_info {
+ my ($dbh, $info_type) = @_;
+ return $dbh->{examplep_get_info}->{$info_type};
+ }
+
+
+ sub FETCH {
+ my ($dbh, $attrib) = @_;
+ # In reality this would interrogate the database engine to
+ # either return dynamic values that cannot be precomputed
+ # or fetch and cache attribute values too expensive to prefetch.
+ # else pass up to DBI to handle
+ return $INC{"DBD/ExampleP.pm"} if $attrib eq 'example_driver_path';
+ return $dbh->SUPER::FETCH($attrib);
+ }
+
+
+ sub STORE {
+ my ($dbh, $attrib, $value) = @_;
+ # would normally validate and only store known attributes
+ # else pass up to DBI to handle
+ if ($attrib eq 'AutoCommit') {
+ # convert AutoCommit values to magic ones to let DBI
+ # know that the driver has 'handled' the AutoCommit attribute
+ $value = ($value) ? -901 : -900;
+ }
+ return $dbh->{$attrib} = $value if $attrib =~ /^examplep_/;
+ return $dbh->SUPER::STORE($attrib, $value);
+ }
+
+ sub DESTROY {
+ my $dbh = shift;
+ $dbh->disconnect if $dbh->FETCH('Active');
+ undef
+ }
+
+
+ # This is an example to demonstrate the use of driver-specific
+ # methods via $dbh->func().
+ # Use it as follows:
+ # my @tables = $dbh->func($re, 'examplep_tables');
+ #
+ # Returns all the tables that match the regular expression $re.
+ sub examplep_tables {
+ my $dbh = shift; my $re = shift;
+ grep { $_ =~ /$re/ } $dbh->tables();
+ }
+
+ sub parse_trace_flag {
+ my ($h, $name) = @_;
+ return 0x01000000 if $name eq 'foo';
+ return 0x02000000 if $name eq 'bar';
+ return 0x04000000 if $name eq 'baz';
+ return 0x08000000 if $name eq 'boo';
+ return 0x10000000 if $name eq 'bop';
+ return $h->SUPER::parse_trace_flag($name);
+ }
+
+ sub private_attribute_info {
+ return { example_driver_path => undef };
+ }
+}
+
+
+{ package DBD::ExampleP::st; # ====== STATEMENT ======
+ $imp_data_size = 0;
+ use strict; no strict 'refs'; # cause problems with filehandles
+
+ sub bind_param {
+ my($sth, $param, $value, $attribs) = @_;
+ $sth->{'dbd_param'}->[$param-1] = $value;
+ return 1;
+ }
+
+
+ sub execute {
+ my($sth, @dir) = @_;
+ my $dir;
+
+ if (@dir) {
+ $sth->bind_param($_, $dir[$_-1]) or return
+ foreach (1..@dir);
+ }
+
+ my $dbd_param = $sth->{'dbd_param'} || [];
+ return $sth->set_err(2, @$dbd_param." values bound when $sth->{NUM_OF_PARAMS} expected")
+ unless @$dbd_param == $sth->{NUM_OF_PARAMS};
+
+ return 0 unless $sth->{NUM_OF_FIELDS}; # not a select
+
+ $dir = $dbd_param->[0] || $sth->{examplep_ex_dir};
+ return $sth->set_err(2, "No bind parameter supplied")
+ unless defined $dir;
+
+ $sth->finish;
+
+ #
+ # If the users asks for directory "long_list_4532", then we fake a
+ # directory with files "file4351", "file4350", ..., "file0".
+ # This is a special case used for testing, especially DBD::Proxy.
+ #
+ if ($dir =~ /^long_list_(\d+)$/) {
+ $sth->{dbd_dir} = [ $1 ]; # array ref indicates special mode
+ $sth->{dbd_datahandle} = undef;
+ }
+ else {
+ $sth->{dbd_dir} = $dir;
+ my $sym = Symbol::gensym(); # "DBD::ExampleP::".++$DBD::ExampleP::gensym;
+ opendir($sym, $dir)
+ or return $sth->set_err(2, "opendir($dir): $!");
+ $sth->{dbd_datahandle} = $sym;
+ }
+ $sth->STORE(Active => 1);
+ return 1;
+ }
+
+
+ sub fetch {
+ my $sth = shift;
+ my $dir = $sth->{dbd_dir};
+ my %s;
+
+ if (ref $dir) { # special fake-data test mode
+ my $num = $dir->[0]--;
+ unless ($num > 0) {
+ $sth->finish();
+ return;
+ }
+ my $time = time;
+ @s{@DBD::ExampleP::statnames} =
+ ( 2051, 1000+$num, 0644, 2, $>, $), 0, 1024,
+ $time, $time, $time, 512, 2, "file$num")
+ }
+ else { # normal mode
+ my $dh = $sth->{dbd_datahandle}
+ or return $sth->set_err($DBI::stderr, "fetch without successful execute");
+ my $f = readdir($dh);
+ unless ($f) {
+ $sth->finish;
+ return;
+ }
+ # untaint $f so that we can use this for DBI taint tests
+ ($f) = ($f =~ m/^(.*)$/);
+ my $file = File::Spec->catfile($dir, $f);
+ # put in all the data fields
+ @s{ @DBD::ExampleP::statnames } = (lstat($file), $f);
+ }
+
+ # return just what fields the query asks for
+ my @new = @s{ @{$sth->{NAME}} };
+
+ return $sth->_set_fbav(\@new);
+ }
+ *fetchrow_arrayref = \&fetch;
+
+
+ sub finish {
+ my $sth = shift;
+ closedir($sth->{dbd_datahandle}) if $sth->{dbd_datahandle};
+ $sth->{dbd_datahandle} = undef;
+ $sth->{dbd_dir} = undef;
+ $sth->SUPER::finish();
+ return 1;
+ }
+
+
+ sub FETCH {
+ my ($sth, $attrib) = @_;
+ # In reality this would interrogate the database engine to
+ # either return dynamic values that cannot be precomputed
+ # or fetch and cache attribute values too expensive to prefetch.
+ if ($attrib eq 'TYPE'){
+ return [ @DBD::ExampleP::stattypes{ @{ $sth->FETCH(q{NAME_lc}) } } ];
+ }
+ elsif ($attrib eq 'PRECISION'){
+ return [ @DBD::ExampleP::statprec{ @{ $sth->FETCH(q{NAME_lc}) } } ];
+ }
+ elsif ($attrib eq 'ParamValues') {
+ my $dbd_param = $sth->{dbd_param} || [];
+ my %pv = map { $_ => $dbd_param->[$_-1] } 1..@$dbd_param;
+ return \%pv;
+ }
+ # else pass up to DBI to handle
+ return $sth->SUPER::FETCH($attrib);
+ }
+
+
+ sub STORE {
+ my ($sth, $attrib, $value) = @_;
+ # would normally validate and only store known attributes
+ # else pass up to DBI to handle
+ return $sth->{$attrib} = $value
+ if $attrib eq 'NAME' or $attrib eq 'NULLABLE' or $attrib eq 'SCALE' or $attrib eq 'PRECISION';
+ return $sth->SUPER::STORE($attrib, $value);
+ }
+
+ *parse_trace_flag = \&DBD::ExampleP::db::parse_trace_flag;
+}
+
+1;
+# vim: sw=4:ts=8
diff --git a/lib/DBD/File.pm b/lib/DBD/File.pm
new file mode 100644
index 0000000..d4d57ae
--- /dev/null
+++ b/lib/DBD/File.pm
@@ -0,0 +1,1637 @@
+# -*- perl -*-
+#
+# DBD::File - A base class for implementing DBI drivers that
+# act on plain files
+#
+# This module is currently maintained by
+#
+# H.Merijn Brand & Jens Rehsack
+#
+# The original author is Jochen Wiedmann.
+#
+# Copyright (C) 2009,2010 by H.Merijn Brand & Jens Rehsack
+# Copyright (C) 2004 by Jeff Zucker
+# Copyright (C) 1998 by Jochen Wiedmann
+#
+# All rights reserved.
+#
+# You may distribute this module under the terms of either the GNU
+# General Public License or the Artistic License, as specified in
+# the Perl README file.
+
+require 5.008;
+
+use strict;
+use warnings;
+
+use DBI ();
+
+package DBD::File;
+
+use strict;
+use warnings;
+
+use base qw(DBI::DBD::SqlEngine);
+use Carp;
+use vars qw(@ISA $VERSION $drh);
+
+$VERSION = "0.40";
+
+$drh = undef; # holds driver handle(s) once initialized
+
+my %accessors = (
+ get_meta => "get_file_meta",
+ set_meta => "set_file_meta",
+ clear_meta => "clear_file_meta",
+ );
+
+sub driver ($;$)
+{
+ my ($class, $attr) = @_;
+
+ # Drivers typically use a singleton object for the $drh
+ # We use a hash here to have one singleton per subclass.
+ # (Otherwise DBD::CSV and DBD::DBM, for example, would
+ # share the same driver object which would cause problems.)
+ # An alternative would be not not cache the $drh here at all
+ # and require that subclasses do that. Subclasses should do
+ # their own caching, so caching here just provides extra safety.
+ $drh->{$class} and return $drh->{$class};
+
+ $attr ||= {};
+ { no strict "refs";
+ unless ($attr->{Attribution}) {
+ $class eq "DBD::File" and
+ $attr->{Attribution} = "$class by Jeff Zucker";
+ $attr->{Attribution} ||= ${$class . "::ATTRIBUTION"} ||
+ "oops the author of $class forgot to define this";
+ }
+ $attr->{Version} ||= ${$class . "::VERSION"};
+ $attr->{Name} or ($attr->{Name} = $class) =~ s/^DBD\:\://;
+ }
+
+ $drh->{$class} = $class->SUPER::driver ($attr);
+
+ my $prefix = DBI->driver_prefix ($class);
+ if ($prefix) {
+ my $dbclass = $class . "::db";
+ while (my ($accessor, $funcname) = each %accessors) {
+ my $method = $prefix . $accessor;
+ $dbclass->can ($method) and next;
+ my $inject = sprintf <<'EOI', $dbclass, $method, $dbclass, $funcname;
+sub %s::%s
+{
+ my $func = %s->can (q{%s});
+ goto &$func;
+ }
+EOI
+ eval $inject;
+ $dbclass->install_method ($method);
+ }
+ }
+
+ # XXX inject DBD::XXX::Statement unless exists
+
+ return $drh->{$class};
+ } # driver
+
+sub CLONE
+{
+ undef $drh;
+ } # CLONE
+
+# ====== DRIVER ================================================================
+
+package DBD::File::dr;
+
+use strict;
+use warnings;
+
+use vars qw(@ISA $imp_data_size);
+
+@DBD::File::dr::ISA = qw(DBI::DBD::SqlEngine::dr);
+$DBD::File::dr::imp_data_size = 0;
+
+sub dsn_quote
+{
+ my $str = shift;
+ ref $str and return "";
+ defined $str or return "";
+ $str =~ s/([;:\\])/\\$1/g;
+ return $str;
+ } # dsn_quote
+
+sub data_sources ($;$)
+{
+ my ($drh, $attr) = @_;
+ my $dir = $attr && exists $attr->{f_dir}
+ ? $attr->{f_dir}
+ : File::Spec->curdir ();
+ my %attrs;
+ $attr and %attrs = %$attr;
+ delete $attrs{f_dir};
+ my $dsnextra = join ";", map { $_ . "=" . dsn_quote ($attrs{$_}) } keys %attrs;
+ my ($dirh) = Symbol::gensym ();
+ unless (opendir $dirh, $dir) {
+ $drh->set_err ($DBI::stderr, "Cannot open directory $dir: $!");
+ return;
+ }
+
+ my ($file, @dsns, %names, $driver);
+ $driver = $drh->{ImplementorClass} =~ m/^dbd\:\:([^\:]+)\:\:/i ? $1 : "File";
+
+ while (defined ($file = readdir ($dirh))) {
+ my $d = File::Spec->catdir ($dir, $file);
+ # allow current dir ... it can be a data_source too
+ $file ne File::Spec->updir () && -d $d and
+ push @dsns, "DBI:$driver:f_dir=" . dsn_quote ($d) . ($dsnextra ? ";$dsnextra" : "");
+ }
+ return @dsns;
+ } # data_sources
+
+sub disconnect_all
+{
+ } # disconnect_all
+
+sub DESTROY
+{
+ undef;
+ } # DESTROY
+
+# ====== DATABASE ==============================================================
+
+package DBD::File::db;
+
+use strict;
+use warnings;
+
+use vars qw(@ISA $imp_data_size);
+
+use Carp;
+require File::Spec;
+require Cwd;
+use Scalar::Util qw(refaddr); # in CORE since 5.7.3
+
+@DBD::File::db::ISA = qw(DBI::DBD::SqlEngine::db);
+$DBD::File::db::imp_data_size = 0;
+
+sub set_versions
+{
+ my $dbh = shift;
+ $dbh->{f_version} = $DBD::File::VERSION;
+
+ return $dbh->SUPER::set_versions ();
+ } # set_versions
+
+sub init_valid_attributes
+{
+ my $dbh = shift;
+
+ $dbh->{f_valid_attrs} = {
+ f_version => 1, # DBD::File version
+ f_dir => 1, # base directory
+ f_ext => 1, # file extension
+ f_schema => 1, # schema name
+ f_meta => 1, # meta data for tables
+ f_meta_map => 1, # mapping table for identifier case
+ f_lock => 1, # Table locking mode
+ f_lockfile => 1, # Table lockfile extension
+ f_encoding => 1, # Encoding of the file
+ f_valid_attrs => 1, # File valid attributes
+ f_readonly_attrs => 1, # File readonly attributes
+ };
+ $dbh->{f_readonly_attrs} = {
+ f_version => 1, # DBD::File version
+ f_valid_attrs => 1, # File valid attributes
+ f_readonly_attrs => 1, # File readonly attributes
+ };
+
+ return $dbh->SUPER::init_valid_attributes ();
+ } # init_valid_attributes
+
+sub init_default_attributes
+{
+ my ($dbh, $phase) = @_;
+
+ # must be done first, because setting flags implicitly calls $dbdname::db->STORE
+ $dbh->SUPER::init_default_attributes ($phase);
+
+ # DBI::BD::SqlEngine::dr::connect will detect old-style drivers and
+ # don't call twice
+ unless (defined $phase) {
+ # we have an "old" driver here
+ $phase = defined $dbh->{sql_init_phase};
+ $phase and $phase = $dbh->{sql_init_phase};
+ }
+
+ if (0 == $phase) {
+ # check whether we're running in a Gofer server or not (see
+ # validate_FETCH_attr for details)
+ $dbh->{f_in_gofer} = (defined $INC{"DBD/Gofer.pm"} && (caller(5))[0] eq "DBI::Gofer::Execute");
+ # f_ext should not be initialized
+ # f_map is deprecated (but might return)
+ $dbh->{f_dir} = Cwd::abs_path (File::Spec->curdir ());
+ $dbh->{f_meta} = {};
+ $dbh->{f_meta_map} = {}; # choose new name because it contains other keys
+
+ # complete derived attributes, if required
+ (my $drv_class = $dbh->{ImplementorClass}) =~ s/::db$//;
+ my $drv_prefix = DBI->driver_prefix ($drv_class);
+ my $valid_attrs = $drv_prefix . "valid_attrs";
+ my $ro_attrs = $drv_prefix . "readonly_attrs";
+
+ my @comp_attrs = ();
+ if (exists $dbh->{$drv_prefix . "meta"} and !$dbh->{f_in_gofer}) {
+ my $attr = $dbh->{$drv_prefix . "meta"};
+ defined $attr and defined $dbh->{$valid_attrs} and
+ !defined $dbh->{$valid_attrs}{$attr} and
+ $dbh->{$valid_attrs}{$attr} = 1;
+
+ my %h;
+ tie %h, "DBD::File::TieTables", $dbh;
+ $dbh->{$attr} = \%h;
+
+ push @comp_attrs, "meta";
+ }
+
+ foreach my $comp_attr (@comp_attrs) {
+ my $attr = $drv_prefix . $comp_attr;
+ defined $dbh->{$valid_attrs} and !defined $dbh->{$valid_attrs}{$attr} and
+ $dbh->{$valid_attrs}{$attr} = 1;
+ defined $dbh->{$ro_attrs} and !defined $dbh->{$ro_attrs}{$attr} and
+ $dbh->{$ro_attrs}{$attr} = 1;
+ }
+ }
+
+ return $dbh;
+ } # init_default_attributes
+
+sub disconnect ($)
+{
+ %{$_[0]->{f_meta}} = ();
+ return $_[0]->SUPER::disconnect ();
+ } # disconnect
+
+sub validate_FETCH_attr
+{
+ my ($dbh, $attrib) = @_;
+
+ # If running in a Gofer server, access to our tied compatibility hash
+ # would force Gofer to serialize the tieing object including it's
+ # private $dbh reference used to do the driver function calls.
+ # This will result in nasty exceptions. So return a copy of the
+ # f_meta structure instead, which is the source of for the compatibility
+ # tie-hash. It's not as good as liked, but the best we can do in this
+ # situation.
+ if ($dbh->{f_in_gofer}) {
+ (my $drv_class = $dbh->{ImplementorClass}) =~ s/::db$//;
+ my $drv_prefix = DBI->driver_prefix ($drv_class);
+ exists $dbh->{$drv_prefix . "meta"} && $attrib eq $dbh->{$drv_prefix . "meta"} and
+ $attrib = "f_meta";
+ }
+
+ return $attrib;
+ } # validate_FETCH_attr
+
+sub validate_STORE_attr
+{
+ my ($dbh, $attrib, $value) = @_;
+
+ if ($attrib eq "f_dir") {
+ -d $value or
+ return $dbh->set_err ($DBI::stderr, "No such directory '$value'");
+ File::Spec->file_name_is_absolute ($value) or
+ $value = Cwd::abs_path ($value);
+ }
+
+ if ($attrib eq "f_ext") {
+ $value eq "" || $value =~ m{^\.\w+(?:/[rR]*)?$} or
+ carp "'$value' doesn't look like a valid file extension attribute\n";
+ }
+
+ (my $drv_class = $dbh->{ImplementorClass}) =~ s/::db$//;
+ my $drv_prefix = DBI->driver_prefix ($drv_class);
+
+ if (exists $dbh->{$drv_prefix . "meta"}) {
+ my $attr = $dbh->{$drv_prefix . "meta"};
+ if ($attrib eq $attr) {
+ while (my ($k, $v) = each %$value) {
+ $dbh->{$attrib}{$k} = $v;
+ }
+ }
+ }
+
+ return $dbh->SUPER::validate_STORE_attr ($attrib, $value);
+ } # validate_STORE_attr
+
+sub get_f_versions
+{
+ my ($dbh, $table) = @_;
+
+ my $class = $dbh->{ImplementorClass};
+ $class =~ s/::db$/::Table/;
+ my (undef, $meta);
+ $table and (undef, $meta) = $class->get_table_meta ($dbh, $table, 1);
+ unless ($meta) {
+ $meta = {};
+ $class->bootstrap_table_meta ($dbh, $meta, $table);
+ }
+
+ my $dver;
+ my $dtype = "IO::File";
+ eval {
+ $dver = IO::File->VERSION ();
+
+ # when we're still alive here, everthing went ok - no need to check for $@
+ $dtype .= " ($dver)";
+ };
+
+ $meta->{f_encoding} and $dtype .= " + " . $meta->{f_encoding} . " encoding";
+
+ return sprintf "%s using %s", $dbh->{f_version}, $dtype;
+ } # get_f_versions
+
+sub get_single_table_meta
+{
+ my ($dbh, $table, $attr) = @_;
+ my $meta;
+
+ $table eq "." and
+ return $dbh->FETCH ($attr);
+
+ (my $class = $dbh->{ImplementorClass}) =~ s/::db$/::Table/;
+ (undef, $meta) = $class->get_table_meta ($dbh, $table, 1);
+ $meta or croak "No such table '$table'";
+
+ # prevent creation of undef attributes
+ return $class->get_table_meta_attr ($meta, $attr);
+ } # get_single_table_meta
+
+sub get_file_meta
+{
+ my ($dbh, $table, $attr) = @_;
+
+ my $gstm = $dbh->{ImplementorClass}->can ("get_single_table_meta");
+
+ $table eq "*" and
+ $table = [ ".", keys %{$dbh->{f_meta}} ];
+ $table eq "+" and
+ $table = [ grep { m/^[_A-Za-z0-9]+$/ } keys %{$dbh->{f_meta}} ];
+ ref $table eq "Regexp" and
+ $table = [ grep { $_ =~ $table } keys %{$dbh->{f_meta}} ];
+
+ ref $table || ref $attr or
+ return &$gstm ($dbh, $table, $attr);
+
+ ref $table or $table = [ $table ];
+ ref $attr or $attr = [ $attr ];
+ "ARRAY" eq ref $table or
+ croak "Invalid argument for \$table - SCALAR, Regexp or ARRAY expected but got " . ref $table;
+ "ARRAY" eq ref $attr or
+ croak "Invalid argument for \$attr - SCALAR or ARRAY expected but got " . ref $attr;
+
+ my %results;
+ foreach my $tname (@{$table}) {
+ my %tattrs;
+ foreach my $aname (@{$attr}) {
+ $tattrs{$aname} = &$gstm ($dbh, $tname, $aname);
+ }
+ $results{$tname} = \%tattrs;
+ }
+
+ return \%results;
+ } # get_file_meta
+
+sub set_single_table_meta
+{
+ my ($dbh, $table, $attr, $value) = @_;
+ my $meta;
+
+ $table eq "." and
+ return $dbh->STORE ($attr, $value);
+
+ (my $class = $dbh->{ImplementorClass}) =~ s/::db$/::Table/;
+ (undef, $meta) = $class->get_table_meta ($dbh, $table, 1);
+ $meta or croak "No such table '$table'";
+ $class->set_table_meta_attr ($meta, $attr, $value);
+
+ return $dbh;
+ } # set_single_table_meta
+
+sub set_file_meta
+{
+ my ($dbh, $table, $attr, $value) = @_;
+
+ my $sstm = $dbh->{ImplementorClass}->can ("set_single_table_meta");
+
+ $table eq "*" and
+ $table = [ ".", keys %{$dbh->{f_meta}} ];
+ $table eq "+" and
+ $table = [ grep { m/^[_A-Za-z0-9]+$/ } keys %{$dbh->{f_meta}} ];
+ ref ($table) eq "Regexp" and
+ $table = [ grep { $_ =~ $table } keys %{$dbh->{f_meta}} ];
+
+ ref $table || ref $attr or
+ return &$sstm ($dbh, $table, $attr, $value);
+
+ ref $table or $table = [ $table ];
+ ref $attr or $attr = { $attr => $value };
+ "ARRAY" eq ref $table or
+ croak "Invalid argument for \$table - SCALAR, Regexp or ARRAY expected but got " . ref $table;
+ "HASH" eq ref $attr or
+ croak "Invalid argument for \$attr - SCALAR or HASH expected but got " . ref $attr;
+
+ foreach my $tname (@{$table}) {
+ my %tattrs;
+ while (my ($aname, $aval) = each %$attr) {
+ &$sstm ($dbh, $tname, $aname, $aval);
+ }
+ }
+
+ return $dbh;
+ } # set_file_meta
+
+sub clear_file_meta
+{
+ my ($dbh, $table) = @_;
+
+ (my $class = $dbh->{ImplementorClass}) =~ s/::db$/::Table/;
+ my (undef, $meta) = $class->get_table_meta ($dbh, $table, 1);
+ $meta and %{$meta} = ();
+
+ return;
+ } # clear_file_meta
+
+sub get_avail_tables
+{
+ my $dbh = shift;
+
+ my @tables = $dbh->SUPER::get_avail_tables ();
+ my $dir = $dbh->{f_dir};
+ my $dirh = Symbol::gensym ();
+
+ unless (opendir $dirh, $dir) {
+ $dbh->set_err ($DBI::stderr, "Cannot open directory $dir: $!");
+ return @tables;
+ }
+
+ my $class = $dbh->FETCH ("ImplementorClass");
+ $class =~ s/::db$/::Table/;
+ my ($file, %names);
+ my $schema = exists $dbh->{f_schema}
+ ? defined $dbh->{f_schema} && $dbh->{f_schema} ne ""
+ ? $dbh->{f_schema} : undef
+ : eval { getpwuid ((stat $dir)[4]) }; # XXX Win32::pwent
+ my %seen;
+ while (defined ($file = readdir ($dirh))) {
+ my ($tbl, $meta) = $class->get_table_meta ($dbh, $file, 0, 0) or next; # XXX
+ # $tbl && $meta && -f $meta->{f_fqfn} or next;
+ $seen{defined $schema ? $schema : "\0"}{$tbl}++ or
+ push @tables, [ undef, $schema, $tbl, "TABLE", "FILE" ];
+ }
+ closedir $dirh or
+ $dbh->set_err ($DBI::stderr, "Cannot close directory $dir: $!");
+
+ return @tables;
+ } # get_avail_tables
+
+# ====== Tie-Meta ==============================================================
+
+package DBD::File::TieMeta;
+
+use Carp qw(croak);
+require Tie::Hash;
+@DBD::File::TieMeta::ISA = qw(Tie::Hash);
+
+sub TIEHASH
+{
+ my ($class, $tblClass, $tblMeta) = @_;
+
+ my $self = bless ({ tblClass => $tblClass, tblMeta => $tblMeta, }, $class);
+ return $self;
+ } # new
+
+sub STORE
+{
+ my ($self, $meta_attr, $meta_val) = @_;
+
+ $self->{tblClass}->set_table_meta_attr ($self->{tblMeta}, $meta_attr, $meta_val);
+
+ return;
+ } # STORE
+
+sub FETCH
+{
+ my ($self, $meta_attr) = @_;
+
+ return $self->{tblClass}->get_table_meta_attr ($self->{tblMeta}, $meta_attr);
+ } # FETCH
+
+sub FIRSTKEY
+{
+ my $a = scalar keys %{$_[0]->{tblMeta}};
+ each %{$_[0]->{tblMeta}};
+ } # FIRSTKEY
+
+sub NEXTKEY
+{
+ each %{$_[0]->{tblMeta}};
+ } # NEXTKEY
+
+sub EXISTS
+{
+ exists $_[0]->{tblMeta}{$_[1]};
+ } # EXISTS
+
+sub DELETE
+{
+ croak "Can't delete single attributes from table meta structure";
+ } # DELETE
+
+sub CLEAR
+{
+ %{$_[0]->{tblMeta}} = ()
+ } # CLEAR
+
+sub SCALAR
+{
+ scalar %{$_[0]->{tblMeta}}
+ } # SCALAR
+
+# ====== Tie-Tables ============================================================
+
+package DBD::File::TieTables;
+
+use Carp qw(croak);
+require Tie::Hash;
+@DBD::File::TieTables::ISA = qw(Tie::Hash);
+
+sub TIEHASH
+{
+ my ($class, $dbh) = @_;
+
+ (my $tbl_class = $dbh->{ImplementorClass}) =~ s/::db$/::Table/;
+ my $self = bless ({ dbh => $dbh, tblClass => $tbl_class, }, $class);
+ return $self;
+ } # new
+
+sub STORE
+{
+ my ($self, $table, $tbl_meta) = @_;
+
+ "HASH" eq ref $tbl_meta or
+ croak "Invalid data for storing as table meta data (must be hash)";
+
+ (undef, my $meta) = $self->{tblClass}->get_table_meta ($self->{dbh}, $table, 1);
+ $meta or croak "Invalid table name '$table'";
+
+ while (my ($meta_attr, $meta_val) = each %$tbl_meta) {
+ $self->{tblClass}->set_table_meta_attr ($meta, $meta_attr, $meta_val);
+ }
+
+ return;
+ } # STORE
+
+sub FETCH
+{
+ my ($self, $table) = @_;
+
+ (undef, my $meta) = $self->{tblClass}->get_table_meta ($self->{dbh}, $table, 1);
+ $meta or croak "Invalid table name '$table'";
+
+ my %h;
+ tie %h, "DBD::File::TieMeta", $self->{tblClass}, $meta;
+
+ return \%h;
+ } # FETCH
+
+sub FIRSTKEY
+{
+ my $a = scalar keys %{$_[0]->{dbh}->{f_meta}};
+ each %{$_[0]->{dbh}->{f_meta}};
+ } # FIRSTKEY
+
+sub NEXTKEY
+{
+ each %{$_[0]->{dbh}->{f_meta}};
+ } # NEXTKEY
+
+sub EXISTS
+{
+ exists $_[0]->{dbh}->{f_meta}->{$_[1]} or
+ exists $_[0]->{dbh}->{f_meta_map}->{$_[1]};
+ } # EXISTS
+
+sub DELETE
+{
+ my ($self, $table) = @_;
+
+ (undef, my $meta) = $self->{tblClass}->get_table_meta ($self->{dbh}, $table, 1);
+ $meta or croak "Invalid table name '$table'";
+
+ delete $_[0]->{dbh}->{f_meta}->{$meta->{table_name}};
+ } # DELETE
+
+sub CLEAR
+{
+ %{$_[0]->{dbh}->{f_meta}} = ();
+ %{$_[0]->{dbh}->{f_meta_map}} = ();
+ } # CLEAR
+
+sub SCALAR
+{
+ scalar %{$_[0]->{dbh}->{f_meta}}
+ } # SCALAR
+
+# ====== STATEMENT =============================================================
+
+package DBD::File::st;
+
+use strict;
+use warnings;
+
+use vars qw(@ISA $imp_data_size);
+
+@DBD::File::st::ISA = qw(DBI::DBD::SqlEngine::st);
+$DBD::File::st::imp_data_size = 0;
+
+my %supported_attrs = (
+ TYPE => 1,
+ PRECISION => 1,
+ NULLABLE => 1,
+ );
+
+sub FETCH
+{
+ my ($sth, $attr) = @_;
+
+ if ($supported_attrs{$attr}) {
+ my $stmt = $sth->{sql_stmt};
+
+ if (exists $sth->{ImplementorClass} &&
+ exists $sth->{sql_stmt} &&
+ $sth->{sql_stmt}->isa ("SQL::Statement")) {
+
+ # fill overall_defs unless we know
+ unless (exists $sth->{f_overall_defs} && ref $sth->{f_overall_defs}) {
+ my $all_meta =
+ $sth->{Database}->func ("*", "table_defs", "get_file_meta");
+ while (my ($tbl, $meta) = each %$all_meta) {
+ exists $meta->{table_defs} && ref $meta->{table_defs} or next;
+ foreach (keys %{$meta->{table_defs}{columns}}) {
+ $sth->{f_overall_defs}{$_} = $meta->{table_defs}{columns}{$_};
+ }
+ }
+ }
+
+ my @colnames = $sth->sql_get_colnames ();
+
+ $attr eq "TYPE" and
+ return [ map { $sth->{f_overall_defs}{$_}{data_type} || "CHAR" }
+ @colnames ];
+
+ $attr eq "PRECISION" and
+ return [ map { $sth->{f_overall_defs}{$_}{data_length} || 0 }
+ @colnames ];
+
+ $attr eq "NULLABLE" and
+ return [ map { ( grep m/^NOT NULL$/ =>
+ @{ $sth->{f_overall_defs}{$_}{constraints} || [] })
+ ? 0 : 1 }
+ @colnames ];
+ }
+ }
+
+ return $sth->SUPER::FETCH ($attr);
+ } # FETCH
+
+# ====== SQL::STATEMENT ========================================================
+
+package DBD::File::Statement;
+
+use strict;
+use warnings;
+
+@DBD::File::Statement::ISA = qw( DBI::DBD::SqlEngine::Statement );
+
+sub open_table ($$$$$)
+{
+ my ($self, $data, $table, $createMode, $lockMode) = @_;
+
+ my $class = ref $self;
+ $class =~ s/::Statement/::Table/;
+
+ my $flags = {
+ createMode => $createMode,
+ lockMode => $lockMode,
+ };
+ $self->{command} eq "DROP" and $flags->{dropMode} = 1;
+
+ return $class->new ($data, { table => $table }, $flags);
+ } # open_table
+
+# ====== SQL::TABLE ============================================================
+
+package DBD::File::Table;
+
+use strict;
+use warnings;
+
+use Carp;
+require IO::File;
+require File::Basename;
+require File::Spec;
+require Cwd;
+
+# We may have a working flock () built-in but that doesn't mean that locking
+# will work on NFS (flock () may hang hard)
+my $locking = eval { flock STDOUT, 0; 1 };
+
+@DBD::File::Table::ISA = qw( DBI::DBD::SqlEngine::Table );
+
+# ====== FLYWEIGHT SUPPORT =====================================================
+
+my $fn_any_ext_regex = qr/\.[^.]*/;
+
+# Flyweight support for table_info
+# The functions file2table, init_table_meta, default_table_meta and
+# get_table_meta are using $self arguments for polymorphism only. The
+# must not rely on an instantiated DBD::File::Table
+sub file2table
+{
+ my ($self, $meta, $file, $file_is_table, $respect_case) = @_;
+
+ $file eq "." || $file eq ".." and return; # XXX would break a possible DBD::Dir
+
+ my ($ext, $req) = ("", 0);
+ if ($meta->{f_ext}) {
+ ($ext, my $opt) = split m/\//, $meta->{f_ext};
+ if ($ext && $opt) {
+ $opt =~ m/r/i and $req = 1;
+ }
+ }
+
+ # (my $tbl = $file) =~ s/$ext$//i;
+ my ($tbl, $basename, $dir, $fn_ext, $user_spec_file);
+ if ($file_is_table and defined $meta->{f_file}) {
+ $tbl = $file;
+ ($basename, $dir, $fn_ext) = File::Basename::fileparse ($meta->{f_file}, $fn_any_ext_regex);
+ $file = $basename . $fn_ext;
+ $user_spec_file = 1;
+ }
+ else {
+ ($basename, $dir, undef) = File::Basename::fileparse ($file, $ext);
+ $file = $tbl = $basename;
+ $user_spec_file = 0;
+ }
+
+ if (!$respect_case and $meta->{sql_identifier_case} == 1) { # XXX SQL_IC_UPPER
+ $basename = uc $basename;
+ $tbl = uc $tbl;
+ }
+ if( !$respect_case and $meta->{sql_identifier_case} == 2) { # XXX SQL_IC_LOWER
+ $basename = lc $basename;
+ $tbl = lc $tbl;
+ }
+
+ my $searchdir = File::Spec->file_name_is_absolute ($dir)
+ ? ($dir =~ s|/$||, $dir)
+ : Cwd::abs_path (File::Spec->catdir ($meta->{f_dir}, $dir));
+ -d $searchdir or
+ croak "-d $searchdir: $!";
+
+ $searchdir eq $meta->{f_dir} and
+ $dir = "";
+
+ unless ($user_spec_file) {
+ $file_is_table and $file = "$basename$ext";
+
+ # Fully Qualified File Name
+ my $cmpsub;
+ if ($respect_case) {
+ $cmpsub = sub {
+ my ($fn, undef, $sfx) = File::Basename::fileparse ($_, $fn_any_ext_regex);
+ $sfx = '' if $^O eq 'VMS' and $sfx eq '.'; # no extension turns up as a dot
+ $fn eq $basename and
+ return (lc $sfx eq lc $ext or !$req && !$sfx);
+ return 0;
+ }
+ }
+ else {
+ $cmpsub = sub {
+ my ($fn, undef, $sfx) = File::Basename::fileparse ($_, $fn_any_ext_regex);
+ $sfx = '' if $^O eq 'VMS' and $sfx eq '.'; # no extension turns up as a dot
+ lc $fn eq lc $basename and
+ return (lc $sfx eq lc $ext or !$req && !$sfx);
+ return 0;
+ }
+ }
+
+ opendir my $dh, $searchdir or croak "Can't open '$searchdir': $!";
+ my @f = sort { length $b <=> length $a } grep { &$cmpsub ($_) } readdir $dh;
+ @f > 0 && @f <= 2 and $file = $f[0];
+ !$respect_case && $meta->{sql_identifier_case} == 4 and # XXX SQL_IC_MIXED
+ ($tbl = $file) =~ s/$ext$//i;
+ closedir $dh or croak "Can't close '$searchdir': $!";
+
+ my $tmpfn = $file;
+ if ($ext && $req) {
+ # File extension required
+ $tmpfn =~ s/$ext$//i or return;
+ }
+ }
+
+ my $fqfn = File::Spec->catfile ($searchdir, $file);
+ my $fqbn = File::Spec->catfile ($searchdir, $basename);
+
+ $meta->{f_fqfn} = $fqfn;
+ $meta->{f_fqbn} = $fqbn;
+ defined $meta->{f_lockfile} && $meta->{f_lockfile} and
+ $meta->{f_fqln} = $meta->{f_fqbn} . $meta->{f_lockfile};
+
+ $dir && !$user_spec_file and $tbl = File::Spec->catfile ($dir, $tbl);
+ $meta->{table_name} = $tbl;
+
+ return $tbl;
+ } # file2table
+
+sub bootstrap_table_meta
+{
+ my ($self, $dbh, $meta, $table) = @_;
+
+ exists $meta->{f_dir} or $meta->{f_dir} = $dbh->{f_dir};
+ defined $meta->{f_ext} or $meta->{f_ext} = $dbh->{f_ext};
+ defined $meta->{f_encoding} or $meta->{f_encoding} = $dbh->{f_encoding};
+ exists $meta->{f_lock} or $meta->{f_lock} = $dbh->{f_lock};
+ exists $meta->{f_lockfile} or $meta->{f_lockfile} = $dbh->{f_lockfile};
+ defined $meta->{f_schema} or $meta->{f_schema} = $dbh->{f_schema};
+ defined $meta->{sql_identifier_case} or
+ $meta->{sql_identifier_case} = $dbh->{sql_identifier_case};
+ } # bootstrap_table_meta
+
+sub init_table_meta
+{
+ my ($self, $dbh, $meta, $table) = @_;
+
+ return;
+ } # init_table_meta
+
+sub get_table_meta ($$$$;$)
+{
+ my ($self, $dbh, $table, $file_is_table, $respect_case) = @_;
+ unless (defined $respect_case) {
+ $respect_case = 0;
+ $table =~ s/^\"// and $respect_case = 1; # handle quoted identifiers
+ $table =~ s/\"$//;
+ }
+
+ unless ($respect_case) {
+ defined $dbh->{f_meta_map}{$table} and $table = $dbh->{f_meta_map}{$table};
+ }
+
+ my $meta = {};
+ defined $dbh->{f_meta}{$table} and $meta = $dbh->{f_meta}{$table};
+
+ unless ($meta->{initialized}) {
+ $self->bootstrap_table_meta ($dbh, $meta, $table);
+
+ unless (defined $meta->{f_fqfn}) {
+ $self->file2table ($meta, $table, $file_is_table, $respect_case) or return;
+ }
+
+ if (defined $meta->{table_name} and $table ne $meta->{table_name}) {
+ $dbh->{f_meta_map}{$table} = $meta->{table_name};
+ $table = $meta->{table_name};
+ }
+
+ # now we know a bit more - let's check if user can't use consequent spelling
+ # XXX add know issue about reset sql_identifier_case here ...
+ if (defined $dbh->{f_meta}{$table} && defined $dbh->{f_meta}{$table}{initialized}) {
+ $meta = $dbh->{f_meta}{$table};
+ $self->file2table ($meta, $table, $file_is_table, $respect_case) or
+ return unless $dbh->{f_meta}{$table}{initialized};
+ }
+ unless ($dbh->{f_meta}{$table}{initialized}) {
+ $self->init_table_meta ($dbh, $meta, $table);
+ $meta->{initialized} = 1;
+ $dbh->{f_meta}{$table} = $meta;
+ }
+ }
+
+ return ($table, $meta);
+ } # get_table_meta
+
+my %reset_on_modify = (
+ f_file => "f_fqfn",
+ f_dir => "f_fqfn",
+ f_ext => "f_fqfn",
+ f_lockfile => "f_fqfn", # forces new file2table call
+ );
+
+my %compat_map = map { $_ => "f_$_" } qw( file ext lock lockfile );
+
+sub register_reset_on_modify
+{
+ my ($proto, $extra_resets) = @_;
+ %reset_on_modify = (%reset_on_modify, %$extra_resets);
+ return;
+ } # register_reset_on_modify
+
+sub register_compat_map
+{
+ my ($proto, $extra_compat_map) = @_;
+ %compat_map = (%compat_map, %$extra_compat_map);
+ return;
+ } # register_compat_map
+
+sub get_table_meta_attr
+{
+ my ($class, $meta, $attrib) = @_;
+ exists $compat_map{$attrib} and
+ $attrib = $compat_map{$attrib};
+ exists $meta->{$attrib} and
+ return $meta->{$attrib};
+ return;
+ } # get_table_meta_attr
+
+sub set_table_meta_attr
+{
+ my ($class, $meta, $attrib, $value) = @_;
+ exists $compat_map{$attrib} and
+ $attrib = $compat_map{$attrib};
+ $class->table_meta_attr_changed ($meta, $attrib, $value);
+ $meta->{$attrib} = $value;
+ } # set_table_meta_attr
+
+sub table_meta_attr_changed
+{
+ my ($class, $meta, $attrib, $value) = @_;
+ defined $reset_on_modify{$attrib} and
+ delete $meta->{$reset_on_modify{$attrib}} and
+ $meta->{initialized} = 0;
+ } # table_meta_attr_changed
+
+# ====== FILE OPEN =============================================================
+
+sub open_file ($$$)
+{
+ my ($self, $meta, $attrs, $flags) = @_;
+
+ defined $meta->{f_fqfn} && $meta->{f_fqfn} ne "" or croak "No filename given";
+
+ my ($fh, $fn);
+ unless ($meta->{f_dontopen}) {
+ $fn = $meta->{f_fqfn};
+ if ($flags->{createMode}) {
+ -f $meta->{f_fqfn} and
+ croak "Cannot create table $attrs->{table}: Already exists";
+ $fh = IO::File->new ($fn, "a+") or
+ croak "Cannot open $fn for writing: $! (" . ($!+0) . ")";
+ }
+ else {
+ unless ($fh = IO::File->new ($fn, ($flags->{lockMode} ? "r+" : "r"))) {
+ croak "Cannot open $fn: $! (" . ($!+0) . ")";
+ }
+ }
+
+ if ($fh) {
+ $fh->seek (0, 0) or
+ croak "Error while seeking back: $!";
+ if (my $enc = $meta->{f_encoding}) {
+ binmode $fh, ":encoding($enc)" or
+ croak "Failed to set encoding layer '$enc' on $fn: $!";
+ }
+ else {
+ binmode $fh or croak "Failed to set binary mode on $fn: $!";
+ }
+ }
+
+ $meta->{fh} = $fh;
+ }
+ if ($meta->{f_fqln}) {
+ $fn = $meta->{f_fqln};
+ if ($flags->{createMode}) {
+ -f $fn and
+ croak "Cannot create table lock for $attrs->{table}: Already exists";
+ $fh = IO::File->new ($fn, "a+") or
+ croak "Cannot open $fn for writing: $! (" . ($!+0) . ")";
+ }
+ else {
+ unless ($fh = IO::File->new ($fn, ($flags->{lockMode} ? "r+" : "r"))) {
+ croak "Cannot open $fn: $! (" . ($!+0) . ")";
+ }
+ }
+
+ $meta->{lockfh} = $fh;
+ }
+
+ if ($locking && $fh) {
+ my $lm = defined $flags->{f_lock}
+ && $flags->{f_lock} =~ m/^[012]$/
+ ? $flags->{f_lock}
+ : $flags->{lockMode} ? 2 : 1;
+ if ($lm == 2) {
+ flock $fh, 2 or croak "Cannot obtain exclusive lock on $fn: $!";
+ }
+ elsif ($lm == 1) {
+ flock $fh, 1 or croak "Cannot obtain shared lock on $fn: $!";
+ }
+ # $lm = 0 is forced no locking at all
+ }
+ } # open_file
+
+# ====== SQL::Eval API =========================================================
+
+sub new
+{
+ my ($className, $data, $attrs, $flags) = @_;
+ my $dbh = $data->{Database};
+
+ my ($tblnm, $meta) = $className->get_table_meta ($dbh, $attrs->{table}, 1) or
+ croak "Cannot find appropriate file for table '$attrs->{table}'";
+ $attrs->{table} = $tblnm;
+
+ # Being a bit dirty here, as SQL::Statement::Structure does not offer
+ # me an interface to the data I want
+ $flags->{createMode} && $data->{sql_stmt}{table_defs} and
+ $meta->{table_defs} = $data->{sql_stmt}{table_defs};
+
+ $className->open_file ($meta, $attrs, $flags);
+
+ my $columns = {};
+ my $array = [];
+ my $tbl = {
+ %{$attrs},
+ meta => $meta,
+ col_names => $meta->{col_names} || [],
+ };
+ return $className->SUPER::new ($tbl);
+ } # new
+
+sub drop ($)
+{
+ my ($self, $data) = @_;
+ my $meta = $self->{meta};
+ # We have to close the file before unlinking it: Some OS'es will
+ # refuse the unlink otherwise.
+ $meta->{fh} and $meta->{fh}->close ();
+ $meta->{lockfh} and $meta->{lockfh}->close ();
+ undef $meta->{fh};
+ undef $meta->{lockfh};
+ $meta->{f_fqfn} and unlink $meta->{f_fqfn};
+ $meta->{f_fqln} and unlink $meta->{f_fqln};
+ delete $data->{Database}{f_meta}{$self->{table}};
+ return 1;
+ } # drop
+
+sub seek ($$$$)
+{
+ my ($self, $data, $pos, $whence) = @_;
+ my $meta = $self->{meta};
+ if ($whence == 0 && $pos == 0) {
+ $pos = defined $meta->{first_row_pos} ? $meta->{first_row_pos} : 0;
+ }
+ elsif ($whence != 2 || $pos != 0) {
+ croak "Illegal seek position: pos = $pos, whence = $whence";
+ }
+
+ $meta->{fh}->seek ($pos, $whence) or
+ croak "Error while seeking in " . $meta->{f_fqfn} . ": $!";
+ } # seek
+
+sub truncate ($$)
+{
+ my ($self, $data) = @_;
+ my $meta = $self->{meta};
+ $meta->{fh}->truncate ($meta->{fh}->tell ()) or
+ croak "Error while truncating " . $meta->{f_fqfn} . ": $!";
+ return 1;
+ } # truncate
+
+sub DESTROY
+{
+ my $self = shift;
+ my $meta = $self->{meta};
+ $meta->{fh} and $meta->{fh}->close ();
+ $meta->{lockfh} and $meta->{lockfh}->close ();
+ undef $meta->{fh};
+ undef $meta->{lockfh};
+ } # DESTROY
+
+1;
+
+__END__
+
+=head1 NAME
+
+DBD::File - Base class for writing file based DBI drivers
+
+=head1 SYNOPSIS
+
+This module is a base class for writing other L<DBD|DBI::DBD>s.
+It is not intended to function as a DBD itself (though it is possible).
+If you want to access flat files, use L<DBD::AnyData|DBD::AnyData>, or
+L<DBD::CSV|DBD::CSV> (both of which are subclasses of DBD::File).
+
+=head1 DESCRIPTION
+
+The DBD::File module is not a true L<DBI|DBI> driver, but an abstract
+base class for deriving concrete DBI drivers from it. The implication
+is, that these drivers work with plain files, for example CSV files or
+INI files. The module is based on the L<SQL::Statement|SQL::Statement>
+module, a simple SQL engine.
+
+See L<DBI|DBI> for details on DBI, L<SQL::Statement|SQL::Statement> for
+details on SQL::Statement and L<DBD::CSV|DBD::CSV>, L<DBD::DBM|DBD::DBM>
+or L<DBD::AnyData|DBD::AnyData> for example drivers.
+
+=head2 Metadata
+
+The following attributes are handled by DBI itself and not by DBD::File,
+thus they all work as expected:
+
+ Active
+ ActiveKids
+ CachedKids
+ CompatMode (Not used)
+ InactiveDestroy
+ AutoInactiveDestroy
+ Kids
+ PrintError
+ RaiseError
+ Warn (Not used)
+
+=head3 The following DBI attributes are handled by DBD::File:
+
+=head4 AutoCommit
+
+Always on.
+
+=head4 ChopBlanks
+
+Works.
+
+=head4 NUM_OF_FIELDS
+
+Valid after C<< $sth->execute >>.
+
+=head4 NUM_OF_PARAMS
+
+Valid after C<< $sth->prepare >>.
+
+=head4 NAME
+
+Valid after C<< $sth->execute >>; undef for Non-Select statements.
+
+=head4 NULLABLE
+
+Not really working, always returns an array ref of ones, except the
+affected table has been created in this session. Valid after
+C<< $sth->execute >>; undef for non-select statements.
+
+=head3 The following DBI attributes and methods are not supported:
+
+=over 4
+
+=item bind_param_inout
+
+=item CursorName
+
+=item LongReadLen
+
+=item LongTruncOk
+
+=back
+
+=head3 DBD::File specific attributes
+
+In addition to the DBI attributes, you can use the following dbh
+attributes:
+
+=head4 f_dir
+
+This attribute is used for setting the directory where the files are
+opened and it defaults to the current directory (F<.>). Usually you set
+it on the dbh but it may be overridden per table (see L<f_meta>).
+
+When the value for C<f_dir> is a relative path, it is converted into
+the appropriate absolute path name (based on the current working
+directory) when the dbh attribute is set.
+
+See L<KNOWN BUGS AND LIMITATIONS>.
+
+=head4 f_ext
+
+This attribute is used for setting the file extension. The format is:
+
+ extension{/flag}
+
+where the /flag is optional and the extension is case-insensitive.
+C<f_ext> allows you to specify an extension which:
+
+=over
+
+=item *
+
+makes DBD::File prefer F<table.extension> over F<table>.
+
+=item *
+
+makes the table name the filename minus the extension.
+
+=back
+
+ DBI:CSV:f_dir=data;f_ext=.csv
+
+In the above example and when C<f_dir> contains both F<table.csv> and
+F<table>, DBD::File will open F<table.csv> and the table will be
+named "table". If F<table.csv> does not exist but F<table> does
+that file is opened and the table is also called "table".
+
+If C<f_ext> is not specified and F<table.csv> exists it will be opened
+and the table will be called "table.csv" which is probably not what
+you want.
+
+NOTE: even though extensions are case-insensitive, table names are
+not.
+
+ DBI:CSV:f_dir=data;f_ext=.csv/r
+
+The C<r> flag means the file extension is required and any filename
+that does not match the extension is ignored.
+
+Usually you set it on the dbh but it may be overridden per table
+(see L<f_meta>).
+
+=head4 f_schema
+
+This will set the schema name and defaults to the owner of the
+directory in which the table file resides. You can set C<f_schema> to
+C<undef>.
+
+ my $dbh = DBI->connect ("dbi:CSV:", "", "", {
+ f_schema => undef,
+ f_dir => "data",
+ f_ext => ".csv/r",
+ }) or die $DBI::errstr;
+
+By setting the schema you affect the results from the tables call:
+
+ my @tables = $dbh->tables ();
+
+ # no f_schema
+ "merijn".foo
+ "merijn".bar
+
+ # f_schema => "dbi"
+ "dbi".foo
+ "dbi".bar
+
+ # f_schema => undef
+ foo
+ bar
+
+Defining C<f_schema> to the empty string is equal to setting it to C<undef>
+so the DSN can be C<"dbi:CSV:f_schema=;f_dir=.">.
+
+=head4 f_lock
+
+The C<f_lock> attribute is used to set the locking mode on the opened
+table files. Note that not all platforms support locking. By default,
+tables are opened with a shared lock for reading, and with an
+exclusive lock for writing. The supported modes are:
+
+ 0: No locking at all.
+
+ 1: Shared locks will be used.
+
+ 2: Exclusive locks will be used.
+
+But see L<KNOWN BUGS|/"KNOWN BUGS AND LIMITATIONS"> below.
+
+=head4 f_lockfile
+
+If you wish to use a lockfile extension other than C<.lck>, simply specify
+the C<f_lockfile> attribute:
+
+ $dbh = DBI->connect ("dbi:DBM:f_lockfile=.foo");
+ $dbh->{f_lockfile} = ".foo";
+ $dbh->{f_meta}{qux}{f_lockfile} = ".foo";
+
+If you wish to disable locking, set the C<f_lockfile> to C<0>.
+
+ $dbh = DBI->connect ("dbi:DBM:f_lockfile=0");
+ $dbh->{f_lockfile} = 0;
+ $dbh->{f_meta}{qux}{f_lockfile} = 0;
+
+=head4 f_encoding
+
+With this attribute, you can set the encoding in which the file is opened.
+This is implemented using C<< binmode $fh, ":encoding(<f_encoding>)" >>.
+
+=head4 f_meta
+
+Private data area which contains information about the tables this
+module handles. Table meta data might not be available until the
+table has been accessed for the first time e.g., by issuing a select
+on it however it is possible to pre-initialize attributes for each table
+you use.
+
+DBD::File recognizes the (public) attributes C<f_ext>, C<f_dir>,
+C<f_file>, C<f_encoding>, C<f_lock>, C<f_lockfile>, C<f_schema>,
+C<col_names>, C<table_name> and C<sql_identifier_case>. Be very careful
+when modifying attributes you do not know, the consequence might be a
+destroyed or corrupted table.
+
+C<f_file> is an attribute applicable to table meta data only and you
+will not find a corresponding attribute in the dbh. Whilst it may be
+reasonable to have several tables with the same column names, it is
+not for the same file name. If you need access to the same file using
+different table names, use C<SQL::Statement> as the SQL engine and the
+C<AS> keyword:
+
+ SELECT * FROM tbl AS t1, tbl AS t2 WHERE t1.id = t2.id
+
+C<f_file> can be an absolute path name or a relative path name but if
+it is relative, it is interpreted as being relative to the C<f_dir>
+attribute of the table meta data. When C<f_file> is set DBD::File will
+use C<f_file> as specified and will not attempt to work out an
+alternative for C<f_file> using the C<table name> and C<f_ext>
+attribute.
+
+While C<f_meta> is a private and readonly attribute (which means, you
+cannot modify it's values), derived drivers might provide restricted
+write access through another attribute. Well known accessors are
+C<csv_tables> for L<DBD::CSV>, C<ad_tables> for L<DBD::AnyData> and
+C<dbm_tables> for L<DBD::DBM>.
+
+=head3 Internally private attributes to deal with SQL backends:
+
+Do not modify any of these private attributes unless you understand
+the implications of doing so. The behavior of DBD::File and derived
+DBDs might be unpredictable when one or more of those attributes are
+modified.
+
+=head4 sql_nano_version
+
+Contains the version of loaded DBI::SQL::Nano.
+
+=head4 sql_statement_version
+
+Contains the version of loaded SQL::Statement.
+
+=head4 sql_handler
+
+Contains either the text 'SQL::Statement' or 'DBI::SQL::Nano'.
+
+=head4 sql_ram_tables
+
+Contains optionally temporary tables.
+
+=head4 sql_flags
+
+Contains optional flags to instantiate the SQL::Parser parsing engine
+when SQL::Statement is used as SQL engine. See L<SQL::Parser> for valid
+flags.
+
+=head2 Driver private methods
+
+=head3 Default DBI methods
+
+=head4 data_sources
+
+The C<data_sources> method returns a list of subdirectories of the current
+directory in the form "dbi:CSV:f_dir=$dirname".
+
+If you want to read the subdirectories of another directory, use
+
+ my ($drh) = DBI->install_driver ("CSV");
+ my (@list) = $drh->data_sources (f_dir => "/usr/local/csv_data");
+
+=head4 list_tables
+
+This method returns a list of file names inside $dbh->{f_dir}.
+Example:
+
+ my ($dbh) = DBI->connect ("dbi:CSV:f_dir=/usr/local/csv_data");
+ my (@list) = $dbh->func ("list_tables");
+
+Note that the list includes all files contained in the directory, even
+those that have non-valid table names, from the view of SQL.
+
+=head3 Additional methods
+
+The following methods are only available via their documented name when
+DBD::File is used directly. Because this is only reasonable for testing
+purposes, the real names must be used instead. Those names can be computed
+by replacing the C<f_> in the method name with the driver prefix.
+
+=head4 f_versions
+
+Signature:
+
+ sub f_versions (;$)
+ {
+ my ($table_name) = @_;
+ $table_name ||= ".";
+ ...
+ }
+
+Returns the versions of the driver, including the DBI version, the Perl
+version, DBI::PurePerl version (if DBI::PurePerl is active) and the version
+of the SQL engine in use.
+
+ my $dbh = DBI->connect ("dbi:File:");
+ my $f_versions = $dbh->f_versions ();
+ print "$f_versions\n";
+ __END__
+ # DBD::File 0.39 using SQL::Statement 1.28
+ # DBI 1.612
+ # OS netbsd (5.99.24)
+ # Perl 5.010001 (x86_64-netbsd-thread-multi)
+
+Called in list context, f_versions will return an array containing each
+line as single entry.
+
+Some drivers might use the optional (table name) argument and modify
+version information related to the table (e.g. DBD::DBM provides storage
+backend information for the requested table, when it has a table name).
+
+=head4 f_get_meta
+
+Signature:
+
+ sub f_get_meta ($$)
+ {
+ my ($table_name, $attrib) = @_;
+ ...
+ }
+
+Returns the value of a meta attribute set for a specific table, if any.
+See L<f_meta> for the possible attributes.
+
+A table name of C<"."> (single dot) is interpreted as the default table.
+This will retrieve the appropriate attribute globally from the dbh.
+This has the same restrictions as C<< $dbh->{$attrib} >>.
+
+=head4 f_set_meta
+
+Signature:
+
+ sub f_set_meta ($$$)
+ {
+ my ($table_name, $attrib, $value) = @_;
+ ...
+ }
+
+Sets the value of a meta attribute set for a specific table.
+See L<f_meta> for the possible attributes.
+
+A table name of C<"."> (single dot) is interpreted as the default table
+which will set the specified attribute globally for the dbh.
+This has the same restrictions as C<< $dbh->{$attrib} = $value >>.
+
+=head4 f_clear_meta
+
+Signature:
+
+ sub f_clear_meta ($)
+ {
+ my ($table_name) = @_;
+ ...
+ }
+
+Clears the table specific meta information in the private storage of the
+dbh.
+
+=head1 SQL ENGINES
+
+DBD::File currently supports two SQL engines: L<SQL::Statement|SQL::Statement>
+and L<DBI::SQL::Nano::Statement_|DBI::SQL::Nano>. DBI::SQL::Nano supports a
+I<very> limited subset of SQL statements, but it might be faster for some
+very simple tasks. SQL::Statement in contrast supports a much larger subset
+of ANSI SQL.
+
+To use SQL::Statement, you need at least version 1.28 of
+SQL::Statement and the environment variable C<DBI_SQL_NANO> must not
+be set to a true value.
+
+=head1 KNOWN BUGS AND LIMITATIONS
+
+=over 4
+
+=item *
+
+This module uses flock () internally but flock is not available on all
+platforms. On MacOS and Windows 95 there is no locking at all (perhaps
+not so important on MacOS and Windows 95, as there is only a single
+user).
+
+=item *
+
+The module stores details about the handled tables in a private area
+of the driver handle (C<$drh>). This data area is not shared between
+different driver instances, so several C<< DBI->connect () >> calls will
+cause different table instances and private data areas.
+
+This data area is filled for the first time when a table is accessed,
+either via an SQL statement or via C<table_info> and is not
+destroyed until the table is dropped or the driver handle is released.
+Manual destruction is possible via L<f_clear_meta>.
+
+The following attributes are preserved in the data area and will
+evaluated instead of driver globals:
+
+=over 8
+
+=item f_ext
+
+=item f_dir
+
+=item f_lock
+
+=item f_lockfile
+
+=item f_encoding
+
+=item f_schema
+
+=item col_names
+
+=item sql_identifier_case
+
+=back
+
+The following attributes are preserved in the data area only and
+cannot be set globally.
+
+=over 8
+
+=item f_file
+
+=back
+
+The following attributes are preserved in the data area only and are
+computed when initializing the data area:
+
+=over 8
+
+=item f_fqfn
+
+=item f_fqbn
+
+=item f_fqln
+
+=item table_name
+
+=back
+
+For DBD::CSV tables this means, once opened "foo.csv" as table named "foo",
+another table named "foo" accessing the file "foo.txt" cannot be opened.
+Accessing "foo" will always access the file "foo.csv" in memorized
+C<f_dir>, locking C<f_lockfile> via memorized C<f_lock>.
+
+You can use L<f_clear_meta> or the C<f_file> attribute for a specific table
+to work around this.
+
+=item *
+
+When used with SQL::Statement and temporary tables e.g.,
+
+ CREATE TEMP TABLE ...
+
+the table data processing bypasses DBD::File::Table. No file system
+calls will be made and there are no clashes with existing (file based)
+tables with the same name. Temporary tables are chosen over file
+tables, but they will not covered by C<table_info>.
+
+=back
+
+=head1 AUTHOR
+
+This module is currently maintained by
+
+H.Merijn Brand < h.m.brand at xs4all.nl > and
+Jens Rehsack < rehsack at googlemail.com >
+
+The original author is Jochen Wiedmann.
+
+=head1 COPYRIGHT AND LICENSE
+
+ Copyright (C) 2009-2010 by H.Merijn Brand & Jens Rehsack
+ Copyright (C) 2004-2009 by Jeff Zucker
+ Copyright (C) 1998-2004 by Jochen Wiedmann
+
+All rights reserved.
+
+You may freely distribute and/or modify this module under the terms of
+either the GNU General Public License (GPL) or the Artistic License, as
+specified in the Perl README file.
+
+=head1 SEE ALSO
+
+L<DBI|DBI>, L<DBD::DBM|DBD::DBM>, L<DBD::CSV|DBD::CSV>, L<Text::CSV|Text::CSV>,
+L<Text::CSV_XS|Text::CSV_XS>, L<SQL::Statement|SQL::Statement>, and
+L<DBI::SQL::Nano|DBI::SQL::Nano>
+
+=cut
diff --git a/lib/DBD/File/Developers.pod b/lib/DBD/File/Developers.pod
new file mode 100644
index 0000000..a9bef85
--- /dev/null
+++ b/lib/DBD/File/Developers.pod
@@ -0,0 +1,556 @@
+=head1 NAME
+
+DBD::File::Developers - Developers documentation for DBD::File
+
+=head1 SYNOPSIS
+
+ package DBD::myDriver;
+
+ use base qw(DBD::File);
+
+ sub driver
+ {
+ ...
+ my $drh = $proto->SUPER::driver($attr);
+ ...
+ return $drh->{class};
+ }
+
+ sub CLONE { ... }
+
+ package DBD::myDriver::dr;
+
+ @ISA = qw(DBD::File::dr);
+
+ sub data_sources { ... }
+ ...
+
+ package DBD::myDriver::db;
+
+ @ISA = qw(DBD::File::db);
+
+ sub init_valid_attributes { ... }
+ sub init_default_attributes { ... }
+ sub set_versions { ... }
+ sub validate_STORE_attr { my ($dbh, $attrib, $value) = @_; ... }
+ sub validate_FETCH_attr { my ($dbh, $attrib) = @_; ... }
+ sub get_myd_versions { ... }
+
+ package DBD::myDriver::st;
+
+ @ISA = qw(DBD::File::st);
+
+ sub FETCH { ... }
+ sub STORE { ... }
+
+ package DBD::myDriver::Statement;
+
+ @ISA = qw(DBD::File::Statement);
+
+ package DBD::myDriver::Table;
+
+ @ISA = qw(DBD::File::Table);
+
+ my %reset_on_modify = (
+ myd_abc => "myd_foo",
+ myd_mno => "myd_bar",
+ );
+ __PACKAGE__->register_reset_on_modify( \%reset_on_modify );
+ my %compat_map = (
+ abc => 'foo_abc',
+ xyz => 'foo_xyz',
+ );
+ __PACKAGE__->register_compat_map( \%compat_map );
+
+ sub bootstrap_table_meta { ... }
+ sub init_table_meta { ... }
+ sub table_meta_attr_changed { ... }
+ sub open_file { ... }
+
+ sub fetch_row { ... }
+ sub push_row { ... }
+ sub push_names { ... }
+
+ # optimize the SQL engine by add one or more of
+ sub update_current_row { ... }
+ # or
+ sub update_specific_row { ... }
+ # or
+ sub update_one_row { ... }
+ # or
+ sub insert_new_row { ... }
+ # or
+ sub delete_current_row { ... }
+ # or
+ sub delete_one_row { ... }
+
+=head1 DESCRIPTION
+
+This document describes how DBD developers can write DBD::File based DBI
+drivers. It supplements L<DBI::DBD> and L<DBI::DBD::SqlEngine::Developers>,
+which you should read first.
+
+=head1 CLASSES
+
+Each DBI driver must provide a package global C<driver> method and three
+DBI related classes:
+
+=over 4
+
+=item DBD::File::dr
+
+Driver package, contains the methods DBI calls indirectly via DBI
+interface:
+
+ DBI->connect ('DBI:DBM:', undef, undef, {})
+
+ # invokes
+ package DBD::DBM::dr;
+ @DBD::DBM::dr::ISA = qw(DBD::File::dr);
+
+ sub connect ($$;$$$)
+ {
+ ...
+ }
+
+Similar for C<< data_sources () >> and C<< disconnect_all() >>.
+
+Pure Perl DBI drivers derived from DBD::File do not usually need to
+override any of the methods provided through the DBD::XXX::dr package
+however if you need additional initialization in the connect method
+you may need to.
+
+=item DBD::File::db
+
+Contains the methods which are called through DBI database handles
+(C<< $dbh >>). e.g.,
+
+ $sth = $dbh->prepare ("select * from foo");
+ # returns the f_encoding setting for table foo
+ $dbh->csv_get_meta ("foo", "f_encoding");
+
+DBD::File provides the typical methods required here. Developers who
+write DBI drivers based on DBD::File need to override the methods C<<
+set_versions >> and C<< init_valid_attributes >>.
+
+=item DBD::File::st
+
+Contains the methods to deal with prepared statement handles. e.g.,
+
+ $sth->execute () or die $sth->errstr;
+
+=back
+
+=head2 DBD::File
+
+This is the main package containing the routines to initialize
+DBD::File based DBI drivers. Primarily the C<< DBD::File::driver >>
+method is invoked, either directly from DBI when the driver is
+initialized or from the derived class.
+
+ package DBD::DBM;
+
+ use base qw( DBD::File );
+
+ sub driver
+ {
+ my ( $class, $attr ) = @_;
+ ...
+ my $drh = $class->SUPER::driver( $attr );
+ ...
+ return $drh;
+ }
+
+It is not necessary to implement your own driver method as long as
+additional initialization (e.g. installing more private driver
+methods) is not required. You do not need to call C<< setup_driver >>
+as DBD::File takes care of it.
+
+=head2 DBD::File::dr
+
+The driver package contains the methods DBI calls indirectly via the DBI
+interface (see L<DBI/DBI Class Methods>).
+
+DBD::File based DBI drivers usually do not need to implement anything here,
+it is enough to do the basic initialization:
+
+ package DBD:XXX::dr;
+
+ @DBD::XXX::dr::ISA = qw (DBD::File::dr);
+ $DBD::XXX::dr::imp_data_size = 0;
+ $DBD::XXX::dr::data_sources_attr = undef;
+ $DBD::XXX::ATTRIBUTION = "DBD::XXX $DBD::XXX::VERSION by Hans Mustermann";
+
+=head2 DBD::File::db
+
+This package defines the database methods, which are called via the DBI
+database handle C<< $dbh >>.
+
+Methods provided by DBD::File:
+
+=over 4
+
+=item ping
+
+Simply returns the content of the C<< Active >> attribute. Override
+when your driver needs more complicated actions here.
+
+=item prepare
+
+Prepares a new SQL statement to execute. Returns a statement handle,
+C<< $sth >> - instance of the DBD:XXX::st. It is neither required nor
+recommended to override this method.
+
+=item FETCH
+
+Fetches an attribute of a DBI database object. Private handle attributes
+must have a prefix (this is mandatory). If a requested attribute is
+detected as a private attribute without a valid prefix, the driver prefix
+(written as C<$drv_prefix>) is added.
+
+The driver prefix is extracted from the attribute name and verified against
+C<< $dbh->{ $drv_prefix . "valid_attrs" } >> (when it exists). If the
+requested attribute value is not listed as a valid attribute, this method
+croaks. If the attribute is valid and readonly (listed in C<< $dbh->{
+$drv_prefix . "readonly_attrs" } >> when it exists), a real copy of the
+attribute value is returned. So it's not possible to modify
+C<f_valid_attrs> from outside of DBD::File::db or a derived class.
+
+=item STORE
+
+Stores a database private attribute. Private handle attributes must have a
+prefix (this is mandatory). If a requested attribute is detected as a private
+attribute without a valid prefix, the driver prefix (written as
+C<$drv_prefix>) is added. If the database handle has an attribute
+C<${drv_prefix}_valid_attrs> - for attribute names which are not listed in
+that hash, this method croaks. If the database handle has an attribute
+C<${drv_prefix}_readonly_attrs>, only attributes which are not listed there
+can be stored (once they are initialized). Trying to overwrite such an
+immutable attribute forces this method to croak.
+
+An example of a valid attributes list can be found in
+C<< DBD::File::db::init_valid_attributes >>.
+
+=item set_versions
+
+This method sets the attribute C<f_version> with the version of DBD::File.
+
+This method is called at the begin of the C<connect ()> phase.
+
+When overriding this method, do not forget to invoke the superior one.
+
+=item init_valid_attributes
+
+This method is called after the database handle is instantiated as the
+first attribute initialization.
+
+C<< DBD::File::db::init_valid_attributes >> initializes the attributes
+C<f_valid_attrs> and C<f_readonly_attrs>.
+
+When overriding this method, do not forget to invoke the superior one,
+preferably before doing anything else. Compatibility table attribute
+access must be initialized here to allow DBD::File to instantiate the
+map tie:
+
+ # for DBD::CSV
+ $dbh->{csv_meta} = "csv_tables";
+ # for DBD::DBM
+ $dbh->{dbm_meta} = "dbm_tables";
+ # for DBD::AnyData
+ $dbh->{ad_meta} = "ad_tables";
+
+=item init_default_attributes
+
+This method is called after the database handle is instantiated to
+initialize the default attributes.
+
+C<< DBD::File::db::init_default_attributes >> initializes the attributes
+C<f_dir>, C<f_meta>, C<f_meta_map>, C<f_version>.
+
+When the derived implementor class provides the attribute to validate
+attributes (e.g. C<< $dbh->{dbm_valid_attrs} = {...}; >>) or the attribute
+containing the immutable attributes (e.g. C<< $dbh->{dbm_readonly_attrs}
+= {...}; >>), the attributes C<drv_valid_attrs>, C<drv_readonly_attrs>,
+C<drv_version> and C<drv_meta> are added (when available) to the list of
+valid and immutable attributes (where C<drv_> is interpreted as the driver
+prefix).
+
+If C<drv_meta> is set, an attribute with the name in C<drv_meta> is
+initialized providing restricted read/write access to the meta data of the
+tables using C<DBD::File::TieTables> in the first (table) level and
+C<DBD::File::TieMeta> for the meta attribute level. C<DBD::File::TieTables>
+uses C<DBD::DRV::Table::get_table_meta> to initialize the second level
+tied hash on FETCH/STORE. The C<DBD::File::TieMeta> class uses
+C<DBD::DRV::Table::get_table_meta_attr> to FETCH attribute values and
+C<DBD::DRV::Table::set_table_meta_attr> to STORE attribute values. This
+allows it to map meta attributes for compatibility reasons.
+
+=item get_single_table_meta
+
+=item get_file_meta
+
+Retrieve an attribute from a table's meta information. The method
+signature is C<< get_file_meta ($dbh, $table, $attr) >>. This method
+is called by the injected db handle method C<< ${drv_prefix}get_meta
+>>.
+
+While get_file_meta allows C<$table> or C<$attr> to be a list of tables or
+attributes to retrieve, get_single_table_meta allows only one table name
+and only one attribute name. A table name of C<'.'> (single dot) is
+interpreted as the default table and this will retrieve the appropriate
+attribute globally from the dbh. This has the same restrictions as
+C<< $dbh->{$attrib} >>.
+
+get_file_meta allows C<'+'> and C<'*'> as wildcards for table names and
+C<$table> being a regular expression matching against the table names
+(evaluated without the default table). The table name C<'*'> is
+I<all currently known tables, including the default one>. The table
+name C<'+'> is I<all table names which conform to
+ANSI file name restrictions> (/^[_A-Za-z0-9]+$/).
+
+The table meta information is retrieved using the get_table_meta and
+get_table_meta_attr methods of the table class of the implementation.
+
+=item set_single_table_meta
+
+=item set_file_meta
+
+Sets an attribute in a table's meta information. The method signature is
+C<< set_file_meta ($dbh, $table, $attr, $value) >>. This method is called
+by the injected db handle method C<< ${drv_prefix}set_meta >>.
+
+While set_file_meta allows C<$table> to be a list of tables and C<$attr>
+to be a hash of several attributes to set, set_single_table_meta allows
+only one table name and only one attribute name/value pair.
+
+The wildcard characters for the table name are the same as for
+get_file_meta.
+
+The table meta information is updated using the get_table_meta and
+set_table_meta_attr methods of the table class of the implementation.
+
+=item clear_file_meta
+
+Clears all meta information cached about a table. The method signature is
+C<< clear_file_meta ($dbh, $table) >>. This method is called
+by the injected db handle method C<< ${drv_prefix}clear_meta >>.
+
+=back
+
+=head2 DBD::File::st
+
+Contains the methods to deal with prepared statement handles:
+
+=over 4
+
+=item FETCH
+
+Fetches statement handle attributes. Supported attributes (for full overview
+see L<DBI/Statement Handle Attributes>) are C<NAME>, C<TYPE>, C<PRECISION>
+and C<NULLABLE> in case that SQL::Statement is used as SQL execution engine
+and a statement is successful prepared. When SQL::Statement has additional
+information about a table, those information are returned. Otherwise, the
+same defaults as in L<DBI::DBD::SqlEngine> are used.
+
+This method usually requires extending in a derived implementation.
+See L<DBD::CSV> or L<DBD::DBM> for some example.
+
+=back
+
+=head2 DBD::File::Statement
+
+Derives from DBI::SQL::Nano::Statement to provide following method:
+
+=over 4
+
+=item open_table
+
+Implements the open_table method required by L<SQL::Statement> and
+L<DBI::SQL::Nano>. All the work for opening the file(s) belonging to the
+table is handled and parameterized in DBD::File::Table. Unless you intend
+to add anything to the following implementation, an empty DBD::XXX::Statement
+package satisfies DBD::File.
+
+ sub open_table ($$$$$)
+ {
+ my ($self, $data, $table, $createMode, $lockMode) = @_;
+
+ my $class = ref $self;
+ $class =~ s/::Statement/::Table/;
+
+ my $flags = {
+ createMode => $createMode,
+ lockMode => $lockMode,
+ };
+ $self->{command} eq "DROP" and $flags->{dropMode} = 1;
+
+ return $class->new ($data, { table => $table }, $flags);
+ } # open_table
+
+=back
+
+=head2 DBD::File::Table
+
+Derives from DBI::SQL::Nano::Table and provides physical file access for
+the table data which are stored in the files.
+
+=over 4
+
+=item file2table
+
+This method tries to map a filename to the associated table
+name. It is called with a partially filled meta structure for the
+resulting table containing at least the following attributes:
+C<< f_ext >>, C<< f_dir >>, C<< f_lockfile >> and C<< sql_identifier_case >>.
+
+If a file/table map can be found then this method sets the C<< f_fqfn
+>>, C<< f_fqbn >>, C<< f_fqln >> and C<< table_name >> attributes in
+the meta structure. If a map cannot be found the table name will be
+undef.
+
+=item bootstrap_table_meta
+
+Initializes a table meta structure. Can be safely overridden in a
+derived class, as long as the C<< SUPER >> method is called at the end
+of the overridden method.
+
+It copies the following attributes from the database into the table meta data
+C<< f_dir >>, C<< f_ext >>, C<< f_encoding >>, C<< f_lock >>, C<< f_schema >>,
+C<< f_lockfile >> and C<< sql_identifier_case >> and makes them sticky to the
+table.
+
+This method should be called before you attempt to map between file
+name and table name to ensure the correct directory, extension etc. are
+used.
+
+=item init_table_meta
+
+Initializes more attributes of the table meta data - usually more
+expensive ones (e.g. those which require class instantiations) - when
+the file name and the table name could mapped.
+
+=item get_table_meta
+
+Returns the table meta data. If there are none for the required
+table, a new one is initialized. When it fails, nothing is
+returned. On success, the name of the table and the meta data
+structure is returned.
+
+=item get_table_meta_attr
+
+Returns a single attribute from the table meta data. If the attribute
+name appears in C<%compat_map>, the attribute name is updated from
+there.
+
+=item set_table_meta_attr
+
+Sets a single attribute in the table meta data. If the attribute
+name appears in C<%compat_map>, the attribute name is updated from
+there.
+
+=item table_meta_attr_changed
+
+Called when an attribute of the meta data is modified.
+
+If the modified attribute requires to reset a calculated attribute, the
+calculated attribute is reset (deleted from meta data structure) and
+the I<initialized> flag is removed, too. The decision is made based on
+C<%register_reset_on_modify>.
+
+=item register_reset_on_modify
+
+Allows C<set_table_meta_attr> to reset meta attributes when special
+attributes are modified. For DBD::File, modifying one of C<f_file>, C<f_dir>,
+C<f_ext> or C<f_lockfile> will reset C<f_fqfn>. DBD::DBM extends the
+list for C<dbm_type> and C<dbm_mldbm> to reset the value of C<dbm_tietype>.
+
+If your DBD has calculated values in the meta data area, then call
+C<register_reset_on_modify>:
+
+ my %reset_on_modify = ( "xxx_foo" => "xxx_bar" );
+ __PACKAGE__->register_reset_on_modify( \%reset_on_modify );
+
+=item register_compat_map
+
+Allows C<get_table_meta_attr> and C<set_table_meta_attr> to update the
+attribute name to the current favored one:
+
+ # from DBD::DBM
+ my %compat_map = ( "dbm_ext" => "f_ext" );
+ __PACKAGE__->register_compat_map( \%compat_map );
+
+=item open_file
+
+Called to open the table's data file.
+
+Depending on the attributes set in the table's meta data, the
+following steps are performed. Unless C<< f_dontopen >> is set to a
+true value, C<< f_fqfn >> must contain the full qualified file name
+for the table to work on (file2table ensures this). The encoding in
+C<< f_encoding >> is applied if set and the file is opened. If
+C<<f_fqln >> (full qualified lock name) is set, this file is opened,
+too. Depending on the value in C<< f_lock >>, the appropriate lock is
+set on the opened data file or lock file.
+
+After this is done, a derived class might add more steps in an overridden
+C<< open_file >> method.
+
+=item new
+
+Instantiates the table. This is done in 3 steps:
+
+ 1. get the table meta data
+ 2. open the data file
+ 3. bless the table data structure using inherited constructor new
+
+It is not recommended to override the constructor of the table class.
+Find a reasonable place to add you extensions in one of the above four
+methods.
+
+=item drop
+
+Implements the abstract table method for the C<< DROP >>
+command. Discards table meta data after all files belonging to the
+table are closed and unlinked.
+
+Overriding this method might be reasonable in very rare cases.
+
+=item seek
+
+Implements the abstract table method used when accessing the table from the
+engine. C<< seek >> is called every time the engine uses dumb algorithms
+for iterating over the table content.
+
+=item truncate
+
+Implements the abstract table method used when dumb table algorithms
+for C<< UPDATE >> or C<< DELETE >> need to truncate the table storage
+after the last written row.
+
+=back
+
+You should consult the documentation of C<< SQL::Eval::Table >> (see
+L<SQL::Eval>) to get more information about the abstract methods of the
+table's base class you have to override and a description of the table
+meta information expected by the SQL engines.
+
+=head1 AUTHOR
+
+The module DBD::File is currently maintained by
+
+H.Merijn Brand < h.m.brand at xs4all.nl > and
+Jens Rehsack < rehsack at googlemail.com >
+
+The original author is Jochen Wiedmann.
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2010 by H.Merijn Brand & Jens Rehsack
+
+All rights reserved.
+
+You may freely distribute and/or modify this module under the terms of
+either the GNU General Public License (GPL) or the Artistic License, as
+specified in the Perl README file.
+
+=cut
diff --git a/lib/DBD/File/HowTo.pod b/lib/DBD/File/HowTo.pod
new file mode 100644
index 0000000..3d45257
--- /dev/null
+++ b/lib/DBD/File/HowTo.pod
@@ -0,0 +1,270 @@
+=head1 NAME
+
+DBD::File::HowTo - Guide to create DBD::File based driver
+
+=head1 SYNOPSIS
+
+ perldoc DBD::File::HowTo
+ perldoc DBI
+ perldoc DBI::DBD
+ perldoc DBD::File::Developers
+ perldoc DBI::DBD::SqlEngine::Developers
+ perldoc DBI::DBD::SqlEngine
+ perldoc SQL::Eval
+ perldoc DBI::DBD::SqlEngine::HowTo
+ perldoc SQL::Statement::Embed
+ perldoc DBD::File
+ perldoc DBD::File::HowTo
+ perldoc DBD::File::Developers
+
+=head1 DESCRIPTION
+
+This document provides a step-by-step guide, how to create a new
+C<DBD::File> based DBD. It expects that you carefully read the L<DBI>
+documentation and that you're familiar with L<DBI::DBD> and had read and
+understood L<DBD::ExampleP>.
+
+This document addresses experienced developers who are really sure that
+they need to invest time when writing a new DBI Driver. Writing a DBI
+Driver is neither a weekend project nor an easy job for hobby coders
+after work. Expect one or two man-month of time for the first start.
+
+Those who are still reading, should be able to sing the rules of
+L<DBI::DBD/CREATING A NEW DRIVER>.
+
+Of course, DBD::File is a DBI::DBD::SqlEngine and you surely read
+L<DBI::DBD::SqlEngine::HowTo> before continuing here.
+
+=head1 CREATING DRIVER CLASSES
+
+Do you have an entry in DBI's DBD registry? For this guide, a prefix of
+C<foo_> is assumed.
+
+=head2 Sample Skeleton
+
+ package DBD::Foo;
+
+ use strict;
+ use warnings;
+ use vars qw(@ISA $VERSION);
+ use base qw(DBD::File);
+
+ use DBI ();
+
+ $VERSION = "0.001";
+
+ package DBD::Foo::dr;
+
+ use vars qw(@ISA $imp_data_size);
+
+ @ISA = qw(DBD::File::dr);
+ $imp_data_size = 0;
+
+ package DBD::Foo::db;
+
+ use vars qw(@ISA $imp_data_size);
+
+ @ISA = qw(DBD::File::db);
+ $imp_data_size = 0;
+
+ package DBD::Foo::st;
+
+ use vars qw(@ISA $imp_data_size);
+
+ @ISA = qw(DBD::File::st);
+ $imp_data_size = 0;
+
+ package DBD::Foo::Statement;
+
+ use vars qw(@ISA);
+
+ @ISA = qw(DBD::File::Statement);
+
+ package DBD::Foo::Table;
+
+ use vars qw(@ISA);
+
+ @ISA = qw(DBD::File::Table);
+
+ 1;
+
+Tiny, eh? And all you have now is a DBD named foo which will is able to
+deal with temporary tables, as long as you use L<SQL::Statement>. In
+L<DBI::SQL::Nano> environments, this DBD can do nothing.
+
+=head2 Start over
+
+Based on L<DBI::DBD::SqlEngine::HowTo>, we're now having a driver which
+could do basic things. Of course, it should now derive from DBD::File
+instead of DBI::DBD::SqlEngine, shouldn't it?
+
+DBD::File extends DBI::DBD::SqlEngine to deal with any kind of files.
+In principle, the only extensions required are to the table class:
+
+ package DBD::Foo::Table;
+
+ sub bootstrap_table_meta
+ {
+ my ( $self, $dbh, $meta, $table ) = @_;
+
+ # initialize all $meta attributes which might be relevant for
+ # file2table
+
+ return $self->SUPER::bootstrap_table_meta($dbh, $meta, $table);
+ }
+
+ sub init_table_meta
+ {
+ my ( $self, $dbh, $meta, $table ) = @_;
+
+ # called after $meta contains the results from file2table
+ # initialize all missing $meta attributes
+
+ $self->SUPER::init_table_meta( $dbh, $meta, $table );
+ }
+
+In case C<DBD::File::Table::open_file> doesn't open the files as the driver
+needs that, override it!
+
+ sub open_file
+ {
+ my ( $self, $meta, $attrs, $flags ) = @_;
+ # ensure that $meta->{f_dontopen} is set
+ $self->SUPER::open_file( $meta, $attrs, $flags );
+ # now do what ever needs to be done
+ }
+
+Combined with the methods implemented using the L<SQL::Statement::Embed>
+guide, the table is full working and you could try a start over.
+
+=head2 User comfort
+
+C<DBD::File> since C<0.39> consolidates all persistent meta data of a table
+into a single structure stored in C<< $dbh->{f_meta} >>. While DBD::File
+provides only readonly access to this structure, modifications are still
+allowed.
+
+Primarily DBD::File provides access via setters C<get_file_meta>,
+C<set_file_meta> and C<clear_file_meta>. Those methods are easily
+accessible by the users via the C<< $dbh->func () >> interface provided
+by DBI. Well, many users don't feel comfortize when calling
+
+ # don't require extension for tables cars
+ $dbh->func ("cars", "f_ext", ".csv", "set_file_meta");
+
+DBD::File will inject a method into your driver to increase the user
+comfort to allow:
+
+ # don't require extension for tables cars
+ $dbh->foo_set_meta ("cars", "f_ext", ".csv");
+
+Better, but here and there users likes to do:
+
+ # don't require extension for tables cars
+ $dbh->{foo_tables}->{cars}->{f_ext} = ".csv";
+
+This interface is provided when derived DBD's define following in
+C<init_valid_attributes> (please compare carefully with the example in
+DBI::DBD::SqlEngine::HowTo):
+
+ sub init_valid_attributes
+ {
+ my $dbh = $_[0];
+
+ $dbh->SUPER::init_valid_attributes ();
+
+ $dbh->{foo_valid_attrs} = {
+ foo_version => 1, # contains version of this driver
+ foo_valid_attrs => 1, # contains the valid attributes of foo drivers
+ foo_readonly_attrs => 1, # contains immutable attributes of foo drivers
+ foo_bar => 1, # contains the bar attribute
+ foo_baz => 1, # contains the baz attribute
+ foo_manager => 1, # contains the manager of the driver instance
+ foo_manager_type => 1, # contains the manager class of the driver instance
+ foo_meta => 1, # contains the public interface to modify table meta attributes
+ };
+ $dbh->{foo_readonly_attrs} = {
+ foo_version => 1, # ensure no-one modifies the driver version
+ foo_valid_attrs => 1, # do not permit to add more valid attributes ...
+ foo_readonly_attrs => 1, # ... or make the immutable mutable
+ foo_manager => 1, # manager is set internally only
+ foo_meta => 1, # ensure public interface to modify table meta attributes are immutable
+ };
+
+ $dbh->{foo_meta} = "foo_tables";
+
+ return $dbh;
+ }
+
+This provides a tied hash in C<< $dbh->{foo_tables} >> and a tied hash for
+each table's meta data in C<< $dbh->{foo_tables}->{$table_name} >>.
+Modifications on the table meta attributes are done using the table
+methods:
+
+ sub get_table_meta_attr { ... }
+ sub set_table_meta_attr { ... }
+
+Both methods can adjust the attribute name for compatibility reasons, e.g.
+when former versions of the DBD allowed different names to be used for the
+same flag:
+
+ my %compat_map = (
+ abc => 'foo_abc',
+ xyz => 'foo_xyz',
+ );
+ __PACKAGE__->register_compat_map( \%compat_map );
+
+If any user modification on a meta attribute needs reinitialization of
+the meta structure (in case of C<DBD::File> these are the attributes
+C<f_file>, C<f_dir>, C<f_ext> and C<f_lockfile>), inform DBD::File by
+doing
+
+ my %reset_on_modify = (
+ foo_xyz => "foo_bar",
+ foo_abc => "foo_bar",
+ );
+ __PACKAGE__->register_reset_on_modify( \%reset_on_modify );
+
+The next access to the table meta data will force DBD::File to re-do the
+entire meta initialization process.
+
+Any further action which needs to be taken can handled in
+C<table_meta_attr_changed>:
+
+ sub table_meta_attr_changed
+ {
+ my ($class, $meta, $attrib, $value) = @_;
+ ...
+ $class->SUPER::table_meta_attr_changed ($meta, $attrib, $value);
+ }
+
+This is done before the new value is set in C<$meta>, so the attribute
+changed handler can act depending on the old value.
+
+=head2 Testing
+
+Now you should have your own DBD::File based driver. Was easy, wasn't it?
+But does it work well? Prove it by writing tests and remember to use
+dbd_edit_mm_attribs from L<DBI::DBD> to ensure testing even rare cases.
+
+=head1 AUTHOR
+
+This guide is written by Jens Rehsack. DBD::File is written by Jochen
+Wiedmann and Jeff Zucker.
+
+The module DBD::File is currently maintained by
+
+H.Merijn Brand < h.m.brand at xs4all.nl > and
+Jens Rehsack < rehsack at googlemail.com >
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2010 by H.Merijn Brand & Jens Rehsack
+
+All rights reserved.
+
+You may freely distribute and/or modify this module under the terms of
+either the GNU General Public License (GPL) or the Artistic License, as
+specified in the Perl README file.
+
+=cut
diff --git a/lib/DBD/File/Roadmap.pod b/lib/DBD/File/Roadmap.pod
new file mode 100644
index 0000000..804d759
--- /dev/null
+++ b/lib/DBD/File/Roadmap.pod
@@ -0,0 +1,176 @@
+=head1 NAME
+
+DBD::File::Roadmap - Planned Enhancements for DBD::File and pure Perl DBD's
+
+Jens Rehsack - May 2010
+
+=head1 SYNOPSIS
+
+This document gives a high level overview of the future of the DBD::File DBI
+driver and groundwork for pure Perl DBI drivers.
+
+The planned enhancements cover features, testing, performance, reliability,
+extensibility and more.
+
+=head1 CHANGES AND ENHANCEMENTS
+
+=head2 Features
+
+There are some features missing we would like to add, but there is
+no time plan:
+
+=over 4
+
+=item LOCK TABLE
+
+The newly implemented internal common table meta storage area would allow
+us to implement LOCK TABLE support based on file system C<flock ()>
+support.
+
+=item Transaction support
+
+While DBD::AnyData recommends explicitly committing by importing and
+exporting tables, DBD::File might be enhanced in a future version to allow
+transparent transactions using the temporary tables of SQL::Statement as
+shadow (dirty) tables.
+
+Transaction support will heavily rely on lock table support.
+
+=item Data Dictionary Persistence
+
+SQL::Statement provides dictionary information when a "CREATE TABLE ..."
+statement is executed. This dictionary is preserved for some statement
+handle attribute fetches (as C<NULLABLE> or C<PRECISION>).
+
+It is planned to extend DBD::File to support data dictionaries to work
+on the tables in it. It is not planned to support one table in different
+dictionaries, but you can have several dictionaries in one directory.
+
+=item SQL Engine selecting on connect
+
+Currently the SQL engine selected is chosen during the loading of the module
+L<DBI::SQL::Nano>. Ideally end users should be able to select the engine
+used in C<< DBI->connect () >> with a special DBD::File attribute.
+
+=back
+
+Other points of view to the planned features (and more features for the
+SQL::Statement engine) are shown in L<SQL::Statement::Roadmap>.
+
+=head2 Testing
+
+DBD::File and the dependent DBD::DBM requires a lot more automated tests
+covering API stability and compatibility with optional modules
+like SQL::Statement.
+
+=head2 Performance
+
+Several arguments for support of features like indexes on columns
+and cursors are made for DBD::CSV (which is a DBD::File based driver,
+too). Similar arguments could be made for DBD::DBM, DBD::AnyData,
+DBD::RAM or DBD::PO etc.
+
+To improve the performance of the underlying SQL engines, a clean
+reimplementation seems to be required. Currently both engines are
+prematurely optimized and therefore it is not trivial to provide
+further optimization without the risk of breaking existing features.
+
+Join the DBI developers IRC channel at L<irc://irc.perl.org/dbi> to
+participate or post to the DBI Developers Mailing List.
+
+=head2 Reliability
+
+DBD::File currently lacks the following points:
+
+=over 4
+
+=item duplicate table names
+
+It is currently possible to access a table quoted with a relative path
+(a) and additionally using an absolute path (b). If (a) and (b) are
+the same file that is not recognized (except for
+flock protection handled by the Operating System) and two independent
+tables are handled.
+
+=item invalid table names
+
+The current implementation does not prevent someone choosing a
+directory name as a physical file name for the table to open.
+
+=back
+
+=head2 Extensibility
+
+I (Jens Rehsack) have some (partially for example only) DBD's in mind:
+
+=over 4
+
+=item DBD::Sys
+
+Derive DBD::Sys from a common code base shared with DBD::File which handles
+all the emulation DBI needs (as getinfo, SQL engine handling, ...)
+
+=item DBD::Dir
+
+Provide a DBD::File derived to work with fixed table definitions through the
+file system to demonstrate how DBI / Pure Perl DBDs could handle databases
+with hierarchical structures.
+
+=item DBD::Join
+
+Provide a DBI driver which is able to manage multiple connections to other
+Databases (as DBD::Multiplex), but allow them to point to different data
+sources and allow joins between the tables of them:
+
+ # Example
+ # Let table 'lsof' being a table in DBD::Sys giving a list of open files using lsof utility
+ # Let table 'dir' being a atable from DBD::Dir
+ $sth = $dbh->prepare( "select * from dir,lsof where path='/documents' and dir.entry = lsof.filename" )
+ $sth->execute(); # gives all open files in '/documents'
+ ...
+
+ # Let table 'filesys' a DBD::Sys table of known file systems on current host
+ # Let table 'applications' a table of your Configuration Management Database
+ # where current applications (relocatable, with mountpoints for filesystems)
+ # are stored
+ $sth = dbh->prepare( "select * from applications,filesys where " .
+ "application.mountpoint = filesys.mountpoint and ".
+ "filesys.mounted is true" );
+ $sth->execute(); # gives all currently mounted applications on this host
+
+=back
+
+=head1 PRIORITIES
+
+Our priorities are focussed on current issues. Initially many new test
+cases for DBD::File and DBD::DBM should be added to the DBI test
+suite. After that some additional documentation on how to use the
+DBD::File API will be provided.
+
+Any additional priorities will come later and can be modified by (paying)
+users.
+
+=head1 RESOURCES AND CONTRIBUTIONS
+
+See L<http://dbi.perl.org/contributing> for I<how you can help>.
+
+If your company has benefited from DBI, please consider if
+it could make a donation to The Perl Foundation "DBI Development"
+fund at L<http://dbi.perl.org/donate> to secure future development.
+
+Alternatively, if your company would benefit from a specific new
+DBI feature, please consider sponsoring it's development through
+the options listed in the section "Commercial Support from the Author"
+on L<http://dbi.perl.org/support/>.
+
+Using such targeted financing allows you to contribute to DBI
+development and rapidly get something specific and directly valuable
+to you in return.
+
+My company also offers annual support contracts for the DBI, which
+provide another way to support the DBI and get something specific
+in return. Contact me for details.
+
+Thank you.
+
+=cut
diff --git a/lib/DBD/Gofer.pm b/lib/DBD/Gofer.pm
new file mode 100644
index 0000000..afd8201
--- /dev/null
+++ b/lib/DBD/Gofer.pm
@@ -0,0 +1,1292 @@
+{
+ package DBD::Gofer;
+
+ use strict;
+
+ require DBI;
+ require DBI::Gofer::Request;
+ require DBI::Gofer::Response;
+ require Carp;
+
+ our $VERSION = sprintf("0.%06d", q$Revision: 15326 $ =~ /(\d+)/o);
+
+# $Id: Gofer.pm 15326 2012-06-06 16:32:38Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+
+
+ # attributes we'll allow local STORE
+ our %xxh_local_store_attrib = map { $_=>1 } qw(
+ Active
+ CachedKids
+ Callbacks
+ DbTypeSubclass
+ ErrCount Executed
+ FetchHashKeyName
+ HandleError HandleSetErr
+ InactiveDestroy
+ AutoInactiveDestroy
+ PrintError PrintWarn
+ Profile
+ RaiseError
+ RootClass
+ ShowErrorStatement
+ Taint TaintIn TaintOut
+ TraceLevel
+ Warn
+ dbi_quote_identifier_cache
+ dbi_connect_closure
+ dbi_go_execute_unique
+ );
+ our %xxh_local_store_attrib_if_same_value = map { $_=>1 } qw(
+ Username
+ dbi_connect_method
+ );
+
+ our $drh = undef; # holds driver handle once initialized
+ our $methods_already_installed;
+
+ sub driver{
+ return $drh if $drh;
+
+ DBI->setup_driver('DBD::Gofer');
+
+ unless ($methods_already_installed++) {
+ my $opts = { O=> 0x0004 }; # IMA_KEEP_ERR
+ DBD::Gofer::db->install_method('go_dbh_method', $opts);
+ DBD::Gofer::st->install_method('go_sth_method', $opts);
+ DBD::Gofer::st->install_method('go_clone_sth', $opts);
+ DBD::Gofer::db->install_method('go_cache', $opts);
+ DBD::Gofer::st->install_method('go_cache', $opts);
+ }
+
+ my($class, $attr) = @_;
+ $class .= "::dr";
+ ($drh) = DBI::_new_drh($class, {
+ 'Name' => 'Gofer',
+ 'Version' => $VERSION,
+ 'Attribution' => 'DBD Gofer by Tim Bunce',
+ });
+
+ $drh;
+ }
+
+
+ sub CLONE {
+ undef $drh;
+ }
+
+
+ sub go_cache {
+ my $h = shift;
+ $h->{go_cache} = shift if @_;
+ # return handle's override go_cache, if it has one
+ return $h->{go_cache} if defined $h->{go_cache};
+ # or else the transports default go_cache
+ return $h->{go_transport}->{go_cache};
+ }
+
+
+ sub set_err_from_response { # set error/warn/info and propagate warnings
+ my $h = shift;
+ my $response = shift;
+ if (my $warnings = $response->warnings) {
+ warn $_ for @$warnings;
+ }
+ my ($err, $errstr, $state) = $response->err_errstr_state;
+ # Only set_err() if there's an error else leave the current values
+ # (The current values will normally be set undef by the DBI dispatcher
+ # except for methods marked KEEPERR such as ping.)
+ $h->set_err($err, $errstr, $state) if defined $err;
+ return undef;
+ }
+
+
+ sub install_methods_proxy {
+ my ($installed_methods) = @_;
+ while ( my ($full_method, $attr) = each %$installed_methods ) {
+ # need to install both a DBI dispatch stub and a proxy stub
+ # (the dispatch stub may be already here due to local driver use)
+
+ DBI->_install_method($full_method, "", $attr||{})
+ unless defined &{$full_method};
+
+ # now install proxy stubs on the driver side
+ $full_method =~ m/^DBI::(\w\w)::(\w+)$/
+ or die "Invalid method name '$full_method' for install_method";
+ my ($type, $method) = ($1, $2);
+ my $driver_method = "DBD::Gofer::${type}::${method}";
+ next if defined &{$driver_method};
+ my $sub;
+ if ($type eq 'db') {
+ $sub = sub { return shift->go_dbh_method(undef, $method, @_) };
+ }
+ else {
+ $sub = sub { shift->set_err($DBI::stderr, "Can't call \$${type}h->$method when using DBD::Gofer"); return; };
+ }
+ no strict 'refs';
+ *$driver_method = $sub;
+ }
+ }
+}
+
+
+{ package DBD::Gofer::dr; # ====== DRIVER ======
+
+ $imp_data_size = 0;
+ use strict;
+
+ sub connect_cached {
+ my ($drh, $dsn, $user, $auth, $attr)= @_;
+ $attr ||= {};
+ return $drh->SUPER::connect_cached($dsn, $user, $auth, {
+ (%$attr),
+ go_connect_method => $attr->{go_connect_method} || 'connect_cached',
+ });
+ }
+
+
+ sub connect {
+ my($drh, $dsn, $user, $auth, $attr)= @_;
+ my $orig_dsn = $dsn;
+
+ # first remove dsn= and everything after it
+ my $remote_dsn = ($dsn =~ s/;?\bdsn=(.*)$// && $1)
+ or return $drh->set_err($DBI::stderr, "No dsn= argument in '$orig_dsn'");
+
+ if ($attr->{go_bypass}) { # don't use DBD::Gofer for this connection
+ # useful for testing with DBI_AUTOPROXY, e.g., t/03handle.t
+ return DBI->connect($remote_dsn, $user, $auth, $attr);
+ }
+
+ my %go_attr;
+ # extract any go_ attributes from the connect() attr arg
+ for my $k (grep { /^go_/ } keys %$attr) {
+ $go_attr{$k} = delete $attr->{$k};
+ }
+ # then override those with any attributes embedded in our dsn (not remote_dsn)
+ for my $kv (grep /=/, split /;/, $dsn, -1) {
+ my ($k, $v) = split /=/, $kv, 2;
+ $go_attr{ "go_$k" } = $v;
+ }
+
+ if (not ref $go_attr{go_policy}) { # if not a policy object already
+ my $policy_class = $go_attr{go_policy} || 'classic';
+ $policy_class = "DBD::Gofer::Policy::$policy_class"
+ unless $policy_class =~ /::/;
+ _load_class($policy_class)
+ or return $drh->set_err($DBI::stderr, "Can't load $policy_class: $@");
+ # replace policy name in %go_attr with policy object
+ $go_attr{go_policy} = eval { $policy_class->new(\%go_attr) }
+ or return $drh->set_err($DBI::stderr, "Can't instanciate $policy_class: $@");
+ }
+ # policy object is left in $go_attr{go_policy} so transport can see it
+ my $go_policy = $go_attr{go_policy};
+
+ if ($go_attr{go_cache} and not ref $go_attr{go_cache}) { # if not a cache object already
+ my $cache_class = $go_attr{go_cache};
+ $cache_class = "DBI::Util::CacheMemory" if $cache_class eq '1';
+ _load_class($cache_class)
+ or return $drh->set_err($DBI::stderr, "Can't load $cache_class $@");
+ $go_attr{go_cache} = eval { $cache_class->new() }
+ or $drh->set_err(0, "Can't instanciate $cache_class: $@"); # warning
+ }
+
+ # delete any other attributes that don't apply to transport
+ my $go_connect_method = delete $go_attr{go_connect_method};
+
+ my $transport_class = delete $go_attr{go_transport}
+ or return $drh->set_err($DBI::stderr, "No transport= argument in '$orig_dsn'");
+ $transport_class = "DBD::Gofer::Transport::$transport_class"
+ unless $transport_class =~ /::/;
+ _load_class($transport_class)
+ or return $drh->set_err($DBI::stderr, "Can't load $transport_class: $@");
+ my $go_transport = eval { $transport_class->new(\%go_attr) }
+ or return $drh->set_err($DBI::stderr, "Can't instanciate $transport_class: $@");
+
+ my $request_class = "DBI::Gofer::Request";
+ my $go_request = eval {
+ my $go_attr = { %$attr };
+ # XXX user/pass of fwd server vs db server ? also impact of autoproxy
+ if ($user) {
+ $go_attr->{Username} = $user;
+ $go_attr->{Password} = $auth;
+ }
+ # delete any attributes we can't serialize (or don't want to)
+ delete @{$go_attr}{qw(Profile HandleError HandleSetErr Callbacks)};
+ # delete any attributes that should only apply to the client-side
+ delete @{$go_attr}{qw(RootClass DbTypeSubclass)};
+
+ $go_connect_method ||= $go_policy->connect_method($remote_dsn, $go_attr) || 'connect';
+ $request_class->new({
+ dbh_connect_call => [ $go_connect_method, $remote_dsn, $user, $auth, $go_attr ],
+ })
+ } or return $drh->set_err($DBI::stderr, "Can't instanciate $request_class: $@");
+
+ my ($dbh, $dbh_inner) = DBI::_new_dbh($drh, {
+ 'Name' => $dsn,
+ 'USER' => $user,
+ go_transport => $go_transport,
+ go_request => $go_request,
+ go_policy => $go_policy,
+ });
+
+ # mark as inactive temporarily for STORE. Active not set until connected() called.
+ $dbh->STORE(Active => 0);
+
+ # should we ping to check the connection
+ # and fetch dbh attributes
+ my $skip_connect_check = $go_policy->skip_connect_check($attr, $dbh);
+ if (not $skip_connect_check) {
+ if (not $dbh->go_dbh_method(undef, 'ping')) {
+ return undef if $dbh->err; # error already recorded, typically
+ return $dbh->set_err($DBI::stderr, "ping failed");
+ }
+ }
+
+ return $dbh;
+ }
+
+ sub _load_class { # return true or false+$@
+ my $class = shift;
+ (my $pm = $class) =~ s{::}{/}g;
+ $pm .= ".pm";
+ return 1 if eval { require $pm };
+ delete $INC{$pm}; # shouldn't be needed (perl bug?) and assigning undef isn't enough
+ undef; # error in $@
+ }
+
+}
+
+
+{ package DBD::Gofer::db; # ====== DATABASE ======
+ $imp_data_size = 0;
+ use strict;
+ use Carp qw(carp croak);
+
+ my %dbh_local_store_attrib = %DBD::Gofer::xxh_local_store_attrib;
+
+ sub connected {
+ shift->STORE(Active => 1);
+ }
+
+ sub go_dbh_method {
+ my $dbh = shift;
+ my $meta = shift;
+ # @_ now contains ($method_name, @args)
+
+ my $request = $dbh->{go_request};
+ $request->init_request([ wantarray, @_ ], $dbh);
+ ++$dbh->{go_request_count};
+
+ my $go_policy = $dbh->{go_policy};
+ my $dbh_attribute_update = $go_policy->dbh_attribute_update();
+ $request->dbh_attributes( $go_policy->dbh_attribute_list() )
+ if $dbh_attribute_update eq 'every'
+ or $dbh->{go_request_count}==1;
+
+ $request->dbh_last_insert_id_args($meta->{go_last_insert_id_args})
+ if $meta->{go_last_insert_id_args};
+
+ my $transport = $dbh->{go_transport}
+ or return $dbh->set_err($DBI::stderr, "Not connected (no transport)");
+
+ local $transport->{go_cache} = $dbh->{go_cache}
+ if defined $dbh->{go_cache};
+
+ my ($response, $retransmit_sub) = $transport->transmit_request($request);
+ $response ||= $transport->receive_response($request, $retransmit_sub);
+ $dbh->{go_response} = $response
+ or die "No response object returned by $transport";
+
+ die "response '$response' returned by $transport is not a response object"
+ unless UNIVERSAL::isa($response,"DBI::Gofer::Response");
+
+ if (my $dbh_attributes = $response->dbh_attributes) {
+
+ # XXX installed_methods piggybacks on dbh_attributes for now
+ if (my $installed_methods = delete $dbh_attributes->{dbi_installed_methods}) {
+ DBD::Gofer::install_methods_proxy($installed_methods)
+ if $dbh->{go_request_count}==1;
+ }
+
+ # XXX we don't STORE here, we just stuff the value into the attribute cache
+ $dbh->{$_} = $dbh_attributes->{$_}
+ for keys %$dbh_attributes;
+ }
+
+ my $rv = $response->rv;
+ if (my $resultset_list = $response->sth_resultsets) {
+ # dbh method call returned one or more resultsets
+ # (was probably a metadata method like table_info)
+ #
+ # setup an sth but don't execute/forward it
+ my $sth = $dbh->prepare(undef, { go_skip_prepare_check => 1 });
+ # set the sth response to our dbh response
+ (tied %$sth)->{go_response} = $response;
+ # setup the sth with the results in our response
+ $sth->more_results;
+ # and return that new sth as if it came from original request
+ $rv = [ $sth ];
+ }
+ elsif (!$rv) { # should only occur for major transport-level error
+ #carp("no rv in response { @{[ %$response ]} }");
+ $rv = [ ];
+ }
+
+ DBD::Gofer::set_err_from_response($dbh, $response);
+
+ return (wantarray) ? @$rv : $rv->[0];
+ }
+
+
+ # Methods that should be forwarded but can be cached
+ for my $method (qw(
+ tables table_info column_info primary_key_info foreign_key_info statistics_info
+ data_sources type_info_all get_info
+ parse_trace_flags parse_trace_flag
+ func
+ )) {
+ my $policy_name = "cache_$method";
+ my $super_name = "SUPER::$method";
+ my $sub = sub {
+ my $dbh = shift;
+ my $rv;
+
+ # if we know the remote side doesn't override the DBI's default method
+ # then we might as well just call the DBI's default method on the client
+ # (which may, in turn, call other methods that are forwarded, like get_info)
+ if ($dbh->{dbi_default_methods}{$method} && $dbh->{go_policy}->skip_default_methods()) {
+ $dbh->trace_msg(" !! $method: using local default as remote method is also default\n");
+ return $dbh->$super_name(@_);
+ }
+
+ my $cache;
+ my $cache_key;
+ if (my $cache_it = $dbh->{go_policy}->$policy_name(undef, $dbh, @_)) {
+ $cache = $dbh->{go_meta_cache} ||= {}; # keep separate from go_cache
+ $cache_key = sprintf "%s_wa%d(%s)", $policy_name, wantarray||0,
+ join(",\t", map { # XXX basic but sufficient for now
+ !ref($_) ? DBI::neat($_,1e6)
+ : ref($_) eq 'ARRAY' ? DBI::neat_list($_,1e6,",\001")
+ : ref($_) eq 'HASH' ? do { my @k = sort keys %$_; DBI::neat_list([@k,@{$_}{@k}],1e6,",\002") }
+ : do { warn "unhandled argument type ($_)"; $_ }
+ } @_);
+ if ($rv = $cache->{$cache_key}) {
+ $dbh->trace_msg("$method(@_) returning previously cached value ($cache_key)\n",4);
+ my @cache_rv = @$rv;
+ # if it's an sth we have to clone it
+ $cache_rv[0] = $cache_rv[0]->go_clone_sth if UNIVERSAL::isa($cache_rv[0],'DBI::st');
+ return (wantarray) ? @cache_rv : $cache_rv[0];
+ }
+ }
+
+ $rv = [ (wantarray)
+ ? ($dbh->go_dbh_method(undef, $method, @_))
+ : scalar $dbh->go_dbh_method(undef, $method, @_)
+ ];
+
+ if ($cache) {
+ $dbh->trace_msg("$method(@_) caching return value ($cache_key)\n",4);
+ my @cache_rv = @$rv;
+ # if it's an sth we have to clone it
+ #$cache_rv[0] = $cache_rv[0]->go_clone_sth
+ # if UNIVERSAL::isa($cache_rv[0],'DBI::st');
+ $cache->{$cache_key} = \@cache_rv
+ unless UNIVERSAL::isa($cache_rv[0],'DBI::st'); # XXX cloning sth not yet done
+ }
+
+ return (wantarray) ? @$rv : $rv->[0];
+ };
+ no strict 'refs';
+ *$method = $sub;
+ }
+
+
+ # Methods that can use the DBI defaults for some situations/drivers
+ for my $method (qw(
+ quote quote_identifier
+ )) { # XXX keep DBD::Gofer::Policy::Base in sync
+ my $policy_name = "locally_$method";
+ my $super_name = "SUPER::$method";
+ my $sub = sub {
+ my $dbh = shift;
+
+ # if we know the remote side doesn't override the DBI's default method
+ # then we might as well just call the DBI's default method on the client
+ # (which may, in turn, call other methods that are forwarded, like get_info)
+ if ($dbh->{dbi_default_methods}{$method} && $dbh->{go_policy}->skip_default_methods()) {
+ $dbh->trace_msg(" !! $method: using local default as remote method is also default\n");
+ return $dbh->$super_name(@_);
+ }
+
+ # false: use remote gofer
+ # 1: use local DBI default method
+ # code ref: use the code ref
+ my $locally = $dbh->{go_policy}->$policy_name($dbh, @_);
+ if ($locally) {
+ return $locally->($dbh, @_) if ref $locally eq 'CODE';
+ return $dbh->$super_name(@_);
+ }
+ return $dbh->go_dbh_method(undef, $method, @_); # propagate context
+ };
+ no strict 'refs';
+ *$method = $sub;
+ }
+
+
+ # Methods that should always fail
+ for my $method (qw(
+ begin_work commit rollback
+ )) {
+ no strict 'refs';
+ *$method = sub { return shift->set_err($DBI::stderr, "$method not available with DBD::Gofer") }
+ }
+
+
+ sub do {
+ my ($dbh, $sql, $attr, @args) = @_;
+ delete $dbh->{Statement}; # avoid "Modification of non-creatable hash value attempted"
+ $dbh->{Statement} = $sql; # for profiling and ShowErrorStatement
+ my $meta = { go_last_insert_id_args => $attr->{go_last_insert_id_args} };
+ return $dbh->go_dbh_method($meta, 'do', $sql, $attr, @args);
+ }
+
+ sub ping {
+ my $dbh = shift;
+ return $dbh->set_err(0, "can't ping while not connected") # warning
+ unless $dbh->SUPER::FETCH('Active');
+ my $skip_ping = $dbh->{go_policy}->skip_ping();
+ return ($skip_ping) ? 1 : $dbh->go_dbh_method(undef, 'ping', @_);
+ }
+
+ sub last_insert_id {
+ my $dbh = shift;
+ my $response = $dbh->{go_response} or return undef;
+ return $response->last_insert_id;
+ }
+
+ sub FETCH {
+ my ($dbh, $attrib) = @_;
+
+ # FETCH is effectively already cached because the DBI checks the
+ # attribute cache in the handle before calling FETCH
+ # and this FETCH copies the value into the attribute cache
+
+ # forward driver-private attributes (except ours)
+ if ($attrib =~ m/^[a-z]/ && $attrib !~ /^go_/) {
+ my $value = $dbh->go_dbh_method(undef, 'FETCH', $attrib);
+ $dbh->{$attrib} = $value; # XXX forces caching by DBI
+ return $dbh->{$attrib} = $value;
+ }
+
+ # else pass up to DBI to handle
+ return $dbh->SUPER::FETCH($attrib);
+ }
+
+ sub STORE {
+ my ($dbh, $attrib, $value) = @_;
+ if ($attrib eq 'AutoCommit') {
+ croak "Can't enable transactions when using DBD::Gofer" if !$value;
+ return $dbh->SUPER::STORE($attrib => ($value) ? -901 : -900);
+ }
+ return $dbh->SUPER::STORE($attrib => $value)
+ # we handle this attribute locally
+ if $dbh_local_store_attrib{$attrib}
+ # or it's a private_ (application) attribute
+ or $attrib =~ /^private_/
+ # or not yet connected (ie being called by DBI->connect)
+ or not $dbh->FETCH('Active');
+
+ return $dbh->SUPER::STORE($attrib => $value)
+ if $DBD::Gofer::xxh_local_store_attrib_if_same_value{$attrib}
+ && do { # values are the same
+ my $crnt = $dbh->FETCH($attrib);
+ local $^W;
+ (defined($value) ^ defined($crnt))
+ ? 0 # definedness differs
+ : $value eq $crnt;
+ };
+
+ # dbh attributes are set at connect-time - see connect()
+ carp("Can't alter \$dbh->{$attrib} after handle created with DBD::Gofer") if $dbh->FETCH('Warn');
+ return $dbh->set_err($DBI::stderr, "Can't alter \$dbh->{$attrib} after handle created with DBD::Gofer");
+ }
+
+ sub disconnect {
+ my $dbh = shift;
+ $dbh->{go_transport} = undef;
+ $dbh->STORE(Active => 0);
+ }
+
+ sub prepare {
+ my ($dbh, $statement, $attr)= @_;
+
+ return $dbh->set_err($DBI::stderr, "Can't prepare when disconnected")
+ unless $dbh->FETCH('Active');
+
+ $attr = { %$attr } if $attr; # copy so we can edit
+
+ my $policy = delete($attr->{go_policy}) || $dbh->{go_policy};
+ my $lii_args = delete $attr->{go_last_insert_id_args};
+ my $go_prepare = delete($attr->{go_prepare_method})
+ || $dbh->{go_prepare_method}
+ || $policy->prepare_method($dbh, $statement, $attr)
+ || 'prepare'; # e.g. for code not using placeholders
+ my $go_cache = delete $attr->{go_cache};
+ # set to undef if there are no attributes left for the actual prepare call
+ $attr = undef if $attr and not %$attr;
+
+ my ($sth, $sth_inner) = DBI::_new_sth($dbh, {
+ Statement => $statement,
+ go_prepare_call => [ 0, $go_prepare, $statement, $attr ],
+ # go_method_calls => [], # autovivs if needed
+ go_request => $dbh->{go_request},
+ go_transport => $dbh->{go_transport},
+ go_policy => $policy,
+ go_last_insert_id_args => $lii_args,
+ go_cache => $go_cache,
+ });
+ $sth->STORE(Active => 0);
+
+ my $skip_prepare_check = $policy->skip_prepare_check($attr, $dbh, $statement, $attr, $sth);
+ if (not $skip_prepare_check) {
+ $sth->go_sth_method() or return undef;
+ }
+
+ return $sth;
+ }
+
+ sub prepare_cached {
+ my ($dbh, $sql, $attr, $if_active)= @_;
+ $attr ||= {};
+ return $dbh->SUPER::prepare_cached($sql, {
+ %$attr,
+ go_prepare_method => $attr->{go_prepare_method} || 'prepare_cached',
+ }, $if_active);
+ }
+
+ *go_cache = \&DBD::Gofer::go_cache;
+}
+
+
+{ package DBD::Gofer::st; # ====== STATEMENT ======
+ $imp_data_size = 0;
+ use strict;
+
+ my %sth_local_store_attrib = (%DBD::Gofer::xxh_local_store_attrib, NUM_OF_FIELDS => 1);
+
+ sub go_sth_method {
+ my ($sth, $meta) = @_;
+
+ if (my $ParamValues = $sth->{ParamValues}) {
+ my $ParamAttr = $sth->{ParamAttr};
+ # XXX the sort here is a hack to work around a DBD::Sybase bug
+ # but only works properly for params 1..9
+ # (reverse because of the unshift)
+ my @params = reverse sort keys %$ParamValues;
+ if (@params > 9 && ($sth->{Database}{go_dsn}||'') =~ /dbi:Sybase/) {
+ # if more than 9 then we need to do a proper numeric sort
+ # also warn to alert user of this issue
+ warn "Sybase param binding order hack in use";
+ @params = sort { $b <=> $a } @params;
+ }
+ for my $p (@params) {
+ # unshift to put binds before execute call
+ unshift @{ $sth->{go_method_calls} },
+ [ 'bind_param', $p, $ParamValues->{$p}, $ParamAttr->{$p} ];
+ }
+ }
+
+ my $dbh = $sth->{Database} or die "panic";
+ ++$dbh->{go_request_count};
+
+ my $request = $sth->{go_request};
+ $request->init_request($sth->{go_prepare_call}, $sth);
+ $request->sth_method_calls(delete $sth->{go_method_calls})
+ if $sth->{go_method_calls};
+ $request->sth_result_attr({}); # (currently) also indicates this is an sth request
+
+ $request->dbh_last_insert_id_args($meta->{go_last_insert_id_args})
+ if $meta->{go_last_insert_id_args};
+
+ my $go_policy = $sth->{go_policy};
+ my $dbh_attribute_update = $go_policy->dbh_attribute_update();
+ $request->dbh_attributes( $go_policy->dbh_attribute_list() )
+ if $dbh_attribute_update eq 'every'
+ or $dbh->{go_request_count}==1;
+
+ my $transport = $sth->{go_transport}
+ or return $sth->set_err($DBI::stderr, "Not connected (no transport)");
+
+ local $transport->{go_cache} = $sth->{go_cache}
+ if defined $sth->{go_cache};
+
+ my ($response, $retransmit_sub) = $transport->transmit_request($request);
+ $response ||= $transport->receive_response($request, $retransmit_sub);
+ $sth->{go_response} = $response
+ or die "No response object returned by $transport";
+ $dbh->{go_response} = $response; # mainly for last_insert_id
+
+ if (my $dbh_attributes = $response->dbh_attributes) {
+ # XXX we don't STORE here, we just stuff the value into the attribute cache
+ $dbh->{$_} = $dbh_attributes->{$_}
+ for keys %$dbh_attributes;
+ # record the values returned, so we know that we have fetched
+ # values are which we have fetched (see dbh->FETCH method)
+ $dbh->{go_dbh_attributes_fetched} = $dbh_attributes;
+ }
+
+ my $rv = $response->rv; # may be undef on error
+ if ($response->sth_resultsets) {
+ # setup first resultset - including sth attributes
+ $sth->more_results;
+ }
+ else {
+ $sth->STORE(Active => 0);
+ $sth->{go_rows} = $rv;
+ }
+ # set error/warn/info (after more_results as that'll clear err)
+ DBD::Gofer::set_err_from_response($sth, $response);
+
+ return $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 NAME
+
+DBD::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 to
+transport the requests to the remote DBI. If $transport doesn't contain any
+double 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 because
+everything after C<dsn=> is assumed to be the DSN that the remote DBI should
+use.
+
+The C<...> represents attributes that influence the operation of the Gofer
+driver or transport. These are described below or in the documentation of the
+transport module being used.
+
+=encoding ISO8859-1
+
+=head1 DESCRIPTION
+
+DBD::Gofer is a DBI database driver that forwards requests to another DBI
+driver, usually in a separate process, often on a separate machine. It tries to
+be as transparent as possible so it appears that you are using the remote
+driver directly.
+
+DBD::Gofer is very similar to DBD::Proxy. The major difference is that with
+DBD::Gofer no state is maintained on the remote end. That means every
+request contains all the information needed to create the required state. (So,
+for example, every request includes the DSN to connect to.) Each request can be
+sent to any available server. The server executes the request and returns a
+single 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 Cases
+
+This may seem like pointless overhead but there are situations where this is a
+very good thing. Let's consider a specific case.
+
+Imagine using DBD::Gofer with an http transport. Your application calls
+connect(), prepare("select * from table where foo=?"), bind_param(), and execute().
+At this point DBD::Gofer builds a request containing all the information
+about the method calls. It then uses the httpd transport to send that request
+to an apache web server.
+
+This 'dbi execute' web server executes the request (using DBI::Gofer::Execute
+and 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 the
+results, such as $sth->{NAME}. This response is sent back to DBD::Gofer which
+unpacks it and presents it to the application as if it had executed the
+statement itself.
+
+=head2 Advantages
+
+Okay, but you still don't see the point? Well let's consider what we've gained:
+
+=head3 Connection Pooling and Throttling
+
+The 'dbi execute' web server leverages all the functionality of web
+infrastructure in terms of load balancing, high-availability, firewalls, access
+management, proxying, caching.
+
+At its most basic level you get a configurable pool of persistent database connections.
+
+=head3 Simple Scaling
+
+Got thousands of processes all trying to connect to the database? You can use
+DBD::Gofer to connect them to your smaller pool of 'dbi execute' web servers instead.
+
+=head3 Caching
+
+Client-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-trips
+
+DBD::Gofer sends as few requests as possible (dependent on the policy being used).
+
+=head3 Thin Clients / Unsupported Platforms
+
+You no longer need drivers for your database on every system. DBD::Gofer is pure perl.
+
+=head1 CONSTRAINTS
+
+There 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 database
+handle 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 statement 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 transactions
+
+AutoCommit only. Transactions aren't supported.
+
+(In theory transactions could be supported when using a transport that
+maintains a connection, like C<stream> does. If you're interested in this
+please get in touch via dbi-dev@perl.org)
+
+=head2 You can't call driver-private sth methods
+
+But that's rarely needed anyway.
+
+=head1 GENERAL CAVEATS
+
+A few important things to keep in mind when using DBD::Gofer:
+
+=head2 Temporary tables, locks, and other per-connection persistent state
+
+You 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 different
+database 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 your
+code 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 Attributes
+
+Some driver-private dbh attributes may not be available if the driver has not
+implemented the private_attribute_info() method (added in DBI 1.54).
+
+=head2 Driver-private Statement Handle Attributes
+
+Driver-private sth attributes can be set in the prepare() call. TODO
+
+Some driver-private sth attributes may not be available if the driver has not
+implemented the private_attribute_info() method (added in DBI 1.54).
+
+=head2 Multiple Resultsets
+
+Multiple resultsets are supported only if the driver supports the more_results() method
+(an exception is made for DBD::Sybase).
+
+=head2 Statement activity that also updates dbh attributes
+
+Some drivers may update one or more dbh attributes after performing activity on
+a child sth. For example, DBD::mysql provides $dbh->{mysql_insertid} in addition to
+$sth->{mysql_insertid}. Currently mysql_insertid is supported via a hack but a
+more general mechanism is needed for other drivers to use.
+
+=head2 Methods that report an error always return undef
+
+With DBD::Gofer, a method that sets an error always return an undef or empty list.
+That shouldn't be a problem in practice because the DBI doesn't define any
+methods that return meaningful values while also reporting an error.
+
+=head2 Subclassing only applies to client-side
+
+The RootClass and DbTypeSubclass attributes are not passed to the Gofer server.
+
+=head1 CAVEATS FOR SPECIFIC METHODS
+
+=head2 last_insert_id
+
+To enable use of last_insert_id you need to indicate to DBD::Gofer that you'd
+like to use it. You do that my adding a C<go_last_insert_id_args> attribute to
+the do() or prepare() method calls. For example:
+
+ $dbh->do($sql, { go_last_insert_id_args => [...] });
+
+or
+
+ $sth = $dbh->prepare($sql, { go_last_insert_id_args => [...] });
+
+The array reference should contains the args that you want passed to the
+last_insert_id() method.
+
+=head2 execute_for_fetch
+
+The array methods bind_param_array() and execute_array() are supported.
+When execute_array() is called the data is serialized and executed in a single
+round-trip to the Gofer server. This makes it very fast, but requires enough
+memory to store all the serialized data.
+
+The execute_for_fetch() method currently isn't optimised, it uses the DBI
+fallback behaviour of executing each tuple individually.
+(It could be implemented as a wrapper for execute_array() - patches welcome.)
+
+=head1 TRANSPORTS
+
+DBD::Gofer doesn't concern itself with transporting requests and responses to and fro.
+For that it uses special Gofer transport modules.
+
+Gofer transport modules usually come in pairs: one for the 'client' DBD::Gofer
+driver to use and one for the remote 'server' end. They have very similar names:
+
+ DBD::Gofer::Transport::<foo>
+ DBI::Gofer::Transport::<foo>
+
+Sometimes the transports on the DBD and DBI sides may have different names. For
+example DBD::Gofer::Transport::http is typically used with DBI::Gofer::Transport::mod_perl
+(DBD::Gofer::Transport::http and DBI::Gofer::Transport::mod_perl modules are
+part of the GoferTransport-http distribution).
+
+=head2 Bundled Transports
+
+Several transport modules are provided with DBD::Gofer:
+
+=head3 null
+
+The null transport is the simplest of them all. It doesn't actually transport the request anywhere.
+It just serializes (freezes) the request into a string, then thaws it back into
+a data structure before passing it to DBI::Gofer::Execute to execute. The same
+freeze and thaw is applied to the results.
+
+The null transport is the best way to test if your application will work with Gofer.
+Just set the DBI_AUTOPROXY environment variable to "C<dbi:Gofer:transport=null;policy=pedantic>"
+(see L</Using DBI_AUTOPROXY> below) and run your application, or ideally its test suite, as usual.
+
+It doesn't take any parameters.
+
+=head3 pipeone
+
+The pipeone transport launches a subprocess for each request. It passes in the
+request and reads the response.
+
+The fact that a new subprocess is started for each request ensures that the
+server side is truly stateless. While this does make the transport I<very> slow,
+it is useful as a way to test that your application doesn't depend on
+per-connection state, such as temporary tables, persisting between requests.
+
+It's also useful both as a proof of concept and as a base class for the stream
+driver.
+
+=head3 stream
+
+The stream driver also launches a subprocess and writes requests and reads
+responses, like the pipeone transport. In this case, however, the subprocess
+is expected to handle more that one request. (Though it will be automatically
+restarted if it exits.)
+
+This is the first transport that is truly useful because it can launch the
+subprocess on a remote machine using C<ssh>. This means you can now use DBD::Gofer
+to easily access any databases that's accessible from any system you can login to.
+You also get all the benefits of ssh, including encryption and optional compression.
+
+See L</Using DBI_AUTOPROXY> below for an example.
+
+=head2 Other Transports
+
+Implementing a Gofer transport is I<very> simple, and more transports are very welcome.
+Just take a look at any existing transports that are similar to your needs.
+
+=head3 http
+
+See the GoferTransport-http distribution on CPAN: http://search.cpan.org/dist/GoferTransport-http/
+
+=head3 Gearman
+
+I know Ask Bjørn Hansen has implemented a transport for the C<gearman> distributed
+job system, though it's not on CPAN at the time of writing this.
+
+=head1 CONNECTING
+
+Simply prefix your existing DSN with "C<dbi:Gofer:transport=$transport;dsn=>"
+where $transport is the name of the Gofer transport you want to use (see L</TRANSPORTS>).
+The C<transport> and C<dsn> attributes must be specified and the C<dsn> attributes must be last.
+
+Other attributes can be specified in the DSN to configure DBD::Gofer and/or the
+Gofer transport module being used. The main attributes after C<transport>, are
+C<url> and C<policy>. These and other attributes are described below.
+
+=head2 Using DBI_AUTOPROXY
+
+The simplest way to try out DBD::Gofer is to set the DBI_AUTOPROXY environment variable.
+In this case you don't include the C<dsn=> part. For example:
+
+ export DBI_AUTOPROXY="dbi:Gofer:transport=null"
+
+or, for a more useful example, try:
+
+ export DBI_AUTOPROXY="dbi:Gofer:transport=stream;url=ssh:user@example.com"
+
+=head2 Connection Attributes
+
+These attributes can be specified in the DSN. They can also be passed in the
+\%attr parameter of the DBI connect method by adding a "C<go_>" prefix to the name.
+
+=head3 transport
+
+Specifies the Gofer transport class to use. Required. See L</TRANSPORTS> above.
+
+If the value does not include C<::> then "C<DBD::Gofer::Transport::>" is prefixed.
+
+The transport object can be accessed via $h->{go_transport}.
+
+=head3 dsn
+
+Specifies the DSN for the remote side to connect to. Required, and must be last.
+
+=head3 url
+
+Used to tell the transport where to connect to. The exact form of the value depends on the transport used.
+
+=head3 policy
+
+Specifies the policy to use. See L</CONFIGURING BEHAVIOUR POLICY>.
+
+If the value does not include C<::> then "C<DBD::Gofer::Policy>" is prefixed.
+
+The policy object can be accessed via $h->{go_policy}.
+
+=head3 timeout
+
+Specifies a timeout, in seconds, to use when waiting for responses from the server side.
+
+=head3 retry_limit
+
+Specifies the number of times a failed request will be retried. Default is 0.
+
+=head3 retry_hook
+
+Specifies a code reference to be called to decide if a failed request should be retried.
+The code reference is called like this:
+
+ $transport = $h->{go_transport};
+ $retry = $transport->go_retry_hook->($request, $response, $transport);
+
+If it returns true then the request will be retried, upto the C<retry_limit>.
+If it returns a false but defined value then the request will not be retried.
+If it returns undef then the default behaviour will be used, as if C<retry_hook>
+had not been specified.
+
+The default behaviour is to retry requests where $request->is_idempotent is true,
+or the error message matches C</induced by DBI_GOFER_RANDOM/>.
+
+=head3 cache
+
+Specifies that client-side caching should be performed. The value is the name
+of a cache class to use.
+
+Any class implementing get($key) and set($key, $value) methods can be used.
+That includes a great many powerful caching classes on CPAN, including the
+Cache and Cache::Cache distributions.
+
+You can use "C<cache=1>" is a shortcut for "C<cache=DBI::Util::CacheMemory>".
+See L<DBI::Util::CacheMemory> for a description of this simple fast default cache.
+
+The cache object can be accessed via $h->go_cache. For example:
+
+ $dbh->go_cache->clear; # free up memory being used by the cache
+
+The cache keys are the frozen (serialized) requests, and the values are the
+frozen responses.
+
+The default behaviour is to only use the cache for requests where
+$request->is_idempotent is true (i.e., the dbh has the ReadOnly attribute set
+or the SQL statement is obviously a SELECT without a FOR UPDATE clause.)
+
+For even more control you can use the C<go_cache> attribute to pass in an
+instantiated cache object. Individual methods, including prepare(), can also
+specify alternative caches via the C<go_cache> attribute. For example, to
+specify no caching for a particular query, you could use
+
+ $sth = $dbh->prepare( $sql, { go_cache => 0 } );
+
+This can be used to implement different caching policies for different statements.
+
+It's interesting to note that DBD::Gofer can be used to add client-side caching
+to any (gofer compatible) application, with no code changes and no need for a
+gofer server. Just set the DBI_AUTOPROXY environment variable like this:
+
+ DBI_AUTOPROXY='dbi:Gofer:transport=null;cache=1'
+
+=head1 CONFIGURING BEHAVIOUR POLICY
+
+DBD::Gofer supports a 'policy' mechanism that allows you to fine-tune the number of round-trips to the Gofer server.
+The policies are grouped into classes (which may be subclassed) and referenced by the name of the class.
+
+The L<DBD::Gofer::Policy::Base> class is the base class for all the policy
+packages and describes all the available policies.
+
+Three policy packages are supplied with DBD::Gofer:
+
+L<DBD::Gofer::Policy::pedantic> is most 'transparent' but slowest because it
+makes more round-trips to the Gofer server.
+
+L<DBD::Gofer::Policy::classic> is a reasonable compromise - it's the default policy.
+
+L<DBD::Gofer::Policy::rush> is fastest, but may require code changes in your applications.
+
+Generally the default C<classic> policy is fine. When first testing an existing
+application with Gofer it is a good idea to start with the C<pedantic> policy
+first and then switch to C<classic> or a custom policy, for final testing.
+
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=head1 ACKNOWLEDGEMENTS
+
+The development of DBD::Gofer and related modules was sponsored by
+Shopzilla.com (L<http://Shopzilla.com>), where I currently work.
+
+=head1 SEE ALSO
+
+L<DBI::Gofer::Request>, L<DBI::Gofer::Response>, L<DBI::Gofer::Execute>.
+
+L<DBI::Gofer::Transport::Base>, L<DBD::Gofer::Policy::Base>.
+
+L<DBI>
+
+=head1 Caveats for specific drivers
+
+This section aims to record issues to be aware of when using Gofer with specific drivers.
+It usually only documents issues that are not natural consequences of the limitations
+of the Gofer approach - as documented above.
+
+=head1 TODO
+
+This is just a random brain dump... (There's more in the source of the Changes file, not the pod)
+
+Document policy mechanism
+
+Add mechanism for transports to list config params and for Gofer to apply any that match (and warn if any left over?)
+
+Driver-private sth attributes - set via prepare() - change DBI spec
+
+add hooks into transport base class for checking & updating a result set cache
+ ie via a standard cache interface such as:
+ http://search.cpan.org/~robm/Cache-FastMmap/FastMmap.pm
+ http://search.cpan.org/~bradfitz/Cache-Memcached/lib/Cache/Memcached.pm
+ http://search.cpan.org/~dclinton/Cache-Cache/
+ http://search.cpan.org/~cleishman/Cache/
+Also caching instructions could be passed through the httpd transport layer
+in such a way that appropriate http cache headers are added to the results
+so that web caches (squid etc) could be used to implement the caching.
+(MUST require the use of GET rather than POST requests.)
+
+Rework handling of installed_methods to not piggyback on dbh_attributes?
+
+Perhaps support transactions for transports where it's possible (ie null and stream)?
+Would make stream transport (ie ssh) more useful to more people.
+
+Make sth_result_attr more like dbh_attributes (using '*' etc)
+
+Add @val = FETCH_many(@names) to DBI in C and use in Gofer/Execute?
+
+Implement _new_sth in C.
+
+=cut
diff --git a/lib/DBD/Gofer/Policy/Base.pm b/lib/DBD/Gofer/Policy/Base.pm
new file mode 100644
index 0000000..1725b03
--- /dev/null
+++ b/lib/DBD/Gofer/Policy/Base.pm
@@ -0,0 +1,162 @@
+package DBD::Gofer::Policy::Base;
+
+# $Id: Base.pm 10087 2007-10-16 12:42:37Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+use Carp;
+
+our $VERSION = sprintf("0.%06d", q$Revision: 10087 $ =~ /(\d+)/o);
+our $AUTOLOAD;
+
+my %policy_defaults = (
+ # force connect method (unless overridden by go_connect_method=>'...' attribute)
+ # if false: call same method on client as on server
+ connect_method => 'connect',
+ # force prepare method (unless overridden by go_prepare_method=>'...' attribute)
+ # if false: call same method on client as on server
+ prepare_method => 'prepare',
+ skip_connect_check => 0,
+ skip_default_methods => 0,
+ skip_prepare_check => 0,
+ skip_ping => 0,
+ dbh_attribute_update => 'every',
+ dbh_attribute_list => ['*'],
+ locally_quote => 0,
+ locally_quote_identifier => 0,
+ cache_parse_trace_flags => 1,
+ cache_parse_trace_flag => 1,
+ cache_data_sources => 1,
+ cache_type_info_all => 1,
+ cache_tables => 0,
+ cache_table_info => 0,
+ cache_column_info => 0,
+ cache_primary_key_info => 0,
+ cache_foreign_key_info => 0,
+ cache_statistics_info => 0,
+ cache_get_info => 0,
+ cache_func => 0,
+);
+
+my $base_policy_file = $INC{"DBD/Gofer/Policy/Base.pm"};
+
+__PACKAGE__->create_policy_subs(\%policy_defaults);
+
+sub create_policy_subs {
+ my ($class, $policy_defaults) = @_;
+
+ while ( my ($policy_name, $policy_default) = each %$policy_defaults) {
+ my $policy_attr_name = "go_$policy_name";
+ my $sub = sub {
+ # $policy->foo($attr, ...)
+ #carp "$policy_name($_[1],...)";
+ # return the policy default value unless an attribute overrides it
+ return (ref $_[1] && exists $_[1]->{$policy_attr_name})
+ ? $_[1]->{$policy_attr_name}
+ : $policy_default;
+ };
+ no strict 'refs';
+ *{$class . '::' . $policy_name} = $sub;
+ }
+}
+
+sub AUTOLOAD {
+ carp "Unknown policy name $AUTOLOAD used";
+ # only warn once
+ no strict 'refs';
+ *$AUTOLOAD = sub { undef };
+ return undef;
+}
+
+sub new {
+ my ($class, $args) = @_;
+ my $policy = {};
+ bless $policy, $class;
+}
+
+sub DESTROY { };
+
+1;
+
+=head1 NAME
+
+DBD::Gofer::Policy::Base - Base class for DBD::Gofer policies
+
+=head1 SYNOPSIS
+
+ $dbh = DBI->connect("dbi:Gofer:transport=...;policy=...", ...)
+
+=head1 DESCRIPTION
+
+DBD::Gofer can be configured via a 'policy' mechanism that allows you to
+fine-tune the number of round-trips to the Gofer server. The policies are
+grouped into classes (which may be subclassed) and referenced by the name of
+the class.
+
+The L<DBD::Gofer::Policy::Base> class is the base class for all the policy
+classes and describes all the individual policy items.
+
+The Base policy is not used directly. You should use a policy class derived from it.
+
+=head1 POLICY CLASSES
+
+Three policy classes are supplied with DBD::Gofer:
+
+L<DBD::Gofer::Policy::pedantic> is most 'transparent' but slowest because it
+makes more round-trips to the Gofer server.
+
+L<DBD::Gofer::Policy::classic> is a reasonable compromise - it's the default policy.
+
+L<DBD::Gofer::Policy::rush> is fastest, but may require code changes in your applications.
+
+Generally the default C<classic> policy is fine. When first testing an existing
+application with Gofer it is a good idea to start with the C<pedantic> policy
+first and then switch to C<classic> or a custom policy, for final testing.
+
+=head1 POLICY ITEMS
+
+These are temporary docs: See the source code for list of policies and their defaults.
+
+In a future version the policies and their defaults will be defined in the pod and parsed out at load-time.
+
+See the source code to this module for more details.
+
+=head1 POLICY CUSTOMIZATION
+
+XXX This area of DBD::Gofer is subject to change.
+
+There are three ways to customize policies:
+
+Policy classes are designed to influence the overall behaviour of DBD::Gofer
+with existing, unaltered programs, so they work in a reasonably optimal way
+without requiring code changes. You can implement new policy classes as
+subclasses of existing policies.
+
+In many cases individual policy items can be overridden on a case-by-case basis
+within your application code. You do this by passing a corresponding
+C<<go_<policy_name>>> attribute into DBI methods by your application code.
+This let's you fine-tune the behaviour for special cases.
+
+The policy items are implemented as methods. In many cases the methods are
+passed parameters relating to the DBD::Gofer code being executed. This means
+the policy can implement dynamic behaviour that varies depending on the
+particular circumstances, such as the particular statement being executed.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=cut
+
diff --git a/lib/DBD/Gofer/Policy/classic.pm b/lib/DBD/Gofer/Policy/classic.pm
new file mode 100644
index 0000000..8f828f0
--- /dev/null
+++ b/lib/DBD/Gofer/Policy/classic.pm
@@ -0,0 +1,79 @@
+package DBD::Gofer::Policy::classic;
+
+# $Id: classic.pm 10087 2007-10-16 12:42:37Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+our $VERSION = sprintf("0.%06d", q$Revision: 10087 $ =~ /(\d+)/o);
+
+use base qw(DBD::Gofer::Policy::Base);
+
+__PACKAGE__->create_policy_subs({
+
+ # always use connect_cached on server
+ connect_method => 'connect_cached',
+
+ # use same methods on server as is called on client
+ prepare_method => '',
+
+ # don't skip the connect check since that also sets dbh attributes
+ # although this makes connect more expensive, that's partly offset
+ # by skip_ping=>1 below, which makes connect_cached very fast.
+ skip_connect_check => 0,
+
+ # most code doesn't rely on sth attributes being set after prepare
+ skip_prepare_check => 1,
+
+ # we're happy to use local method if that's the same as the remote
+ skip_default_methods => 1,
+
+ # ping is not important for DBD::Gofer and most transports
+ skip_ping => 1,
+
+ # only update dbh attributes on first contact with server
+ dbh_attribute_update => 'first',
+
+ # we'd like to set locally_* but can't because drivers differ
+
+ # get_info results usually don't change
+ cache_get_info => 1,
+});
+
+
+1;
+
+=head1 NAME
+
+DBD::Gofer::Policy::classic - The 'classic' policy for DBD::Gofer
+
+=head1 SYNOPSIS
+
+ $dbh = DBI->connect("dbi:Gofer:transport=...;policy=classic", ...)
+
+The C<classic> policy is the default DBD::Gofer policy, so need not be included in the DSN.
+
+=head1 DESCRIPTION
+
+Temporary docs: See the source code for list of policies and their defaults.
+
+In a future version the policies and their defaults will be defined in the pod and parsed out at load-time.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=cut
+
diff --git a/lib/DBD/Gofer/Policy/pedantic.pm b/lib/DBD/Gofer/Policy/pedantic.pm
new file mode 100644
index 0000000..6829bea
--- /dev/null
+++ b/lib/DBD/Gofer/Policy/pedantic.pm
@@ -0,0 +1,53 @@
+package DBD::Gofer::Policy::pedantic;
+
+# $Id: pedantic.pm 10087 2007-10-16 12:42:37Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+our $VERSION = sprintf("0.%06d", q$Revision: 10087 $ =~ /(\d+)/o);
+
+use base qw(DBD::Gofer::Policy::Base);
+
+# the 'pedantic' policy is the same as the Base policy
+
+1;
+
+=head1 NAME
+
+DBD::Gofer::Policy::pedantic - The 'pedantic' policy for DBD::Gofer
+
+=head1 SYNOPSIS
+
+ $dbh = DBI->connect("dbi:Gofer:transport=...;policy=pedantic", ...)
+
+=head1 DESCRIPTION
+
+The C<pedantic> policy tries to be as transparent as possible. To do this it
+makes round-trips to the server for almost every DBI method call.
+
+This is the best policy to use when first testing existing code with Gofer.
+Once it's working well you should consider moving to the C<classic> policy or defining your own policy class.
+
+Temporary docs: See the source code for list of policies and their defaults.
+
+In a future version the policies and their defaults will be defined in the pod and parsed out at load-time.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=cut
+
diff --git a/lib/DBD/Gofer/Policy/rush.pm b/lib/DBD/Gofer/Policy/rush.pm
new file mode 100644
index 0000000..9cfd582
--- /dev/null
+++ b/lib/DBD/Gofer/Policy/rush.pm
@@ -0,0 +1,90 @@
+package DBD::Gofer::Policy::rush;
+
+# $Id: rush.pm 10087 2007-10-16 12:42:37Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+our $VERSION = sprintf("0.%06d", q$Revision: 10087 $ =~ /(\d+)/o);
+
+use base qw(DBD::Gofer::Policy::Base);
+
+__PACKAGE__->create_policy_subs({
+
+ # always use connect_cached on server
+ connect_method => 'connect_cached',
+
+ # use same methods on server as is called on client
+ # (because code not using placeholders would bloat the sth cache)
+ prepare_method => '',
+
+ # Skipping the connect check is fast, but it also skips
+ # fetching the remote dbh attributes!
+ # Make sure that your application doesn't need access to dbh attributes.
+ skip_connect_check => 1,
+
+ # most code doesn't rely on sth attributes being set after prepare
+ skip_prepare_check => 1,
+
+ # we're happy to use local method if that's the same as the remote
+ skip_default_methods => 1,
+
+ # ping is almost meaningless for DBD::Gofer and most transports anyway
+ skip_ping => 1,
+
+ # don't update dbh attributes at all
+ # XXX actually we currently need dbh_attribute_update for skip_default_methods to work
+ # and skip_default_methods is more valuable to us than the cost of dbh_attribute_update
+ dbh_attribute_update => 'none', # actually means 'first' currently
+ #dbh_attribute_list => undef,
+
+ # we'd like to set locally_* but can't because drivers differ
+
+ # in a rush assume metadata doesn't change
+ cache_tables => 1,
+ cache_table_info => 1,
+ cache_column_info => 1,
+ cache_primary_key_info => 1,
+ cache_foreign_key_info => 1,
+ cache_statistics_info => 1,
+ cache_get_info => 1,
+});
+
+
+1;
+
+=head1 NAME
+
+DBD::Gofer::Policy::rush - The 'rush' policy for DBD::Gofer
+
+=head1 SYNOPSIS
+
+ $dbh = DBI->connect("dbi:Gofer:transport=...;policy=rush", ...)
+
+=head1 DESCRIPTION
+
+The C<rush> policy tries to make as few round-trips as possible.
+It's the opposite end of the policy spectrum to the C<pedantic> policy.
+
+Temporary docs: See the source code for list of policies and their defaults.
+
+In a future version the policies and their defaults will be defined in the pod and parsed out at load-time.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=cut
+
diff --git a/lib/DBD/Gofer/Transport/Base.pm b/lib/DBD/Gofer/Transport/Base.pm
new file mode 100644
index 0000000..fe0d078
--- /dev/null
+++ b/lib/DBD/Gofer/Transport/Base.pm
@@ -0,0 +1,410 @@
+package DBD::Gofer::Transport::Base;
+
+# $Id: Base.pm 14120 2010-06-07 19:52:19Z hmbrand $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+use base qw(DBI::Gofer::Transport::Base);
+
+our $VERSION = sprintf("0.%06d", q$Revision: 14120 $ =~ /(\d+)/o);
+
+__PACKAGE__->mk_accessors(qw(
+ trace
+ go_dsn
+ go_url
+ go_policy
+ go_timeout
+ go_retry_hook
+ go_retry_limit
+ go_cache
+ cache_hit
+ cache_miss
+ cache_store
+));
+__PACKAGE__->mk_accessors_using(make_accessor_autoviv_hashref => qw(
+ meta
+));
+
+
+sub new {
+ my ($class, $args) = @_;
+ $args->{$_} = 0 for (qw(cache_hit cache_miss cache_store));
+ $args->{keep_meta_frozen} ||= 1 if $args->{go_cache};
+ #warn "args @{[ %$args ]}\n";
+ return $class->SUPER::new($args);
+}
+
+
+sub _init_trace { $ENV{DBD_GOFER_TRACE} || 0 }
+
+
+sub new_response {
+ my $self = shift;
+ return DBI::Gofer::Response->new(@_);
+}
+
+
+sub transmit_request {
+ my ($self, $request) = @_;
+ my $trace = $self->trace;
+ my $response;
+
+ my ($go_cache, $request_cache_key);
+ if ($go_cache = $self->{go_cache}) {
+ $request_cache_key
+ = $request->{meta}{request_cache_key}
+ = $self->get_cache_key_for_request($request);
+ if ($request_cache_key) {
+ my $frozen_response = eval { $go_cache->get($request_cache_key) };
+ if ($frozen_response) {
+ $self->_dump("cached response found for ".ref($request), $request)
+ if $trace;
+ $response = $self->thaw_response($frozen_response);
+ $self->trace_msg("transmit_request is returning a response from cache $go_cache\n")
+ if $trace;
+ ++$self->{cache_hit};
+ return $response;
+ }
+ warn $@ if $@;
+ ++$self->{cache_miss};
+ $self->trace_msg("transmit_request cache miss\n")
+ if $trace;
+ }
+ }
+
+ my $to = $self->go_timeout;
+ my $transmit_sub = sub {
+ $self->trace_msg("transmit_request\n") if $trace;
+ local $SIG{ALRM} = sub { die "TIMEOUT\n" } if $to;
+
+ my $response = eval {
+ local $SIG{PIPE} = sub {
+ my $extra = ($! eq "Broken pipe") ? "" : " ($!)";
+ die "Unable to send request: Broken pipe$extra\n";
+ };
+ alarm($to) if $to;
+ $self->transmit_request_by_transport($request);
+ };
+ alarm(0) if $to;
+
+ if ($@) {
+ return $self->transport_timedout("transmit_request", $to)
+ if $@ eq "TIMEOUT\n";
+ return $self->new_response({ err => 1, errstr => $@ });
+ }
+
+ return $response;
+ };
+
+ $response = $self->_transmit_request_with_retries($request, $transmit_sub);
+
+ if ($response) {
+ my $frozen_response = delete $response->{meta}{frozen};
+ $self->_store_response_in_cache($frozen_response, $request_cache_key)
+ if $request_cache_key;
+ }
+
+ $self->trace_msg("transmit_request is returning a response itself\n")
+ if $trace && $response;
+
+ return $response unless wantarray;
+ return ($response, $transmit_sub);
+}
+
+
+sub _transmit_request_with_retries {
+ my ($self, $request, $transmit_sub) = @_;
+ my $response;
+ do {
+ $response = $transmit_sub->();
+ } while ( $response && $self->response_needs_retransmit($request, $response) );
+ return $response;
+}
+
+
+sub receive_response {
+ my ($self, $request, $retransmit_sub) = @_;
+ my $to = $self->go_timeout;
+
+ my $receive_sub = sub {
+ $self->trace_msg("receive_response\n");
+ local $SIG{ALRM} = sub { die "TIMEOUT\n" } if $to;
+
+ my $response = eval {
+ alarm($to) if $to;
+ $self->receive_response_by_transport($request);
+ };
+ alarm(0) if $to;
+
+ if ($@) {
+ return $self->transport_timedout("receive_response", $to)
+ if $@ eq "TIMEOUT\n";
+ return $self->new_response({ err => 1, errstr => $@ });
+ }
+ return $response;
+ };
+
+ my $response;
+ do {
+ $response = $receive_sub->();
+ if ($self->response_needs_retransmit($request, $response)) {
+ $response = $self->_transmit_request_with_retries($request, $retransmit_sub);
+ $response ||= $receive_sub->();
+ }
+ } while ( $self->response_needs_retransmit($request, $response) );
+
+ if ($response) {
+ my $frozen_response = delete $response->{meta}{frozen};
+ my $request_cache_key = $request->{meta}{request_cache_key};
+ $self->_store_response_in_cache($frozen_response, $request_cache_key)
+ if $request_cache_key && $self->{go_cache};
+ }
+
+ return $response;
+}
+
+
+sub response_retry_preference {
+ my ($self, $request, $response) = @_;
+
+ # give the user a chance to express a preference (or undef for default)
+ if (my $go_retry_hook = $self->go_retry_hook) {
+ my $retry = $go_retry_hook->($request, $response, $self);
+ $self->trace_msg(sprintf "go_retry_hook returned %s\n",
+ (defined $retry) ? $retry : 'undef');
+ return $retry if defined $retry;
+ }
+
+ # This is the main decision point. We don't retry requests that got
+ # as far as executing because the error is probably from the database
+ # (not transport) so retrying is unlikely to help. But note that any
+ # severe transport error occuring after execute is likely to return
+ # a new response object that doesn't have the execute flag set. Beware!
+ return 0 if $response->executed_flag_set;
+
+ return 1 if ($response->errstr || '') =~ m/induced by DBI_GOFER_RANDOM/;
+
+ return 1 if $request->is_idempotent; # i.e. is SELECT or ReadOnly was set
+
+ return undef; # we couldn't make up our mind
+}
+
+
+sub response_needs_retransmit {
+ my ($self, $request, $response) = @_;
+
+ my $err = $response->err
+ or return 0; # nothing went wrong
+
+ my $retry = $self->response_retry_preference($request, $response);
+
+ if (!$retry) { # false or undef
+ $self->trace_msg("response_needs_retransmit: response not suitable for retry\n");
+ return 0;
+ }
+
+ # we'd like to retry but have we retried too much already?
+
+ my $retry_limit = $self->go_retry_limit;
+ if (!$retry_limit) {
+ $self->trace_msg("response_needs_retransmit: retries disabled (retry_limit not set)\n");
+ return 0;
+ }
+
+ my $request_meta = $request->meta;
+ my $retry_count = $request_meta->{retry_count} || 0;
+ if ($retry_count >= $retry_limit) {
+ $self->trace_msg("response_needs_retransmit: $retry_count is too many retries\n");
+ # XXX should be possible to disable altering the err
+ $response->errstr(sprintf "%s (after %d retries by gofer)", $response->errstr, $retry_count);
+ return 0;
+ }
+
+ # will retry now, do the admin
+ ++$retry_count;
+ $self->trace_msg("response_needs_retransmit: retry $retry_count\n");
+
+ # hook so response_retry_preference can defer some code execution
+ # until we've checked retry_count and retry_limit.
+ if (ref $retry eq 'CODE') {
+ $retry->($retry_count, $retry_limit)
+ and warn "should return false"; # protect future use
+ }
+
+ ++$request_meta->{retry_count}; # update count for this request object
+ ++$self->meta->{request_retry_count}; # update cumulative transport stats
+
+ return 1;
+}
+
+
+sub transport_timedout {
+ my ($self, $method, $timeout) = @_;
+ $timeout ||= $self->go_timeout;
+ return $self->new_response({ err => 1, errstr => "DBD::Gofer $method timed-out after $timeout seconds" });
+}
+
+
+# return undef if we don't want to cache this request
+# subclasses may use more specialized rules
+sub get_cache_key_for_request {
+ my ($self, $request) = @_;
+
+ # we only want to cache idempotent requests
+ # is_idempotent() is true if GOf_REQUEST_IDEMPOTENT or GOf_REQUEST_READONLY set
+ return undef if not $request->is_idempotent;
+
+ # XXX would be nice to avoid the extra freeze here
+ my $key = $self->freeze_request($request, undef, 1);
+
+ #use Digest::MD5; warn "get_cache_key_for_request: ".Digest::MD5::md5_base64($key)."\n";
+
+ return $key;
+}
+
+
+sub _store_response_in_cache {
+ my ($self, $frozen_response, $request_cache_key) = @_;
+ my $go_cache = $self->{go_cache}
+ or return;
+
+ # new() ensures that enabling go_cache also enables keep_meta_frozen
+ warn "No meta frozen in response" if !$frozen_response;
+ warn "No request_cache_key" if !$request_cache_key;
+
+ if ($frozen_response && $request_cache_key) {
+ $self->trace_msg("receive_response added response to cache $go_cache\n");
+ eval { $go_cache->set($request_cache_key, $frozen_response) };
+ warn $@ if $@;
+ ++$self->{cache_store};
+ }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+DBD::Gofer::Transport::Base - base class for DBD::Gofer client transports
+
+=head1 SYNOPSIS
+
+ my $remote_dsn = "..."
+ DBI->connect("dbi:Gofer:transport=...;url=...;timeout=...;retry_limit=...;dsn=$remote_dsn",...)
+
+or, enable by setting the DBI_AUTOPROXY environment variable:
+
+ export DBI_AUTOPROXY='dbi:Gofer:transport=...;url=...'
+
+which will force I<all> DBI connections to be made via that Gofer server.
+
+=head1 DESCRIPTION
+
+This is the base class for all DBD::Gofer client transports.
+
+=head1 ATTRIBUTES
+
+Gofer transport attributes can be specified either in the attributes parameter
+of the connect() method call, or in the DSN string. When used in the DSN
+string, attribute names don't have the C<go_> prefix.
+
+=head2 go_dsn
+
+The full DBI DSN that the Gofer server should connect to on your behalf.
+
+When used in the DSN it must be the last element in the DSN string.
+
+=head2 go_timeout
+
+A time limit for sending a request and receiving a response. Some drivers may
+implement sending and receiving as separate steps, in which case (currently)
+the timeout applies to each separately.
+
+If a request needs to be resent then the timeout is restarted for each sending
+of a request and receiving of a response.
+
+=head2 go_retry_limit
+
+The maximum number of times an request may be retried. The default is 2.
+
+=head2 go_retry_hook
+
+This subroutine reference is called, if defined, for each response received where $response->err is true.
+
+The subroutine is pass three parameters: the request object, the response object, and the transport object.
+
+If it returns an undefined value then the default retry behaviour is used. See L</RETRY ON ERROR> below.
+
+If it returns a defined but false value then the request is not resent.
+
+If it returns true value then the request is resent, so long as the number of retries does not exceed C<go_retry_limit>.
+
+=head1 RETRY ON ERROR
+
+The default retry on error behaviour is:
+
+ - Retry if the error was due to DBI_GOFER_RANDOM. See L<DBI::Gofer::Execute>.
+
+ - Retry if $request->is_idempotent returns true. See L<DBI::Gofer::Request>.
+
+A retry won't be allowed if the number of previous retries has reached C<go_retry_limit>.
+
+=head1 TRACING
+
+Tracing of gofer requests and responses can be enabled by setting the
+C<DBD_GOFER_TRACE> environment variable. A value of 1 gives a reasonably
+compact summary of each request and response. A value of 2 or more gives a
+detailed, and voluminous, dump.
+
+The trace is written using DBI->trace_msg() and so is written to the default
+DBI trace output, which is usually STDERR.
+
+=head1 METHODS
+
+I<This section is currently far from complete.>
+
+=head2 response_retry_preference
+
+ $retry = $transport->response_retry_preference($request, $response);
+
+The response_retry_preference is called by DBD::Gofer when considering if a
+request should be retried after an error.
+
+Returns true (would like to retry), false (must not retry), undef (no preference).
+
+If a true value is returned in the form of a CODE ref then, if DBD::Gofer does
+decide to retry the request, it calls the code ref passing $retry_count, $retry_limit.
+Can be used for logging and/or to implement exponential backoff behaviour.
+Currently the called code must return using C<return;> to allow for future extensions.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007-2008, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=head1 SEE ALSO
+
+L<DBD::Gofer>, L<DBI::Gofer::Request>, L<DBI::Gofer::Response>, L<DBI::Gofer::Execute>.
+
+and some example transports:
+
+L<DBD::Gofer::Transport::stream>
+
+L<DBD::Gofer::Transport::http>
+
+L<DBI::Gofer::Transport::mod_perl>
+
+=cut
diff --git a/lib/DBD/Gofer/Transport/corostream.pm b/lib/DBD/Gofer/Transport/corostream.pm
new file mode 100644
index 0000000..6e79278
--- /dev/null
+++ b/lib/DBD/Gofer/Transport/corostream.pm
@@ -0,0 +1,144 @@
+package DBD::Gofer::Transport::corostream;
+
+use strict;
+use warnings;
+
+use Carp;
+
+use Coro::Select; # a slow but coro-aware replacement for CORE::select (global effect!)
+
+use Coro;
+use Coro::Handle;
+
+use base qw(DBD::Gofer::Transport::stream);
+
+# XXX ensure DBI_PUREPERL for parent doesn't pass to child
+sub start_pipe_command {
+ local $ENV{DBI_PUREPERL} = $ENV{DBI_PUREPERL_COROCHILD}; # typically undef
+ my $connection = shift->SUPER::start_pipe_command(@_);
+ return $connection;
+}
+
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+DBD::Gofer::Transport::corostream - Async DBD::Gofer stream transport using Coro and AnyEvent
+
+=head1 SYNOPSIS
+
+ DBI_AUTOPROXY="dbi:Gofer:transport=corostream" perl some-perl-script-using-dbi.pl
+
+or
+
+ $dsn = ...; # the DSN for the driver and database you want to use
+ $dbh = DBI->connect("dbi:Gofer:transport=corostream;dsn=$dsn", ...);
+
+=head1 DESCRIPTION
+
+The I<BIG WIN> from using L<Coro> is that it enables the use of existing
+DBI frameworks like L<DBIx::Class>.
+
+=head1 KNOWN ISSUES AND LIMITATIONS
+
+ - Uses Coro::Select so alters CORE::select globally
+ Parent class probably needs refactoring to enable a more encapsulated approach.
+
+ - Doesn't prevent multiple concurrent requests
+ Probably just needs a per-connection semaphore
+
+ - Coro has many caveats. Caveat emptor.
+
+=head1 STATUS
+
+THIS IS CURRENTLY JUST A PROOF-OF-CONCEPT IMPLEMENTATION FOR EXPERIMENTATION.
+
+Please note that I have no plans to develop this code further myself.
+I'd very much welcome contributions. Interested? Let me know!
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2010, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=head1 SEE ALSO
+
+L<DBD::Gofer::Transport::stream>
+
+L<DBD::Gofer>
+
+=head1 APPENDIX
+
+Example code:
+
+ #!perl
+
+ use strict;
+ use warnings;
+ use Time::HiRes qw(time);
+
+ BEGIN { $ENV{PERL_ANYEVENT_STRICT} = 1; $ENV{PERL_ANYEVENT_VERBOSE} = 1; }
+
+ use AnyEvent;
+
+ BEGIN { $ENV{DBI_TRACE} = 0; $ENV{DBI_GOFER_TRACE} = 0; $ENV{DBD_GOFER_TRACE} = 0; };
+
+ use DBI;
+
+ $ENV{DBI_AUTOPROXY} = 'dbi:Gofer:transport=corostream';
+
+ my $ticker = AnyEvent->timer( after => 0, interval => 0.1, cb => sub {
+ warn sprintf "-tick- %.2f\n", time
+ } );
+
+ warn "connecting...\n";
+ my $dbh = DBI->connect("dbi:NullP:");
+ warn "...connected\n";
+
+ for (1..3) {
+ warn "entering DBI...\n";
+ $dbh->do("sleep 0.3"); # pseudo-sql understood by the DBD::NullP driver
+ warn "...returned\n";
+ }
+
+ warn "done.";
+
+Example output:
+
+ $ perl corogofer.pl
+ connecting...
+ -tick- 1293631437.14
+ -tick- 1293631437.14
+ ...connected
+ entering DBI...
+ -tick- 1293631437.25
+ -tick- 1293631437.35
+ -tick- 1293631437.45
+ -tick- 1293631437.55
+ ...returned
+ entering DBI...
+ -tick- 1293631437.66
+ -tick- 1293631437.76
+ -tick- 1293631437.86
+ ...returned
+ entering DBI...
+ -tick- 1293631437.96
+ -tick- 1293631438.06
+ -tick- 1293631438.16
+ ...returned
+ done. at corogofer.pl line 39.
+
+You can see that the timer callback is firing while the code 'waits' inside the
+do() method for the response from the database. Normally that would block.
+
+=cut
diff --git a/lib/DBD/Gofer/Transport/null.pm b/lib/DBD/Gofer/Transport/null.pm
new file mode 100644
index 0000000..4b8d86c
--- /dev/null
+++ b/lib/DBD/Gofer/Transport/null.pm
@@ -0,0 +1,111 @@
+package DBD::Gofer::Transport::null;
+
+# $Id: null.pm 10087 2007-10-16 12:42:37Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+use base qw(DBD::Gofer::Transport::Base);
+
+use DBI::Gofer::Execute;
+
+our $VERSION = sprintf("0.%06d", q$Revision: 10087 $ =~ /(\d+)/o);
+
+__PACKAGE__->mk_accessors(qw(
+ pending_response
+ transmit_count
+));
+
+my $executor = DBI::Gofer::Execute->new();
+
+
+sub transmit_request_by_transport {
+ my ($self, $request) = @_;
+ $self->transmit_count( ($self->transmit_count()||0) + 1 ); # just for tests
+
+ my $frozen_request = $self->freeze_request($request);
+
+ # ...
+ # the request is magically transported over to ... ourselves
+ # ...
+
+ my $response = $executor->execute_request( $self->thaw_request($frozen_request, undef, 1) );
+
+ # put response 'on the shelf' ready for receive_response()
+ $self->pending_response( $response );
+
+ return undef;
+}
+
+
+sub receive_response_by_transport {
+ my $self = shift;
+
+ my $response = $self->pending_response;
+
+ my $frozen_response = $self->freeze_response($response, undef, 1);
+
+ # ...
+ # the response is magically transported back to ... ourselves
+ # ...
+
+ return $self->thaw_response($frozen_response);
+}
+
+
+1;
+__END__
+
+=head1 NAME
+
+DBD::Gofer::Transport::null - DBD::Gofer client transport for testing
+
+=head1 SYNOPSIS
+
+ my $original_dsn = "..."
+ DBI->connect("dbi:Gofer:transport=null;dsn=$original_dsn",...)
+
+or, enable by setting the DBI_AUTOPROXY environment variable:
+
+ export DBI_AUTOPROXY="dbi:Gofer:transport=null"
+
+=head1 DESCRIPTION
+
+Connect via DBD::Gofer but execute the requests within the same process.
+
+This is a quick and simple way to test applications for compatibility with the
+(few) restrictions that DBD::Gofer imposes.
+
+It also provides a simple, portable way for the DBI test suite to be used to
+test DBD::Gofer on all platforms with no setup.
+
+Also, by measuring the difference in performance between normal connections and
+connections via C<dbi:Gofer:transport=null> the basic cost of using DBD::Gofer
+can be measured. Furthermore, the additional cost of more advanced transports can be
+isolated by comparing their performance with the null transport.
+
+The C<t/85gofer.t> script in the DBI distribution includes a comparative benchmark.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=head1 SEE ALSO
+
+L<DBD::Gofer::Transport::Base>
+
+L<DBD::Gofer>
+
+=cut
diff --git a/lib/DBD/Gofer/Transport/pipeone.pm b/lib/DBD/Gofer/Transport/pipeone.pm
new file mode 100644
index 0000000..3df2bf3
--- /dev/null
+++ b/lib/DBD/Gofer/Transport/pipeone.pm
@@ -0,0 +1,253 @@
+package DBD::Gofer::Transport::pipeone;
+
+# $Id: pipeone.pm 10087 2007-10-16 12:42:37Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+use Carp;
+use Fcntl;
+use IO::Select;
+use IPC::Open3 qw(open3);
+use Symbol qw(gensym);
+
+use base qw(DBD::Gofer::Transport::Base);
+
+our $VERSION = sprintf("0.%06d", q$Revision: 10087 $ =~ /(\d+)/o);
+
+__PACKAGE__->mk_accessors(qw(
+ connection_info
+ go_perl
+));
+
+
+sub new {
+ my ($self, $args) = @_;
+ $args->{go_perl} ||= do {
+ ($INC{"blib.pm"}) ? [ $^X, '-Mblib' ] : [ $^X ];
+ };
+ if (not ref $args->{go_perl}) {
+ # user can override the perl to be used, either with an array ref
+ # containing the command name and args to use, or with a string
+ # (ie via the DSN) in which case, to enable args to be passed,
+ # we split on two or more consecutive spaces (otherwise the path
+ # to perl couldn't contain a space itself).
+ $args->{go_perl} = [ split /\s{2,}/, $args->{go_perl} ];
+ }
+ return $self->SUPER::new($args);
+}
+
+
+# nonblock($fh) puts filehandle into nonblocking mode
+sub nonblock {
+ my $fh = shift;
+ my $flags = fcntl($fh, F_GETFL, 0)
+ or croak "Can't get flags for filehandle $fh: $!";
+ fcntl($fh, F_SETFL, $flags | O_NONBLOCK)
+ or croak "Can't make filehandle $fh nonblocking: $!";
+}
+
+
+sub start_pipe_command {
+ my ($self, $cmd) = @_;
+ $cmd = [ $cmd ] unless ref $cmd eq 'ARRAY';
+
+ # if it's important that the subprocess uses the same
+ # (versions of) modules as us then the caller should
+ # set PERL5LIB itself.
+
+ # limit various forms of insanity, for now
+ local $ENV{DBI_TRACE}; # use DBI_GOFER_TRACE instead
+ local $ENV{DBI_AUTOPROXY};
+ local $ENV{DBI_PROFILE};
+
+ my ($wfh, $rfh, $efh) = (gensym, gensym, gensym);
+ my $pid = open3($wfh, $rfh, $efh, @$cmd)
+ or die "error starting @$cmd: $!\n";
+ if ($self->trace) {
+ $self->trace_msg(sprintf("Started pid $pid: @$cmd {fd: w%d r%d e%d, ppid=$$}\n", fileno $wfh, fileno $rfh, fileno $efh),0);
+ }
+ nonblock($rfh);
+ nonblock($efh);
+ my $ios = IO::Select->new($rfh, $efh);
+
+ return {
+ cmd=>$cmd,
+ pid=>$pid,
+ wfh=>$wfh, rfh=>$rfh, efh=>$efh,
+ ios=>$ios,
+ };
+}
+
+
+sub cmd_as_string {
+ my $self = shift;
+ # XXX meant to return a properly shell-escaped string suitable for system
+ # but its only for debugging so that can wait
+ my $connection_info = $self->connection_info;
+ return join " ", map { (m/^[-:\w]*$/) ? $_ : "'$_'" } @{$connection_info->{cmd}};
+}
+
+
+sub transmit_request_by_transport {
+ my ($self, $request) = @_;
+
+ my $frozen_request = $self->freeze_request($request);
+
+ my $cmd = [ @{$self->go_perl}, qw(-MDBI::Gofer::Transport::pipeone -e run_one_stdio)];
+ my $info = $self->start_pipe_command($cmd);
+
+ my $wfh = delete $info->{wfh};
+ # send frozen request
+ local $\;
+ print $wfh $frozen_request
+ or warn "error writing to @$cmd: $!\n";
+ # indicate that there's no more
+ close $wfh
+ or die "error closing pipe to @$cmd: $!\n";
+
+ $self->connection_info( $info );
+ return;
+}
+
+
+sub read_response_from_fh {
+ my ($self, $fh_actions) = @_;
+ my $trace = $self->trace;
+
+ my $info = $self->connection_info || die;
+ my ($ios) = @{$info}{qw(ios)};
+ my $errors = 0;
+ my $complete;
+
+ die "No handles to read response from" unless $ios->count;
+
+ while ($ios->count) {
+ my @readable = $ios->can_read();
+ for my $fh (@readable) {
+ local $_;
+ my $actions = $fh_actions->{$fh} || die "panic: no action for $fh";
+ my $rv = sysread($fh, $_='', 1024*31); # to fit in 32KB slab
+ unless ($rv) { # error (undef) or end of file (0)
+ my $action;
+ unless (defined $rv) { # was an error
+ $self->trace_msg("error on handle $fh: $!\n") if $trace >= 4;
+ $action = $actions->{error} || $actions->{eof};
+ ++$errors;
+ # XXX an error may be a permenent condition of the handle
+ # if so we'll loop here - not good
+ }
+ else {
+ $action = $actions->{eof};
+ $self->trace_msg("eof on handle $fh\n") if $trace >= 4;
+ }
+ if ($action->($fh)) {
+ $self->trace_msg("removing $fh from handle set\n") if $trace >= 4;
+ $ios->remove($fh);
+ }
+ next;
+ }
+ # action returns true if the response is now complete
+ # (we finish all handles
+ $actions->{read}->($fh) && ++$complete;
+ }
+ last if $complete;
+ }
+ return $errors;
+}
+
+
+sub receive_response_by_transport {
+ my $self = shift;
+
+ my $info = $self->connection_info || die;
+ my ($pid, $rfh, $efh, $ios, $cmd) = @{$info}{qw(pid rfh efh ios cmd)};
+
+ my $frozen_response;
+ my $stderr_msg;
+
+ $self->read_response_from_fh( {
+ $efh => {
+ error => sub { warn "error reading response stderr: $!"; 1 },
+ eof => sub { warn "eof on stderr" if 0; 1 },
+ read => sub { $stderr_msg .= $_; 0 },
+ },
+ $rfh => {
+ error => sub { warn "error reading response: $!"; 1 },
+ eof => sub { warn "eof on stdout" if 0; 1 },
+ read => sub { $frozen_response .= $_; 0 },
+ },
+ });
+
+ waitpid $info->{pid}, 0
+ or warn "waitpid: $!"; # XXX do something more useful?
+
+ die ref($self)." command (@$cmd) failed: $stderr_msg"
+ if not $frozen_response; # no output on stdout at all
+
+ # XXX need to be able to detect and deal with corruption
+ my $response = $self->thaw_response($frozen_response);
+
+ if ($stderr_msg) {
+ # add stderr messages as warnings (for PrintWarn)
+ $response->add_err(0, $stderr_msg, undef, $self->trace)
+ # but ignore warning from old version of blib
+ unless $stderr_msg =~ /^Using .*blib/ && "@$cmd" =~ /-Mblib/;
+ }
+
+ return $response;
+}
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+DBD::Gofer::Transport::pipeone - DBD::Gofer client transport for testing
+
+=head1 SYNOPSIS
+
+ $original_dsn = "...";
+ DBI->connect("dbi:Gofer:transport=pipeone;dsn=$original_dsn",...)
+
+or, enable by setting the DBI_AUTOPROXY environment variable:
+
+ export DBI_AUTOPROXY="dbi:Gofer:transport=pipeone"
+
+=head1 DESCRIPTION
+
+Connect via DBD::Gofer and execute each request by starting executing a subprocess.
+
+This is, as you might imagine, spectacularly inefficient!
+
+It's only intended for testing. Specifically it demonstrates that the server
+side is completely stateless.
+
+It also provides a base class for the much more useful L<DBD::Gofer::Transport::stream>
+transport.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=head1 SEE ALSO
+
+L<DBD::Gofer::Transport::Base>
+
+L<DBD::Gofer>
+
+=cut
diff --git a/lib/DBD/Gofer/Transport/stream.pm b/lib/DBD/Gofer/Transport/stream.pm
new file mode 100644
index 0000000..61e211c
--- /dev/null
+++ b/lib/DBD/Gofer/Transport/stream.pm
@@ -0,0 +1,292 @@
+package DBD::Gofer::Transport::stream;
+
+# $Id: stream.pm 14598 2010-12-21 22:53:25Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+use Carp;
+
+use base qw(DBD::Gofer::Transport::pipeone);
+
+our $VERSION = sprintf("0.%06d", q$Revision: 14598 $ =~ /(\d+)/o);
+
+__PACKAGE__->mk_accessors(qw(
+ go_persist
+));
+
+my $persist_all = 5;
+my %persist;
+
+
+sub _connection_key {
+ my ($self) = @_;
+ return join "~", $self->go_url||"", @{ $self->go_perl || [] };
+}
+
+
+sub _connection_get {
+ my ($self) = @_;
+
+ my $persist = $self->go_persist; # = 0 can force non-caching
+ $persist = $persist_all if not defined $persist;
+ my $key = ($persist) ? $self->_connection_key : '';
+ if ($persist{$key} && $self->_connection_check($persist{$key})) {
+ $self->trace_msg("reusing persistent connection $key\n",0) if $self->trace >= 1;
+ return $persist{$key};
+ }
+
+ my $connection = $self->_make_connection;
+
+ if ($key) {
+ %persist = () if keys %persist > $persist_all; # XXX quick hack to limit subprocesses
+ $persist{$key} = $connection;
+ }
+
+ return $connection;
+}
+
+
+sub _connection_check {
+ my ($self, $connection) = @_;
+ $connection ||= $self->connection_info;
+ my $pid = $connection->{pid};
+ my $ok = (kill 0, $pid);
+ $self->trace_msg("_connection_check: $ok (pid $$)\n",0) if $self->trace;
+ return $ok;
+}
+
+
+sub _connection_kill {
+ my ($self) = @_;
+ my $connection = $self->connection_info;
+ my ($pid, $wfh, $rfh, $efh) = @{$connection}{qw(pid wfh rfh efh)};
+ $self->trace_msg("_connection_kill: closing write handle\n",0) if $self->trace;
+ # closing the write file handle should be enough, generally
+ close $wfh;
+ # in future we may want to be more aggressive
+ #close $rfh; close $efh; kill 15, $pid
+ # but deleting from the persist cache...
+ delete $persist{ $self->_connection_key };
+ # ... and removing the connection_info should suffice
+ $self->connection_info( undef );
+ return;
+}
+
+
+sub _make_connection {
+ my ($self) = @_;
+
+ my $go_perl = $self->go_perl;
+ my $cmd = [ @$go_perl, qw(-MDBI::Gofer::Transport::stream -e run_stdio_hex)];
+
+ #push @$cmd, "DBI_TRACE=2=/tmp/goferstream.log", "sh", "-c";
+ if (my $url = $self->go_url) {
+ die "Only 'ssh:user\@host' style url supported by this transport"
+ unless $url =~ s/^ssh://;
+ my $ssh = $url;
+ my $setup_env = join "||", map { "source $_ 2>/dev/null" } qw(.bash_profile .bash_login .profile);
+ my $setup = $setup_env.q{; exec "$@"};
+ # don't use $^X on remote system by default as it's possibly wrong
+ $cmd->[0] = 'perl' if "@$go_perl" eq $^X;
+ # -x not only 'Disables X11 forwarding' but also makes connections *much* faster
+ unshift @$cmd, qw(ssh -xq), split(' ', $ssh), qw(bash -c), $setup;
+ }
+
+ $self->trace_msg("new connection: @$cmd\n",0) if $self->trace;
+
+ # XXX add a handshake - some message from DBI::Gofer::Transport::stream that's
+ # sent as soon as it starts that we can wait for to report success - and soak up
+ # and report useful warnings etc from ssh before we get it? Increases latency though.
+ my $connection = $self->start_pipe_command($cmd);
+ return $connection;
+}
+
+
+sub transmit_request_by_transport {
+ my ($self, $request) = @_;
+ my $trace = $self->trace;
+
+ my $connection = $self->connection_info || do {
+ my $con = $self->_connection_get;
+ $self->connection_info( $con );
+ $con;
+ };
+
+ my $encoded_request = unpack("H*", $self->freeze_request($request));
+ $encoded_request .= "\015\012";
+
+ my $wfh = $connection->{wfh};
+ $self->trace_msg(sprintf("transmit_request_by_transport: to fh %s fd%d\n", $wfh, fileno($wfh)),0)
+ if $trace >= 4;
+
+ # send frozen request
+ local $\;
+ $wfh->print($encoded_request) # autoflush enabled
+ or do {
+ my $err = $!;
+ # XXX could/should make new connection and retry
+ $self->_connection_kill;
+ die "Error sending request: $err";
+ };
+ $self->trace_msg("Request sent: $encoded_request\n",0) if $trace >= 4;
+
+ return undef; # indicate no response yet (so caller calls receive_response_by_transport)
+}
+
+
+sub receive_response_by_transport {
+ my $self = shift;
+ my $trace = $self->trace;
+
+ $self->trace_msg("receive_response_by_transport: awaiting response\n",0) if $trace >= 4;
+ my $connection = $self->connection_info || die;
+ my ($pid, $rfh, $efh, $cmd) = @{$connection}{qw(pid rfh efh cmd)};
+
+ my $errno = 0;
+ my $encoded_response;
+ my $stderr_msg;
+
+ $self->read_response_from_fh( {
+ $efh => {
+ error => sub { warn "error reading response stderr: $!"; $errno||=$!; 1 },
+ eof => sub { warn "eof reading efh" if $trace >= 4; 1 },
+ read => sub { $stderr_msg .= $_; 0 },
+ },
+ $rfh => {
+ error => sub { warn "error reading response: $!"; $errno||=$!; 1 },
+ eof => sub { warn "eof reading rfh" if $trace >= 4; 1 },
+ read => sub { $encoded_response .= $_; ($encoded_response=~s/\015\012$//) ? 1 : 0 },
+ },
+ });
+
+ # if we got no output on stdout at all then the command has
+ # probably exited, possibly with an error to stderr.
+ # Turn this situation into a reasonably useful DBI error.
+ if (not $encoded_response) {
+ my @msg;
+ push @msg, "error while reading response: $errno" if $errno;
+ if ($stderr_msg) {
+ chomp $stderr_msg;
+ push @msg, sprintf "error reported by \"%s\" (pid %d%s): %s",
+ $self->cmd_as_string,
+ $pid, ((kill 0, $pid) ? "" : ", exited"),
+ $stderr_msg;
+ }
+ die join(", ", "No response received", @msg)."\n";
+ }
+
+ $self->trace_msg("Response received: $encoded_response\n",0)
+ if $trace >= 4;
+
+ $self->trace_msg("Gofer stream stderr message: $stderr_msg\n",0)
+ if $stderr_msg && $trace;
+
+ my $frozen_response = pack("H*", $encoded_response);
+
+ # XXX need to be able to detect and deal with corruption
+ my $response = $self->thaw_response($frozen_response);
+
+ if ($stderr_msg) {
+ # add stderr messages as warnings (for PrintWarn)
+ $response->add_err(0, $stderr_msg, undef, $trace)
+ # but ignore warning from old version of blib
+ unless $stderr_msg =~ /^Using .*blib/ && "@$cmd" =~ /-Mblib/;
+ }
+
+ return $response;
+}
+
+sub transport_timedout {
+ my $self = shift;
+ $self->_connection_kill;
+ return $self->SUPER::transport_timedout(@_);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+DBD::Gofer::Transport::stream - DBD::Gofer transport for stdio streaming
+
+=head1 SYNOPSIS
+
+ DBI->connect('dbi:Gofer:transport=stream;url=ssh:username@host.example.com;dsn=dbi:...',...)
+
+or, enable by setting the DBI_AUTOPROXY environment variable:
+
+ export DBI_AUTOPROXY='dbi:Gofer:transport=stream;url=ssh:username@host.example.com'
+
+=head1 DESCRIPTION
+
+Without the C<url=> parameter it launches a subprocess as
+
+ perl -MDBI::Gofer::Transport::stream -e run_stdio_hex
+
+and feeds requests into it and reads responses from it. But that's not very useful.
+
+With a C<url=ssh:username@host.example.com> parameter it uses ssh to launch the subprocess
+on a remote system. That's much more useful!
+
+It gives you secure remote access to DBI databases on any system you can login to.
+Using ssh also gives you optional compression and many other features (see the
+ssh manual for how to configure that and many other options via ~/.ssh/config file).
+
+The actual command invoked is something like:
+
+ ssh -xq ssh:username@host.example.com bash -c $setup $run
+
+where $run is the command shown above, and $command is
+
+ . .bash_profile 2>/dev/null || . .bash_login 2>/dev/null || . .profile 2>/dev/null; exec "$@"
+
+which is trying (in a limited and fairly unportable way) to setup the environment
+(PATH, PERL5LIB etc) as it would be if you had logged in to that system.
+
+The "C<perl>" used in the command will default to the value of $^X when not using ssh.
+On most systems that's the full path to the perl that's currently executing.
+
+
+=head1 PERSISTENCE
+
+Currently gofer stream connections persist (remain connected) after all
+database handles have been disconnected. This makes later connections in the
+same process very fast.
+
+Currently up to 5 different gofer stream connections (based on url) can
+persist. If more than 5 are in the cache when a new connection is made then
+the cache is cleared before adding the new connection. Simple but effective.
+
+=head1 TO DO
+
+Document go_perl attribute
+
+Automatically reconnect (within reason) if there's a transport error.
+
+Decide on default for persistent connection - on or off? limits? ttl?
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=head1 SEE ALSO
+
+L<DBD::Gofer::Transport::Base>
+
+L<DBD::Gofer>
+
+=cut
diff --git a/lib/DBD/NullP.pm b/lib/DBD/NullP.pm
new file mode 100644
index 0000000..b1f8a71
--- /dev/null
+++ b/lib/DBD/NullP.pm
@@ -0,0 +1,166 @@
+{
+ package DBD::NullP;
+
+ require DBI;
+ require Carp;
+
+ @EXPORT = qw(); # Do NOT @EXPORT anything.
+ $VERSION = sprintf("12.%06d", q$Revision: 14714 $ =~ /(\d+)/o);
+
+# $Id: NullP.pm 14714 2011-02-22 17:27:07Z timbo $
+#
+# Copyright (c) 1994-2007 Tim Bunce
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+ $drh = undef; # holds driver handle once initialised
+
+ sub driver{
+ return $drh if $drh;
+ my($class, $attr) = @_;
+ $class .= "::dr";
+ ($drh) = DBI::_new_drh($class, {
+ 'Name' => 'NullP',
+ 'Version' => $VERSION,
+ 'Attribution' => 'DBD Example Null Perl stub by Tim Bunce',
+ }, [ qw'example implementors private data']);
+ $drh;
+ }
+
+ sub CLONE {
+ undef $drh;
+ }
+}
+
+
+{ package DBD::NullP::dr; # ====== DRIVER ======
+ $imp_data_size = 0;
+ use strict;
+
+ sub connect { # normally overridden, but a handy default
+ my $dbh = shift->SUPER::connect(@_)
+ or return;
+ $dbh->STORE(Active => 1);
+ $dbh;
+ }
+
+
+ sub DESTROY { undef }
+}
+
+
+{ package DBD::NullP::db; # ====== DATABASE ======
+ $imp_data_size = 0;
+ use strict;
+ use Carp qw(croak);
+
+ sub prepare {
+ my ($dbh, $statement)= @_;
+
+ my ($outer, $sth) = DBI::_new_sth($dbh, {
+ 'Statement' => $statement,
+ });
+
+ return $outer;
+ }
+
+ sub FETCH {
+ my ($dbh, $attrib) = @_;
+ # In reality this would interrogate the database engine to
+ # either return dynamic values that cannot be precomputed
+ # or fetch and cache attribute values too expensive to prefetch.
+ return $dbh->SUPER::FETCH($attrib);
+ }
+
+ sub STORE {
+ my ($dbh, $attrib, $value) = @_;
+ # would normally validate and only store known attributes
+ # else pass up to DBI to handle
+ if ($attrib eq 'AutoCommit') {
+ Carp::croak("Can't disable AutoCommit") unless $value;
+ # convert AutoCommit values to magic ones to let DBI
+ # know that the driver has 'handled' the AutoCommit attribute
+ $value = ($value) ? -901 : -900;
+ }
+ return $dbh->SUPER::STORE($attrib, $value);
+ }
+
+ sub ping { 1 }
+
+ sub disconnect {
+ shift->STORE(Active => 0);
+ }
+
+}
+
+
+{ package DBD::NullP::st; # ====== STATEMENT ======
+ $imp_data_size = 0;
+ use strict;
+
+ 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..@_);
+ if ($sth->{Statement} =~ m/^ \s* SELECT \s+/xmsi) {
+ $sth->STORE(NUM_OF_FIELDS => 1);
+ $sth->{NAME} = [ "fieldname" ];
+ # just for the sake of returning something, we return the params
+ my $params = $sth->{ParamValues} || {};
+ $sth->{dbd_nullp_data} = [ @{$params}{ sort keys %$params } ];
+ $sth->STORE(Active => 1);
+ }
+ # force a sleep - handy for testing
+ elsif ($sth->{Statement} =~ m/^ \s* SLEEP \s+ (\S+) /xmsi) {
+ my $secs = $1;
+ if (eval { require Time::HiRes; defined &Time::HiRes::sleep }) {
+ Time::HiRes::sleep($secs);
+ }
+ else {
+ sleep $secs;
+ }
+ }
+ # force an error - handy for testing
+ elsif ($sth->{Statement} =~ m/^ \s* ERROR \s+ (\d+) \s* (.*) /xmsi) {
+ return $sth->set_err($1, $2);
+ }
+ # anything else is silently ignored, sucessfully
+ 1;
+ }
+
+ sub fetchrow_arrayref {
+ my $sth = shift;
+ my $data = $sth->{dbd_nullp_data};
+ if (!$data || !@$data) {
+ $sth->finish; # no more data so finish
+ return undef;
+ }
+ return $sth->_set_fbav(shift @$data);
+ }
+ *fetch = \&fetchrow_arrayref; # alias
+
+ sub FETCH {
+ my ($sth, $attrib) = @_;
+ # would normally validate and only fetch known attributes
+ # else pass up to DBI to handle
+ return $sth->SUPER::FETCH($attrib);
+ }
+
+ sub STORE {
+ my ($sth, $attrib, $value) = @_;
+ # would normally validate and only store known attributes
+ # else pass up to DBI to handle
+ return $sth->SUPER::STORE($attrib, $value);
+ }
+
+}
+
+1;
diff --git a/lib/DBD/Proxy.pm b/lib/DBD/Proxy.pm
new file mode 100644
index 0000000..6c9e14d
--- /dev/null
+++ b/lib/DBD/Proxy.pm
@@ -0,0 +1,997 @@
+# -*- perl -*-
+#
+#
+# DBD::Proxy - DBI Proxy driver
+#
+#
+# Copyright (c) 1997,1998 Jochen Wiedmann
+#
+# The DBD::Proxy module is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself. In particular permission
+# is granted to Tim Bunce for distributing this as a part of the DBI.
+#
+#
+# Author: Jochen Wiedmann
+# Am Eisteich 9
+# 72555 Metzingen
+# Germany
+#
+# Email: joe@ispsoft.de
+# Phone: +49 7123 14881
+#
+
+use strict;
+use Carp;
+
+require DBI;
+DBI->require_version(1.0201);
+
+use RPC::PlClient 0.2000; # XXX change to 0.2017 once it's released
+
+{ package DBD::Proxy::RPC::PlClient;
+ @DBD::Proxy::RPC::PlClient::ISA = qw(RPC::PlClient);
+ sub Call {
+ my $self = shift;
+ if ($self->{debug}) {
+ my ($rpcmeth, $obj, $method, @args) = @_;
+ local $^W; # silence undefs
+ Carp::carp("Server $rpcmeth $method(@args)");
+ }
+ return $self->SUPER::Call(@_);
+ }
+}
+
+
+package DBD::Proxy;
+
+use vars qw($VERSION $drh %ATTR);
+
+$VERSION = "0.2004";
+
+$drh = undef; # holds driver handle once initialised
+
+%ATTR = ( # common to db & st, see also %ATTR in DBD::Proxy::db & ::st
+ 'Warn' => 'local',
+ 'Active' => 'local',
+ 'Kids' => 'local',
+ 'CachedKids' => 'local',
+ 'PrintError' => 'local',
+ 'RaiseError' => 'local',
+ 'HandleError' => 'local',
+ 'TraceLevel' => 'cached',
+ 'CompatMode' => 'local',
+);
+
+sub driver ($$) {
+ if (!$drh) {
+ my($class, $attr) = @_;
+
+ $class .= "::dr";
+
+ $drh = DBI::_new_drh($class, {
+ 'Name' => 'Proxy',
+ 'Version' => $VERSION,
+ 'Attribution' => 'DBD::Proxy by Jochen Wiedmann',
+ });
+ $drh->STORE(CompatMode => 1); # disable DBI dispatcher attribute cache (for FETCH)
+ }
+ $drh;
+}
+
+sub CLONE {
+ undef $drh;
+}
+
+sub proxy_set_err {
+ my ($h,$errmsg) = @_;
+ my ($err, $state) = ($errmsg =~ s/ \[err=(.*?),state=(.*?)\]//)
+ ? ($1, $2) : (1, ' ' x 5);
+ return $h->set_err($err, $errmsg, $state);
+}
+
+package DBD::Proxy::dr; # ====== DRIVER ======
+
+$DBD::Proxy::dr::imp_data_size = 0;
+
+sub connect ($$;$$) {
+ my($drh, $dsn, $user, $auth, $attr)= @_;
+ my($dsnOrig) = $dsn;
+
+ my %attr = %$attr;
+ my ($var, $val);
+ while (length($dsn)) {
+ if ($dsn =~ /^dsn=(.*)/) {
+ $attr{'dsn'} = $1;
+ last;
+ }
+ if ($dsn =~ /^(.*?);(.*)/) {
+ $var = $1;
+ $dsn = $2;
+ } else {
+ $var = $dsn;
+ $dsn = '';
+ }
+ if ($var =~ /^(.*?)=(.*)/) {
+ $var = $1;
+ $val = $2;
+ $attr{$var} = $val;
+ }
+ }
+
+ my $err = '';
+ if (!defined($attr{'hostname'})) { $err .= " Missing hostname."; }
+ if (!defined($attr{'port'})) { $err .= " Missing port."; }
+ if (!defined($attr{'dsn'})) { $err .= " Missing remote dsn."; }
+
+ # Create a cipher object, if requested
+ my $cipherRef = undef;
+ if ($attr{'cipher'}) {
+ $cipherRef = eval { $attr{'cipher'}->new(pack('H*',
+ $attr{'key'})) };
+ if ($@) { $err .= " Cannot create cipher object: $@."; }
+ }
+ my $userCipherRef = undef;
+ if ($attr{'userkey'}) {
+ my $cipher = $attr{'usercipher'} || $attr{'cipher'};
+ $userCipherRef = eval { $cipher->new(pack('H*', $attr{'userkey'})) };
+ if ($@) { $err .= " Cannot create usercipher object: $@."; }
+ }
+
+ return DBD::Proxy::proxy_set_err($drh, $err) if $err; # Returns undef
+
+ my %client_opts = (
+ 'peeraddr' => $attr{'hostname'},
+ 'peerport' => $attr{'port'},
+ 'socket_proto' => 'tcp',
+ 'application' => $attr{dsn},
+ 'user' => $user || '',
+ 'password' => $auth || '',
+ 'version' => $DBD::Proxy::VERSION,
+ 'cipher' => $cipherRef,
+ 'debug' => $attr{debug} || 0,
+ 'timeout' => $attr{timeout} || undef,
+ 'logfile' => $attr{logfile} || undef
+ );
+ # Options starting with 'proxy_rpc_' are forwarded to the RPC layer after
+ # stripping the prefix.
+ while (my($var,$val) = each %attr) {
+ if ($var =~ s/^proxy_rpc_//) {
+ $client_opts{$var} = $val;
+ }
+ }
+ # Create an RPC::PlClient object.
+ my($client, $msg) = eval { DBD::Proxy::RPC::PlClient->new(%client_opts) };
+
+ return DBD::Proxy::proxy_set_err($drh, "Cannot log in to DBI::ProxyServer: $@")
+ if $@; # Returns undef
+ return DBD::Proxy::proxy_set_err($drh, "Constructor didn't return a handle: $msg")
+ unless ($msg =~ /^((?:\w+|\:\:)+)=(\w+)/); # Returns undef
+
+ $msg = RPC::PlClient::Object->new($1, $client, $msg);
+
+ my $max_proto_ver;
+ my ($server_ver_str) = eval { $client->Call('Version') };
+ if ( $@ ) {
+ # Server denies call, assume legacy protocol.
+ $max_proto_ver = 1;
+ } else {
+ # Parse proxy server version.
+ my ($server_ver_num) = $server_ver_str =~ /^DBI::ProxyServer\s+([\d\.]+)/;
+ $max_proto_ver = $server_ver_num >= 0.3 ? 2 : 1;
+ }
+ my $req_proto_ver;
+ if ( exists $attr{proxy_lazy_prepare} ) {
+ $req_proto_ver = ($attr{proxy_lazy_prepare} == 0) ? 2 : 1;
+ return DBD::Proxy::proxy_set_err($drh,
+ "DBI::ProxyServer does not support synchronous statement preparation.")
+ if $max_proto_ver < $req_proto_ver;
+ }
+
+ # Switch to user specific encryption mode, if desired
+ if ($userCipherRef) {
+ $client->{'cipher'} = $userCipherRef;
+ }
+
+ # create a 'blank' dbh
+ my $this = DBI::_new_dbh($drh, {
+ 'Name' => $dsnOrig,
+ 'proxy_dbh' => $msg,
+ 'proxy_client' => $client,
+ 'RowCacheSize' => $attr{'RowCacheSize'} || 20,
+ 'proxy_proto_ver' => $req_proto_ver || 1
+ });
+
+ foreach $var (keys %attr) {
+ if ($var =~ /proxy_/) {
+ $this->{$var} = $attr{$var};
+ }
+ }
+ $this->SUPER::STORE('Active' => 1);
+
+ $this;
+}
+
+
+sub DESTROY { undef }
+
+
+package DBD::Proxy::db; # ====== DATABASE ======
+
+$DBD::Proxy::db::imp_data_size = 0;
+
+# XXX probably many more methods need to be added here
+# in order to trigger our AUTOLOAD to redirect them to the server.
+# (Unless the sub is declared it's bypassed by perl method lookup.)
+# See notes in ToDo about method metadata
+# The question is whether to add all the methods in %DBI::DBI_methods
+# to the corresponding classes (::db, ::st etc)
+# Also need to consider methods that, if proxied, would change the server state
+# in a way that might not be visible on the client, ie begin_work -> AutoCommit.
+
+sub commit;
+sub connected;
+sub rollback;
+sub ping;
+
+
+use vars qw(%ATTR $AUTOLOAD);
+
+# inherited: STORE / FETCH against this class.
+# local: STORE / FETCH against parent class.
+# cached: STORE to remote and local objects, FETCH from local.
+# remote: STORE / FETCH against remote object only (default).
+#
+# Note: Attribute names starting with 'proxy_' always treated as 'inherited'.
+#
+%ATTR = ( # see also %ATTR in DBD::Proxy::st
+ %DBD::Proxy::ATTR,
+ RowCacheSize => 'inherited',
+ #AutoCommit => 'cached',
+ 'FetchHashKeyName' => 'cached',
+ Statement => 'local',
+ Driver => 'local',
+ dbi_connect_closure => 'local',
+ Username => 'local',
+);
+
+sub AUTOLOAD {
+ my $method = $AUTOLOAD;
+ $method =~ s/(.*::(.*)):://;
+ my $class = $1;
+ my $type = $2;
+ #warn "AUTOLOAD of $method (class=$class, type=$type)";
+ my %expand = (
+ 'method' => $method,
+ 'class' => $class,
+ 'type' => $type,
+ 'call' => "$method(\@_)",
+ # XXX was trying to be smart but was tripping up over the DBI's own
+ # smartness. Disabled, but left here in case there are issues.
+ # 'call' => (UNIVERSAL::can("DBI::_::$type", $method)) ? "$method(\@_)" : "func(\@_, '$method')",
+ );
+
+ my $method_code = q{
+ package ~class~;
+ sub ~method~ {
+ my $h = shift;
+ local $@;
+ my @result = wantarray
+ ? eval { $h->{'proxy_~type~h'}->~call~ }
+ : eval { scalar $h->{'proxy_~type~h'}->~call~ };
+ return DBD::Proxy::proxy_set_err($h, $@) if $@;
+ return wantarray ? @result : $result[0];
+ }
+ };
+ $method_code =~ s/\~(\w+)\~/$expand{$1}/eg;
+ local $SIG{__DIE__} = 'DEFAULT';
+ my $err = do { local $@; eval $method_code.2; $@ };
+ die $err if $err;
+ goto &$AUTOLOAD;
+}
+
+sub DESTROY {
+ my $dbh = shift;
+ local $@ if $@; # protect $@
+ $dbh->disconnect if $dbh->SUPER::FETCH('Active');
+}
+
+sub disconnect ($) {
+ my ($dbh) = @_;
+
+ # Sadly the Proxy too-often disagrees with the backend database
+ # on the subject of 'Active'. In the short term, I'd like the
+ # Proxy to ease up and let me decide when it's proper to go over
+ # the wire. This ultimately applies to finish() as well.
+ #return unless $dbh->SUPER::FETCH('Active');
+
+ # Drop database connection at remote end
+ my $rdbh = $dbh->{'proxy_dbh'};
+ if ( $rdbh ) {
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ eval { $rdbh->disconnect() } ;
+ DBD::Proxy::proxy_set_err($dbh, $@) if $@;
+ }
+
+ # Close TCP connect to remote
+ # XXX possibly best left till DESTROY? Add a config attribute to choose?
+ #$dbh->{proxy_client}->Disconnect(); # Disconnect method requires newer PlRPC module
+ $dbh->{proxy_client}->{socket} = undef; # hack
+
+ $dbh->SUPER::STORE('Active' => 0);
+ 1;
+}
+
+
+sub STORE ($$$) {
+ my($dbh, $attr, $val) = @_;
+ my $type = $ATTR{$attr} || 'remote';
+
+ if ($attr eq 'TraceLevel') {
+ warn("TraceLevel $val");
+ my $pc = $dbh->{proxy_client} || die;
+ $pc->{logfile} ||= 1; # XXX hack
+ $pc->{debug} = ($val && $val >= 4);
+ $pc->Debug("$pc debug enabled") if $pc->{debug};
+ }
+
+ if ($attr =~ /^proxy_/ || $type eq 'inherited') {
+ $dbh->{$attr} = $val;
+ return 1;
+ }
+
+ if ($type eq 'remote' || $type eq 'cached') {
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ my $result = eval { $dbh->{'proxy_dbh'}->STORE($attr => $val) };
+ return DBD::Proxy::proxy_set_err($dbh, $@) if $@; # returns undef
+ $dbh->SUPER::STORE($attr => $val) if $type eq 'cached';
+ return $result;
+ }
+ return $dbh->SUPER::STORE($attr => $val);
+}
+
+sub FETCH ($$) {
+ my($dbh, $attr) = @_;
+ # we only get here for cached attribute values if the handle is in CompatMode
+ # otherwise the DBI dispatcher handles the FETCH itself from the attribute cache.
+ my $type = $ATTR{$attr} || 'remote';
+
+ if ($attr =~ /^proxy_/ || $type eq 'inherited' || $type eq 'cached') {
+ return $dbh->{$attr};
+ }
+
+ return $dbh->SUPER::FETCH($attr) unless $type eq 'remote';
+
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ my $result = eval { $dbh->{'proxy_dbh'}->FETCH($attr) };
+ return DBD::Proxy::proxy_set_err($dbh, $@) if $@;
+ return $result;
+}
+
+sub prepare ($$;$) {
+ my($dbh, $stmt, $attr) = @_;
+ my $sth = DBI::_new_sth($dbh, {
+ 'Statement' => $stmt,
+ 'proxy_attr' => $attr,
+ 'proxy_cache_only' => 0,
+ 'proxy_params' => [],
+ }
+ );
+ my $proto_ver = $dbh->{'proxy_proto_ver'};
+ if ( $proto_ver > 1 ) {
+ $sth->{'proxy_attr_cache'} = {cache_filled => 0};
+ my $rdbh = $dbh->{'proxy_dbh'};
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ my $rsth = eval { $rdbh->prepare($sth->{'Statement'}, $sth->{'proxy_attr'}, undef, $proto_ver) };
+ return DBD::Proxy::proxy_set_err($sth, $@) if $@;
+ return DBD::Proxy::proxy_set_err($sth, "Constructor didn't return a handle: $rsth")
+ unless ($rsth =~ /^((?:\w+|\:\:)+)=(\w+)/);
+
+ my $client = $dbh->{'proxy_client'};
+ $rsth = RPC::PlClient::Object->new($1, $client, $rsth);
+
+ $sth->{'proxy_sth'} = $rsth;
+ # If statement is a positioned update we do not want any readahead.
+ $sth->{'RowCacheSize'} = 1 if $stmt =~ /\bfor\s+update\b/i;
+ # Since resources are used by prepared remote handle, mark us active.
+ $sth->SUPER::STORE(Active => 1);
+ }
+ $sth;
+}
+
+sub quote {
+ my $dbh = shift;
+ my $proxy_quote = $dbh->{proxy_quote} || 'remote';
+
+ return $dbh->SUPER::quote(@_)
+ if $proxy_quote eq 'local' && @_ == 1;
+
+ # For the common case of only a single argument
+ # (no $data_type) we could learn and cache the behaviour.
+ # Or we could probe the driver with a few test cases.
+ # Or we could add a way to ask the DBI::ProxyServer
+ # if $dbh->can('quote') == \&DBI::_::db::quote.
+ # Tim
+ #
+ # Sounds all *very* smart to me. I'd rather suggest to
+ # implement some of the typical quote possibilities
+ # and let the user set
+ # $dbh->{'proxy_quote'} = 'backslash_escaped';
+ # for example.
+ # Jochen
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ my $result = eval { $dbh->{'proxy_dbh'}->quote(@_) };
+ return DBD::Proxy::proxy_set_err($dbh, $@) if $@;
+ return $result;
+}
+
+sub table_info {
+ my $dbh = shift;
+ my $rdbh = $dbh->{'proxy_dbh'};
+ #warn "table_info(@_)";
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ my($numFields, $names, $types, @rows) = eval { $rdbh->table_info(@_) };
+ return DBD::Proxy::proxy_set_err($dbh, $@) if $@;
+ my ($sth, $inner) = DBI::_new_sth($dbh, {
+ 'Statement' => "SHOW TABLES",
+ 'proxy_params' => [],
+ 'proxy_data' => \@rows,
+ 'proxy_attr_cache' => {
+ 'NUM_OF_PARAMS' => 0,
+ 'NUM_OF_FIELDS' => $numFields,
+ 'NAME' => $names,
+ 'TYPE' => $types,
+ 'cache_filled' => 1
+ },
+ 'proxy_cache_only' => 1,
+ });
+ $sth->SUPER::STORE('NUM_OF_FIELDS' => $numFields);
+ $inner->{NAME} = $names;
+ $inner->{TYPE} = $types;
+ $sth->SUPER::STORE('Active' => 1); # already execute()'d
+ $sth->{'proxy_rows'} = @rows;
+ return $sth;
+}
+
+sub tables {
+ my $dbh = shift;
+ #warn "tables(@_)";
+ return $dbh->SUPER::tables(@_);
+}
+
+
+sub type_info_all {
+ my $dbh = shift;
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ my $result = eval { $dbh->{'proxy_dbh'}->type_info_all(@_) };
+ return DBD::Proxy::proxy_set_err($dbh, $@) if $@;
+ return $result;
+}
+
+
+package DBD::Proxy::st; # ====== STATEMENT ======
+
+$DBD::Proxy::st::imp_data_size = 0;
+
+use vars qw(%ATTR);
+
+# inherited: STORE to current object. FETCH from current if exists, else call up
+# to the (proxy) database object.
+# local: STORE / FETCH against parent class.
+# cache_only: STORE noop (read-only). FETCH from private_* if exists, else call
+# remote and cache the result.
+# remote: STORE / FETCH against remote object only (default).
+#
+# Note: Attribute names starting with 'proxy_' always treated as 'inherited'.
+#
+%ATTR = ( # see also %ATTR in DBD::Proxy::db
+ %DBD::Proxy::ATTR,
+ 'Database' => 'local',
+ 'RowsInCache' => 'local',
+ 'RowCacheSize' => 'inherited',
+ 'NULLABLE' => 'cache_only',
+ 'NAME' => 'cache_only',
+ 'TYPE' => 'cache_only',
+ 'PRECISION' => 'cache_only',
+ 'SCALE' => 'cache_only',
+ 'NUM_OF_FIELDS' => 'cache_only',
+ 'NUM_OF_PARAMS' => 'cache_only'
+);
+
+*AUTOLOAD = \&DBD::Proxy::db::AUTOLOAD;
+
+sub execute ($@) {
+ my $sth = shift;
+ my $params = @_ ? \@_ : $sth->{'proxy_params'};
+
+ # new execute, so delete any cached rows from previous execute
+ undef $sth->{'proxy_data'};
+ undef $sth->{'proxy_rows'};
+
+ my $rsth = $sth->{proxy_sth};
+ my $dbh = $sth->FETCH('Database');
+ my $proto_ver = $dbh->{proxy_proto_ver};
+
+ my ($numRows, @outData);
+
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ if ( $proto_ver > 1 ) {
+ ($numRows, @outData) = eval { $rsth->execute($params, $proto_ver) };
+ return DBD::Proxy::proxy_set_err($sth, $@) if $@;
+
+ # Attributes passed back only on the first execute() of a statement.
+ unless ($sth->{proxy_attr_cache}->{cache_filled}) {
+ my ($numFields, $numParams, $names, $types) = splice(@outData, 0, 4);
+ $sth->{'proxy_attr_cache'} = {
+ 'NUM_OF_FIELDS' => $numFields,
+ 'NUM_OF_PARAMS' => $numParams,
+ 'NAME' => $names,
+ 'cache_filled' => 1
+ };
+ $sth->SUPER::STORE('NUM_OF_FIELDS' => $numFields);
+ $sth->SUPER::STORE('NUM_OF_PARAMS' => $numParams);
+ }
+
+ }
+ else {
+ if ($rsth) {
+ ($numRows, @outData) = eval { $rsth->execute($params, $proto_ver) };
+ return DBD::Proxy::proxy_set_err($sth, $@) if $@;
+
+ }
+ else {
+ my $rdbh = $dbh->{'proxy_dbh'};
+
+ # Legacy prepare is actually prepare + first execute on the server.
+ ($rsth, @outData) =
+ eval { $rdbh->prepare($sth->{'Statement'},
+ $sth->{'proxy_attr'}, $params, $proto_ver) };
+ return DBD::Proxy::proxy_set_err($sth, $@) if $@;
+ return DBD::Proxy::proxy_set_err($sth, "Constructor didn't return a handle: $rsth")
+ unless ($rsth =~ /^((?:\w+|\:\:)+)=(\w+)/);
+
+ my $client = $dbh->{'proxy_client'};
+ $rsth = RPC::PlClient::Object->new($1, $client, $rsth);
+
+ my ($numFields, $numParams, $names, $types) = splice(@outData, 0, 4);
+ $sth->{'proxy_sth'} = $rsth;
+ $sth->{'proxy_attr_cache'} = {
+ 'NUM_OF_FIELDS' => $numFields,
+ 'NUM_OF_PARAMS' => $numParams,
+ 'NAME' => $names
+ };
+ $sth->SUPER::STORE('NUM_OF_FIELDS' => $numFields);
+ $sth->SUPER::STORE('NUM_OF_PARAMS' => $numParams);
+ $numRows = shift @outData;
+ }
+ }
+ # Always condition active flag.
+ $sth->SUPER::STORE('Active' => 1) if $sth->FETCH('NUM_OF_FIELDS'); # is SELECT
+ $sth->{'proxy_rows'} = $numRows;
+ # Any remaining items are output params.
+ if (@outData) {
+ foreach my $p (@$params) {
+ if (ref($p->[0])) {
+ my $ref = shift @outData;
+ ${$p->[0]} = $$ref;
+ }
+ }
+ }
+
+ $sth->{'proxy_rows'} || '0E0';
+}
+
+sub fetch ($) {
+ my $sth = shift;
+
+ my $data = $sth->{'proxy_data'};
+
+ $sth->{'proxy_rows'} = 0 unless defined $sth->{'proxy_rows'};
+
+ if(!$data || !@$data) {
+ return undef unless $sth->SUPER::FETCH('Active');
+
+ my $rsth = $sth->{'proxy_sth'};
+ if (!$rsth) {
+ die "Attempt to fetch row without execute";
+ }
+ my $num_rows = $sth->FETCH('RowCacheSize') || 20;
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ my @rows = eval { $rsth->fetch($num_rows) };
+ return DBD::Proxy::proxy_set_err($sth, $@) if $@;
+ unless (@rows == $num_rows) {
+ undef $sth->{'proxy_data'};
+ # server side has already called finish
+ $sth->SUPER::STORE(Active => 0);
+ }
+ return undef unless @rows;
+ $sth->{'proxy_data'} = $data = [@rows];
+ }
+ my $row = shift @$data;
+
+ $sth->SUPER::STORE(Active => 0) if ( $sth->{proxy_cache_only} and !@$data );
+ $sth->{'proxy_rows'}++;
+ return $sth->_set_fbav($row);
+}
+*fetchrow_arrayref = \&fetch;
+
+sub rows ($) {
+ my $rows = shift->{'proxy_rows'};
+ return (defined $rows) ? $rows : -1;
+}
+
+sub finish ($) {
+ my($sth) = @_;
+ return 1 unless $sth->SUPER::FETCH('Active');
+ my $rsth = $sth->{'proxy_sth'};
+ $sth->SUPER::STORE('Active' => 0);
+ return 0 unless $rsth; # Something's out of sync
+ my $no_finish = exists($sth->{'proxy_no_finish'})
+ ? $sth->{'proxy_no_finish'}
+ : $sth->FETCH('Database')->{'proxy_no_finish'};
+ unless ($no_finish) {
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ my $result = eval { $rsth->finish() };
+ return DBD::Proxy::proxy_set_err($sth, $@) if $@;
+ return $result;
+ }
+ 1;
+}
+
+sub STORE ($$$) {
+ my($sth, $attr, $val) = @_;
+ my $type = $ATTR{$attr} || 'remote';
+
+ if ($attr =~ /^proxy_/ || $type eq 'inherited') {
+ $sth->{$attr} = $val;
+ return 1;
+ }
+
+ if ($type eq 'cache_only') {
+ return 0;
+ }
+
+ if ($type eq 'remote' || $type eq 'cached') {
+ my $rsth = $sth->{'proxy_sth'} or return undef;
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ my $result = eval { $rsth->STORE($attr => $val) };
+ return DBD::Proxy::proxy_set_err($sth, $@) if ($@);
+ return $result if $type eq 'remote'; # else fall through to cache locally
+ }
+ return $sth->SUPER::STORE($attr => $val);
+}
+
+sub FETCH ($$) {
+ my($sth, $attr) = @_;
+
+ if ($attr =~ /^proxy_/) {
+ return $sth->{$attr};
+ }
+
+ my $type = $ATTR{$attr} || 'remote';
+ if ($type eq 'inherited') {
+ if (exists($sth->{$attr})) {
+ return $sth->{$attr};
+ }
+ return $sth->FETCH('Database')->{$attr};
+ }
+
+ if ($type eq 'cache_only' &&
+ exists($sth->{'proxy_attr_cache'}->{$attr})) {
+ return $sth->{'proxy_attr_cache'}->{$attr};
+ }
+
+ if ($type ne 'local') {
+ my $rsth = $sth->{'proxy_sth'} or return undef;
+ local $SIG{__DIE__} = 'DEFAULT';
+ local $@;
+ my $result = eval { $rsth->FETCH($attr) };
+ return DBD::Proxy::proxy_set_err($sth, $@) if $@;
+ return $result;
+ }
+ elsif ($attr eq 'RowsInCache') {
+ my $data = $sth->{'proxy_data'};
+ $data ? @$data : 0;
+ }
+ else {
+ $sth->SUPER::FETCH($attr);
+ }
+}
+
+sub bind_param ($$$@) {
+ my $sth = shift; my $param = shift;
+ $sth->{'proxy_params'}->[$param-1] = [@_];
+}
+*bind_param_inout = \&bind_param;
+
+sub DESTROY {
+ my $sth = shift;
+ $sth->finish if $sth->SUPER::FETCH('Active');
+}
+
+
+1;
+
+
+__END__
+
+=head1 NAME
+
+DBD::Proxy - A proxy driver for the DBI
+
+=head1 SYNOPSIS
+
+ use DBI;
+
+ $dbh = DBI->connect("dbi:Proxy:hostname=$host;port=$port;dsn=$db",
+ $user, $passwd);
+
+ # See the DBI module documentation for full details
+
+=head1 DESCRIPTION
+
+DBD::Proxy is a Perl module for connecting to a database via a remote
+DBI driver. See L<DBD::Gofer> for an alternative with different trade-offs.
+
+This is of course not needed for DBI drivers which already
+support connecting to a remote database, but there are engines which
+don't offer network connectivity.
+
+Another application is offering database access through a firewall, as
+the driver offers query based restrictions. For example you can
+restrict queries to exactly those that are used in a given CGI
+application.
+
+Speaking of CGI, another application is (or rather, will be) to reduce
+the database connect/disconnect overhead from CGI scripts by using
+proxying the connect_cached method. The proxy server will hold the
+database connections open in a cache. The CGI script then trades the
+database connect/disconnect overhead for the DBD::Proxy
+connect/disconnect overhead which is typically much less.
+I<Note that the connect_cached method is new and still experimental.>
+
+
+=head1 CONNECTING TO THE DATABASE
+
+Before connecting to a remote database, you must ensure, that a Proxy
+server is running on the remote machine. There's no default port, so
+you have to ask your system administrator for the port number. See
+L<DBI::ProxyServer> for details.
+
+Say, your Proxy server is running on machine "alpha", port 3334, and
+you'd like to connect to an ODBC database called "mydb" as user "joe"
+with password "hello". When using DBD::ODBC directly, you'd do a
+
+ $dbh = DBI->connect("DBI:ODBC:mydb", "joe", "hello");
+
+With DBD::Proxy this becomes
+
+ $dsn = "DBI:Proxy:hostname=alpha;port=3334;dsn=DBI:ODBC:mydb";
+ $dbh = DBI->connect($dsn, "joe", "hello");
+
+You see, this is mainly the same. The DBD::Proxy module will create a
+connection to the Proxy server on "alpha" which in turn will connect
+to the ODBC database.
+
+Refer to the L<DBI> documentation on the C<connect> method for a way
+to automatically use DBD::Proxy without having to change your code.
+
+DBD::Proxy's DSN string has the format
+
+ $dsn = "DBI:Proxy:key1=val1; ... ;keyN=valN;dsn=valDSN";
+
+In other words, it is a collection of key/value pairs. The following
+keys are recognized:
+
+=over 4
+
+=item hostname
+
+=item port
+
+Hostname and port of the Proxy server; these keys must be present,
+no defaults. Example:
+
+ hostname=alpha;port=3334
+
+=item dsn
+
+The value of this attribute will be used as a dsn name by the Proxy
+server. Thus it must have the format C<DBI:driver:...>, in particular
+it will contain colons. The I<dsn> value may contain semicolons, hence
+this key *must* be the last and it's value will be the complete
+remaining part of the dsn. Example:
+
+ dsn=DBI:ODBC:mydb
+
+=item cipher
+
+=item key
+
+=item usercipher
+
+=item userkey
+
+By using these fields you can enable encryption. If you set,
+for example,
+
+ cipher=$class;key=$key
+
+(note the semicolon) then DBD::Proxy will create a new cipher object
+by executing
+
+ $cipherRef = $class->new(pack("H*", $key));
+
+and pass this object to the RPC::PlClient module when creating a
+client. See L<RPC::PlClient>. Example:
+
+ cipher=IDEA;key=97cd2375efa329aceef2098babdc9721
+
+The usercipher/userkey attributes allow you to use two phase encryption:
+The cipher/key encryption will be used in the login and authorisation
+phase. Once the client is authorised, he will change to usercipher/userkey
+encryption. Thus the cipher/key pair is a B<host> based secret, typically
+less secure than the usercipher/userkey secret and readable by anyone.
+The usercipher/userkey secret is B<your> private secret.
+
+Of course encryption requires an appropriately configured server. See
+<DBD::ProxyServer/CONFIGURATION FILE>.
+
+=item debug
+
+Turn on debugging mode
+
+=item stderr
+
+This attribute will set the corresponding attribute of the RPC::PlClient
+object, thus logging will not use syslog(), but redirected to stderr.
+This is the default under Windows.
+
+ stderr=1
+
+=item logfile
+
+Similar to the stderr attribute, but output will be redirected to the
+given file.
+
+ logfile=/dev/null
+
+=item RowCacheSize
+
+The DBD::Proxy driver supports this attribute (which is DBI standard,
+as of DBI 1.02). It's used to reduce network round-trips by fetching
+multiple rows in one go. The current default value is 20, but this may
+change.
+
+
+=item proxy_no_finish
+
+This attribute can be used to reduce network traffic: If the
+application is calling $sth->finish() then the proxy tells the server
+to finish the remote statement handle. Of course this slows down things
+quite a lot, but is perfectly good for reducing memory usage with
+persistent connections.
+
+However, if you set the I<proxy_no_finish> attribute to a TRUE value,
+either in the database handle or in the statement handle, then finish()
+calls will be supressed. This is what you want, for example, in small
+and fast CGI applications.
+
+=item proxy_quote
+
+This attribute can be used to reduce network traffic: By default calls
+to $dbh->quote() are passed to the remote driver. Of course this slows
+down things quite a lot, but is the safest default behaviour.
+
+However, if you set the I<proxy_quote> attribute to the value 'C<local>'
+either in the database handle or in the statement handle, and the call
+to quote has only one parameter, then the local default DBI quote
+method will be used (which will be faster but may be wrong).
+
+=back
+
+=head1 KNOWN ISSUES
+
+=head2 Unproxied method calls
+
+If a method isn't being proxied, try declaring a stub sub in the appropriate
+package (DBD::Proxy::db for a dbh method, and DBD::Proxy::st for an sth method).
+For example:
+
+ sub DBD::Proxy::db::selectall_arrayref;
+
+That will enable selectall_arrayref to be proxied.
+
+Currently many methods aren't explicitly proxied and so you get the DBI's
+default methods executed on the client.
+
+Some of those methods, like selectall_arrayref, may then call other methods
+that are proxied (selectall_arrayref calls fetchall_arrayref which calls fetch
+which is proxied). So things may appear to work but operate more slowly than
+the could.
+
+This may all change in a later version.
+
+=head2 Complex handle attributes
+
+Sometimes handles are having complex attributes like hash refs or
+array refs and not simple strings or integers. For example, with
+DBD::CSV, you would like to write something like
+
+ $dbh->{"csv_tables"}->{"passwd"} =
+ { "sep_char" => ":", "eol" => "\n";
+
+The above example would advice the CSV driver to assume the file
+"passwd" to be in the format of the /etc/passwd file: Colons as
+separators and a line feed without carriage return as line
+terminator.
+
+Surprisingly this example doesn't work with the proxy driver. To understand
+the reasons, you should consider the following: The Perl compiler is
+executing the above example in two steps:
+
+=over
+
+=item 1
+
+The first step is fetching the value of the key "csv_tables" in the
+handle $dbh. The value returned is complex, a hash ref.
+
+=item 2
+
+The second step is storing some value (the right hand side of the
+assignment) as the key "passwd" in the hash ref from step 1.
+
+=back
+
+This becomes a little bit clearer, if we rewrite the above code:
+
+ $tables = $dbh->{"csv_tables"};
+ $tables->{"passwd"} = { "sep_char" => ":", "eol" => "\n";
+
+While the examples work fine without the proxy, the fail due to a
+subtle difference in step 1: By DBI magic, the hash ref
+$dbh->{'csv_tables'} is returned from the server to the client.
+The client creates a local copy. This local copy is the result of
+step 1. In other words, step 2 modifies a local copy of the hash ref,
+but not the server's hash ref.
+
+The workaround is storing the modified local copy back to the server:
+
+ $tables = $dbh->{"csv_tables"};
+ $tables->{"passwd"} = { "sep_char" => ":", "eol" => "\n";
+ $dbh->{"csv_tables"} = $tables;
+
+
+=head1 AUTHOR AND COPYRIGHT
+
+This module is Copyright (c) 1997, 1998
+
+ Jochen Wiedmann
+ Am Eisteich 9
+ 72555 Metzingen
+ Germany
+
+ Email: joe@ispsoft.de
+ Phone: +49 7123 14887
+
+The DBD::Proxy module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. In particular permission
+is granted to Tim Bunce for distributing this as a part of the DBI.
+
+
+=head1 SEE ALSO
+
+L<DBI>, L<RPC::PlClient>, L<Storable>
+
+=cut
diff --git a/lib/DBD/Sponge.pm b/lib/DBD/Sponge.pm
new file mode 100644
index 0000000..2413bc0
--- /dev/null
+++ b/lib/DBD/Sponge.pm
@@ -0,0 +1,305 @@
+{
+ package DBD::Sponge;
+
+ require DBI;
+ require Carp;
+
+ our @EXPORT = qw(); # Do NOT @EXPORT anything.
+ our $VERSION = sprintf("12.%06d", q$Revision: 10002 $ =~ /(\d+)/o);
+
+
+# $Id: Sponge.pm 10002 2007-09-26 21:03:25Z timbo $
+#
+# Copyright (c) 1994-2003 Tim Bunce Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+ $drh = undef; # holds driver handle once initialised
+ my $methods_already_installed;
+
+ sub driver{
+ return $drh if $drh;
+
+ DBD::Sponge::db->install_method("sponge_test_installed_method")
+ unless $methods_already_installed++;
+
+ my($class, $attr) = @_;
+ $class .= "::dr";
+ ($drh) = DBI::_new_drh($class, {
+ 'Name' => 'Sponge',
+ 'Version' => $VERSION,
+ 'Attribution' => "DBD::Sponge $VERSION (fake cursor driver) by Tim Bunce",
+ });
+ $drh;
+ }
+
+ sub CLONE {
+ undef $drh;
+ }
+}
+
+
+{ package DBD::Sponge::dr; # ====== DRIVER ======
+ $imp_data_size = 0;
+ # we use default (dummy) connect method
+}
+
+
+{ package DBD::Sponge::db; # ====== DATABASE ======
+ $imp_data_size = 0;
+ use strict;
+
+ sub prepare {
+ my($dbh, $statement, $attribs) = @_;
+ my $rows = delete $attribs->{'rows'}
+ or return $dbh->set_err($DBI::stderr,"No rows attribute supplied to prepare");
+ my ($outer, $sth) = DBI::_new_sth($dbh, {
+ 'Statement' => $statement,
+ 'rows' => $rows,
+ (map { exists $attribs->{$_} ? ($_=>$attribs->{$_}) : () }
+ qw(execute_hook)
+ ),
+ });
+ if (my $behave_like = $attribs->{behave_like}) {
+ $outer->{$_} = $behave_like->{$_}
+ foreach (qw(RaiseError PrintError HandleError ShowErrorStatement));
+ }
+
+ if ($statement =~ /^\s*insert\b/) { # very basic, just for testing execute_array()
+ $sth->{is_insert} = 1;
+ my $NUM_OF_PARAMS = $attribs->{NUM_OF_PARAMS}
+ or return $dbh->set_err($DBI::stderr,"NUM_OF_PARAMS not specified for INSERT statement");
+ $sth->STORE('NUM_OF_PARAMS' => $attribs->{NUM_OF_PARAMS} );
+ }
+ else { #assume select
+
+ # we need to set NUM_OF_FIELDS
+ my $numFields;
+ if ($attribs->{'NUM_OF_FIELDS'}) {
+ $numFields = $attribs->{'NUM_OF_FIELDS'};
+ } elsif ($attribs->{'NAME'}) {
+ $numFields = @{$attribs->{NAME}};
+ } elsif ($attribs->{'TYPE'}) {
+ $numFields = @{$attribs->{TYPE}};
+ } elsif (my $firstrow = $rows->[0]) {
+ $numFields = scalar @$firstrow;
+ } else {
+ return $dbh->set_err($DBI::stderr, 'Cannot determine NUM_OF_FIELDS');
+ }
+ $sth->STORE('NUM_OF_FIELDS' => $numFields);
+ $sth->{NAME} = $attribs->{NAME}
+ || [ map { "col$_" } 1..$numFields ];
+ $sth->{TYPE} = $attribs->{TYPE}
+ || [ (DBI::SQL_VARCHAR()) x $numFields ];
+ $sth->{PRECISION} = $attribs->{PRECISION}
+ || [ map { length($sth->{NAME}->[$_]) } 0..$numFields -1 ];
+ $sth->{SCALE} = $attribs->{SCALE}
+ || [ (0) x $numFields ];
+ $sth->{NULLABLE} = $attribs->{NULLABLE}
+ || [ (2) x $numFields ];
+ }
+
+ $outer;
+ }
+
+ sub type_info_all {
+ my ($dbh) = @_;
+ my $ti = [
+ { TYPE_NAME => 0,
+ DATA_TYPE => 1,
+ PRECISION => 2,
+ LITERAL_PREFIX => 3,
+ LITERAL_SUFFIX => 4,
+ CREATE_PARAMS => 5,
+ NULLABLE => 6,
+ CASE_SENSITIVE => 7,
+ SEARCHABLE => 8,
+ UNSIGNED_ATTRIBUTE=> 9,
+ MONEY => 10,
+ AUTO_INCREMENT => 11,
+ LOCAL_TYPE_NAME => 12,
+ MINIMUM_SCALE => 13,
+ MAXIMUM_SCALE => 14,
+ },
+ [ 'VARCHAR', DBI::SQL_VARCHAR(), undef, "'","'", undef, 0, 1, 1, 0, 0,0,undef,0,0 ],
+ ];
+ return $ti;
+ }
+
+ sub FETCH {
+ my ($dbh, $attrib) = @_;
+ # In reality this would interrogate the database engine to
+ # either return dynamic values that cannot be precomputed
+ # or fetch and cache attribute values too expensive to prefetch.
+ return 1 if $attrib eq 'AutoCommit';
+ # else pass up to DBI to handle
+ return $dbh->SUPER::FETCH($attrib);
+ }
+
+ sub STORE {
+ my ($dbh, $attrib, $value) = @_;
+ # would normally validate and only store known attributes
+ # else pass up to DBI to handle
+ if ($attrib eq 'AutoCommit') {
+ return 1 if $value; # is already set
+ Carp::croak("Can't disable AutoCommit");
+ }
+ return $dbh->SUPER::STORE($attrib, $value);
+ }
+
+ sub sponge_test_installed_method {
+ my ($dbh, @args) = @_;
+ return $dbh->set_err(42, "not enough parameters") unless @args >= 2;
+ return \@args;
+ }
+}
+
+
+{ package DBD::Sponge::st; # ====== STATEMENT ======
+ $imp_data_size = 0;
+ use strict;
+
+ sub execute {
+ my $sth = shift;
+
+ # hack to support ParamValues (when not using bind_param)
+ $sth->{ParamValues} = (@_) ? { map { $_ => $_[$_-1] } 1..@_ } : undef;
+
+ if (my $hook = $sth->{execute_hook}) {
+ &$hook($sth, @_) or return;
+ }
+
+ if ($sth->{is_insert}) {
+ my $row;
+ $row = (@_) ? [ @_ ] : die "bind_param not supported yet" ;
+ my $NUM_OF_PARAMS = $sth->{NUM_OF_PARAMS};
+ return $sth->set_err($DBI::stderr, @$row." values bound (@$row) but $NUM_OF_PARAMS expected")
+ if @$row != $NUM_OF_PARAMS;
+ { local $^W; $sth->trace_msg("inserting (@$row)\n"); }
+ push @{ $sth->{rows} }, $row;
+ }
+ else { # mark select sth as Active
+ $sth->STORE(Active => 1);
+ }
+ # else do nothing for select as data is already in $sth->{rows}
+ return 1;
+ }
+
+ sub fetch {
+ my ($sth) = @_;
+ my $row = shift @{$sth->{'rows'}};
+ unless ($row) {
+ $sth->STORE(Active => 0);
+ return undef;
+ }
+ return $sth->_set_fbav($row);
+ }
+ *fetchrow_arrayref = \&fetch;
+
+ sub FETCH {
+ my ($sth, $attrib) = @_;
+ # would normally validate and only fetch known attributes
+ # else pass up to DBI to handle
+ return $sth->SUPER::FETCH($attrib);
+ }
+
+ sub STORE {
+ my ($sth, $attrib, $value) = @_;
+ # would normally validate and only store known attributes
+ # else pass up to DBI to handle
+ return $sth->SUPER::STORE($attrib, $value);
+ }
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+DBD::Sponge - Create a DBI statement handle from Perl data
+
+=head1 SYNOPSIS
+
+ my $sponge = DBI->connect("dbi:Sponge:","","",{ RaiseError => 1 });
+ my $sth = $sponge->prepare($statement, {
+ rows => $data,
+ NAME => $names,
+ %attr
+ }
+ );
+
+=head1 DESCRIPTION
+
+DBD::Sponge is useful for making a Perl data structure accessible through a
+standard DBI statement handle. This may be useful to DBD module authors who
+need to transform data in this way.
+
+=head1 METHODS
+
+=head2 connect()
+
+ my $sponge = DBI->connect("dbi:Sponge:","","",{ RaiseError => 1 });
+
+Here's a sample syntax for creating a database handle for the Sponge driver.
+No username and password are needed.
+
+=head2 prepare()
+
+ my $sth = $sponge->prepare($statement, {
+ rows => $data,
+ NAME => $names,
+ %attr
+ }
+ );
+
+=over 4
+
+=item *
+
+The C<$statement> here is an arbitrary statement or name you want
+to provide as identity of your data. If you're using DBI::Profile
+it will appear in the profile data.
+
+Generally it's expected that you are preparing a statement handle
+as if a C<select> statement happened.
+
+=item *
+
+C<$data> is a reference to the data you are providing, given as an array of arrays.
+
+=item *
+
+C<$names> is a reference an array of column names for the C<$data> you are providing.
+The number and order should match the number and ordering of the C<$data> columns.
+
+=item *
+
+C<%attr> is a hash of other standard DBI attributes that you might pass to a prepare statement.
+
+Currently only NAME, TYPE, and PRECISION are supported.
+
+=back
+
+=head1 BUGS
+
+Using this module to prepare INSERT-like statements is not currently documented.
+
+=head1 AUTHOR AND COPYRIGHT
+
+This module is Copyright (c) 2003 Tim Bunce
+
+Documentation initially written by Mark Stosberg
+
+The DBD::Sponge module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. In particular permission
+is granted to Tim Bunce for distributing this as a part of the DBI.
+
+=head1 SEE ALSO
+
+L<DBI>
+
+=cut
diff --git a/lib/DBI/Const/GetInfo/ANSI.pm b/lib/DBI/Const/GetInfo/ANSI.pm
new file mode 100644
index 0000000..428ce37
--- /dev/null
+++ b/lib/DBI/Const/GetInfo/ANSI.pm
@@ -0,0 +1,236 @@
+# $Id: ANSI.pm 8696 2007-01-24 23:12:38Z timbo $
+#
+# Copyright (c) 2002 Tim Bunce Ireland
+#
+# Constant data describing ANSI CLI info types and return values for the
+# SQLGetInfo() method of ODBC.
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+package DBI::Const::GetInfo::ANSI;
+
+=head1 NAME
+
+DBI::Const::GetInfo::ANSI - ISO/IEC SQL/CLI Constants for GetInfo
+
+=head1 SYNOPSIS
+
+ The API for this module is private and subject to change.
+
+=head1 DESCRIPTION
+
+Information requested by GetInfo().
+
+See: A.1 C header file SQLCLI.H, Page 316, 317.
+
+The API for this module is private and subject to change.
+
+=head1 REFERENCES
+
+ ISO/IEC FCD 9075-3:200x Information technology - Database Languages -
+ SQL - Part 3: Call-Level Interface (SQL/CLI)
+
+ SC32 N00744 = WG3:VIE-005 = H2-2002-007
+
+ Date: 2002-01-15
+
+=cut
+
+my
+$VERSION = sprintf("2.%06d", q$Revision: 8696 $ =~ /(\d+)/o);
+
+
+%InfoTypes =
+(
+ SQL_ALTER_TABLE => 86
+, SQL_CATALOG_NAME => 10003
+, SQL_COLLATING_SEQUENCE => 10004
+, SQL_CURSOR_COMMIT_BEHAVIOR => 23
+, SQL_CURSOR_SENSITIVITY => 10001
+, SQL_DATA_SOURCE_NAME => 2
+, SQL_DATA_SOURCE_READ_ONLY => 25
+, SQL_DBMS_NAME => 17
+, SQL_DBMS_VERSION => 18
+, SQL_DEFAULT_TRANSACTION_ISOLATION => 26
+, SQL_DESCRIBE_PARAMETER => 10002
+, SQL_FETCH_DIRECTION => 8
+, SQL_GETDATA_EXTENSIONS => 81
+, SQL_IDENTIFIER_CASE => 28
+, SQL_INTEGRITY => 73
+, SQL_MAXIMUM_CATALOG_NAME_LENGTH => 34
+, SQL_MAXIMUM_COLUMNS_IN_GROUP_BY => 97
+, SQL_MAXIMUM_COLUMNS_IN_ORDER_BY => 99
+, SQL_MAXIMUM_COLUMNS_IN_SELECT => 100
+, SQL_MAXIMUM_COLUMNS_IN_TABLE => 101
+, SQL_MAXIMUM_COLUMN_NAME_LENGTH => 30
+, SQL_MAXIMUM_CONCURRENT_ACTIVITIES => 1
+, SQL_MAXIMUM_CURSOR_NAME_LENGTH => 31
+, SQL_MAXIMUM_DRIVER_CONNECTIONS => 0
+, SQL_MAXIMUM_IDENTIFIER_LENGTH => 10005
+, SQL_MAXIMUM_SCHEMA_NAME_LENGTH => 32
+, SQL_MAXIMUM_STMT_OCTETS => 20000
+, SQL_MAXIMUM_STMT_OCTETS_DATA => 20001
+, SQL_MAXIMUM_STMT_OCTETS_SCHEMA => 20002
+, SQL_MAXIMUM_TABLES_IN_SELECT => 106
+, SQL_MAXIMUM_TABLE_NAME_LENGTH => 35
+, SQL_MAXIMUM_USER_NAME_LENGTH => 107
+, SQL_NULL_COLLATION => 85
+, SQL_ORDER_BY_COLUMNS_IN_SELECT => 90
+, SQL_OUTER_JOIN_CAPABILITIES => 115
+, SQL_SCROLL_CONCURRENCY => 43
+, SQL_SEARCH_PATTERN_ESCAPE => 14
+, SQL_SERVER_NAME => 13
+, SQL_SPECIAL_CHARACTERS => 94
+, SQL_TRANSACTION_CAPABLE => 46
+, SQL_TRANSACTION_ISOLATION_OPTION => 72
+, SQL_USER_NAME => 47
+);
+
+=head2 %ReturnTypes
+
+See: Codes and data types for implementation information (Table 28), Page 85, 86.
+
+Mapped to ODBC datatype names.
+
+=cut
+
+%ReturnTypes = # maxlen
+(
+ SQL_ALTER_TABLE => 'SQLUINTEGER bitmask' # INTEGER
+, SQL_CATALOG_NAME => 'SQLCHAR' # CHARACTER (1)
+, SQL_COLLATING_SEQUENCE => 'SQLCHAR' # CHARACTER (254)
+, SQL_CURSOR_COMMIT_BEHAVIOR => 'SQLUSMALLINT' # SMALLINT
+, SQL_CURSOR_SENSITIVITY => 'SQLUINTEGER' # INTEGER
+, SQL_DATA_SOURCE_NAME => 'SQLCHAR' # CHARACTER (128)
+, SQL_DATA_SOURCE_READ_ONLY => 'SQLCHAR' # CHARACTER (1)
+, SQL_DBMS_NAME => 'SQLCHAR' # CHARACTER (254)
+, SQL_DBMS_VERSION => 'SQLCHAR' # CHARACTER (254)
+, SQL_DEFAULT_TRANSACTION_ISOLATION => 'SQLUINTEGER' # INTEGER
+, SQL_DESCRIBE_PARAMETER => 'SQLCHAR' # CHARACTER (1)
+, SQL_FETCH_DIRECTION => 'SQLUINTEGER bitmask' # INTEGER
+, SQL_GETDATA_EXTENSIONS => 'SQLUINTEGER bitmask' # INTEGER
+, SQL_IDENTIFIER_CASE => 'SQLUSMALLINT' # SMALLINT
+, SQL_INTEGRITY => 'SQLCHAR' # CHARACTER (1)
+, SQL_MAXIMUM_CATALOG_NAME_LENGTH => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_COLUMNS_IN_GROUP_BY => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_COLUMNS_IN_ORDER_BY => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_COLUMNS_IN_SELECT => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_COLUMNS_IN_TABLE => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_COLUMN_NAME_LENGTH => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_CONCURRENT_ACTIVITIES => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_CURSOR_NAME_LENGTH => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_DRIVER_CONNECTIONS => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_IDENTIFIER_LENGTH => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_SCHEMA_NAME_LENGTH => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_STMT_OCTETS => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_STMT_OCTETS_DATA => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_STMT_OCTETS_SCHEMA => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_TABLES_IN_SELECT => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_TABLE_NAME_LENGTH => 'SQLUSMALLINT' # SMALLINT
+, SQL_MAXIMUM_USER_NAME_LENGTH => 'SQLUSMALLINT' # SMALLINT
+, SQL_NULL_COLLATION => 'SQLUSMALLINT' # SMALLINT
+, SQL_ORDER_BY_COLUMNS_IN_SELECT => 'SQLCHAR' # CHARACTER (1)
+, SQL_OUTER_JOIN_CAPABILITIES => 'SQLUINTEGER bitmask' # INTEGER
+, SQL_SCROLL_CONCURRENCY => 'SQLUINTEGER bitmask' # INTEGER
+, SQL_SEARCH_PATTERN_ESCAPE => 'SQLCHAR' # CHARACTER (1)
+, SQL_SERVER_NAME => 'SQLCHAR' # CHARACTER (128)
+, SQL_SPECIAL_CHARACTERS => 'SQLCHAR' # CHARACTER (254)
+, SQL_TRANSACTION_CAPABLE => 'SQLUSMALLINT' # SMALLINT
+, SQL_TRANSACTION_ISOLATION_OPTION => 'SQLUINTEGER bitmask' # INTEGER
+, SQL_USER_NAME => 'SQLCHAR' # CHARACTER (128)
+);
+
+=head2 %ReturnValues
+
+See: A.1 C header file SQLCLI.H, Page 317, 318.
+
+=cut
+
+$ReturnValues{SQL_ALTER_TABLE} =
+{
+ SQL_AT_ADD_COLUMN => 0x00000001
+, SQL_AT_DROP_COLUMN => 0x00000002
+, SQL_AT_ALTER_COLUMN => 0x00000004
+, SQL_AT_ADD_CONSTRAINT => 0x00000008
+, SQL_AT_DROP_CONSTRAINT => 0x00000010
+};
+$ReturnValues{SQL_CURSOR_COMMIT_BEHAVIOR} =
+{
+ SQL_CB_DELETE => 0
+, SQL_CB_CLOSE => 1
+, SQL_CB_PRESERVE => 2
+};
+$ReturnValues{SQL_FETCH_DIRECTION} =
+{
+ SQL_FD_FETCH_NEXT => 0x00000001
+, SQL_FD_FETCH_FIRST => 0x00000002
+, SQL_FD_FETCH_LAST => 0x00000004
+, SQL_FD_FETCH_PRIOR => 0x00000008
+, SQL_FD_FETCH_ABSOLUTE => 0x00000010
+, SQL_FD_FETCH_RELATIVE => 0x00000020
+};
+$ReturnValues{SQL_GETDATA_EXTENSIONS} =
+{
+ SQL_GD_ANY_COLUMN => 0x00000001
+, SQL_GD_ANY_ORDER => 0x00000002
+};
+$ReturnValues{SQL_IDENTIFIER_CASE} =
+{
+ SQL_IC_UPPER => 1
+, SQL_IC_LOWER => 2
+, SQL_IC_SENSITIVE => 3
+, SQL_IC_MIXED => 4
+};
+$ReturnValues{SQL_NULL_COLLATION} =
+{
+ SQL_NC_HIGH => 1
+, SQL_NC_LOW => 2
+};
+$ReturnValues{SQL_OUTER_JOIN_CAPABILITIES} =
+{
+ SQL_OUTER_JOIN_LEFT => 0x00000001
+, SQL_OUTER_JOIN_RIGHT => 0x00000002
+, SQL_OUTER_JOIN_FULL => 0x00000004
+, SQL_OUTER_JOIN_NESTED => 0x00000008
+, SQL_OUTER_JOIN_NOT_ORDERED => 0x00000010
+, SQL_OUTER_JOIN_INNER => 0x00000020
+, SQL_OUTER_JOIN_ALL_COMPARISON_OPS => 0x00000040
+};
+$ReturnValues{SQL_SCROLL_CONCURRENCY} =
+{
+ SQL_SCCO_READ_ONLY => 0x00000001
+, SQL_SCCO_LOCK => 0x00000002
+, SQL_SCCO_OPT_ROWVER => 0x00000004
+, SQL_SCCO_OPT_VALUES => 0x00000008
+};
+$ReturnValues{SQL_TRANSACTION_ACCESS_MODE} =
+{
+ SQL_TRANSACTION_READ_ONLY => 0x00000001
+, SQL_TRANSACTION_READ_WRITE => 0x00000002
+};
+$ReturnValues{SQL_TRANSACTION_CAPABLE} =
+{
+ SQL_TC_NONE => 0
+, SQL_TC_DML => 1
+, SQL_TC_ALL => 2
+, SQL_TC_DDL_COMMIT => 3
+, SQL_TC_DDL_IGNORE => 4
+};
+$ReturnValues{SQL_TRANSACTION_ISOLATION} =
+{
+ SQL_TRANSACTION_READ_UNCOMMITTED => 0x00000001
+, SQL_TRANSACTION_READ_COMMITTED => 0x00000002
+, SQL_TRANSACTION_REPEATABLE_READ => 0x00000004
+, SQL_TRANSACTION_SERIALIZABLE => 0x00000008
+};
+
+1;
+
+=head1 TODO
+
+Corrections, e.g.:
+
+ SQL_TRANSACTION_ISOLATION_OPTION vs. SQL_TRANSACTION_ISOLATION
+
+=cut
diff --git a/lib/DBI/Const/GetInfo/ODBC.pm b/lib/DBI/Const/GetInfo/ODBC.pm
new file mode 100644
index 0000000..0f71a06
--- /dev/null
+++ b/lib/DBI/Const/GetInfo/ODBC.pm
@@ -0,0 +1,1363 @@
+# $Id: ODBC.pm 11373 2008-06-02 19:01:33Z timbo $
+#
+# Copyright (c) 2002 Tim Bunce Ireland
+#
+# Constant data describing Microsoft ODBC info types and return values
+# for the SQLGetInfo() method of ODBC.
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+package DBI::Const::GetInfo::ODBC;
+
+=head1 NAME
+
+DBI::Const::GetInfo::ODBC - ODBC Constants for GetInfo
+
+=head1 SYNOPSIS
+
+ The API for this module is private and subject to change.
+
+=head1 DESCRIPTION
+
+Information requested by GetInfo().
+
+The API for this module is private and subject to change.
+
+=head1 REFERENCES
+
+ MDAC SDK 2.6
+ ODBC version number (0x0351)
+
+ sql.h
+ sqlext.h
+
+=cut
+
+my
+$VERSION = sprintf("2.%06d", q$Revision: 11373 $ =~ /(\d+)/o);
+
+
+%InfoTypes =
+(
+ SQL_ACCESSIBLE_PROCEDURES => 20
+, SQL_ACCESSIBLE_TABLES => 19
+, SQL_ACTIVE_CONNECTIONS => 0
+, SQL_ACTIVE_ENVIRONMENTS => 116
+, SQL_ACTIVE_STATEMENTS => 1
+, SQL_AGGREGATE_FUNCTIONS => 169
+, SQL_ALTER_DOMAIN => 117
+, SQL_ALTER_TABLE => 86
+, SQL_ASYNC_MODE => 10021
+, SQL_BATCH_ROW_COUNT => 120
+, SQL_BATCH_SUPPORT => 121
+, SQL_BOOKMARK_PERSISTENCE => 82
+, SQL_CATALOG_LOCATION => 114 # SQL_QUALIFIER_LOCATION
+, SQL_CATALOG_NAME => 10003
+, SQL_CATALOG_NAME_SEPARATOR => 41 # SQL_QUALIFIER_NAME_SEPARATOR
+, SQL_CATALOG_TERM => 42 # SQL_QUALIFIER_TERM
+, SQL_CATALOG_USAGE => 92 # SQL_QUALIFIER_USAGE
+, SQL_COLLATION_SEQ => 10004
+, SQL_COLUMN_ALIAS => 87
+, SQL_CONCAT_NULL_BEHAVIOR => 22
+, SQL_CONVERT_BIGINT => 53
+, SQL_CONVERT_BINARY => 54
+, SQL_CONVERT_BIT => 55
+, SQL_CONVERT_CHAR => 56
+, SQL_CONVERT_DATE => 57
+, SQL_CONVERT_DECIMAL => 58
+, SQL_CONVERT_DOUBLE => 59
+, SQL_CONVERT_FLOAT => 60
+, SQL_CONVERT_FUNCTIONS => 48
+, SQL_CONVERT_GUID => 173
+, SQL_CONVERT_INTEGER => 61
+, SQL_CONVERT_INTERVAL_DAY_TIME => 123
+, SQL_CONVERT_INTERVAL_YEAR_MONTH => 124
+, SQL_CONVERT_LONGVARBINARY => 71
+, SQL_CONVERT_LONGVARCHAR => 62
+, SQL_CONVERT_NUMERIC => 63
+, SQL_CONVERT_REAL => 64
+, SQL_CONVERT_SMALLINT => 65
+, SQL_CONVERT_TIME => 66
+, SQL_CONVERT_TIMESTAMP => 67
+, SQL_CONVERT_TINYINT => 68
+, SQL_CONVERT_VARBINARY => 69
+, SQL_CONVERT_VARCHAR => 70
+, SQL_CONVERT_WCHAR => 122
+, SQL_CONVERT_WLONGVARCHAR => 125
+, SQL_CONVERT_WVARCHAR => 126
+, SQL_CORRELATION_NAME => 74
+, SQL_CREATE_ASSERTION => 127
+, SQL_CREATE_CHARACTER_SET => 128
+, SQL_CREATE_COLLATION => 129
+, SQL_CREATE_DOMAIN => 130
+, SQL_CREATE_SCHEMA => 131
+, SQL_CREATE_TABLE => 132
+, SQL_CREATE_TRANSLATION => 133
+, SQL_CREATE_VIEW => 134
+, SQL_CURSOR_COMMIT_BEHAVIOR => 23
+, SQL_CURSOR_ROLLBACK_BEHAVIOR => 24
+, SQL_CURSOR_SENSITIVITY => 10001
+, SQL_DATA_SOURCE_NAME => 2
+, SQL_DATA_SOURCE_READ_ONLY => 25
+, SQL_DATABASE_NAME => 16
+, SQL_DATETIME_LITERALS => 119
+, SQL_DBMS_NAME => 17
+, SQL_DBMS_VER => 18
+, SQL_DDL_INDEX => 170
+, SQL_DEFAULT_TXN_ISOLATION => 26
+, SQL_DESCRIBE_PARAMETER => 10002
+, SQL_DM_VER => 171
+, SQL_DRIVER_HDBC => 3
+, SQL_DRIVER_HDESC => 135
+, SQL_DRIVER_HENV => 4
+, SQL_DRIVER_HLIB => 76
+, SQL_DRIVER_HSTMT => 5
+, SQL_DRIVER_NAME => 6
+, SQL_DRIVER_ODBC_VER => 77
+, SQL_DRIVER_VER => 7
+, SQL_DROP_ASSERTION => 136
+, SQL_DROP_CHARACTER_SET => 137
+, SQL_DROP_COLLATION => 138
+, SQL_DROP_DOMAIN => 139
+, SQL_DROP_SCHEMA => 140
+, SQL_DROP_TABLE => 141
+, SQL_DROP_TRANSLATION => 142
+, SQL_DROP_VIEW => 143
+, SQL_DYNAMIC_CURSOR_ATTRIBUTES1 => 144
+, SQL_DYNAMIC_CURSOR_ATTRIBUTES2 => 145
+, SQL_EXPRESSIONS_IN_ORDERBY => 27
+, SQL_FETCH_DIRECTION => 8
+, SQL_FILE_USAGE => 84
+, SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1 => 146
+, SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2 => 147
+, SQL_GETDATA_EXTENSIONS => 81
+, SQL_GROUP_BY => 88
+, SQL_IDENTIFIER_CASE => 28
+, SQL_IDENTIFIER_QUOTE_CHAR => 29
+, SQL_INDEX_KEYWORDS => 148
+# SQL_INFO_DRIVER_START => 1000
+# SQL_INFO_FIRST => 0
+# SQL_INFO_LAST => 114 # SQL_QUALIFIER_LOCATION
+, SQL_INFO_SCHEMA_VIEWS => 149
+, SQL_INSERT_STATEMENT => 172
+, SQL_INTEGRITY => 73
+, SQL_KEYSET_CURSOR_ATTRIBUTES1 => 150
+, SQL_KEYSET_CURSOR_ATTRIBUTES2 => 151
+, SQL_KEYWORDS => 89
+, SQL_LIKE_ESCAPE_CLAUSE => 113
+, SQL_LOCK_TYPES => 78
+, SQL_MAXIMUM_CATALOG_NAME_LENGTH => 34 # SQL_MAX_CATALOG_NAME_LEN
+, SQL_MAXIMUM_COLUMNS_IN_GROUP_BY => 97 # SQL_MAX_COLUMNS_IN_GROUP_BY
+, SQL_MAXIMUM_COLUMNS_IN_INDEX => 98 # SQL_MAX_COLUMNS_IN_INDEX
+, SQL_MAXIMUM_COLUMNS_IN_ORDER_BY => 99 # SQL_MAX_COLUMNS_IN_ORDER_BY
+, SQL_MAXIMUM_COLUMNS_IN_SELECT => 100 # SQL_MAX_COLUMNS_IN_SELECT
+, SQL_MAXIMUM_COLUMN_NAME_LENGTH => 30 # SQL_MAX_COLUMN_NAME_LEN
+, SQL_MAXIMUM_CONCURRENT_ACTIVITIES => 1 # SQL_MAX_CONCURRENT_ACTIVITIES
+, SQL_MAXIMUM_CURSOR_NAME_LENGTH => 31 # SQL_MAX_CURSOR_NAME_LEN
+, SQL_MAXIMUM_DRIVER_CONNECTIONS => 0 # SQL_MAX_DRIVER_CONNECTIONS
+, SQL_MAXIMUM_IDENTIFIER_LENGTH => 10005 # SQL_MAX_IDENTIFIER_LEN
+, SQL_MAXIMUM_INDEX_SIZE => 102 # SQL_MAX_INDEX_SIZE
+, SQL_MAXIMUM_ROW_SIZE => 104 # SQL_MAX_ROW_SIZE
+, SQL_MAXIMUM_SCHEMA_NAME_LENGTH => 32 # SQL_MAX_SCHEMA_NAME_LEN
+, SQL_MAXIMUM_STATEMENT_LENGTH => 105 # SQL_MAX_STATEMENT_LEN
+, SQL_MAXIMUM_TABLES_IN_SELECT => 106 # SQL_MAX_TABLES_IN_SELECT
+, SQL_MAXIMUM_USER_NAME_LENGTH => 107 # SQL_MAX_USER_NAME_LEN
+, SQL_MAX_ASYNC_CONCURRENT_STATEMENTS => 10022
+, SQL_MAX_BINARY_LITERAL_LEN => 112
+, SQL_MAX_CATALOG_NAME_LEN => 34
+, SQL_MAX_CHAR_LITERAL_LEN => 108
+, SQL_MAX_COLUMNS_IN_GROUP_BY => 97
+, SQL_MAX_COLUMNS_IN_INDEX => 98
+, SQL_MAX_COLUMNS_IN_ORDER_BY => 99
+, SQL_MAX_COLUMNS_IN_SELECT => 100
+, SQL_MAX_COLUMNS_IN_TABLE => 101
+, SQL_MAX_COLUMN_NAME_LEN => 30
+, SQL_MAX_CONCURRENT_ACTIVITIES => 1
+, SQL_MAX_CURSOR_NAME_LEN => 31
+, SQL_MAX_DRIVER_CONNECTIONS => 0
+, SQL_MAX_IDENTIFIER_LEN => 10005
+, SQL_MAX_INDEX_SIZE => 102
+, SQL_MAX_OWNER_NAME_LEN => 32
+, SQL_MAX_PROCEDURE_NAME_LEN => 33
+, SQL_MAX_QUALIFIER_NAME_LEN => 34
+, SQL_MAX_ROW_SIZE => 104
+, SQL_MAX_ROW_SIZE_INCLUDES_LONG => 103
+, SQL_MAX_SCHEMA_NAME_LEN => 32
+, SQL_MAX_STATEMENT_LEN => 105
+, SQL_MAX_TABLES_IN_SELECT => 106
+, SQL_MAX_TABLE_NAME_LEN => 35
+, SQL_MAX_USER_NAME_LEN => 107
+, SQL_MULTIPLE_ACTIVE_TXN => 37
+, SQL_MULT_RESULT_SETS => 36
+, SQL_NEED_LONG_DATA_LEN => 111
+, SQL_NON_NULLABLE_COLUMNS => 75
+, SQL_NULL_COLLATION => 85
+, SQL_NUMERIC_FUNCTIONS => 49
+, SQL_ODBC_API_CONFORMANCE => 9
+, SQL_ODBC_INTERFACE_CONFORMANCE => 152
+, SQL_ODBC_SAG_CLI_CONFORMANCE => 12
+, SQL_ODBC_SQL_CONFORMANCE => 15
+, SQL_ODBC_SQL_OPT_IEF => 73
+, SQL_ODBC_VER => 10
+, SQL_OJ_CAPABILITIES => 115
+, SQL_ORDER_BY_COLUMNS_IN_SELECT => 90
+, SQL_OUTER_JOINS => 38
+, SQL_OUTER_JOIN_CAPABILITIES => 115 # SQL_OJ_CAPABILITIES
+, SQL_OWNER_TERM => 39
+, SQL_OWNER_USAGE => 91
+, SQL_PARAM_ARRAY_ROW_COUNTS => 153
+, SQL_PARAM_ARRAY_SELECTS => 154
+, SQL_POSITIONED_STATEMENTS => 80
+, SQL_POS_OPERATIONS => 79
+, SQL_PROCEDURES => 21
+, SQL_PROCEDURE_TERM => 40
+, SQL_QUALIFIER_LOCATION => 114
+, SQL_QUALIFIER_NAME_SEPARATOR => 41
+, SQL_QUALIFIER_TERM => 42
+, SQL_QUALIFIER_USAGE => 92
+, SQL_QUOTED_IDENTIFIER_CASE => 93
+, SQL_ROW_UPDATES => 11
+, SQL_SCHEMA_TERM => 39 # SQL_OWNER_TERM
+, SQL_SCHEMA_USAGE => 91 # SQL_OWNER_USAGE
+, SQL_SCROLL_CONCURRENCY => 43
+, SQL_SCROLL_OPTIONS => 44
+, SQL_SEARCH_PATTERN_ESCAPE => 14
+, SQL_SERVER_NAME => 13
+, SQL_SPECIAL_CHARACTERS => 94
+, SQL_SQL92_DATETIME_FUNCTIONS => 155
+, SQL_SQL92_FOREIGN_KEY_DELETE_RULE => 156
+, SQL_SQL92_FOREIGN_KEY_UPDATE_RULE => 157
+, SQL_SQL92_GRANT => 158
+, SQL_SQL92_NUMERIC_VALUE_FUNCTIONS => 159
+, SQL_SQL92_PREDICATES => 160
+, SQL_SQL92_RELATIONAL_JOIN_OPERATORS => 161
+, SQL_SQL92_REVOKE => 162
+, SQL_SQL92_ROW_VALUE_CONSTRUCTOR => 163
+, SQL_SQL92_STRING_FUNCTIONS => 164
+, SQL_SQL92_VALUE_EXPRESSIONS => 165
+, SQL_SQL_CONFORMANCE => 118
+, SQL_STANDARD_CLI_CONFORMANCE => 166
+, SQL_STATIC_CURSOR_ATTRIBUTES1 => 167
+, SQL_STATIC_CURSOR_ATTRIBUTES2 => 168
+, SQL_STATIC_SENSITIVITY => 83
+, SQL_STRING_FUNCTIONS => 50
+, SQL_SUBQUERIES => 95
+, SQL_SYSTEM_FUNCTIONS => 51
+, SQL_TABLE_TERM => 45
+, SQL_TIMEDATE_ADD_INTERVALS => 109
+, SQL_TIMEDATE_DIFF_INTERVALS => 110
+, SQL_TIMEDATE_FUNCTIONS => 52
+, SQL_TRANSACTION_CAPABLE => 46 # SQL_TXN_CAPABLE
+, SQL_TRANSACTION_ISOLATION_OPTION => 72 # SQL_TXN_ISOLATION_OPTION
+, SQL_TXN_CAPABLE => 46
+, SQL_TXN_ISOLATION_OPTION => 72
+, SQL_UNION => 96
+, SQL_UNION_STATEMENT => 96 # SQL_UNION
+, SQL_USER_NAME => 47
+, SQL_XOPEN_CLI_YEAR => 10000
+);
+
+=head2 %ReturnTypes
+
+See: mk:@MSITStore:X:\dm\cli\mdac\sdk26\Docs\odbc.chm::/htm/odbcsqlgetinfo.htm
+
+ => : alias
+ => !!! : edited
+
+=cut
+
+%ReturnTypes =
+(
+ SQL_ACCESSIBLE_PROCEDURES => 'SQLCHAR' # 20
+, SQL_ACCESSIBLE_TABLES => 'SQLCHAR' # 19
+, SQL_ACTIVE_CONNECTIONS => 'SQLUSMALLINT' # 0 =>
+, SQL_ACTIVE_ENVIRONMENTS => 'SQLUSMALLINT' # 116
+, SQL_ACTIVE_STATEMENTS => 'SQLUSMALLINT' # 1 =>
+, SQL_AGGREGATE_FUNCTIONS => 'SQLUINTEGER bitmask' # 169
+, SQL_ALTER_DOMAIN => 'SQLUINTEGER bitmask' # 117
+, SQL_ALTER_TABLE => 'SQLUINTEGER bitmask' # 86
+, SQL_ASYNC_MODE => 'SQLUINTEGER' # 10021
+, SQL_BATCH_ROW_COUNT => 'SQLUINTEGER bitmask' # 120
+, SQL_BATCH_SUPPORT => 'SQLUINTEGER bitmask' # 121
+, SQL_BOOKMARK_PERSISTENCE => 'SQLUINTEGER bitmask' # 82
+, SQL_CATALOG_LOCATION => 'SQLUSMALLINT' # 114
+, SQL_CATALOG_NAME => 'SQLCHAR' # 10003
+, SQL_CATALOG_NAME_SEPARATOR => 'SQLCHAR' # 41
+, SQL_CATALOG_TERM => 'SQLCHAR' # 42
+, SQL_CATALOG_USAGE => 'SQLUINTEGER bitmask' # 92
+, SQL_COLLATION_SEQ => 'SQLCHAR' # 10004
+, SQL_COLUMN_ALIAS => 'SQLCHAR' # 87
+, SQL_CONCAT_NULL_BEHAVIOR => 'SQLUSMALLINT' # 22
+, SQL_CONVERT_BIGINT => 'SQLUINTEGER bitmask' # 53
+, SQL_CONVERT_BINARY => 'SQLUINTEGER bitmask' # 54
+, SQL_CONVERT_BIT => 'SQLUINTEGER bitmask' # 55
+, SQL_CONVERT_CHAR => 'SQLUINTEGER bitmask' # 56
+, SQL_CONVERT_DATE => 'SQLUINTEGER bitmask' # 57
+, SQL_CONVERT_DECIMAL => 'SQLUINTEGER bitmask' # 58
+, SQL_CONVERT_DOUBLE => 'SQLUINTEGER bitmask' # 59
+, SQL_CONVERT_FLOAT => 'SQLUINTEGER bitmask' # 60
+, SQL_CONVERT_FUNCTIONS => 'SQLUINTEGER bitmask' # 48
+, SQL_CONVERT_GUID => 'SQLUINTEGER bitmask' # 173
+, SQL_CONVERT_INTEGER => 'SQLUINTEGER bitmask' # 61
+, SQL_CONVERT_INTERVAL_DAY_TIME => 'SQLUINTEGER bitmask' # 123
+, SQL_CONVERT_INTERVAL_YEAR_MONTH => 'SQLUINTEGER bitmask' # 124
+, SQL_CONVERT_LONGVARBINARY => 'SQLUINTEGER bitmask' # 71
+, SQL_CONVERT_LONGVARCHAR => 'SQLUINTEGER bitmask' # 62
+, SQL_CONVERT_NUMERIC => 'SQLUINTEGER bitmask' # 63
+, SQL_CONVERT_REAL => 'SQLUINTEGER bitmask' # 64
+, SQL_CONVERT_SMALLINT => 'SQLUINTEGER bitmask' # 65
+, SQL_CONVERT_TIME => 'SQLUINTEGER bitmask' # 66
+, SQL_CONVERT_TIMESTAMP => 'SQLUINTEGER bitmask' # 67
+, SQL_CONVERT_TINYINT => 'SQLUINTEGER bitmask' # 68
+, SQL_CONVERT_VARBINARY => 'SQLUINTEGER bitmask' # 69
+, SQL_CONVERT_VARCHAR => 'SQLUINTEGER bitmask' # 70
+, SQL_CONVERT_WCHAR => 'SQLUINTEGER bitmask' # 122 => !!!
+, SQL_CONVERT_WLONGVARCHAR => 'SQLUINTEGER bitmask' # 125 => !!!
+, SQL_CONVERT_WVARCHAR => 'SQLUINTEGER bitmask' # 126 => !!!
+, SQL_CORRELATION_NAME => 'SQLUSMALLINT' # 74
+, SQL_CREATE_ASSERTION => 'SQLUINTEGER bitmask' # 127
+, SQL_CREATE_CHARACTER_SET => 'SQLUINTEGER bitmask' # 128
+, SQL_CREATE_COLLATION => 'SQLUINTEGER bitmask' # 129
+, SQL_CREATE_DOMAIN => 'SQLUINTEGER bitmask' # 130
+, SQL_CREATE_SCHEMA => 'SQLUINTEGER bitmask' # 131
+, SQL_CREATE_TABLE => 'SQLUINTEGER bitmask' # 132
+, SQL_CREATE_TRANSLATION => 'SQLUINTEGER bitmask' # 133
+, SQL_CREATE_VIEW => 'SQLUINTEGER bitmask' # 134
+, SQL_CURSOR_COMMIT_BEHAVIOR => 'SQLUSMALLINT' # 23
+, SQL_CURSOR_ROLLBACK_BEHAVIOR => 'SQLUSMALLINT' # 24
+, SQL_CURSOR_SENSITIVITY => 'SQLUINTEGER' # 10001
+, SQL_DATA_SOURCE_NAME => 'SQLCHAR' # 2
+, SQL_DATA_SOURCE_READ_ONLY => 'SQLCHAR' # 25
+, SQL_DATABASE_NAME => 'SQLCHAR' # 16
+, SQL_DATETIME_LITERALS => 'SQLUINTEGER bitmask' # 119
+, SQL_DBMS_NAME => 'SQLCHAR' # 17
+, SQL_DBMS_VER => 'SQLCHAR' # 18
+, SQL_DDL_INDEX => 'SQLUINTEGER bitmask' # 170
+, SQL_DEFAULT_TXN_ISOLATION => 'SQLUINTEGER' # 26
+, SQL_DESCRIBE_PARAMETER => 'SQLCHAR' # 10002
+, SQL_DM_VER => 'SQLCHAR' # 171
+, SQL_DRIVER_HDBC => 'SQLUINTEGER' # 3
+, SQL_DRIVER_HDESC => 'SQLUINTEGER' # 135
+, SQL_DRIVER_HENV => 'SQLUINTEGER' # 4
+, SQL_DRIVER_HLIB => 'SQLUINTEGER' # 76
+, SQL_DRIVER_HSTMT => 'SQLUINTEGER' # 5
+, SQL_DRIVER_NAME => 'SQLCHAR' # 6
+, SQL_DRIVER_ODBC_VER => 'SQLCHAR' # 77
+, SQL_DRIVER_VER => 'SQLCHAR' # 7
+, SQL_DROP_ASSERTION => 'SQLUINTEGER bitmask' # 136
+, SQL_DROP_CHARACTER_SET => 'SQLUINTEGER bitmask' # 137
+, SQL_DROP_COLLATION => 'SQLUINTEGER bitmask' # 138
+, SQL_DROP_DOMAIN => 'SQLUINTEGER bitmask' # 139
+, SQL_DROP_SCHEMA => 'SQLUINTEGER bitmask' # 140
+, SQL_DROP_TABLE => 'SQLUINTEGER bitmask' # 141
+, SQL_DROP_TRANSLATION => 'SQLUINTEGER bitmask' # 142
+, SQL_DROP_VIEW => 'SQLUINTEGER bitmask' # 143
+, SQL_DYNAMIC_CURSOR_ATTRIBUTES1 => 'SQLUINTEGER bitmask' # 144
+, SQL_DYNAMIC_CURSOR_ATTRIBUTES2 => 'SQLUINTEGER bitmask' # 145
+, SQL_EXPRESSIONS_IN_ORDERBY => 'SQLCHAR' # 27
+, SQL_FETCH_DIRECTION => 'SQLUINTEGER bitmask' # 8 => !!!
+, SQL_FILE_USAGE => 'SQLUSMALLINT' # 84
+, SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1 => 'SQLUINTEGER bitmask' # 146
+, SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2 => 'SQLUINTEGER bitmask' # 147
+, SQL_GETDATA_EXTENSIONS => 'SQLUINTEGER bitmask' # 81
+, SQL_GROUP_BY => 'SQLUSMALLINT' # 88
+, SQL_IDENTIFIER_CASE => 'SQLUSMALLINT' # 28
+, SQL_IDENTIFIER_QUOTE_CHAR => 'SQLCHAR' # 29
+, SQL_INDEX_KEYWORDS => 'SQLUINTEGER bitmask' # 148
+# SQL_INFO_DRIVER_START => '' # 1000 =>
+# SQL_INFO_FIRST => 'SQLUSMALLINT' # 0 =>
+# SQL_INFO_LAST => 'SQLUSMALLINT' # 114 =>
+, SQL_INFO_SCHEMA_VIEWS => 'SQLUINTEGER bitmask' # 149
+, SQL_INSERT_STATEMENT => 'SQLUINTEGER bitmask' # 172
+, SQL_INTEGRITY => 'SQLCHAR' # 73
+, SQL_KEYSET_CURSOR_ATTRIBUTES1 => 'SQLUINTEGER bitmask' # 150
+, SQL_KEYSET_CURSOR_ATTRIBUTES2 => 'SQLUINTEGER bitmask' # 151
+, SQL_KEYWORDS => 'SQLCHAR' # 89
+, SQL_LIKE_ESCAPE_CLAUSE => 'SQLCHAR' # 113
+, SQL_LOCK_TYPES => 'SQLUINTEGER bitmask' # 78 => !!!
+, SQL_MAXIMUM_CATALOG_NAME_LENGTH => 'SQLUSMALLINT' # 34 =>
+, SQL_MAXIMUM_COLUMNS_IN_GROUP_BY => 'SQLUSMALLINT' # 97 =>
+, SQL_MAXIMUM_COLUMNS_IN_INDEX => 'SQLUSMALLINT' # 98 =>
+, SQL_MAXIMUM_COLUMNS_IN_ORDER_BY => 'SQLUSMALLINT' # 99 =>
+, SQL_MAXIMUM_COLUMNS_IN_SELECT => 'SQLUSMALLINT' # 100 =>
+, SQL_MAXIMUM_COLUMN_NAME_LENGTH => 'SQLUSMALLINT' # 30 =>
+, SQL_MAXIMUM_CONCURRENT_ACTIVITIES => 'SQLUSMALLINT' # 1 =>
+, SQL_MAXIMUM_CURSOR_NAME_LENGTH => 'SQLUSMALLINT' # 31 =>
+, SQL_MAXIMUM_DRIVER_CONNECTIONS => 'SQLUSMALLINT' # 0 =>
+, SQL_MAXIMUM_IDENTIFIER_LENGTH => 'SQLUSMALLINT' # 10005 =>
+, SQL_MAXIMUM_INDEX_SIZE => 'SQLUINTEGER' # 102 =>
+, SQL_MAXIMUM_ROW_SIZE => 'SQLUINTEGER' # 104 =>
+, SQL_MAXIMUM_SCHEMA_NAME_LENGTH => 'SQLUSMALLINT' # 32 =>
+, SQL_MAXIMUM_STATEMENT_LENGTH => 'SQLUINTEGER' # 105 =>
+, SQL_MAXIMUM_TABLES_IN_SELECT => 'SQLUSMALLINT' # 106 =>
+, SQL_MAXIMUM_USER_NAME_LENGTH => 'SQLUSMALLINT' # 107 =>
+, SQL_MAX_ASYNC_CONCURRENT_STATEMENTS => 'SQLUINTEGER' # 10022
+, SQL_MAX_BINARY_LITERAL_LEN => 'SQLUINTEGER' # 112
+, SQL_MAX_CATALOG_NAME_LEN => 'SQLUSMALLINT' # 34
+, SQL_MAX_CHAR_LITERAL_LEN => 'SQLUINTEGER' # 108
+, SQL_MAX_COLUMNS_IN_GROUP_BY => 'SQLUSMALLINT' # 97
+, SQL_MAX_COLUMNS_IN_INDEX => 'SQLUSMALLINT' # 98
+, SQL_MAX_COLUMNS_IN_ORDER_BY => 'SQLUSMALLINT' # 99
+, SQL_MAX_COLUMNS_IN_SELECT => 'SQLUSMALLINT' # 100
+, SQL_MAX_COLUMNS_IN_TABLE => 'SQLUSMALLINT' # 101
+, SQL_MAX_COLUMN_NAME_LEN => 'SQLUSMALLINT' # 30
+, SQL_MAX_CONCURRENT_ACTIVITIES => 'SQLUSMALLINT' # 1
+, SQL_MAX_CURSOR_NAME_LEN => 'SQLUSMALLINT' # 31
+, SQL_MAX_DRIVER_CONNECTIONS => 'SQLUSMALLINT' # 0
+, SQL_MAX_IDENTIFIER_LEN => 'SQLUSMALLINT' # 10005
+, SQL_MAX_INDEX_SIZE => 'SQLUINTEGER' # 102
+, SQL_MAX_OWNER_NAME_LEN => 'SQLUSMALLINT' # 32 =>
+, SQL_MAX_PROCEDURE_NAME_LEN => 'SQLUSMALLINT' # 33
+, SQL_MAX_QUALIFIER_NAME_LEN => 'SQLUSMALLINT' # 34 =>
+, SQL_MAX_ROW_SIZE => 'SQLUINTEGER' # 104
+, SQL_MAX_ROW_SIZE_INCLUDES_LONG => 'SQLCHAR' # 103
+, SQL_MAX_SCHEMA_NAME_LEN => 'SQLUSMALLINT' # 32
+, SQL_MAX_STATEMENT_LEN => 'SQLUINTEGER' # 105
+, SQL_MAX_TABLES_IN_SELECT => 'SQLUSMALLINT' # 106
+, SQL_MAX_TABLE_NAME_LEN => 'SQLUSMALLINT' # 35
+, SQL_MAX_USER_NAME_LEN => 'SQLUSMALLINT' # 107
+, SQL_MULTIPLE_ACTIVE_TXN => 'SQLCHAR' # 37
+, SQL_MULT_RESULT_SETS => 'SQLCHAR' # 36
+, SQL_NEED_LONG_DATA_LEN => 'SQLCHAR' # 111
+, SQL_NON_NULLABLE_COLUMNS => 'SQLUSMALLINT' # 75
+, SQL_NULL_COLLATION => 'SQLUSMALLINT' # 85
+, SQL_NUMERIC_FUNCTIONS => 'SQLUINTEGER bitmask' # 49
+, SQL_ODBC_API_CONFORMANCE => 'SQLUSMALLINT' # 9 => !!!
+, SQL_ODBC_INTERFACE_CONFORMANCE => 'SQLUINTEGER' # 152
+, SQL_ODBC_SAG_CLI_CONFORMANCE => 'SQLUSMALLINT' # 12 => !!!
+, SQL_ODBC_SQL_CONFORMANCE => 'SQLUSMALLINT' # 15 => !!!
+, SQL_ODBC_SQL_OPT_IEF => 'SQLCHAR' # 73 =>
+, SQL_ODBC_VER => 'SQLCHAR' # 10
+, SQL_OJ_CAPABILITIES => 'SQLUINTEGER bitmask' # 115
+, SQL_ORDER_BY_COLUMNS_IN_SELECT => 'SQLCHAR' # 90
+, SQL_OUTER_JOINS => 'SQLCHAR' # 38 => !!!
+, SQL_OUTER_JOIN_CAPABILITIES => 'SQLUINTEGER bitmask' # 115 =>
+, SQL_OWNER_TERM => 'SQLCHAR' # 39 =>
+, SQL_OWNER_USAGE => 'SQLUINTEGER bitmask' # 91 =>
+, SQL_PARAM_ARRAY_ROW_COUNTS => 'SQLUINTEGER' # 153
+, SQL_PARAM_ARRAY_SELECTS => 'SQLUINTEGER' # 154
+, SQL_POSITIONED_STATEMENTS => 'SQLUINTEGER bitmask' # 80 => !!!
+, SQL_POS_OPERATIONS => 'SQLINTEGER bitmask' # 79
+, SQL_PROCEDURES => 'SQLCHAR' # 21
+, SQL_PROCEDURE_TERM => 'SQLCHAR' # 40
+, SQL_QUALIFIER_LOCATION => 'SQLUSMALLINT' # 114 =>
+, SQL_QUALIFIER_NAME_SEPARATOR => 'SQLCHAR' # 41 =>
+, SQL_QUALIFIER_TERM => 'SQLCHAR' # 42 =>
+, SQL_QUALIFIER_USAGE => 'SQLUINTEGER bitmask' # 92 =>
+, SQL_QUOTED_IDENTIFIER_CASE => 'SQLUSMALLINT' # 93
+, SQL_ROW_UPDATES => 'SQLCHAR' # 11
+, SQL_SCHEMA_TERM => 'SQLCHAR' # 39
+, SQL_SCHEMA_USAGE => 'SQLUINTEGER bitmask' # 91
+, SQL_SCROLL_CONCURRENCY => 'SQLUINTEGER bitmask' # 43 => !!!
+, SQL_SCROLL_OPTIONS => 'SQLUINTEGER bitmask' # 44
+, SQL_SEARCH_PATTERN_ESCAPE => 'SQLCHAR' # 14
+, SQL_SERVER_NAME => 'SQLCHAR' # 13
+, SQL_SPECIAL_CHARACTERS => 'SQLCHAR' # 94
+, SQL_SQL92_DATETIME_FUNCTIONS => 'SQLUINTEGER bitmask' # 155
+, SQL_SQL92_FOREIGN_KEY_DELETE_RULE => 'SQLUINTEGER bitmask' # 156
+, SQL_SQL92_FOREIGN_KEY_UPDATE_RULE => 'SQLUINTEGER bitmask' # 157
+, SQL_SQL92_GRANT => 'SQLUINTEGER bitmask' # 158
+, SQL_SQL92_NUMERIC_VALUE_FUNCTIONS => 'SQLUINTEGER bitmask' # 159
+, SQL_SQL92_PREDICATES => 'SQLUINTEGER bitmask' # 160
+, SQL_SQL92_RELATIONAL_JOIN_OPERATORS => 'SQLUINTEGER bitmask' # 161
+, SQL_SQL92_REVOKE => 'SQLUINTEGER bitmask' # 162
+, SQL_SQL92_ROW_VALUE_CONSTRUCTOR => 'SQLUINTEGER bitmask' # 163
+, SQL_SQL92_STRING_FUNCTIONS => 'SQLUINTEGER bitmask' # 164
+, SQL_SQL92_VALUE_EXPRESSIONS => 'SQLUINTEGER bitmask' # 165
+, SQL_SQL_CONFORMANCE => 'SQLUINTEGER' # 118
+, SQL_STANDARD_CLI_CONFORMANCE => 'SQLUINTEGER bitmask' # 166
+, SQL_STATIC_CURSOR_ATTRIBUTES1 => 'SQLUINTEGER bitmask' # 167
+, SQL_STATIC_CURSOR_ATTRIBUTES2 => 'SQLUINTEGER bitmask' # 168
+, SQL_STATIC_SENSITIVITY => 'SQLUINTEGER bitmask' # 83 => !!!
+, SQL_STRING_FUNCTIONS => 'SQLUINTEGER bitmask' # 50
+, SQL_SUBQUERIES => 'SQLUINTEGER bitmask' # 95
+, SQL_SYSTEM_FUNCTIONS => 'SQLUINTEGER bitmask' # 51
+, SQL_TABLE_TERM => 'SQLCHAR' # 45
+, SQL_TIMEDATE_ADD_INTERVALS => 'SQLUINTEGER bitmask' # 109
+, SQL_TIMEDATE_DIFF_INTERVALS => 'SQLUINTEGER bitmask' # 110
+, SQL_TIMEDATE_FUNCTIONS => 'SQLUINTEGER bitmask' # 52
+, SQL_TRANSACTION_CAPABLE => 'SQLUSMALLINT' # 46 =>
+, SQL_TRANSACTION_ISOLATION_OPTION => 'SQLUINTEGER bitmask' # 72 =>
+, SQL_TXN_CAPABLE => 'SQLUSMALLINT' # 46
+, SQL_TXN_ISOLATION_OPTION => 'SQLUINTEGER bitmask' # 72
+, SQL_UNION => 'SQLUINTEGER bitmask' # 96
+, SQL_UNION_STATEMENT => 'SQLUINTEGER bitmask' # 96 =>
+, SQL_USER_NAME => 'SQLCHAR' # 47
+, SQL_XOPEN_CLI_YEAR => 'SQLCHAR' # 10000
+);
+
+=head2 %ReturnValues
+
+See: sql.h, sqlext.h
+Edited:
+ SQL_TXN_ISOLATION_OPTION
+
+=cut
+
+$ReturnValues{SQL_AGGREGATE_FUNCTIONS} =
+{
+ SQL_AF_AVG => 0x00000001
+, SQL_AF_COUNT => 0x00000002
+, SQL_AF_MAX => 0x00000004
+, SQL_AF_MIN => 0x00000008
+, SQL_AF_SUM => 0x00000010
+, SQL_AF_DISTINCT => 0x00000020
+, SQL_AF_ALL => 0x00000040
+};
+$ReturnValues{SQL_ALTER_DOMAIN} =
+{
+ SQL_AD_CONSTRAINT_NAME_DEFINITION => 0x00000001
+, SQL_AD_ADD_DOMAIN_CONSTRAINT => 0x00000002
+, SQL_AD_DROP_DOMAIN_CONSTRAINT => 0x00000004
+, SQL_AD_ADD_DOMAIN_DEFAULT => 0x00000008
+, SQL_AD_DROP_DOMAIN_DEFAULT => 0x00000010
+, SQL_AD_ADD_CONSTRAINT_INITIALLY_DEFERRED => 0x00000020
+, SQL_AD_ADD_CONSTRAINT_INITIALLY_IMMEDIATE => 0x00000040
+, SQL_AD_ADD_CONSTRAINT_DEFERRABLE => 0x00000080
+, SQL_AD_ADD_CONSTRAINT_NON_DEFERRABLE => 0x00000100
+};
+$ReturnValues{SQL_ALTER_TABLE} =
+{
+ SQL_AT_ADD_COLUMN => 0x00000001
+, SQL_AT_DROP_COLUMN => 0x00000002
+, SQL_AT_ADD_CONSTRAINT => 0x00000008
+, SQL_AT_ADD_COLUMN_SINGLE => 0x00000020
+, SQL_AT_ADD_COLUMN_DEFAULT => 0x00000040
+, SQL_AT_ADD_COLUMN_COLLATION => 0x00000080
+, SQL_AT_SET_COLUMN_DEFAULT => 0x00000100
+, SQL_AT_DROP_COLUMN_DEFAULT => 0x00000200
+, SQL_AT_DROP_COLUMN_CASCADE => 0x00000400
+, SQL_AT_DROP_COLUMN_RESTRICT => 0x00000800
+, SQL_AT_ADD_TABLE_CONSTRAINT => 0x00001000
+, SQL_AT_DROP_TABLE_CONSTRAINT_CASCADE => 0x00002000
+, SQL_AT_DROP_TABLE_CONSTRAINT_RESTRICT => 0x00004000
+, SQL_AT_CONSTRAINT_NAME_DEFINITION => 0x00008000
+, SQL_AT_CONSTRAINT_INITIALLY_DEFERRED => 0x00010000
+, SQL_AT_CONSTRAINT_INITIALLY_IMMEDIATE => 0x00020000
+, SQL_AT_CONSTRAINT_DEFERRABLE => 0x00040000
+, SQL_AT_CONSTRAINT_NON_DEFERRABLE => 0x00080000
+};
+$ReturnValues{SQL_ASYNC_MODE} =
+{
+ SQL_AM_NONE => 0
+, SQL_AM_CONNECTION => 1
+, SQL_AM_STATEMENT => 2
+};
+$ReturnValues{SQL_ATTR_MAX_ROWS} =
+{
+ SQL_CA2_MAX_ROWS_SELECT => 0x00000080
+, SQL_CA2_MAX_ROWS_INSERT => 0x00000100
+, SQL_CA2_MAX_ROWS_DELETE => 0x00000200
+, SQL_CA2_MAX_ROWS_UPDATE => 0x00000400
+, SQL_CA2_MAX_ROWS_CATALOG => 0x00000800
+# SQL_CA2_MAX_ROWS_AFFECTS_ALL =>
+};
+$ReturnValues{SQL_ATTR_SCROLL_CONCURRENCY} =
+{
+ SQL_CA2_READ_ONLY_CONCURRENCY => 0x00000001
+, SQL_CA2_LOCK_CONCURRENCY => 0x00000002
+, SQL_CA2_OPT_ROWVER_CONCURRENCY => 0x00000004
+, SQL_CA2_OPT_VALUES_CONCURRENCY => 0x00000008
+, SQL_CA2_SENSITIVITY_ADDITIONS => 0x00000010
+, SQL_CA2_SENSITIVITY_DELETIONS => 0x00000020
+, SQL_CA2_SENSITIVITY_UPDATES => 0x00000040
+};
+$ReturnValues{SQL_BATCH_ROW_COUNT} =
+{
+ SQL_BRC_PROCEDURES => 0x0000001
+, SQL_BRC_EXPLICIT => 0x0000002
+, SQL_BRC_ROLLED_UP => 0x0000004
+};
+$ReturnValues{SQL_BATCH_SUPPORT} =
+{
+ SQL_BS_SELECT_EXPLICIT => 0x00000001
+, SQL_BS_ROW_COUNT_EXPLICIT => 0x00000002
+, SQL_BS_SELECT_PROC => 0x00000004
+, SQL_BS_ROW_COUNT_PROC => 0x00000008
+};
+$ReturnValues{SQL_BOOKMARK_PERSISTENCE} =
+{
+ SQL_BP_CLOSE => 0x00000001
+, SQL_BP_DELETE => 0x00000002
+, SQL_BP_DROP => 0x00000004
+, SQL_BP_TRANSACTION => 0x00000008
+, SQL_BP_UPDATE => 0x00000010
+, SQL_BP_OTHER_HSTMT => 0x00000020
+, SQL_BP_SCROLL => 0x00000040
+};
+$ReturnValues{SQL_CATALOG_LOCATION} =
+{
+ SQL_CL_START => 0x0001 # SQL_QL_START
+, SQL_CL_END => 0x0002 # SQL_QL_END
+};
+$ReturnValues{SQL_CATALOG_USAGE} =
+{
+ SQL_CU_DML_STATEMENTS => 0x00000001 # SQL_QU_DML_STATEMENTS
+, SQL_CU_PROCEDURE_INVOCATION => 0x00000002 # SQL_QU_PROCEDURE_INVOCATION
+, SQL_CU_TABLE_DEFINITION => 0x00000004 # SQL_QU_TABLE_DEFINITION
+, SQL_CU_INDEX_DEFINITION => 0x00000008 # SQL_QU_INDEX_DEFINITION
+, SQL_CU_PRIVILEGE_DEFINITION => 0x00000010 # SQL_QU_PRIVILEGE_DEFINITION
+};
+$ReturnValues{SQL_CONCAT_NULL_BEHAVIOR} =
+{
+ SQL_CB_NULL => 0x0000
+, SQL_CB_NON_NULL => 0x0001
+};
+$ReturnValues{SQL_CONVERT_} =
+{
+ SQL_CVT_CHAR => 0x00000001
+, SQL_CVT_NUMERIC => 0x00000002
+, SQL_CVT_DECIMAL => 0x00000004
+, SQL_CVT_INTEGER => 0x00000008
+, SQL_CVT_SMALLINT => 0x00000010
+, SQL_CVT_FLOAT => 0x00000020
+, SQL_CVT_REAL => 0x00000040
+, SQL_CVT_DOUBLE => 0x00000080
+, SQL_CVT_VARCHAR => 0x00000100
+, SQL_CVT_LONGVARCHAR => 0x00000200
+, SQL_CVT_BINARY => 0x00000400
+, SQL_CVT_VARBINARY => 0x00000800
+, SQL_CVT_BIT => 0x00001000
+, SQL_CVT_TINYINT => 0x00002000
+, SQL_CVT_BIGINT => 0x00004000
+, SQL_CVT_DATE => 0x00008000
+, SQL_CVT_TIME => 0x00010000
+, SQL_CVT_TIMESTAMP => 0x00020000
+, SQL_CVT_LONGVARBINARY => 0x00040000
+, SQL_CVT_INTERVAL_YEAR_MONTH => 0x00080000
+, SQL_CVT_INTERVAL_DAY_TIME => 0x00100000
+, SQL_CVT_WCHAR => 0x00200000
+, SQL_CVT_WLONGVARCHAR => 0x00400000
+, SQL_CVT_WVARCHAR => 0x00800000
+, SQL_CVT_GUID => 0x01000000
+};
+$ReturnValues{SQL_CONVERT_BIGINT } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_BINARY } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_BIT } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_CHAR } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_DATE } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_DECIMAL } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_DOUBLE } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_FLOAT } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_GUID } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_INTEGER } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_INTERVAL_DAY_TIME } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_INTERVAL_YEAR_MONTH} = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_LONGVARBINARY } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_LONGVARCHAR } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_NUMERIC } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_REAL } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_SMALLINT } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_TIME } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_TIMESTAMP } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_TINYINT } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_VARBINARY } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_VARCHAR } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_WCHAR } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_WLONGVARCHAR } = $ReturnValues{SQL_CONVERT_};
+$ReturnValues{SQL_CONVERT_WVARCHAR } = $ReturnValues{SQL_CONVERT_};
+
+$ReturnValues{SQL_CONVERT_FUNCTIONS} =
+{
+ SQL_FN_CVT_CONVERT => 0x00000001
+, SQL_FN_CVT_CAST => 0x00000002
+};
+$ReturnValues{SQL_CORRELATION_NAME} =
+{
+ SQL_CN_NONE => 0x0000
+, SQL_CN_DIFFERENT => 0x0001
+, SQL_CN_ANY => 0x0002
+};
+$ReturnValues{SQL_CREATE_ASSERTION} =
+{
+ SQL_CA_CREATE_ASSERTION => 0x00000001
+, SQL_CA_CONSTRAINT_INITIALLY_DEFERRED => 0x00000010
+, SQL_CA_CONSTRAINT_INITIALLY_IMMEDIATE => 0x00000020
+, SQL_CA_CONSTRAINT_DEFERRABLE => 0x00000040
+, SQL_CA_CONSTRAINT_NON_DEFERRABLE => 0x00000080
+};
+$ReturnValues{SQL_CREATE_CHARACTER_SET} =
+{
+ SQL_CCS_CREATE_CHARACTER_SET => 0x00000001
+, SQL_CCS_COLLATE_CLAUSE => 0x00000002
+, SQL_CCS_LIMITED_COLLATION => 0x00000004
+};
+$ReturnValues{SQL_CREATE_COLLATION} =
+{
+ SQL_CCOL_CREATE_COLLATION => 0x00000001
+};
+$ReturnValues{SQL_CREATE_DOMAIN} =
+{
+ SQL_CDO_CREATE_DOMAIN => 0x00000001
+, SQL_CDO_DEFAULT => 0x00000002
+, SQL_CDO_CONSTRAINT => 0x00000004
+, SQL_CDO_COLLATION => 0x00000008
+, SQL_CDO_CONSTRAINT_NAME_DEFINITION => 0x00000010
+, SQL_CDO_CONSTRAINT_INITIALLY_DEFERRED => 0x00000020
+, SQL_CDO_CONSTRAINT_INITIALLY_IMMEDIATE => 0x00000040
+, SQL_CDO_CONSTRAINT_DEFERRABLE => 0x00000080
+, SQL_CDO_CONSTRAINT_NON_DEFERRABLE => 0x00000100
+};
+$ReturnValues{SQL_CREATE_SCHEMA} =
+{
+ SQL_CS_CREATE_SCHEMA => 0x00000001
+, SQL_CS_AUTHORIZATION => 0x00000002
+, SQL_CS_DEFAULT_CHARACTER_SET => 0x00000004
+};
+$ReturnValues{SQL_CREATE_TABLE} =
+{
+ SQL_CT_CREATE_TABLE => 0x00000001
+, SQL_CT_COMMIT_PRESERVE => 0x00000002
+, SQL_CT_COMMIT_DELETE => 0x00000004
+, SQL_CT_GLOBAL_TEMPORARY => 0x00000008
+, SQL_CT_LOCAL_TEMPORARY => 0x00000010
+, SQL_CT_CONSTRAINT_INITIALLY_DEFERRED => 0x00000020
+, SQL_CT_CONSTRAINT_INITIALLY_IMMEDIATE => 0x00000040
+, SQL_CT_CONSTRAINT_DEFERRABLE => 0x00000080
+, SQL_CT_CONSTRAINT_NON_DEFERRABLE => 0x00000100
+, SQL_CT_COLUMN_CONSTRAINT => 0x00000200
+, SQL_CT_COLUMN_DEFAULT => 0x00000400
+, SQL_CT_COLUMN_COLLATION => 0x00000800
+, SQL_CT_TABLE_CONSTRAINT => 0x00001000
+, SQL_CT_CONSTRAINT_NAME_DEFINITION => 0x00002000
+};
+$ReturnValues{SQL_CREATE_TRANSLATION} =
+{
+ SQL_CTR_CREATE_TRANSLATION => 0x00000001
+};
+$ReturnValues{SQL_CREATE_VIEW} =
+{
+ SQL_CV_CREATE_VIEW => 0x00000001
+, SQL_CV_CHECK_OPTION => 0x00000002
+, SQL_CV_CASCADED => 0x00000004
+, SQL_CV_LOCAL => 0x00000008
+};
+$ReturnValues{SQL_CURSOR_COMMIT_BEHAVIOR} =
+{
+ SQL_CB_DELETE => 0
+, SQL_CB_CLOSE => 1
+, SQL_CB_PRESERVE => 2
+};
+$ReturnValues{SQL_CURSOR_ROLLBACK_BEHAVIOR} = $ReturnValues{SQL_CURSOR_COMMIT_BEHAVIOR};
+
+$ReturnValues{SQL_CURSOR_SENSITIVITY} =
+{
+ SQL_UNSPECIFIED => 0
+, SQL_INSENSITIVE => 1
+, SQL_SENSITIVE => 2
+};
+$ReturnValues{SQL_DATETIME_LITERALS} =
+{
+ SQL_DL_SQL92_DATE => 0x00000001
+, SQL_DL_SQL92_TIME => 0x00000002
+, SQL_DL_SQL92_TIMESTAMP => 0x00000004
+, SQL_DL_SQL92_INTERVAL_YEAR => 0x00000008
+, SQL_DL_SQL92_INTERVAL_MONTH => 0x00000010
+, SQL_DL_SQL92_INTERVAL_DAY => 0x00000020
+, SQL_DL_SQL92_INTERVAL_HOUR => 0x00000040
+, SQL_DL_SQL92_INTERVAL_MINUTE => 0x00000080
+, SQL_DL_SQL92_INTERVAL_SECOND => 0x00000100
+, SQL_DL_SQL92_INTERVAL_YEAR_TO_MONTH => 0x00000200
+, SQL_DL_SQL92_INTERVAL_DAY_TO_HOUR => 0x00000400
+, SQL_DL_SQL92_INTERVAL_DAY_TO_MINUTE => 0x00000800
+, SQL_DL_SQL92_INTERVAL_DAY_TO_SECOND => 0x00001000
+, SQL_DL_SQL92_INTERVAL_HOUR_TO_MINUTE => 0x00002000
+, SQL_DL_SQL92_INTERVAL_HOUR_TO_SECOND => 0x00004000
+, SQL_DL_SQL92_INTERVAL_MINUTE_TO_SECOND => 0x00008000
+};
+$ReturnValues{SQL_DDL_INDEX} =
+{
+ SQL_DI_CREATE_INDEX => 0x00000001
+, SQL_DI_DROP_INDEX => 0x00000002
+};
+$ReturnValues{SQL_DIAG_CURSOR_ROW_COUNT} =
+{
+ SQL_CA2_CRC_EXACT => 0x00001000
+, SQL_CA2_CRC_APPROXIMATE => 0x00002000
+, SQL_CA2_SIMULATE_NON_UNIQUE => 0x00004000
+, SQL_CA2_SIMULATE_TRY_UNIQUE => 0x00008000
+, SQL_CA2_SIMULATE_UNIQUE => 0x00010000
+};
+$ReturnValues{SQL_DROP_ASSERTION} =
+{
+ SQL_DA_DROP_ASSERTION => 0x00000001
+};
+$ReturnValues{SQL_DROP_CHARACTER_SET} =
+{
+ SQL_DCS_DROP_CHARACTER_SET => 0x00000001
+};
+$ReturnValues{SQL_DROP_COLLATION} =
+{
+ SQL_DC_DROP_COLLATION => 0x00000001
+};
+$ReturnValues{SQL_DROP_DOMAIN} =
+{
+ SQL_DD_DROP_DOMAIN => 0x00000001
+, SQL_DD_RESTRICT => 0x00000002
+, SQL_DD_CASCADE => 0x00000004
+};
+$ReturnValues{SQL_DROP_SCHEMA} =
+{
+ SQL_DS_DROP_SCHEMA => 0x00000001
+, SQL_DS_RESTRICT => 0x00000002
+, SQL_DS_CASCADE => 0x00000004
+};
+$ReturnValues{SQL_DROP_TABLE} =
+{
+ SQL_DT_DROP_TABLE => 0x00000001
+, SQL_DT_RESTRICT => 0x00000002
+, SQL_DT_CASCADE => 0x00000004
+};
+$ReturnValues{SQL_DROP_TRANSLATION} =
+{
+ SQL_DTR_DROP_TRANSLATION => 0x00000001
+};
+$ReturnValues{SQL_DROP_VIEW} =
+{
+ SQL_DV_DROP_VIEW => 0x00000001
+, SQL_DV_RESTRICT => 0x00000002
+, SQL_DV_CASCADE => 0x00000004
+};
+$ReturnValues{SQL_CURSOR_ATTRIBUTES1} =
+{
+ SQL_CA1_NEXT => 0x00000001
+, SQL_CA1_ABSOLUTE => 0x00000002
+, SQL_CA1_RELATIVE => 0x00000004
+, SQL_CA1_BOOKMARK => 0x00000008
+, SQL_CA1_LOCK_NO_CHANGE => 0x00000040
+, SQL_CA1_LOCK_EXCLUSIVE => 0x00000080
+, SQL_CA1_LOCK_UNLOCK => 0x00000100
+, SQL_CA1_POS_POSITION => 0x00000200
+, SQL_CA1_POS_UPDATE => 0x00000400
+, SQL_CA1_POS_DELETE => 0x00000800
+, SQL_CA1_POS_REFRESH => 0x00001000
+, SQL_CA1_POSITIONED_UPDATE => 0x00002000
+, SQL_CA1_POSITIONED_DELETE => 0x00004000
+, SQL_CA1_SELECT_FOR_UPDATE => 0x00008000
+, SQL_CA1_BULK_ADD => 0x00010000
+, SQL_CA1_BULK_UPDATE_BY_BOOKMARK => 0x00020000
+, SQL_CA1_BULK_DELETE_BY_BOOKMARK => 0x00040000
+, SQL_CA1_BULK_FETCH_BY_BOOKMARK => 0x00080000
+};
+$ReturnValues{ SQL_DYNAMIC_CURSOR_ATTRIBUTES1} = $ReturnValues{SQL_CURSOR_ATTRIBUTES1};
+$ReturnValues{SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1} = $ReturnValues{SQL_CURSOR_ATTRIBUTES1};
+$ReturnValues{ SQL_KEYSET_CURSOR_ATTRIBUTES1} = $ReturnValues{SQL_CURSOR_ATTRIBUTES1};
+$ReturnValues{ SQL_STATIC_CURSOR_ATTRIBUTES1} = $ReturnValues{SQL_CURSOR_ATTRIBUTES1};
+
+$ReturnValues{SQL_CURSOR_ATTRIBUTES2} =
+{
+ SQL_CA2_READ_ONLY_CONCURRENCY => 0x00000001
+, SQL_CA2_LOCK_CONCURRENCY => 0x00000002
+, SQL_CA2_OPT_ROWVER_CONCURRENCY => 0x00000004
+, SQL_CA2_OPT_VALUES_CONCURRENCY => 0x00000008
+, SQL_CA2_SENSITIVITY_ADDITIONS => 0x00000010
+, SQL_CA2_SENSITIVITY_DELETIONS => 0x00000020
+, SQL_CA2_SENSITIVITY_UPDATES => 0x00000040
+, SQL_CA2_MAX_ROWS_SELECT => 0x00000080
+, SQL_CA2_MAX_ROWS_INSERT => 0x00000100
+, SQL_CA2_MAX_ROWS_DELETE => 0x00000200
+, SQL_CA2_MAX_ROWS_UPDATE => 0x00000400
+, SQL_CA2_MAX_ROWS_CATALOG => 0x00000800
+, SQL_CA2_CRC_EXACT => 0x00001000
+, SQL_CA2_CRC_APPROXIMATE => 0x00002000
+, SQL_CA2_SIMULATE_NON_UNIQUE => 0x00004000
+, SQL_CA2_SIMULATE_TRY_UNIQUE => 0x00008000
+, SQL_CA2_SIMULATE_UNIQUE => 0x00010000
+};
+$ReturnValues{ SQL_DYNAMIC_CURSOR_ATTRIBUTES2} = $ReturnValues{SQL_CURSOR_ATTRIBUTES2};
+$ReturnValues{SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2} = $ReturnValues{SQL_CURSOR_ATTRIBUTES2};
+$ReturnValues{ SQL_KEYSET_CURSOR_ATTRIBUTES2} = $ReturnValues{SQL_CURSOR_ATTRIBUTES2};
+$ReturnValues{ SQL_STATIC_CURSOR_ATTRIBUTES2} = $ReturnValues{SQL_CURSOR_ATTRIBUTES2};
+
+$ReturnValues{SQL_FETCH_DIRECTION} =
+{
+ SQL_FD_FETCH_NEXT => 0x00000001
+, SQL_FD_FETCH_FIRST => 0x00000002
+, SQL_FD_FETCH_LAST => 0x00000004
+, SQL_FD_FETCH_PRIOR => 0x00000008
+, SQL_FD_FETCH_ABSOLUTE => 0x00000010
+, SQL_FD_FETCH_RELATIVE => 0x00000020
+, SQL_FD_FETCH_RESUME => 0x00000040
+, SQL_FD_FETCH_BOOKMARK => 0x00000080
+};
+$ReturnValues{SQL_FILE_USAGE} =
+{
+ SQL_FILE_NOT_SUPPORTED => 0x0000
+, SQL_FILE_TABLE => 0x0001
+, SQL_FILE_QUALIFIER => 0x0002
+, SQL_FILE_CATALOG => 0x0002 # SQL_FILE_QUALIFIER
+};
+$ReturnValues{SQL_GETDATA_EXTENSIONS} =
+{
+ SQL_GD_ANY_COLUMN => 0x00000001
+, SQL_GD_ANY_ORDER => 0x00000002
+, SQL_GD_BLOCK => 0x00000004
+, SQL_GD_BOUND => 0x00000008
+};
+$ReturnValues{SQL_GROUP_BY} =
+{
+ SQL_GB_NOT_SUPPORTED => 0x0000
+, SQL_GB_GROUP_BY_EQUALS_SELECT => 0x0001
+, SQL_GB_GROUP_BY_CONTAINS_SELECT => 0x0002
+, SQL_GB_NO_RELATION => 0x0003
+, SQL_GB_COLLATE => 0x0004
+};
+$ReturnValues{SQL_IDENTIFIER_CASE} =
+{
+ SQL_IC_UPPER => 1
+, SQL_IC_LOWER => 2
+, SQL_IC_SENSITIVE => 3
+, SQL_IC_MIXED => 4
+};
+$ReturnValues{SQL_INDEX_KEYWORDS} =
+{
+ SQL_IK_NONE => 0x00000000
+, SQL_IK_ASC => 0x00000001
+, SQL_IK_DESC => 0x00000002
+# SQL_IK_ALL =>
+};
+$ReturnValues{SQL_INFO_SCHEMA_VIEWS} =
+{
+ SQL_ISV_ASSERTIONS => 0x00000001
+, SQL_ISV_CHARACTER_SETS => 0x00000002
+, SQL_ISV_CHECK_CONSTRAINTS => 0x00000004
+, SQL_ISV_COLLATIONS => 0x00000008
+, SQL_ISV_COLUMN_DOMAIN_USAGE => 0x00000010
+, SQL_ISV_COLUMN_PRIVILEGES => 0x00000020
+, SQL_ISV_COLUMNS => 0x00000040
+, SQL_ISV_CONSTRAINT_COLUMN_USAGE => 0x00000080
+, SQL_ISV_CONSTRAINT_TABLE_USAGE => 0x00000100
+, SQL_ISV_DOMAIN_CONSTRAINTS => 0x00000200
+, SQL_ISV_DOMAINS => 0x00000400
+, SQL_ISV_KEY_COLUMN_USAGE => 0x00000800
+, SQL_ISV_REFERENTIAL_CONSTRAINTS => 0x00001000
+, SQL_ISV_SCHEMATA => 0x00002000
+, SQL_ISV_SQL_LANGUAGES => 0x00004000
+, SQL_ISV_TABLE_CONSTRAINTS => 0x00008000
+, SQL_ISV_TABLE_PRIVILEGES => 0x00010000
+, SQL_ISV_TABLES => 0x00020000
+, SQL_ISV_TRANSLATIONS => 0x00040000
+, SQL_ISV_USAGE_PRIVILEGES => 0x00080000
+, SQL_ISV_VIEW_COLUMN_USAGE => 0x00100000
+, SQL_ISV_VIEW_TABLE_USAGE => 0x00200000
+, SQL_ISV_VIEWS => 0x00400000
+};
+$ReturnValues{SQL_INSERT_STATEMENT} =
+{
+ SQL_IS_INSERT_LITERALS => 0x00000001
+, SQL_IS_INSERT_SEARCHED => 0x00000002
+, SQL_IS_SELECT_INTO => 0x00000004
+};
+$ReturnValues{SQL_LOCK_TYPES} =
+{
+ SQL_LCK_NO_CHANGE => 0x00000001
+, SQL_LCK_EXCLUSIVE => 0x00000002
+, SQL_LCK_UNLOCK => 0x00000004
+};
+$ReturnValues{SQL_NON_NULLABLE_COLUMNS} =
+{
+ SQL_NNC_NULL => 0x0000
+, SQL_NNC_NON_NULL => 0x0001
+};
+$ReturnValues{SQL_NULL_COLLATION} =
+{
+ SQL_NC_HIGH => 0
+, SQL_NC_LOW => 1
+, SQL_NC_START => 0x0002
+, SQL_NC_END => 0x0004
+};
+$ReturnValues{SQL_NUMERIC_FUNCTIONS} =
+{
+ SQL_FN_NUM_ABS => 0x00000001
+, SQL_FN_NUM_ACOS => 0x00000002
+, SQL_FN_NUM_ASIN => 0x00000004
+, SQL_FN_NUM_ATAN => 0x00000008
+, SQL_FN_NUM_ATAN2 => 0x00000010
+, SQL_FN_NUM_CEILING => 0x00000020
+, SQL_FN_NUM_COS => 0x00000040
+, SQL_FN_NUM_COT => 0x00000080
+, SQL_FN_NUM_EXP => 0x00000100
+, SQL_FN_NUM_FLOOR => 0x00000200
+, SQL_FN_NUM_LOG => 0x00000400
+, SQL_FN_NUM_MOD => 0x00000800
+, SQL_FN_NUM_SIGN => 0x00001000
+, SQL_FN_NUM_SIN => 0x00002000
+, SQL_FN_NUM_SQRT => 0x00004000
+, SQL_FN_NUM_TAN => 0x00008000
+, SQL_FN_NUM_PI => 0x00010000
+, SQL_FN_NUM_RAND => 0x00020000
+, SQL_FN_NUM_DEGREES => 0x00040000
+, SQL_FN_NUM_LOG10 => 0x00080000
+, SQL_FN_NUM_POWER => 0x00100000
+, SQL_FN_NUM_RADIANS => 0x00200000
+, SQL_FN_NUM_ROUND => 0x00400000
+, SQL_FN_NUM_TRUNCATE => 0x00800000
+};
+$ReturnValues{SQL_ODBC_API_CONFORMANCE} =
+{
+ SQL_OAC_NONE => 0x0000
+, SQL_OAC_LEVEL1 => 0x0001
+, SQL_OAC_LEVEL2 => 0x0002
+};
+$ReturnValues{SQL_ODBC_INTERFACE_CONFORMANCE} =
+{
+ SQL_OIC_CORE => 1
+, SQL_OIC_LEVEL1 => 2
+, SQL_OIC_LEVEL2 => 3
+};
+$ReturnValues{SQL_ODBC_SAG_CLI_CONFORMANCE} =
+{
+ SQL_OSCC_NOT_COMPLIANT => 0x0000
+, SQL_OSCC_COMPLIANT => 0x0001
+};
+$ReturnValues{SQL_ODBC_SQL_CONFORMANCE} =
+{
+ SQL_OSC_MINIMUM => 0x0000
+, SQL_OSC_CORE => 0x0001
+, SQL_OSC_EXTENDED => 0x0002
+};
+$ReturnValues{SQL_OJ_CAPABILITIES} =
+{
+ SQL_OJ_LEFT => 0x00000001
+, SQL_OJ_RIGHT => 0x00000002
+, SQL_OJ_FULL => 0x00000004
+, SQL_OJ_NESTED => 0x00000008
+, SQL_OJ_NOT_ORDERED => 0x00000010
+, SQL_OJ_INNER => 0x00000020
+, SQL_OJ_ALL_COMPARISON_OPS => 0x00000040
+};
+$ReturnValues{SQL_OWNER_USAGE} =
+{
+ SQL_OU_DML_STATEMENTS => 0x00000001
+, SQL_OU_PROCEDURE_INVOCATION => 0x00000002
+, SQL_OU_TABLE_DEFINITION => 0x00000004
+, SQL_OU_INDEX_DEFINITION => 0x00000008
+, SQL_OU_PRIVILEGE_DEFINITION => 0x00000010
+};
+$ReturnValues{SQL_PARAM_ARRAY_ROW_COUNTS} =
+{
+ SQL_PARC_BATCH => 1
+, SQL_PARC_NO_BATCH => 2
+};
+$ReturnValues{SQL_PARAM_ARRAY_SELECTS} =
+{
+ SQL_PAS_BATCH => 1
+, SQL_PAS_NO_BATCH => 2
+, SQL_PAS_NO_SELECT => 3
+};
+$ReturnValues{SQL_POSITIONED_STATEMENTS} =
+{
+ SQL_PS_POSITIONED_DELETE => 0x00000001
+, SQL_PS_POSITIONED_UPDATE => 0x00000002
+, SQL_PS_SELECT_FOR_UPDATE => 0x00000004
+};
+$ReturnValues{SQL_POS_OPERATIONS} =
+{
+ SQL_POS_POSITION => 0x00000001
+, SQL_POS_REFRESH => 0x00000002
+, SQL_POS_UPDATE => 0x00000004
+, SQL_POS_DELETE => 0x00000008
+, SQL_POS_ADD => 0x00000010
+};
+$ReturnValues{SQL_QUALIFIER_LOCATION} =
+{
+ SQL_QL_START => 0x0001
+, SQL_QL_END => 0x0002
+};
+$ReturnValues{SQL_QUALIFIER_USAGE} =
+{
+ SQL_QU_DML_STATEMENTS => 0x00000001
+, SQL_QU_PROCEDURE_INVOCATION => 0x00000002
+, SQL_QU_TABLE_DEFINITION => 0x00000004
+, SQL_QU_INDEX_DEFINITION => 0x00000008
+, SQL_QU_PRIVILEGE_DEFINITION => 0x00000010
+};
+$ReturnValues{SQL_QUOTED_IDENTIFIER_CASE} = $ReturnValues{SQL_IDENTIFIER_CASE};
+
+$ReturnValues{SQL_SCHEMA_USAGE} =
+{
+ SQL_SU_DML_STATEMENTS => 0x00000001 # SQL_OU_DML_STATEMENTS
+, SQL_SU_PROCEDURE_INVOCATION => 0x00000002 # SQL_OU_PROCEDURE_INVOCATION
+, SQL_SU_TABLE_DEFINITION => 0x00000004 # SQL_OU_TABLE_DEFINITION
+, SQL_SU_INDEX_DEFINITION => 0x00000008 # SQL_OU_INDEX_DEFINITION
+, SQL_SU_PRIVILEGE_DEFINITION => 0x00000010 # SQL_OU_PRIVILEGE_DEFINITION
+};
+$ReturnValues{SQL_SCROLL_CONCURRENCY} =
+{
+ SQL_SCCO_READ_ONLY => 0x00000001
+, SQL_SCCO_LOCK => 0x00000002
+, SQL_SCCO_OPT_ROWVER => 0x00000004
+, SQL_SCCO_OPT_VALUES => 0x00000008
+};
+$ReturnValues{SQL_SCROLL_OPTIONS} =
+{
+ SQL_SO_FORWARD_ONLY => 0x00000001
+, SQL_SO_KEYSET_DRIVEN => 0x00000002
+, SQL_SO_DYNAMIC => 0x00000004
+, SQL_SO_MIXED => 0x00000008
+, SQL_SO_STATIC => 0x00000010
+};
+$ReturnValues{SQL_SQL92_DATETIME_FUNCTIONS} =
+{
+ SQL_SDF_CURRENT_DATE => 0x00000001
+, SQL_SDF_CURRENT_TIME => 0x00000002
+, SQL_SDF_CURRENT_TIMESTAMP => 0x00000004
+};
+$ReturnValues{SQL_SQL92_FOREIGN_KEY_DELETE_RULE} =
+{
+ SQL_SFKD_CASCADE => 0x00000001
+, SQL_SFKD_NO_ACTION => 0x00000002
+, SQL_SFKD_SET_DEFAULT => 0x00000004
+, SQL_SFKD_SET_NULL => 0x00000008
+};
+$ReturnValues{SQL_SQL92_FOREIGN_KEY_UPDATE_RULE} =
+{
+ SQL_SFKU_CASCADE => 0x00000001
+, SQL_SFKU_NO_ACTION => 0x00000002
+, SQL_SFKU_SET_DEFAULT => 0x00000004
+, SQL_SFKU_SET_NULL => 0x00000008
+};
+$ReturnValues{SQL_SQL92_GRANT} =
+{
+ SQL_SG_USAGE_ON_DOMAIN => 0x00000001
+, SQL_SG_USAGE_ON_CHARACTER_SET => 0x00000002
+, SQL_SG_USAGE_ON_COLLATION => 0x00000004
+, SQL_SG_USAGE_ON_TRANSLATION => 0x00000008
+, SQL_SG_WITH_GRANT_OPTION => 0x00000010
+, SQL_SG_DELETE_TABLE => 0x00000020
+, SQL_SG_INSERT_TABLE => 0x00000040
+, SQL_SG_INSERT_COLUMN => 0x00000080
+, SQL_SG_REFERENCES_TABLE => 0x00000100
+, SQL_SG_REFERENCES_COLUMN => 0x00000200
+, SQL_SG_SELECT_TABLE => 0x00000400
+, SQL_SG_UPDATE_TABLE => 0x00000800
+, SQL_SG_UPDATE_COLUMN => 0x00001000
+};
+$ReturnValues{SQL_SQL92_NUMERIC_VALUE_FUNCTIONS} =
+{
+ SQL_SNVF_BIT_LENGTH => 0x00000001
+, SQL_SNVF_CHAR_LENGTH => 0x00000002
+, SQL_SNVF_CHARACTER_LENGTH => 0x00000004
+, SQL_SNVF_EXTRACT => 0x00000008
+, SQL_SNVF_OCTET_LENGTH => 0x00000010
+, SQL_SNVF_POSITION => 0x00000020
+};
+$ReturnValues{SQL_SQL92_PREDICATES} =
+{
+ SQL_SP_EXISTS => 0x00000001
+, SQL_SP_ISNOTNULL => 0x00000002
+, SQL_SP_ISNULL => 0x00000004
+, SQL_SP_MATCH_FULL => 0x00000008
+, SQL_SP_MATCH_PARTIAL => 0x00000010
+, SQL_SP_MATCH_UNIQUE_FULL => 0x00000020
+, SQL_SP_MATCH_UNIQUE_PARTIAL => 0x00000040
+, SQL_SP_OVERLAPS => 0x00000080
+, SQL_SP_UNIQUE => 0x00000100
+, SQL_SP_LIKE => 0x00000200
+, SQL_SP_IN => 0x00000400
+, SQL_SP_BETWEEN => 0x00000800
+, SQL_SP_COMPARISON => 0x00001000
+, SQL_SP_QUANTIFIED_COMPARISON => 0x00002000
+};
+$ReturnValues{SQL_SQL92_RELATIONAL_JOIN_OPERATORS} =
+{
+ SQL_SRJO_CORRESPONDING_CLAUSE => 0x00000001
+, SQL_SRJO_CROSS_JOIN => 0x00000002
+, SQL_SRJO_EXCEPT_JOIN => 0x00000004
+, SQL_SRJO_FULL_OUTER_JOIN => 0x00000008
+, SQL_SRJO_INNER_JOIN => 0x00000010
+, SQL_SRJO_INTERSECT_JOIN => 0x00000020
+, SQL_SRJO_LEFT_OUTER_JOIN => 0x00000040
+, SQL_SRJO_NATURAL_JOIN => 0x00000080
+, SQL_SRJO_RIGHT_OUTER_JOIN => 0x00000100
+, SQL_SRJO_UNION_JOIN => 0x00000200
+};
+$ReturnValues{SQL_SQL92_REVOKE} =
+{
+ SQL_SR_USAGE_ON_DOMAIN => 0x00000001
+, SQL_SR_USAGE_ON_CHARACTER_SET => 0x00000002
+, SQL_SR_USAGE_ON_COLLATION => 0x00000004
+, SQL_SR_USAGE_ON_TRANSLATION => 0x00000008
+, SQL_SR_GRANT_OPTION_FOR => 0x00000010
+, SQL_SR_CASCADE => 0x00000020
+, SQL_SR_RESTRICT => 0x00000040
+, SQL_SR_DELETE_TABLE => 0x00000080
+, SQL_SR_INSERT_TABLE => 0x00000100
+, SQL_SR_INSERT_COLUMN => 0x00000200
+, SQL_SR_REFERENCES_TABLE => 0x00000400
+, SQL_SR_REFERENCES_COLUMN => 0x00000800
+, SQL_SR_SELECT_TABLE => 0x00001000
+, SQL_SR_UPDATE_TABLE => 0x00002000
+, SQL_SR_UPDATE_COLUMN => 0x00004000
+};
+$ReturnValues{SQL_SQL92_ROW_VALUE_CONSTRUCTOR} =
+{
+ SQL_SRVC_VALUE_EXPRESSION => 0x00000001
+, SQL_SRVC_NULL => 0x00000002
+, SQL_SRVC_DEFAULT => 0x00000004
+, SQL_SRVC_ROW_SUBQUERY => 0x00000008
+};
+$ReturnValues{SQL_SQL92_STRING_FUNCTIONS} =
+{
+ SQL_SSF_CONVERT => 0x00000001
+, SQL_SSF_LOWER => 0x00000002
+, SQL_SSF_UPPER => 0x00000004
+, SQL_SSF_SUBSTRING => 0x00000008
+, SQL_SSF_TRANSLATE => 0x00000010
+, SQL_SSF_TRIM_BOTH => 0x00000020
+, SQL_SSF_TRIM_LEADING => 0x00000040
+, SQL_SSF_TRIM_TRAILING => 0x00000080
+};
+$ReturnValues{SQL_SQL92_VALUE_EXPRESSIONS} =
+{
+ SQL_SVE_CASE => 0x00000001
+, SQL_SVE_CAST => 0x00000002
+, SQL_SVE_COALESCE => 0x00000004
+, SQL_SVE_NULLIF => 0x00000008
+};
+$ReturnValues{SQL_SQL_CONFORMANCE} =
+{
+ SQL_SC_SQL92_ENTRY => 0x00000001
+, SQL_SC_FIPS127_2_TRANSITIONAL => 0x00000002
+, SQL_SC_SQL92_INTERMEDIATE => 0x00000004
+, SQL_SC_SQL92_FULL => 0x00000008
+};
+$ReturnValues{SQL_STANDARD_CLI_CONFORMANCE} =
+{
+ SQL_SCC_XOPEN_CLI_VERSION1 => 0x00000001
+, SQL_SCC_ISO92_CLI => 0x00000002
+};
+$ReturnValues{SQL_STATIC_SENSITIVITY} =
+{
+ SQL_SS_ADDITIONS => 0x00000001
+, SQL_SS_DELETIONS => 0x00000002
+, SQL_SS_UPDATES => 0x00000004
+};
+$ReturnValues{SQL_STRING_FUNCTIONS} =
+{
+ SQL_FN_STR_CONCAT => 0x00000001
+, SQL_FN_STR_INSERT => 0x00000002
+, SQL_FN_STR_LEFT => 0x00000004
+, SQL_FN_STR_LTRIM => 0x00000008
+, SQL_FN_STR_LENGTH => 0x00000010
+, SQL_FN_STR_LOCATE => 0x00000020
+, SQL_FN_STR_LCASE => 0x00000040
+, SQL_FN_STR_REPEAT => 0x00000080
+, SQL_FN_STR_REPLACE => 0x00000100
+, SQL_FN_STR_RIGHT => 0x00000200
+, SQL_FN_STR_RTRIM => 0x00000400
+, SQL_FN_STR_SUBSTRING => 0x00000800
+, SQL_FN_STR_UCASE => 0x00001000
+, SQL_FN_STR_ASCII => 0x00002000
+, SQL_FN_STR_CHAR => 0x00004000
+, SQL_FN_STR_DIFFERENCE => 0x00008000
+, SQL_FN_STR_LOCATE_2 => 0x00010000
+, SQL_FN_STR_SOUNDEX => 0x00020000
+, SQL_FN_STR_SPACE => 0x00040000
+, SQL_FN_STR_BIT_LENGTH => 0x00080000
+, SQL_FN_STR_CHAR_LENGTH => 0x00100000
+, SQL_FN_STR_CHARACTER_LENGTH => 0x00200000
+, SQL_FN_STR_OCTET_LENGTH => 0x00400000
+, SQL_FN_STR_POSITION => 0x00800000
+};
+$ReturnValues{SQL_SUBQUERIES} =
+{
+ SQL_SQ_COMPARISON => 0x00000001
+, SQL_SQ_EXISTS => 0x00000002
+, SQL_SQ_IN => 0x00000004
+, SQL_SQ_QUANTIFIED => 0x00000008
+, SQL_SQ_CORRELATED_SUBQUERIES => 0x00000010
+};
+$ReturnValues{SQL_SYSTEM_FUNCTIONS} =
+{
+ SQL_FN_SYS_USERNAME => 0x00000001
+, SQL_FN_SYS_DBNAME => 0x00000002
+, SQL_FN_SYS_IFNULL => 0x00000004
+};
+$ReturnValues{SQL_TIMEDATE_ADD_INTERVALS} =
+{
+ SQL_FN_TSI_FRAC_SECOND => 0x00000001
+, SQL_FN_TSI_SECOND => 0x00000002
+, SQL_FN_TSI_MINUTE => 0x00000004
+, SQL_FN_TSI_HOUR => 0x00000008
+, SQL_FN_TSI_DAY => 0x00000010
+, SQL_FN_TSI_WEEK => 0x00000020
+, SQL_FN_TSI_MONTH => 0x00000040
+, SQL_FN_TSI_QUARTER => 0x00000080
+, SQL_FN_TSI_YEAR => 0x00000100
+};
+$ReturnValues{SQL_TIMEDATE_FUNCTIONS} =
+{
+ SQL_FN_TD_NOW => 0x00000001
+, SQL_FN_TD_CURDATE => 0x00000002
+, SQL_FN_TD_DAYOFMONTH => 0x00000004
+, SQL_FN_TD_DAYOFWEEK => 0x00000008
+, SQL_FN_TD_DAYOFYEAR => 0x00000010
+, SQL_FN_TD_MONTH => 0x00000020
+, SQL_FN_TD_QUARTER => 0x00000040
+, SQL_FN_TD_WEEK => 0x00000080
+, SQL_FN_TD_YEAR => 0x00000100
+, SQL_FN_TD_CURTIME => 0x00000200
+, SQL_FN_TD_HOUR => 0x00000400
+, SQL_FN_TD_MINUTE => 0x00000800
+, SQL_FN_TD_SECOND => 0x00001000
+, SQL_FN_TD_TIMESTAMPADD => 0x00002000
+, SQL_FN_TD_TIMESTAMPDIFF => 0x00004000
+, SQL_FN_TD_DAYNAME => 0x00008000
+, SQL_FN_TD_MONTHNAME => 0x00010000
+, SQL_FN_TD_CURRENT_DATE => 0x00020000
+, SQL_FN_TD_CURRENT_TIME => 0x00040000
+, SQL_FN_TD_CURRENT_TIMESTAMP => 0x00080000
+, SQL_FN_TD_EXTRACT => 0x00100000
+};
+$ReturnValues{SQL_TXN_CAPABLE} =
+{
+ SQL_TC_NONE => 0
+, SQL_TC_DML => 1
+, SQL_TC_ALL => 2
+, SQL_TC_DDL_COMMIT => 3
+, SQL_TC_DDL_IGNORE => 4
+};
+$ReturnValues{SQL_TRANSACTION_ISOLATION_OPTION} =
+{
+ SQL_TRANSACTION_READ_UNCOMMITTED => 0x00000001 # SQL_TXN_READ_UNCOMMITTED
+, SQL_TRANSACTION_READ_COMMITTED => 0x00000002 # SQL_TXN_READ_COMMITTED
+, SQL_TRANSACTION_REPEATABLE_READ => 0x00000004 # SQL_TXN_REPEATABLE_READ
+, SQL_TRANSACTION_SERIALIZABLE => 0x00000008 # SQL_TXN_SERIALIZABLE
+};
+$ReturnValues{SQL_DEFAULT_TRANSACTION_ISOLATION} = $ReturnValues{SQL_TRANSACTION_ISOLATION_OPTION};
+
+$ReturnValues{SQL_TXN_ISOLATION_OPTION} =
+{
+ SQL_TXN_READ_UNCOMMITTED => 0x00000001
+, SQL_TXN_READ_COMMITTED => 0x00000002
+, SQL_TXN_REPEATABLE_READ => 0x00000004
+, SQL_TXN_SERIALIZABLE => 0x00000008
+};
+$ReturnValues{SQL_DEFAULT_TXN_ISOLATION} = $ReturnValues{SQL_TXN_ISOLATION_OPTION};
+
+$ReturnValues{SQL_TXN_VERSIONING} =
+{
+ SQL_TXN_VERSIONING => 0x00000010
+};
+$ReturnValues{SQL_UNION} =
+{
+ SQL_U_UNION => 0x00000001
+, SQL_U_UNION_ALL => 0x00000002
+};
+$ReturnValues{SQL_UNION_STATEMENT} =
+{
+ SQL_US_UNION => 0x00000001 # SQL_U_UNION
+, SQL_US_UNION_ALL => 0x00000002 # SQL_U_UNION_ALL
+};
+
+1;
+
+=head1 TODO
+
+ Corrections?
+ SQL_NULL_COLLATION: ODBC vs ANSI
+ Unique values for $ReturnValues{...}?, e.g. SQL_FILE_USAGE
+
+=cut
diff --git a/lib/DBI/Const/GetInfoReturn.pm b/lib/DBI/Const/GetInfoReturn.pm
new file mode 100644
index 0000000..d07b7ac
--- /dev/null
+++ b/lib/DBI/Const/GetInfoReturn.pm
@@ -0,0 +1,105 @@
+# $Id: GetInfoReturn.pm 8696 2007-01-24 23:12:38Z timbo $
+#
+# Copyright (c) 2002 Tim Bunce Ireland
+#
+# Constant data describing return values from the DBI getinfo function.
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+package DBI::Const::GetInfoReturn;
+
+use strict;
+
+use Exporter ();
+
+use vars qw(@ISA @EXPORT @EXPORT_OK %GetInfoReturnTypes %GetInfoReturnValues);
+
+@ISA = qw(Exporter);
+@EXPORT = qw(%GetInfoReturnTypes %GetInfoReturnValues);
+
+my
+$VERSION = sprintf("2.%06d", q$Revision: 8696 $ =~ /(\d+)/o);
+
+
+=head1 NAME
+
+DBI::Const::GetInfoReturn - Data and functions for describing GetInfo results
+
+=head1 SYNOPSIS
+
+The interface to this module is undocumented and liable to change.
+
+=head1 DESCRIPTION
+
+Data and functions for describing GetInfo results
+
+=cut
+
+use DBI::Const::GetInfoType;
+
+use DBI::Const::GetInfo::ANSI ();
+use DBI::Const::GetInfo::ODBC ();
+
+%GetInfoReturnTypes =
+(
+ %DBI::Const::GetInfo::ANSI::ReturnTypes
+, %DBI::Const::GetInfo::ODBC::ReturnTypes
+);
+
+%GetInfoReturnValues = ();
+{
+ my $A = \%DBI::Const::GetInfo::ANSI::ReturnValues;
+ my $O = \%DBI::Const::GetInfo::ODBC::ReturnValues;
+ while ( my ($k, $v) = each %$A ) {
+ my %h = ( exists $O->{$k} ) ? ( %$v, %{$O->{$k}} ) : %$v;
+ $GetInfoReturnValues{$k} = \%h;
+ }
+ while ( my ($k, $v) = each %$O ) {
+ next if exists $A->{$k};
+ my %h = %$v;
+ $GetInfoReturnValues{$k} = \%h;
+ }
+}
+
+# -----------------------------------------------------------------------------
+
+sub Format {
+ my $InfoType = shift;
+ my $Value = shift;
+
+ return '' unless defined $Value;
+
+ my $ReturnType = $GetInfoReturnTypes{$InfoType};
+
+ return sprintf '0x%08X', $Value if $ReturnType eq 'SQLUINTEGER bitmask';
+ return sprintf '0x%08X', $Value if $ReturnType eq 'SQLINTEGER bitmask';
+# return '"' . $Value . '"' if $ReturnType eq 'SQLCHAR';
+ return $Value;
+}
+
+
+sub Explain {
+ my $InfoType = shift;
+ my $Value = shift;
+
+ return '' unless defined $Value;
+ return '' unless exists $GetInfoReturnValues{$InfoType};
+
+ $Value = int $Value;
+ my $ReturnType = $GetInfoReturnTypes{$InfoType};
+ my %h = reverse %{$GetInfoReturnValues{$InfoType}};
+
+ if ( $ReturnType eq 'SQLUINTEGER bitmask'|| $ReturnType eq 'SQLINTEGER bitmask') {
+ my @a = ();
+ for my $k ( sort { $a <=> $b } keys %h ) {
+ push @a, $h{$k} if $Value & $k;
+ }
+ return wantarray ? @a : join(' ', @a );
+ }
+ else {
+ return $h{$Value} ||'?';
+ }
+}
+
+1;
diff --git a/lib/DBI/Const/GetInfoType.pm b/lib/DBI/Const/GetInfoType.pm
new file mode 100644
index 0000000..7c01778
--- /dev/null
+++ b/lib/DBI/Const/GetInfoType.pm
@@ -0,0 +1,54 @@
+# $Id: GetInfoType.pm 8696 2007-01-24 23:12:38Z timbo $
+#
+# Copyright (c) 2002 Tim Bunce Ireland
+#
+# Constant data describing info type codes for the DBI getinfo function.
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+package DBI::Const::GetInfoType;
+
+use strict;
+
+use Exporter ();
+
+use vars qw(@ISA @EXPORT @EXPORT_OK %GetInfoType);
+
+@ISA = qw(Exporter);
+@EXPORT = qw(%GetInfoType);
+
+my
+$VERSION = sprintf("2.%06d", q$Revision: 8696 $ =~ /(\d+)/o);
+
+
+=head1 NAME
+
+DBI::Const::GetInfoType - Data describing GetInfo type codes
+
+=head1 SYNOPSIS
+
+ use DBI::Const::GetInfoType;
+
+=head1 DESCRIPTION
+
+Imports a %GetInfoType hash which maps names for GetInfo Type Codes
+into their corresponding numeric values. For example:
+
+ $database_version = $dbh->get_info( $GetInfoType{SQL_DBMS_VER} );
+
+The interface to this module is new and nothing beyond what is
+written here is guaranteed.
+
+=cut
+
+use DBI::Const::GetInfo::ANSI (); # liable to change
+use DBI::Const::GetInfo::ODBC (); # liable to change
+
+%GetInfoType =
+(
+ %DBI::Const::GetInfo::ANSI::InfoTypes # liable to change
+, %DBI::Const::GetInfo::ODBC::InfoTypes # liable to change
+);
+
+1;
diff --git a/lib/DBI/DBD.pm b/lib/DBI/DBD.pm
new file mode 100644
index 0000000..6f8bf8c
--- /dev/null
+++ b/lib/DBI/DBD.pm
@@ -0,0 +1,3489 @@
+package DBI::DBD;
+# vim:ts=8:sw=4
+
+use vars qw($VERSION); # set $VERSION early so we don't confuse PAUSE/CPAN etc
+
+# don't use Revision here because that's not in svn:keywords so that the
+# examples that use it below won't be messed up
+$VERSION = sprintf("12.%06d", q$Id: DBD.pm 15128 2012-02-04 20:51:39Z timbo $ =~ /(\d+)/o);
+
+
+# $Id: DBD.pm 15128 2012-02-04 20:51:39Z timbo $
+#
+# Copyright (c) 1997-2006 Jonathan Leffler, Jochen Wiedmann, Steffen
+# Goeldner and Tim Bunce
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+=head1 NAME
+
+DBI::DBD - Perl DBI Database Driver Writer's Guide
+
+=head1 SYNOPSIS
+
+ perldoc DBI::DBD
+
+=head2 Version and volatility
+
+This document is I<still> a minimal draft which is in need of further work.
+
+The changes will occur both because the B<DBI> specification is changing
+and hence the requirements on B<DBD> drivers change, and because feedback
+from people reading this document will suggest improvements to it.
+
+Please read the B<DBI> documentation first and fully, including the B<DBI> FAQ.
+Then reread the B<DBI> specification again as you're reading this. It'll help.
+
+This document is a patchwork of contributions from various authors.
+More contributions (preferably as patches) are very welcome.
+
+=head1 DESCRIPTION
+
+This document is primarily intended to help people writing new
+database drivers for the Perl Database Interface (Perl DBI).
+It may also help others interested in discovering why the internals of
+a B<DBD> driver are written the way they are.
+
+This is a guide. Few (if any) of the statements in it are completely
+authoritative under all possible circumstances. This means you will
+need to use judgement in applying the guidelines in this document.
+If in I<any> doubt at all, please do contact the I<dbi-dev> mailing list
+(details given below) where Tim Bunce and other driver authors can help.
+
+=head1 CREATING A NEW DRIVER
+
+The first rule for creating a new database driver for the Perl DBI is
+very simple: B<DON'T!>
+
+There is usually a driver already available for the database you want
+to use, almost regardless of which database you choose. Very often, the
+database will provide an ODBC driver interface, so you can often use
+B<DBD::ODBC> to access the database. This is typically less convenient
+on a Unix box than on a Microsoft Windows box, but there are numerous
+options for ODBC driver managers on Unix too, and very often the ODBC
+driver is provided by the database supplier.
+
+Before deciding that you need to write a driver, do your homework to
+ensure that you are not wasting your energies.
+
+[As of December 2002, the consensus is that if you need an ODBC driver
+manager on Unix, then the unixODBC driver (available from
+L<http://www.unixodbc.org/>) is the way to go.]
+
+The second rule for creating a new database driver for the Perl DBI is
+also very simple: B<Don't -- get someone else to do it for you!>
+
+Nevertheless, there are occasions when it is necessary to write a new
+driver, often to use a proprietary language or API to access the
+database more swiftly, or more comprehensively, than an ODBC driver can.
+Then you should read this document very carefully, but with a suitably
+sceptical eye.
+
+If there is something in here that does not make any sense, question it.
+You might be right that the information is bogus, but don't come to that
+conclusion too quickly.
+
+=head2 URLs and mailing lists
+
+The primary web-site for locating B<DBI> software and information is
+
+ http://dbi.perl.org/
+
+There are two main and one auxiliary mailing lists for people working
+with B<DBI>. The primary lists are I<dbi-users@perl.org> for general users
+of B<DBI> and B<DBD> drivers, and I<dbi-dev@perl.org> mainly for B<DBD> driver
+writers (don't join the I<dbi-dev> list unless you have a good reason).
+The auxiliary list is I<dbi-announce@perl.org> for announcing new
+releases of B<DBI> or B<DBD> drivers.
+
+You can join these lists by accessing the web-site L<http://dbi.perl.org/>.
+The lists are closed so you cannot send email to any of the lists
+unless you join the list first.
+
+You should also consider monitoring the I<comp.lang.perl.*> newsgroups,
+especially I<comp.lang.perl.modules>.
+
+=head2 The Cheetah book
+
+The definitive book on Perl DBI is the Cheetah book, so called because
+of the picture on the cover. Its proper title is 'I<Programming the
+Perl DBI: Database programming with Perl>' by Alligator Descartes
+and Tim Bunce, published by O'Reilly Associates, February 2000, ISBN
+1-56592-699-4. Buy it now if you have not already done so, and read it.
+
+=head2 Locating drivers
+
+Before writing a new driver, it is in your interests to find out
+whether there already is a driver for your database. If there is such
+a driver, it would be much easier to make use of it than to write your
+own!
+
+The primary web-site for locating Perl software is
+L<http://search.cpan.org/>. You should look under the various
+modules listings for the software you are after. For example:
+
+ http://search.cpan.org/modlist/Database_Interfaces
+
+Follow the B<DBD::> and B<DBIx::> links at the top to see those subsets.
+
+See the B<DBI> docs for information on B<DBI> web sites and mailing lists.
+
+=head2 Registering a new driver
+
+Before going through any official registration process, you will need
+to establish that there is no driver already in the works. You'll do
+that by asking the B<DBI> mailing lists whether there is such a driver
+available, or whether anybody is working on one.
+
+When you get the go ahead, you will need to establish the name of the
+driver and a prefix for the driver. Typically, the name is based on the
+name of the database software it uses, and the prefix is a contraction
+of that. Hence, B<DBD::Oracle> has the name I<Oracle> and the prefix
+'I<ora_>'. The prefix must be lowercase and contain no underscores other
+than the one at the end.
+
+This information will be recorded in the B<DBI> module. Apart from
+documentation purposes, registration is a prerequisite for
+L<installing private methods|DBI/install_method>.
+
+If you are writing a driver which will not be distributed on CPAN, then
+you should choose a prefix beginning with 'I<x_>', to avoid potential
+prefix collisions with drivers registered in the future. Thus, if you
+wrote a non-CPAN distributed driver called B<DBD::CustomDB>, the prefix
+might be 'I<x_cdb_>'.
+
+This document assumes you are writing a driver called B<DBD::Driver>, and
+that the prefix 'I<drv_>' is assigned to the driver.
+
+=head2 Two styles of database driver
+
+There are two distinct styles of database driver that can be written to
+work with the Perl DBI.
+
+Your driver can be written in pure Perl, requiring no C compiler.
+When feasible, this is the best solution, but most databases are not
+written in such a way that this can be done. Some examples of pure
+Perl drivers are B<DBD::File> and B<DBD::CSV>.
+
+Alternatively, and most commonly, your driver will need to use some C
+code to gain access to the database. This will be classified as a C/XS
+driver.
+
+=head2 What code will you write?
+
+There are a number of files that need to be written for either a pure
+Perl driver or a C/XS driver. There are no extra files needed only by
+a pure Perl driver, but there are several extra files needed only by a
+C/XS driver.
+
+=head3 Files common to pure Perl and C/XS drivers
+
+Assuming that your driver is called B<DBD::Driver>, these files are:
+
+=over 4
+
+=item * F<Makefile.PL>
+
+=item * F<META.yml>
+
+=item * F<README>
+
+=item * F<MANIFEST>
+
+=item * F<Driver.pm>
+
+=item * F<lib/Bundle/DBD/Driver.pm>
+
+=item * F<lib/DBD/Driver/Summary.pm>
+
+=item * F<t/*.t>
+
+=back
+
+The first four files are mandatory. F<Makefile.PL> is used to control
+how the driver is built and installed. The F<README> file tells people
+who download the file about how to build the module and any prerequisite
+software that must be installed. The F<MANIFEST> file is used by the
+standard Perl module distribution mechanism. It lists all the source
+files that need to be distributed with your module. F<Driver.pm> is what
+is loaded by the B<DBI> code; it contains the methods peculiar to your
+driver.
+
+Although the F<META.yml> file is not B<required> you are advised to
+create one. Of particular importance are the I<build_requires> and
+I<configure_requires> attributes which newer CPAN modules understand.
+You use these to tell the CPAN module (and CPANPLUS) that your build
+and configure mechanisms require DBI. The best reference for META.yml
+(at the time of writing) is
+L<http://module-build.sourceforge.net/META-spec-v1.4.html>. You can find
+a reasonable example of a F<META.yml> in DBD::ODBC.
+
+The F<lib/Bundle/DBD/Driver.pm> file allows you to specify other Perl
+modules on which yours depends in a format that allows someone to type a
+simple command and ensure that all the pre-requisites are in place as
+well as building your driver.
+
+The F<lib/DBD/Driver/Summary.pm> file contains (an updated version of) the
+information that was included - or that would have been included - in
+the appendices of the Cheetah book as a summary of the abilities of your
+driver and the associated database.
+
+The files in the F<t> subdirectory are unit tests for your driver.
+You should write your tests as stringently as possible, while taking
+into account the diversity of installations that you can encounter:
+
+=over 4
+
+=item *
+
+Your tests should not casually modify operational databases.
+
+=item *
+
+You should never damage existing tables in a database.
+
+=item *
+
+You should code your tests to use a constrained name space within the
+database. For example, the tables (and all other named objects) that are
+created could all begin with 'I<dbd_drv_>'.
+
+=item *
+
+At the end of a test run, there should be no testing objects left behind
+in the database.
+
+=item *
+
+If you create any databases, you should remove them.
+
+=item *
+
+If your database supports temporary tables that are automatically
+removed at the end of a session, then exploit them as often as possible.
+
+=item *
+
+Try to make your tests independent of each other. If you have a
+test F<t/t11dowhat.t> that depends upon the successful running
+of F<t/t10thingamy.t>, people cannot run the single test case
+F<t/t11dowhat.t>. Further, running F<t/t11dowhat.t> twice in a row is
+likely to fail (at least, if F<t/t11dowhat.t> modifies the database at
+all) because the database at the start of the second run is not what you
+saw at the start of the first run.
+
+=item *
+
+Document in your F<README> file what you do, and what privileges people
+need to do it.
+
+=item *
+
+You can, and probably should, sequence your tests by including a test
+number before an abbreviated version of the test name; the tests are run
+in the order in which the names are expanded by shell-style globbing.
+
+=item *
+
+It is in your interests to ensure that your tests work as widely
+as possible.
+
+=back
+
+Many drivers also install sub-modules B<DBD::Driver::SubModule>
+for any of a variety of different reasons, such as to support
+the metadata methods (see the discussion of L</METADATA METHODS>
+below). Such sub-modules are conventionally stored in the directory
+F<lib/DBD/Driver>. The module itself would usually be in a file
+F<SubModule.pm>. All such sub-modules should themselves be version
+stamped (see the discussions far below).
+
+=head3 Extra files needed by C/XS drivers
+
+The software for a C/XS driver will typically contain at least four
+extra files that are not relevant to a pure Perl driver.
+
+=over 4
+
+=item * F<Driver.xs>
+
+=item * F<Driver.h>
+
+=item * F<dbdimp.h>
+
+=item * F<dbdimp.c>
+
+=back
+
+The F<Driver.xs> file is used to generate C code that Perl can call to gain
+access to the C functions you write that will, in turn, call down onto
+your database software.
+
+The F<Driver.h> header is a stylized header that ensures you can access the
+necessary Perl and B<DBI> macros, types, and function declarations.
+
+The F<dbdimp.h> is used to specify which functions have been implemented by
+your driver.
+
+The F<dbdimp.c> file is where you write the C code that does the real work
+of translating between Perl-ish data types and what the database expects
+to use and return.
+
+There are some (mainly small, but very important) differences between
+the contents of F<Makefile.PL> and F<Driver.pm> for pure Perl and C/XS
+drivers, so those files are described both in the section on creating a
+pure Perl driver and in the section on creating a C/XS driver.
+
+Obviously, you can add extra source code files to the list.
+
+=head2 Requirements on a driver and driver writer
+
+To be remotely useful, your driver must be implemented in a format that
+allows it to be distributed via CPAN, the Comprehensive Perl Archive
+Network (L<http://www.cpan.org/> and L<http://search.cpan.org>).
+Of course, it is easier if you do not have to meet this criterion, but
+you will not be able to ask for much help if you do not do so, and
+no-one is likely to want to install your module if they have to learn a
+new installation mechanism.
+
+=head1 CREATING A PURE PERL DRIVER
+
+Writing a pure Perl driver is surprisingly simple. However, there are
+some problems you should be aware of. The best option is of course
+picking up an existing driver and carefully modifying one method
+after the other.
+
+Also look carefully at B<DBD::AnyData> and B<DBD::Template>.
+
+As an example we take a look at the B<DBD::File> driver, a driver for
+accessing plain files as tables, which is part of the B<DBD::CSV> package.
+
+The minimal set of files we have to implement are F<Makefile.PL>,
+F<README>, F<MANIFEST> and F<Driver.pm>.
+
+=head2 Pure Perl version of Makefile.PL
+
+You typically start with writing F<Makefile.PL>, a Makefile
+generator. The contents of this file are described in detail in
+the L<ExtUtils::MakeMaker> man pages. It is definitely a good idea
+if you start reading them. At least you should know about the
+variables I<CONFIGURE>, I<DEFINED>, I<PM>, I<DIR>, I<EXE_FILES>,
+I<INC>, I<LIBS>, I<LINKTYPE>, I<NAME>, I<OPTIMIZE>, I<PL_FILES>,
+I<VERSION>, I<VERSION_FROM>, I<clean>, I<depend>, I<realclean> from
+the L<ExtUtils::MakeMaker> man page: these are used in almost any
+F<Makefile.PL>.
+
+Additionally read the section on I<Overriding MakeMaker Methods> and the
+descriptions of the I<distcheck>, I<disttest> and I<dist> targets: They
+will definitely be useful for you.
+
+Of special importance for B<DBI> drivers is the I<postamble> method from
+the L<ExtUtils::MM_Unix> man page.
+
+For Emacs users, I recommend the I<libscan> method, which removes
+Emacs backup files (file names which end with a tilde '~') from lists of
+files.
+
+Now an example, I use the word C<Driver> wherever you should insert
+your driver's name:
+
+ # -*- perl -*-
+
+ use ExtUtils::MakeMaker;
+
+ WriteMakefile(
+ dbd_edit_mm_attribs( {
+ 'NAME' => 'DBD::Driver',
+ 'VERSION_FROM' => 'Driver.pm',
+ 'INC' => '',
+ 'dist' => { 'SUFFIX' => '.gz',
+ 'COMPRESS' => 'gzip -9f' },
+ 'realclean' => { FILES => '*.xsi' },
+ 'PREREQ_PM' => '1.03',
+ 'CONFIGURE' => sub {
+ eval {require DBI::DBD;};
+ if ($@) {
+ warn $@;
+ exit 0;
+ }
+ my $dbi_arch_dir = dbd_dbi_arch_dir();
+ if (exists($opts{INC})) {
+ return {INC => "$opts{INC} -I$dbi_arch_dir"};
+ } else {
+ return {INC => "-I$dbi_arch_dir"};
+ }
+ }
+ },
+ { create_pp_tests => 1})
+ );
+
+ package MY;
+ sub postamble { return main::dbd_postamble(@_); }
+ sub libscan {
+ my ($self, $path) = @_;
+ ($path =~ m/\~$/) ? undef : $path;
+ }
+
+Note the calls to C<dbd_edit_mm_attribs()> and C<dbd_postamble()>.
+
+The second hash reference in the call to C<dbd_edit_mm_attribs()>
+(containing C<create_pp_tests()>) is optional; you should not use it
+unless your driver is a pure Perl driver (that is, it does not use C and
+XS code). Therefore, the call to C<dbd_edit_mm_attribs()> is not
+relevant for C/XS drivers and may be omitted; simply use the (single)
+hash reference containing NAME etc as the only argument to C<WriteMakefile()>.
+
+Note that the C<dbd_edit_mm_attribs()> code will fail if you do not have a
+F<t> sub-directory containing at least one test case.
+
+I<PREREQ_PM> tells MakeMaker that DBI (version 1.03 in this case) is
+required for this module. This will issue a warning that DBI 1.03 is
+missing if someone attempts to install your DBD without DBI 1.03. See
+I<CONFIGURE> below for why this does not work reliably in stopping cpan
+testers failing your module if DBI is not installed.
+
+I<CONFIGURE> is a subroutine called by MakeMaker during
+C<WriteMakefile>. By putting the C<require DBI::DBD> in this section
+we can attempt to load DBI::DBD but if it is missing we exit with
+success. As we exit successfully without creating a Makefile when
+DBI::DBD is missing cpan testers will not report a failure. This may
+seem at odds with I<PREREQ_PM> but I<PREREQ_PM> does not cause
+C<WriteMakefile> to fail (unless you also specify PREREQ_FATAL which
+is strongly discouraged by MakeMaker) so C<WriteMakefile> would
+continue to call C<dbd_dbi_arch_dir> and fail.
+
+All drivers must use C<dbd_postamble()> or risk running into problems.
+
+Note the specification of I<VERSION_FROM>; the named file
+(F<Driver.pm>) will be scanned for the first line that looks like an
+assignment to I<$VERSION>, and the subsequent text will be used to
+determine the version number. Note the commentary in
+L<ExtUtils::MakeMaker> on the subject of correctly formatted version
+numbers.
+
+If your driver depends upon external software (it usually will), you
+will need to add code to ensure that your environment is workable
+before the call to C<WriteMakefile()>. If you need to check for the
+existence of an external library and perhaps modify I<INC> to include
+the paths to where the external library header files are located and
+you cannot find the library or header files make sure you output a
+message saying they cannot be found but C<exit 0> (success) B<before>
+calling C<WriteMakefile> or CPAN testers will fail your module if the
+external library is not found.
+
+A full-fledged I<Makefile.PL> can be quite large (for example, the
+files for B<DBD::Oracle> and B<DBD::Informix> are both over 1000 lines
+long, and the Informix one uses - and creates - auxiliary modules
+too).
+
+See also L<ExtUtils::MakeMaker> and L<ExtUtils::MM_Unix>. Consider using
+L<CPAN::MakeMaker> in place of I<ExtUtils::MakeMaker>.
+
+=head2 README
+
+The L<README> file should describe what the driver is for, the
+pre-requisites for the build process, the actual build process, how to
+report errors, and who to report them to.
+
+Users will find ways of breaking the driver build and test process
+which you would never even have dreamed to be possible in your worst
+nightmares. Therefore, you need to write this document defensively,
+precisely and concisely.
+
+As always, use the F<README> from one of the established drivers as a basis
+for your own; the version in B<DBD::Informix> is worth a look as it has
+been quite successful in heading off problems.
+
+=over 4
+
+=item *
+
+Note that users will have versions of Perl and B<DBI> that are both older
+and newer than you expected, but this will seldom cause much trouble.
+When it does, it will be because you are using features of B<DBI> that are
+not supported in the version they are using.
+
+=item *
+
+Note that users will have versions of the database software that are
+both older and newer than you expected. You will save yourself time in
+the long run if you can identify the range of versions which have been
+tested and warn about versions which are not known to be OK.
+
+=item *
+
+Note that many people trying to install your driver will not be experts
+in the database software.
+
+=item *
+
+Note that many people trying to install your driver will not be experts
+in C or Perl.
+
+=back
+
+=head2 MANIFEST
+
+The F<MANIFEST> will be used by the Makefile's dist target to build the
+distribution tar file that is uploaded to CPAN. It should list every
+file that you want to include in your distribution, one per line.
+
+=head2 lib/Bundle/DBD/Driver.pm
+
+The CPAN module provides an extremely powerful bundle mechanism that
+allows you to specify pre-requisites for your driver.
+
+The primary pre-requisite is B<Bundle::DBI>; you may want or need to add
+some more. With the bundle set up correctly, the user can type:
+
+ perl -MCPAN -e 'install Bundle::DBD::Driver'
+
+and Perl will download, compile, test and install all the Perl modules
+needed to build your driver.
+
+The prerequisite modules are listed in the C<CONTENTS> section, with the
+official name of the module followed by a dash and an informal name or
+description.
+
+=over 4
+
+=item *
+
+Listing B<Bundle::DBI> as the main pre-requisite simplifies life.
+
+=item *
+
+Don't forget to list your driver.
+
+=item *
+
+Note that unless the DBMS is itself a Perl module, you cannot list it as
+a pre-requisite in this file.
+
+=item *
+
+You should keep the version of the bundle the same as the version of
+your driver.
+
+=item *
+
+You should add configuration management, copyright, and licencing
+information at the top.
+
+=back
+
+A suitable skeleton for this file is shown below.
+
+ package Bundle::DBD::Driver;
+
+ $VERSION = '0.01';
+
+ 1;
+
+ __END__
+
+ =head1 NAME
+
+ Bundle::DBD::Driver - A bundle to install all DBD::Driver related modules
+
+ =head1 SYNOPSIS
+
+ C<perl -MCPAN -e 'install Bundle::DBD::Driver'>
+
+ =head1 CONTENTS
+
+ Bundle::DBI - Bundle for DBI by TIMB (Tim Bunce)
+
+ DBD::Driver - DBD::Driver by YOU (Your Name)
+
+ =head1 DESCRIPTION
+
+ This bundle includes all the modules used by the Perl Database
+ Interface (DBI) driver for Driver (DBD::Driver), assuming the
+ use of DBI version 1.13 or later, created by Tim Bunce.
+
+ If you've not previously used the CPAN module to install any
+ bundles, you will be interrogated during its setup phase.
+ But when you've done it once, it remembers what you told it.
+ You could start by running:
+
+ C<perl -MCPAN -e 'install Bundle::CPAN'>
+
+ =head1 SEE ALSO
+
+ Bundle::DBI
+
+ =head1 AUTHOR
+
+ Your Name E<lt>F<you@yourdomain.com>E<gt>
+
+ =head1 THANKS
+
+ This bundle was created by ripping off Bundle::libnet created by
+ Graham Barr E<lt>F<gbarr@ti.com>E<gt>, and radically simplified
+ with some information from Jochen Wiedmann E<lt>F<joe@ispsoft.de>E<gt>.
+ The template was then included in the DBI::DBD documentation by
+ Jonathan Leffler E<lt>F<jleffler@informix.com>E<gt>.
+
+ =cut
+
+=head2 lib/DBD/Driver/Summary.pm
+
+There is no substitute for taking the summary file from a driver that
+was documented in the Perl book (such as B<DBD::Oracle> or B<DBD::Informix> or
+B<DBD::ODBC>, to name but three), and adapting it to describe the
+facilities available via B<DBD::Driver> when accessing the Driver database.
+
+=head2 Pure Perl version of Driver.pm
+
+The F<Driver.pm> file defines the Perl module B<DBD::Driver> for your driver.
+It will define a package B<DBD::Driver> along with some version information,
+some variable definitions, and a function C<driver()> which will have a more
+or less standard structure.
+
+It will also define three sub-packages of B<DBD::Driver>:
+
+=over 4
+
+=item DBD::Driver::dr
+
+with methods C<connect()>, C<data_sources()> and C<disconnect_all()>;
+
+=item DBD::Driver::db
+
+with methods such as C<prepare()>;
+
+=item DBD::Driver::st
+
+with methods such as C<execute()> and C<fetch()>.
+
+=back
+
+The F<Driver.pm> file will also contain the documentation specific to
+B<DBD::Driver> in the format used by perldoc.
+
+In a pure Perl driver, the F<Driver.pm> file is the core of the
+implementation. You will need to provide all the key methods needed by B<DBI>.
+
+Now let's take a closer look at an excerpt of F<File.pm> as an example.
+We ignore things that are common to any module (even non-DBI modules)
+or really specific to the B<DBD::File> package.
+
+=head3 The DBD::Driver package
+
+=head4 The header
+
+ package DBD::File;
+
+ use strict;
+ use vars qw($VERSION $drh);
+
+ $VERSION = "1.23.00" # Version number of DBD::File
+
+This is where the version number of your driver is specified, and is
+where F<Makefile.PL> looks for this information. Please ensure that any
+other modules added with your driver are also version stamped so that
+CPAN does not get confused.
+
+It is recommended that you use a two-part (1.23) or three-part (1.23.45)
+version number. Also consider the CPAN system, which gets confused and
+considers version 1.10 to precede version 1.9, so that using a raw CVS,
+RCS or SCCS version number is probably not appropriate (despite being
+very common).
+
+For Subversion you could use:
+
+ $VERSION = sprintf("12.%06d", q$Revision: 12345 $ =~ /(\d+)/o);
+
+(use lots of leading zeros on the second portion so if you move the code to a
+shared repository like svn.perl.org the much larger revision numbers won't
+cause a problem, at least not for a few years). For RCS or CVS you can use:
+
+ $VERSION = sprintf "%d.%02d", '$Revision: 11.21 $ ' =~ /(\d+)\.(\d+)/;
+
+which pads out the fractional part with leading zeros so all is well
+(so long as you don't go past x.99)
+
+ $drh = undef; # holds driver handle once initialized
+
+This is where the driver handle will be stored, once created.
+Note that you may assume there is only one handle for your driver.
+
+=head4 The driver constructor
+
+The C<driver()> method is the driver handle constructor. Note that
+the C<driver()> method is in the B<DBD::Driver> package, not in
+one of the sub-packages B<DBD::Driver::dr>, B<DBD::Driver::db>, or
+B<DBD::Driver::db>.
+
+ sub driver
+ {
+ return $drh if $drh; # already created - return same one
+ my ($class, $attr) = @_;
+
+ $class .= "::dr";
+
+ DBD::Driver::db->install_method('drv_example_dbh_method');
+ DBD::Driver::st->install_method('drv_example_sth_method');
+
+ # not a 'my' since we use it above to prevent multiple drivers
+ $drh = DBI::_new_drh($class, {
+ 'Name' => 'File',
+ 'Version' => $VERSION,
+ 'Attribution' => 'DBD::File by Jochen Wiedmann',
+ })
+ or return undef;
+
+ return $drh;
+ }
+
+This is a reasonable example of how B<DBI> implements its handles. There
+are three kinds: B<driver handles> (typically stored in I<$drh>; from
+now on called I<drh> or I<$drh>), B<database handles> (from now on
+called I<dbh> or I<$dbh>) and B<statement handles> (from now on called
+I<sth> or I<$sth>).
+
+The prototype of C<DBI::_new_drh()> is
+
+ $drh = DBI::_new_drh($class, $public_attrs, $private_attrs);
+
+with the following arguments:
+
+=over 4
+
+=item I<$class>
+
+is typically the class for your driver, (for example, "DBD::File::dr"),
+passed as the first argument to the C<driver()> method.
+
+=item I<$public_attrs>
+
+is a hash ref to attributes like I<Name>, I<Version>, and I<Attribution>.
+These are processed and used by B<DBI>. You had better not make any
+assumptions about them nor should you add private attributes here.
+
+=item I<$private_attrs>
+
+This is another (optional) hash ref with your private attributes.
+B<DBI> will store them and otherwise leave them alone.
+
+=back
+
+The C<DBI::_new_drh()> method and the C<driver()> method both return C<undef>
+for failure (in which case you must look at I<$DBI::err> and I<$DBI::errstr>
+for the failure information, because you have no driver handle to use).
+
+
+=head4 Using install_method() to expose driver-private methods
+
+ DBD::Foo::db->install_method($method_name, \%attr);
+
+Installs the driver-private method named by $method_name into the
+DBI method dispatcher so it can be called directly, avoiding the
+need to use the func() method.
+
+It is called as a static method on the driver class to which the
+method belongs. The method name must begin with the corresponding
+registered driver-private prefix. For example, for DBD::Oracle
+$method_name must being with 'C<ora_>', and for DBD::AnyData it
+must begin with 'C<ad_>'.
+
+The C<\%attr> attributes can be used to provide fine control over how the DBI
+dispatcher handles the dispatching of the method. However it's undocumented
+at the moment. See the IMA_* #define's in DBI.xs and the O=>0x000x values in
+the initialization of %DBI::DBI_methods in DBI.pm. (Volunteers to polish up
+and document the interface are very welcome to get in touch via dbi-dev@perl.org).
+
+Methods installed using install_method default to the standard error
+handling behaviour for DBI methods: clearing err and errstr before
+calling the method, and checking for errors to trigger RaiseError
+etc. on return. This differs from the default behaviour of func().
+
+Note for driver authors: The DBD::Foo::xx->install_method call won't
+work until the class-hierarchy has been setup. Normally the DBI
+looks after that just after the driver is loaded. This means
+install_method() can't be called at the time the driver is loaded
+unless the class-hierarchy is set up first. The way to do that is
+to call the setup_driver() method:
+
+ DBI->setup_driver('DBD::Foo');
+
+before using install_method().
+
+
+=head4 The CLONE special subroutine
+
+Also needed here, in the B<DBD::Driver> package, is a C<CLONE()> method
+that will be called by perl when an interpreter is cloned. All your
+C<CLONE()> method needs to do, currently, is clear the cached I<$drh> so
+the new interpreter won't start using the cached I<$drh> from the old
+interpreter:
+
+ sub CLONE {
+ undef $drh;
+ }
+
+See L<http://search.cpan.org/dist/perl/pod/perlmod.pod#Making_your_module_threadsafe>
+for details.
+
+=head3 The DBD::Driver::dr package
+
+The next lines of code look as follows:
+
+ package DBD::Driver::dr; # ====== DRIVER ======
+
+ $DBD::Driver::dr::imp_data_size = 0;
+
+Note that no I<@ISA> is needed here, or for the other B<DBD::Driver::*>
+classes, because the B<DBI> takes care of that for you when the driver is
+loaded.
+
+ *FIX ME* Explain what the imp_data_size is, so that implementors aren't
+ practicing cargo-cult programming.
+
+=head4 The database handle constructor
+
+The database handle constructor is the driver's (hence the changed
+namespace) C<connect()> method:
+
+ sub connect
+ {
+ my ($drh, $dr_dsn, $user, $auth, $attr) = @_;
+
+ # Some database specific verifications, default settings
+ # and the like can go here. This should only include
+ # syntax checks or similar stuff where it's legal to
+ # 'die' in case of errors.
+ # For example, many database packages requires specific
+ # environment variables to be set; this could be where you
+ # validate that they are set, or default them if they are not set.
+
+ my $driver_prefix = "drv_"; # the assigned prefix for this driver
+
+ # Process attributes from the DSN; we assume ODBC syntax
+ # here, that is, the DSN looks like var1=val1;...;varN=valN
+ foreach my $var ( split /;/, $dr_dsn ) {
+ my ($attr_name, $attr_value) = split '=', $var, 2;
+ return $drh->set_err($DBI::stderr, "Can't parse DSN part '$var'")
+ unless defined $attr_value;
+
+ # add driver prefix to attribute name if it doesn't have it already
+ $attr_name = $driver_prefix.$attr_name
+ unless $attr_name =~ /^$driver_prefix/o;
+
+ # Store attribute into %$attr, replacing any existing value.
+ # The DBI will STORE() these into $dbh after we've connected
+ $attr->{$attr_name} = $attr_value;
+ }
+
+ # Get the attributes we'll use to connect.
+ # We use delete here because these no need to STORE them
+ my $db = delete $attr->{drv_database} || delete $attr->{drv_db}
+ or return $drh->set_err($DBI::stderr, "No database name given in DSN '$dr_dsn'");
+ my $host = delete $attr->{drv_host} || 'localhost';
+ my $port = delete $attr->{drv_port} || 123456;
+
+ # Assume you can attach to your database via drv_connect:
+ my $connection = drv_connect($db, $host, $port, $user, $auth)
+ or return $drh->set_err($DBI::stderr, "Can't connect to $dr_dsn: ...");
+
+ # create a 'blank' dbh (call superclass constructor)
+ my ($outer, $dbh) = DBI::_new_dbh($drh, { Name => $dr_dsn });
+
+ $dbh->STORE('Active', 1 );
+ $dbh->{drv_connection} = $connection;
+
+ return $outer;
+ }
+
+This is mostly the same as in the I<driver handle constructor> above.
+The arguments are described in L<DBI>.
+
+The constructor C<DBI::_new_dbh()> is called, returning a database handle.
+The constructor's prototype is:
+
+ ($outer, $inner) = DBI::_new_dbh($drh, $public_attr, $private_attr);
+
+with similar arguments to those in the I<driver handle constructor>,
+except that the I<$class> is replaced by I<$drh>. The I<Name> attribute
+is a standard B<DBI> attribute (see L<DBI/Database Handle Attributes>).
+
+In scalar context, only the outer handle is returned.
+
+Note the use of the C<STORE()> method for setting the I<dbh> attributes.
+That's because within the driver code, the handle object you have is
+the 'inner' handle of a tied hash, not the outer handle that the
+users of your driver have.
+
+Because you have the inner handle, tie magic doesn't get invoked
+when you get or set values in the hash. This is often very handy for
+speed when you want to get or set simple non-special driver-specific
+attributes.
+
+However, some attribute values, such as those handled by the B<DBI> like
+I<PrintError>, don't actually exist in the hash and must be read via
+C<$h-E<gt>FETCH($attrib)> and set via C<$h-E<gt>STORE($attrib, $value)>.
+If in any doubt, use these methods.
+
+=head4 The data_sources() method
+
+The C<data_sources()> method must populate and return a list of valid data
+sources, prefixed with the "I<dbi:Driver>" incantation that allows them to
+be used in the first argument of the C<DBI-E<gt>connect()> method.
+An example of this might be scanning the F<$HOME/.odbcini> file on Unix
+for ODBC data sources (DSNs).
+
+As a trivial example, consider a fixed list of data sources:
+
+ sub data_sources
+ {
+ my($drh, $attr) = @_;
+ my(@list) = ();
+ # You need more sophisticated code than this to set @list...
+ push @list, "dbi:Driver:abc";
+ push @list, "dbi:Driver:def";
+ push @list, "dbi:Driver:ghi";
+ # End of code to set @list
+ return @list;
+ }
+
+=head4 The disconnect_all() method
+
+If you need to release any resources when the driver is unloaded, you
+can provide a disconnect_all method.
+
+=head4 Other driver handle methods
+
+If you need any other driver handle methods, they can follow here.
+
+=head4 Error handling
+
+It is quite likely that something fails in the connect method.
+With B<DBD::File> for example, you might catch an error when setting the
+current directory to something not existent by using the
+(driver-specific) I<f_dir> attribute.
+
+To report an error, you use the C<set_err()> method:
+
+ $h->set_err($err, $errmsg, $state);
+
+This will ensure that the error is recorded correctly and that
+I<RaiseError> and I<PrintError> etc are handled correctly.
+
+Typically you'll always use the method instance, aka your method's first
+argument.
+
+As C<set_err()> always returns C<undef> your error handling code can
+usually be simplified to something like this:
+
+ return $h->set_err($err, $errmsg, $state) if ...;
+
+=head3 The DBD::Driver::db package
+
+ package DBD::Driver::db; # ====== DATABASE ======
+
+ $DBD::Driver::db::imp_data_size = 0;
+
+=head4 The statement handle constructor
+
+There's nothing much new in the statement handle constructor, which
+is the C<prepare()> method:
+
+ sub prepare
+ {
+ my ($dbh, $statement, @attribs) = @_;
+
+ # create a 'blank' sth
+ my ($outer, $sth) = DBI::_new_sth($dbh, { Statement => $statement });
+
+ $sth->STORE('NUM_OF_PARAMS', ($statement =~ tr/?//));
+
+ $sth->{drv_params} = [];
+
+ return $outer;
+ }
+
+This is still the same -- check the arguments and call the super class
+constructor C<DBI::_new_sth()>. Again, in scalar context, only the outer
+handle is returned. The I<Statement> attribute should be cached as
+shown.
+
+Note the prefix I<drv_> in the attribute names: it is required that
+all your private attributes use a lowercase prefix unique to your driver.
+As mentioned earlier in this document, the B<DBI> contains a registry of
+known driver prefixes and may one day warn about unknown attributes
+that don't have a registered prefix.
+
+Note that we parse the statement here in order to set the attribute
+I<NUM_OF_PARAMS>. The technique illustrated is not very reliable; it can
+be confused by question marks appearing in quoted strings, delimited
+identifiers or in SQL comments that are part of the SQL statement. We
+could set I<NUM_OF_PARAMS> in the C<execute()> method instead because
+the B<DBI> specification explicitly allows a driver to defer this, but then
+the user could not call C<bind_param()>.
+
+=head4 Transaction handling
+
+Pure Perl drivers will rarely support transactions. Thus your C<commit()>
+and C<rollback()> methods will typically be quite simple:
+
+ sub commit
+ {
+ my ($dbh) = @_;
+ if ($dbh->FETCH('Warn')) {
+ warn("Commit ineffective while AutoCommit is on");
+ }
+ 0;
+ }
+
+ sub rollback {
+ my ($dbh) = @_;
+ if ($dbh->FETCH('Warn')) {
+ warn("Rollback ineffective while AutoCommit is on");
+ }
+ 0;
+ }
+
+Or even simpler, just use the default methods provided by the B<DBI> that
+do nothing except return C<undef>.
+
+The B<DBI>'s default C<begin_work()> method can be used by inheritance.
+
+=head4 The STORE() and FETCH() methods
+
+These methods (that we have already used, see above) are called for
+you, whenever the user does a:
+
+ $dbh->{$attr} = $val;
+
+or, respectively,
+
+ $val = $dbh->{$attr};
+
+See L<perltie> for details on tied hash refs to understand why these
+methods are required.
+
+The B<DBI> will handle most attributes for you, in particular attributes
+like I<RaiseError> or I<PrintError>. All you have to do is handle your
+driver's private attributes and any attributes, like I<AutoCommit> and
+I<ChopBlanks>, that the B<DBI> can't handle for you.
+
+A good example might look like this:
+
+ sub STORE
+ {
+ my ($dbh, $attr, $val) = @_;
+ if ($attr eq 'AutoCommit') {
+ # AutoCommit is currently the only standard attribute we have
+ # to consider.
+ if (!$val) { die "Can't disable AutoCommit"; }
+ return 1;
+ }
+ if ($attr =~ m/^drv_/) {
+ # Handle only our private attributes here
+ # Note that we could trigger arbitrary actions.
+ # Ideally we should warn about unknown attributes.
+ $dbh->{$attr} = $val; # Yes, we are allowed to do this,
+ return 1; # but only for our private attributes
+ }
+ # Else pass up to DBI to handle for us
+ $dbh->SUPER::STORE($attr, $val);
+ }
+
+ sub FETCH
+ {
+ my ($dbh, $attr) = @_;
+ if ($attr eq 'AutoCommit') { return 1; }
+ if ($attr =~ m/^drv_/) {
+ # Handle only our private attributes here
+ # Note that we could trigger arbitrary actions.
+ return $dbh->{$attr}; # Yes, we are allowed to do this,
+ # but only for our private attributes
+ }
+ # Else pass up to DBI to handle
+ $dbh->SUPER::FETCH($attr);
+ }
+
+The B<DBI> will actually store and fetch driver-specific attributes (with all
+lowercase names) without warning or error, so there's actually no need to
+implement driver-specific any code in your C<FETCH()> and C<STORE()>
+methods unless you need extra logic/checks, beyond getting or setting
+the value.
+
+Unless your driver documentation indicates otherwise, the return value of
+the C<STORE()> method is unspecified and the caller shouldn't use that value.
+
+=head4 Other database handle methods
+
+As with the driver package, other database handle methods may follow here.
+In particular you should consider a (possibly empty) C<disconnect()>
+method and possibly a C<quote()> method if B<DBI>'s default isn't correct for
+you. You may also need the C<type_info_all()> and C<get_info()> methods,
+as described elsewhere in this document.
+
+Where reasonable use C<$h-E<gt>SUPER::foo()> to call the B<DBI>'s method in
+some or all cases and just wrap your custom behavior around that.
+
+If you want to use private trace flags you'll probably want to be
+able to set them by name. To do that you'll need to define a
+C<parse_trace_flag()> method (note that's "parse_trace_flag", singular,
+not "parse_trace_flags", plural).
+
+ sub parse_trace_flag {
+ my ($h, $name) = @_;
+ return 0x01000000 if $name eq 'foo';
+ return 0x02000000 if $name eq 'bar';
+ return 0x04000000 if $name eq 'baz';
+ return 0x08000000 if $name eq 'boo';
+ return 0x10000000 if $name eq 'bop';
+ return $h->SUPER::parse_trace_flag($name);
+ }
+
+All private flag names must be lowercase, and all private flags
+must be in the top 8 of the 32 bits.
+
+=head3 The DBD::Driver::st package
+
+This package follows the same pattern the others do:
+
+ package DBD::Driver::st;
+
+ $DBD::Driver::st::imp_data_size = 0;
+
+=head4 The execute() and bind_param() methods
+
+This is perhaps the most difficult method because we have to consider
+parameter bindings here. In addition to that, there are a number of
+statement attributes which must be set for inherited B<DBI> methods to
+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 created
+within C<prepare()>. The attribute C<$sth-E<gt>{Database}>, which is
+nothing else than the I<dbh>, was automatically created by B<DBI>.
+
+Finally, note that (as specified in the B<DBI> specification) we return the
+string C<'0E0'> instead of the number 0, so that the result tests true but
+equal to zero.
+
+ $sth->execute() or die $sth->errstr;
+
+=head4 The execute_array(), execute_for_fetch() and bind_param_array() methods
+
+In general, DBD's only need to implement C<execute_for_fetch()> and
+C<bind_param_array>. DBI's default C<execute_array()> will invoke the
+DBD's C<execute_for_fetch()> as needed.
+
+The following sequence describes the interaction between
+DBI C<execute_array> and a DBD's C<execute_for_fetch>:
+
+=over
+
+=item 1
+
+App calls C<$sth-E<gt>execute_array(\%attrs, @array_of_arrays)>
+
+=item 2
+
+If C<@array_of_arrays> was specified, DBI processes C<@array_of_arrays> by calling
+DBD's C<bind_param_array()>. Alternately, App may have directly called
+C<bind_param_array()>
+
+=item 3
+
+DBD validates and binds each array
+
+=item 4
+
+DBI retrieves the validated param arrays from DBD's ParamArray attribute
+
+=item 5
+
+DBI calls DBD's C<execute_for_fetch($fetch_tuple_sub, \@tuple_status)>,
+where C<&$fetch_tuple_sub> is a closure to iterate over the
+returned ParamArray values, and C<\@tuple_status> is an array to receive
+the disposition status of each tuple.
+
+=item 6
+
+DBD iteratively calls C<&$fetch_tuple_sub> to retrieve parameter tuples
+to be added to its bulk database operation/request.
+
+=item 7
+
+when DBD reaches the limit of tuples it can handle in a single database
+operation/request, or the C<&$fetch_tuple_sub> indicates no more
+tuples by returning undef, the DBD executes the bulk operation, and
+reports the disposition of each tuple in \@tuple_status.
+
+=item 8
+
+DBD repeats steps 6 and 7 until all tuples are processed.
+
+=back
+
+E.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() implementation
+requires the use of positional (i.e., '?') placeholders. Drivers
+which B<require> named placeholders must either emulate positional
+placeholders (e.g., see L<DBD::Oracle>), or must implement their own
+execute_array()/execute_for_fetch() methods to properly sequence bound
+parameter arrays.
+
+=head4 Fetching data
+
+Only one method needs to be written for fetching data, C<fetchrow_arrayref()>.
+The other methods, C<fetchrow_array()>, C<fetchall_arrayref()>, etc, as well
+as the database handle's C<select*> methods are part of B<DBI>, and call
+C<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_arrayref
+
+Note the use of the method C<_set_fbav()> -- this is required so that
+C<bind_col()> and C<bind_columns()> work.
+
+If an error occurs which leaves the I<$sth> in a state where remaining rows
+can'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 fallback
+to the B<DBI>'s own method which does the right thing based
+on the number of calls to C<_set_fbav()>.
+
+=head4 The more_results method
+
+If 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 results
+from 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 no
+longer 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 may
+not 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 the
+number 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() wouldn't work across result sets.
+
+
+=head4 Statement attributes
+
+The main difference between I<dbh> and I<sth> attributes is, that you
+should implement a lot of attributes here that are required by
+the B<DBI>, such as I<NAME>, I<NULLABLE>, I<TYPE>, etc. See
+L<DBI/Statement Handle Attributes> for a complete list.
+
+Pay attention to attributes which are marked as read only, such as
+I<NUM_OF_PARAMS>. These attributes can only be set the first time
+a statement is executed. If a statement is prepared, then executed
+multiple times, warnings may be generated.
+
+You can protect against these warnings, and prevent the recalculation
+of attributes which might be expensive to calculate (such as the
+I<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 in
+L<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 same
+as above for I<dbh>'s.
+
+=head4 Other statement methods
+
+A 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 want
+it 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 drivers
+registered 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 Tests
+
+The test process should conform as closely as possibly to the Perl
+standard 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 in
+Chapter 7, "The Standard Perl Library" on L<Test::Harness>.
+
+The tests may need to adapt to the type of database which is being used
+for testing, and to the privileges of the user testing the driver. For
+example, the B<DBD::Informix> test code has to adapt in a number of
+places to the type of database to which it is connected as different
+Informix databases have different capabilities: some of the tests are
+for databases without transaction logs; others are for databases with a
+transaction log; some versions of the server have support for blobs, or
+stored procedures, or user-defined data types, and others do not.
+
+When a complete file of tests must be skipped, you can provide a reason
+in 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 in
+F<DBD/Informix/TestHarness.pm> which is used throughout the
+B<DBD::Informix> tests in the F<t> sub-directory.
+
+=head1 CREATING A C/XS DRIVER
+
+Please also see the section under L<CREATING A PURE PERL DRIVER>
+regarding the creation of the F<Makefile.PL>.
+
+Creating a new C/XS driver from scratch will always be a daunting task.
+You can and should greatly simplify your task by taking a good
+reference driver implementation and modifying that to match the
+database product for which you are writing a driver.
+
+The de facto reference driver has been the one for B<DBD::Oracle> written
+by 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 driver
+maintained by Tim and Jeff Urlwin, because it offers a lot of metadata
+and seems to become the guideline for the future development. (Also as
+B<DBD::Oracle> digs deeper into the Oracle 8 OCI interface it'll get even
+more hairy than it is now.)
+
+The B<DBD::Informix> driver is one driver implemented using embedded SQL
+instead of a function-based API.
+B<DBD::Ingres> may also be worth a look.
+
+=head2 C/XS version of Driver.pm
+
+A 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 defined
+here, but in the XS code, because they declare the size of certain
+C structures.
+
+=item *
+
+Some methods are typically moved to the XS code, in particular
+C<prepare()>, C<execute()>, C<disconnect()>, C<disconnect_all()> and the
+C<STORE()> and C<FETCH()> methods.
+
+=item *
+
+Other methods are still part of F<Driver.pm>, but have callbacks to
+the XS code.
+
+=item *
+
+If the driver-specific parts of the I<imp_drh_t> structure need to be
+formally initialized (which does not seem to be a common requirement),
+then you need to add a call to an appropriate XS function in the driver
+method of C<DBD::Driver::driver()>, and you define the corresponding function
+in F<Driver.xs>, and you define the C code in F<dbdimp.c> and the prototype in
+F<dbdimp.h>.
+
+For example, B<DBD::Informix> has such a requirement, and adds the
+following 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:
+ ST(0) = dbd_ix_dr_driver_init(drh) ? &sv_yes : &sv_no;
+
+and the code in F<dbdimp.h> declares:
+
+ extern int dbd_ix_dr_driver_init(SV *drh);
+
+and the code in F<dbdimp.ec> (equivalent to F<dbdimp.c>) defines:
+
+ /* Formally initialize the DBD::Informix driver structure */
+ int
+ dbd_ix_dr_driver(SV *drh)
+ {
+ D_imp_drh(drh);
+ imp_drh->n_connections = 0; /* No active connections */
+ imp_drh->current_connection = 0; /* No current connection */
+ imp_drh->multipleconnections = (ESQLC_VERSION >= 600) ? True : False;
+ dbd_ix_link_newhead(&imp_drh->head); /* Empty linked list of connections */
+ return 1;
+ }
+
+B<DBD::Oracle> has a similar requirement but gets around it by checking
+whether the private data part of the driver handle is all zeroed out,
+rather than add extra functions.
+
+=back
+
+Now let's take a closer look at an excerpt from F<Oracle.pm> (revised
+heavily to remove idiosyncrasies) as an example, ignoring things that
+were already discussed for pure Perl drivers.
+
+=head3 The connect method
+
+The connect method is the database handle constructor.
+You could write either of two versions of this method: either one which
+takes connection attributes (new code) and one which ignores them (old
+code only).
+
+If you ignore the connection attributes, then you omit all mention of
+the I<$auth> variable (which is a reference to a hash of attributes), and
+the XS system manages the differences for you.
+
+ sub connect
+ {
+ my ($drh, $dbname, $user, $auth, $attr) = @_;
+
+ # Some database specific verifications, default settings
+ # and the like following here. This should only include
+ # syntax checks or similar stuff where it's legal to
+ # 'die' in case of errors.
+
+ my $dbh = DBI::_new_dbh($drh, {
+ 'Name' => $dbname,
+ })
+ or return undef;
+
+ # Call the driver-specific function _login in Driver.xs file which
+ # calls the DBMS-specific function(s) to connect to the database,
+ # and populate internal handle data.
+ DBD::Driver::db::_login($dbh, $dbname, $user, $auth, $attr)
+ or return undef;
+
+ $dbh;
+ }
+
+This is mostly the same as in the pure Perl case, the exception being
+the use of the private C<_login()> callback, which is the function
+that will really connect to the database. It is implemented in
+F<Driver.xst> (you should not implement it) and calls
+C<dbd_db_login6()> or C<dbd_db_login6_sv> from F<dbdimp.c>. See below
+for details.
+
+If your driver has driver-specific attributes which may be passed in the
+connect method and hence end up in C<$attr> in C<dbd_db_login6> then it
+is best to delete any you process so DBI does not send them again
+via STORE after connect. You can do this in C like this:
+
+ DBD_ATTRIB_DELETE(attr, "my_attribute_name",
+ strlen("my_attribute_name"));
+
+However, prior to DBI subversion version 11605 (and fixed post 1.607)
+DBD_ATTRIB_DELETE segfaulted so if you cannot guarantee the DBI version
+will be post 1.607 you need to use:
+
+ hv_delete((HV*)SvRV(attr), "my_attribute_name",
+ strlen("my_attribute_name"), G_DISCARD);
+
+ *FIX ME* Discuss removing attributes in Perl code.
+
+=head3 The disconnect_all method
+
+ *FIX ME* T.B.S
+
+=head3 The data_sources method
+
+If your C<data_sources()> method can be implemented in pure Perl, then do
+so because it is easier than doing it in XS code (see the section above
+for pure Perl drivers).
+
+If your C<data_sources()> method must call onto compiled functions, then
+you will need to define I<dbd_dr_data_sources> in your F<dbdimp.h> file, which
+will trigger F<Driver.xst> (in B<DBI> v1.33 or greater) to generate the XS
+code that calls your actual C function (see the discussion below for
+details) and you do not code anything in F<Driver.pm> to handle it.
+
+=head3 The prepare method
+
+The prepare method is the statement handle constructor, and most of it
+is not new. Like the C<connect()> method, it now has a C callback:
+
+ package DBD::Driver::db; # ====== DATABASE ======
+ use strict;
+
+ sub prepare
+ {
+ my ($dbh, $statement, $attribs) = @_;
+
+ # create a 'blank' sth
+ my $sth = DBI::_new_sth($dbh, {
+ 'Statement' => $statement,
+ })
+ or return undef;
+
+ # Call the driver-specific function _prepare in Driver.xs file
+ # which calls the DBMS-specific function(s) to prepare a statement
+ # and populate internal handle data.
+ DBD::Driver::st::_prepare($sth, $statement, $attribs)
+ or return undef;
+ $sth;
+ }
+
+=head3 The execute method
+
+ *FIX ME* T.B.S
+
+=head3 The fetchrow_arrayref method
+
+ *FIX ME* T.B.S
+
+=head3 Other methods?
+
+ *FIX ME* T.B.S
+
+=head2 Driver.xs
+
+F<Driver.xs> should look something like this:
+
+ #include "Driver.h"
+
+ DBISTATE_DECLARE;
+
+ INCLUDE: Driver.xsi
+
+ MODULE = DBD::Driver PACKAGE = DBD::Driver::dr
+
+ /* Non-standard drh XS methods following here, if any. */
+ /* If none (the usual case), omit the MODULE line above too. */
+
+ MODULE = DBD::Driver PACKAGE = DBD::Driver::db
+
+ /* Non-standard dbh XS methods following here, if any. */
+ /* Currently this includes things like _list_tables from */
+ /* DBD::mSQL and DBD::mysql. */
+
+ MODULE = DBD::Driver PACKAGE = DBD::Driver::st
+
+ /* Non-standard sth XS methods following here, if any. */
+ /* In particular this includes things like _list_fields from */
+ /* DBD::mSQL and DBD::mysql for accessing metadata. */
+
+Note especially the include of F<Driver.xsi> here: B<DBI> inserts stub
+functions for almost all private methods here which will typically do
+much work for you.
+
+Wherever you really have to implement something, it will call a private
+function in F<dbdimp.c>, and this is what you have to implement.
+
+You need to set up an extra routine if your driver needs to export
+constants of its own, analogous to the SQL types available when you say:
+
+ use DBI qw(:sql_types);
+
+ *FIX ME* T.B.S
+
+=head2 Driver.h
+
+F<Driver.h> is very simple and the operational contents should look like this:
+
+ #ifndef DRIVER_H_INCLUDED
+ #define DRIVER_H_INCLUDED
+
+ #define NEED_DBIXS_VERSION 93 /* 93 for DBI versions 1.00 to 1.51+ */
+ #define PERL_NO_GET_CONTEXT /* if used require DBI 1.51+ */
+
+ #include <DBIXS.h> /* installed by the DBI module */
+
+ #include "dbdimp.h"
+
+ #include "dbivport.h" /* see below */
+
+ #include <dbd_xsh.h> /* installed by the DBI module */
+
+ #endif /* DRIVER_H_INCLUDED */
+
+The F<DBIXS.h> header defines most of the interesting information that
+the writer of a driver needs.
+
+The file F<dbd_xsh.h> header provides prototype declarations for the C
+functions that you might decide to implement. Note that you should
+normally only define one of C<dbd_db_login()>, C<dbd_db_login6()> or
+C<dbd_db_login6_sv> unless you are intent on supporting really old
+versions of B<DBI> (prior to B<DBI> 1.06) as well as modern
+versions. The only standard, B<DBI>-mandated functions that you need
+write are those specified in the F<dbd_xsh.h> header. You might also
+add extra driver-specific functions in F<Driver.xs>.
+
+The F<dbivport.h> file should be I<copied> from the latest B<DBI> release
+into your distribution each time you modify your driver. Its job is to
+allow you to enhance your code to work with the latest B<DBI> API while
+still allowing your driver to be compiled and used with older versions
+of the B<DBI> (for example, when the C<DBIh_SET_ERR_CHAR()> macro was added
+to B<DBI> 1.41, an emulation of it was added to F<dbivport.h>). This makes
+users happy and your life easier. Always read the notes in F<dbivport.h>
+to check for any limitations in the emulation that you should be aware
+of.
+
+With B<DBI> v1.51 or better I recommend that the driver defines
+I<PERL_NO_GET_CONTEXT> before F<DBIXS.h> is included. This can significantly
+improve efficiency when running under a thread enabled perl. (Remember that
+the standard perl in most Linux distributions is built with threads enabled.
+So is ActiveState perl for Windows, and perl built for Apache mod_perl2.)
+If you do this there are some things to keep in mind:
+
+=over 4
+
+=item *
+
+If I<PERL_NO_GET_CONTEXT> is defined, then every function that calls the Perl
+API will need to start out with a C<dTHX;> declaration.
+
+=item *
+
+You'll know which functions need this, because the C compiler will
+complain that the undeclared identifier C<my_perl> is used if I<and only if>
+the perl you are using to develop and test your driver has threads enabled.
+
+=item *
+
+If you don't remember to test with a thread-enabled perl before making
+a release it's likely that you'll get failure reports from users who are.
+
+=item *
+
+For driver private functions it is possible to gain even more
+efficiency by replacing C<dTHX;> with C<pTHX_> prepended to the
+parameter list and then C<aTHX_> prepended to the argument list where
+the function is called.
+
+=back
+
+See L<perlguts/How multiple interpreters and concurrency are supported> for
+additional information about I<PERL_NO_GET_CONTEXT>.
+
+=head2 Implementation header dbdimp.h
+
+This header file has two jobs:
+
+First it defines data structures for your private part of the handles.
+
+Second it defines macros that rename the generic names like
+C<dbd_db_login()> to database specific names like C<ora_db_login()>. This
+avoids name clashes and enables use of different drivers when you work
+with a statically linked perl.
+
+It also will have the important task of disabling XS methods that you
+don't want to implement.
+
+Finally, the macros will also be used to select alternate
+implementations of some functions. For example, the C<dbd_db_login()>
+function is not passed the attribute hash.
+
+Since B<DBI> v1.06, if a C<dbd_db_login6()> macro is defined (for a function
+with 6 arguments), it will be used instead with the attribute hash
+passed as the sixth argument.
+
+Since B<DBI> post v1.607, if a C<dbd_db_login6_sv()> macro is defined (for
+a function like dbd_db_login6 but with scalar pointers for the dbname,
+username and password), it will be used instead. This will allow your
+login6 function to see if there are any Unicode characters in the
+dbname.
+
+People used to just pick Oracle's F<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 less readable. It was
+just hell when I had to separate B<DBI> specific parts, Oracle specific
+parts, mSQL specific parts and mysql specific parts in B<DBD::mysql>'s
+I<dbdimp.h> and I<dbdimp.c>. (B<DBD::mysql> was a port of B<DBD::mSQL>
+which was based on B<DBD::Oracle>.) [Seconded, based on the experience
+taking B<DBD::Informix> apart, even though the version inherited in 1996
+was only based on B<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_xsh.h */
+ #define dbd_init drv_dr_init
+ #define dbd_db_login6_sv drv_db_login_sv
+ #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 C<imp_dbh_{dr|db|st}> and the first field
+I<must> be of type I<dbih_drc_t|_dbc_t|_stc_t> and I<must> be called
+C<com>.
+
+You should never access these fields directly, except by using the
+I<DBIc_xxx()> macros below.
+
+=head2 Implementation source dbdimp.c
+
+Conventionally, F<dbdimp.c> is the main implementation file (but
+B<DBD::Informix> calls the file F<dbdimp.ec>). This section includes a
+short note on each function that is used in the F<Driver.xsi> template
+and thus I<has> to be implemented.
+
+Of course, you will probably also need to implement other support
+functions, which should usually be file static if they are placed in
+F<dbdimp.c>. If they are placed in other files, you need to list those
+files in F<Makefile.PL> (and F<MANIFEST>) to handle them correctly.
+
+It is wise to adhere to a namespace convention for your functions to
+avoid conflicts. For example, for a driver with prefix I<drv_>, you
+might call externally visible functions I<dbd_drv_xxxx>. You should also
+avoid non-constant global variables as much as possible to improve the
+support for threading.
+
+Since Perl requires support for function prototypes (ANSI or ISO or
+Standard C), you should write your code using function prototypes too.
+
+It is possible to use either the unmapped names such as C<dbd_init()> or
+the mapped names such as C<dbd_ix_dr_init()> in the F<dbdimp.c> file.
+B<DBD::Informix> uses the mapped names which makes it easier to identify
+where to look for linkage problems at runtime (which will report errors
+using the mapped names).
+
+Most other drivers, and in particular B<DBD::Oracle>, use the unmapped
+names in the source code which makes it a little easier to compare code
+between drivers and eases discussions on the I<dbi-dev> mailing list.
+The majority of the code fragments here will use the unmapped names.
+
+Ultimately, you should provide implementations for most of the
+functions listed in the F<dbd_xsh.h> header. The exceptions are
+optional functions (such as C<dbd_st_rows()>) and those functions with
+alternative signatures, such as C<dbd_db_login6_sv>,
+C<dbd_db_login6()> and I<dbd_db_login()>. Then you should only
+implement one of the alternatives, and generally the 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 first
+loaded; the bootstrap command in C<DBD::Driver::dr::driver()> triggers this,
+and the call is generated in the I<BOOT> section of F<Driver.xst>.
+These statements are needed to allow your driver to use the B<DBI> macros.
+They will include your private header file F<dbdimp.h> in turn.
+Note that I<DBISTATE_INIT> requires the name of the argument to C<dbd_init()>
+to be called C<dbistate()>.
+
+=head3 The dbd_drv_error method
+
+You need a function to record errors so B<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 systems
+provide 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, a
+database or a statement handle.
+
+ D_imp_xxh(h);
+
+This macro will declare and initialize a variable I<imp_xxh> with
+a pointer to your private handle pointer. You may cast this to
+to I<imp_drh_t>, I<imp_dbh_t> or I<imp_sth_t>.
+
+To record the error correctly, equivalent to the C<set_err()> method,
+use one of the C<DBIh_SET_ERR_CHAR(...)> or C<DBIh_SET_ERR_SV(...)> macros,
+which were added in B<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 C<DBIh_SET_ERR_SV> the I<err>, I<errstr>, I<state>, and I<method>
+parameters are C<SV*> (use &sv_undef instead of NULL).
+
+For C<DBIh_SET_ERR_CHAR> the I<err_c>, I<errstr>, I<state>, I<method>
+parameters are C<char*>.
+
+The I<err_i> parameter is an C<IV> that's used instead of I<err_c> if
+I<err_c> is C<Null>.
+
+The I<method> parameter can be ignored.
+
+The C<DBIh_SET_ERR_CHAR> macro is usually the simplest to use when you
+just 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 C<Null>.
+
+To make drivers compatible with B<DBI> < 1.41 you should be using F<dbivport.h>
+as described in L</Driver.h> above.
+
+The (obsolete) macros such as C<DBIh_EVENT2> should be removed from drivers.
+
+The names C<dbis> and C<DBIS>, which were used in previous versions of
+this document, should be replaced with the C<DBIc_DBISTATE(imp_xxh)> macro.
+
+The name C<DBILOGFP>, which was also used in previous versions of this
+document, should be replaced by C<DBIc_LOGPIO(imp_xxh)>.
+
+Your code should not call the C C<E<lt>stdio.hE<gt>> I/O functions; you
+should use C<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 B<DBI> driver. Make
+use of this as often as you can, but don't output anything at a trace
+level less than 3. Levels 1 and 2 are reserved for the B<DBI>.
+
+You can define up to 8 private trace flags using the top 8 bits
+of C<DBIc_TRACE_FLAGS(imp)>, that is: C<0xFF000000>. See the
+C<parse_trace_flag()> method elsewhere in this document.
+
+=head3 The dbd_dr_data_sources method
+
+This method is optional; the support for it was added in B<DBI> v1.33.
+
+As noted in the discussion of F<Driver.pm>, if the data sources
+can be determined by pure Perl code, do it that way. If, as in
+B<DBD::Informix>, the information is obtained by a C function call, 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 B<DBD::Informix> follows, assuming that the
+C<sqgetdbs()> function call shown will return up to 100 databases names,
+with the pointers to each name in the array dbsname and the name strings
+themselves being stores in dbsarea.
+
+ 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);
+ }
+
+The actual B<DBD::Informix> implementation has a number of extra lines of
+code, logs function entry and exit, reports the error from C<sqgetdbs()>,
+and uses C<#define>'d constants for the array sizes.
+
+=head3 The dbd_db_login6 method
+
+ int dbd_db_login6_sv(SV* dbh, imp_dbh_t* imp_dbh, SV* dbname,
+ SV* user, SV* auth, SV *attr);
+
+ or
+
+ 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 C<dbd_drv_error()> above. The arguments
+I<dbname>, I<user>, I<auth> and I<attr> correspond to the arguments of
+the driver handle's C<connect()> method.
+
+You will quite often use database specific attributes here, that are
+specified in the DSN. I recommend you parse the DSN (using Perl) within
+the C<connect()> method and pass the segments of the DSN via the
+attributes parameter through C<_login()> to C<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";
+ }
+
+If you handle any driver specific attributes in the dbd_db_login6
+method you probably want to delete them from C<attr> (as above with
+DBD_ATTRIB_DELETE). If you don't delete your handled attributes DBI
+will call C<STORE> for each attribute after the connect/login and this
+is at best redundant for attributes you have already processed.
+
+B<Note: Until revision 11605 (post DBI 1.607), there was a problem with
+DBD_ATTRIBUTE_DELETE so unless you require a DBI version after 1.607
+you need to replace each DBD_ATTRIBUTE_DELETE call with:>
+
+ hv_delete((HV*)SvRV(attr), key, key_len, G_DISCARD)
+
+Note that you can also obtain standard attributes such as I<AutoCommit> and
+I<ChopBlanks> from the attributes parameter, using C<DBD_ATTRIB_GET_IV> for
+integer attributes.
+
+If, for example, your database does not support transactions but
+I<AutoCommit> is set off (requesting transaction support), then you can
+emulate a 'failure to connect'.
+
+Now you should really connect to the database. In general, if the
+connection fails, it is best to ensure that all allocated resources are
+released so that the handle does not need to be destroyed separately. If
+you are successful (and possibly even if you fail but you have allocated
+some resources), you should use the following macros:
+
+ DBIc_IMPSET_on(imp_dbh);
+
+This indicates that the driver (implementor) has allocated resources in
+the I<imp_dbh> structure and that the implementors private C<dbd_db_destroy()>
+function should be called when the handle is destroyed.
+
+ DBIc_ACTIVE_on(imp_dbh);
+
+This indicates that the handle has an active connection to the server
+and that the C<dbd_db_disconnect()> function should be called before the
+handle is destroyed.
+
+Note that if you do need to fail, you should report errors via the I<drh>
+or I<imp_drh> rather than via I<dbh> or I<imp_dbh> because I<imp_dbh> will be
+destroyed by the failure, so errors recorded in that handle will not be
+visible to B<DBI>, and hence not the user either.
+
+Note too, that the function is passed I<dbh> and I<imp_dbh>, and there
+is a macro C<D_imp_drh_from_dbh> which can recover the I<imp_drh> from
+the I<imp_dbh>. However, there is no B<DBI> macro to provide you with the
+I<drh> given either the I<imp_dbh> or the I<dbh> or the I<imp_drh> (and
+there's no way to recover the I<dbh> given just the I<imp_dbh>).
+
+This suggests that, despite the above notes about C<dbd_drv_error()>
+taking an C<SV *>, it may be better to have two error routines, one
+taking I<imp_dbh> and one taking I<imp_drh> instead. With care, you can
+factor most of the formatting code out so that these are small routines
+calling a common error formatter. See the code in B<DBD::Informix>
+1.05.00 for more information.
+
+The C<dbd_db_login6()> function should return I<TRUE> for success,
+I<FALSE> otherwise.
+
+Drivers implemented long ago may define the five-argument function
+C<dbd_db_login()> instead of C<dbd_db_login6()>. The missing argument is
+the attributes. There are ways to work around the missing attributes,
+but they are ungainly; it is much better to use the 6-argument form.
+Even later drivers will use C<dbd_db_login6_sv()> which provides the
+dbname, username and password as SVs.
+
+=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 I<TRUE> for
+success, I<FALSE> for error.
+
+The arguments I<dbh> and I<imp_dbh> are the same as for C<dbd_db_login6()>
+above; I will omit describing them in what follows, as they appear
+always.
+
+These functions should return I<TRUE> for success, I<FALSE> otherwise.
+
+=head3 The dbd_db_disconnect method
+
+This is your private part of the C<disconnect()> method. Any I<dbh> with
+the I<ACTIVE> flag on must be disconnected. (Note that you have to set
+it in C<dbd_db_connect()> above.)
+
+ int dbd_db_disconnect(SV* dbh, imp_dbh_t* imp_dbh);
+
+The database handle will return I<TRUE> for success, I<FALSE> otherwise.
+In any case it should do a:
+
+ DBIc_ACTIVE_off(imp_dbh);
+
+before returning so B<DBI> knows that C<dbd_db_disconnect()> was executed.
+
+Note that there's nothing to stop a I<dbh> being I<disconnected> while
+it still have active children. If your database API reacts badly to
+trying to use an I<sth> in this situation then you'll need to add code
+like this to all I<sth> methods:
+
+ if (!DBIc_ACTIVE(DBIc_PARENT_COM(imp_sth)))
+ return 0;
+
+Alternatively, you can add code to your driver to keep explicit track of
+the statement handles that exist for each database handle and arrange
+to destroy those handles before disconnecting from the database. There
+is code to do this in B<DBD::Informix>. Similar comments apply to the
+driver handle keeping track of all the database handles.
+
+Note that the code which destroys the subordinate handles should only
+release the associated database resources and mark the handles inactive;
+it does not attempt to free the actual handle structures.
+
+This function should return I<TRUE> for success, I<FALSE> otherwise, but
+it 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 make
+best-efforts to disconnect all database handles - if possible. Some
+databases don't support that, in which case you can do nothing
+but return 'success'.
+
+This function should return I<TRUE> for success, I<FALSE> otherwise, but
+it is not clear what anything can do about a failure.
+
+=head3 The dbd_db_destroy method
+
+This is your private part of the database handle destructor. Any I<dbh> with
+the I<IMPSET> flag on must be destroyed, so that you can safely free
+resources. (Note that you have to set it in C<dbd_db_connect()> above.)
+
+ void dbd_db_destroy(SV* dbh, imp_dbh_t* imp_dbh)
+ {
+ DBIc_IMPSET_off(imp_dbh);
+ }
+
+The B<DBI> F<Driver.xst> code will have called C<dbd_db_disconnect()> for you,
+if the handle is still 'active', before calling C<dbd_db_destroy()>.
+
+Before returning the function must switch I<IMPSET> to off, so B<DBI> knows
+that the destructor was called.
+
+A B<DBI> handle doesn't keep references to its children. But children
+do keep references to their parents. So a database handle won't be
+C<DESTROY>'d until all its children have been C<DESTROY>'d.
+
+=head3 The dbd_db_STORE_attrib method
+
+This function handles
+
+ $dbh->{$key} = $value;
+
+Its prototype is:
+
+ int dbd_db_STORE_attrib(SV* dbh, imp_dbh_t* imp_dbh, SV* keysv,
+ SV* valuesv);
+
+You do not handle all attributes; on the contrary, you should not handle
+B<DBI> attributes here: leave this to B<DBI>. (There are two exceptions,
+I<AutoCommit> and I<ChopBlanks>, which you should care about.)
+
+The return value is I<TRUE> if you have handled the attribute or I<FALSE>
+otherwise. If you are handling an attribute and something fails, you
+should call C<dbd_drv_error()>, so B<DBI> can raise exceptions, if desired.
+If C<dbd_drv_error()> returns, however, you have a problem: the user will
+never know about the error, because he typically will not check
+C<$dbh-E<gt>errstr()>.
+
+I cannot recommend a general way of going on, if C<dbd_drv_error()> returns,
+but there are examples where even the B<DBI> specification expects that
+you C<croak()>. (See the I<AutoCommit> method in L<DBI>.)
+
+If you have to store attributes, you should either use your private
+data structure I<imp_xxx>, the handle hash (via C<(HV*)SvRV(dbh)>), or use
+the private I<imp_data>.
+
+The first is best for internal C values like integers or pointers and
+where speed is important within the driver. The handle hash is best for
+values the user may want to get/set via driver-specific attributes.
+The private I<imp_data> is an additional C<SV> attached to the handle. You
+could think of it as an unnamed handle attribute. It's not normally used.
+
+=head3 The dbd_db_FETCH_attrib method
+
+This is the counterpart of C<dbd_db_STORE_attrib()>, needed for:
+
+ $value = $dbh->{$key};
+
+Its prototype is:
+
+ SV* dbd_db_FETCH_attrib(SV* dbh, imp_dbh_t* imp_dbh, SV* keysv);
+
+Unlike all previous methods this returns an C<SV> with the value. Note
+that you should normally execute C<sv_2mortal()>, if you return a nonconstant
+value. (Constant values are C<&sv_undef>, C<&sv_no> and C<&sv_yes>.)
+
+Note, that B<DBI> implements a caching algorithm for attribute values.
+If you think, that an attribute may be fetched, you store it in the
+I<dbh> itself:
+
+ if (cacheit) /* cache value for later DBI 'quick' fetch? */
+ hv_store((HV*)SvRV(dbh), key, kl, cachesv, 0);
+
+=head3 The dbd_st_prepare method
+
+This is the private part of the C<prepare()> method. Note that you
+B<must not> really execute the statement here. You may, however,
+preparse and validate the statement, or do similar things.
+
+ int dbd_st_prepare(SV* sth, imp_sth_t* imp_sth, char* statement,
+ SV* attribs);
+
+A typical, simple, possibility is to do nothing and rely on the perl
+C<prepare()> code that set the I<Statement> attribute on the handle. This
+attribute can then be used by C<dbd_st_execute()>.
+
+If the driver supports placeholders then the I<NUM_OF_PARAMS> attribute
+must be set correctly by C<dbd_st_prepare()>:
+
+ DBIc_NUM_PARAMS(imp_sth) = ...
+
+If you can, you should also setup attributes like I<NUM_OF_FIELDS>, I<NAME>,
+etc. here, but B<DBI> doesn't require that - they can be deferred until
+execute() is called. However, if you do, document it.
+
+In any case you should set the I<IMPSET> flag, as you did in
+C<dbd_db_connect()> above:
+
+ DBIc_IMPSET_on(imp_sth);
+
+=head3 The dbd_st_execute method
+
+This is where a statement will really be executed.
+
+ int dbd_st_execute(SV* sth, imp_sth_t* imp_sth);
+
+C<dbd_st_execute> should return -2 for any error, -1 if the number of
+rows affected is unknown else it should be the number of affected
+(updated, inserted) rows.
+
+Note that you must be aware a statement may be executed repeatedly.
+Also, you should not expect that C<finish()> will be called between two
+executions, so you might need code, like the following, near the start
+of the function:
+
+ if (DBIc_ACTIVE(imp_sth))
+ dbd_st_finish(h, imp_sth);
+
+If your driver supports the binding of parameters (it should!), but the
+database doesn't, you must do it here. This can be done as follows:
+
+ SV *svp;
+ char* statement = DBD_ATTRIB_GET_PV(h, "Statement", 9, svp, "");
+ int numParam = DBIc_NUM_PARAMS(imp_sth);
+ int i;
+
+ for (i = 0; i < numParam; i++)
+ {
+ char* value = dbd_db_get_param(sth, imp_sth, i);
+ /* It is your drivers task to implement dbd_db_get_param, */
+ /* it must be setup as a counterpart of dbd_bind_ph. */
+ /* Look for '?' and replace it with 'value'. Difficult */
+ /* task, note that you may have question marks inside */
+ /* quotes and comments the like ... :-( */
+ /* See DBD::mysql for an example. (Don't look too deep into */
+ /* the example, you will notice where I was lazy ...) */
+ }
+
+The next thing is to really execute the statement.
+
+Note that you must set the attributes I<NUM_OF_FIELDS>, I<NAME>, etc
+when the statement is successfully executed if the driver has not
+already done so: they may be used even before a potential C<fetchrow()>.
+In particular you have to tell B<DBI> the number of fields that the
+statement has, because it will be used by B<DBI> internally. Thus the
+function will typically ends with:
+
+ if (isSelectStatement) {
+ DBIc_NUM_FIELDS(imp_sth) = numFields;
+ DBIc_ACTIVE_on(imp_sth);
+ }
+
+It is important that the I<ACTIVE> flag only be set for C<SELECT>
+statements (or any other statements that can return many
+values from the database using a cursor-like mechanism). See
+C<dbd_db_connect()> above for more explanations.
+
+There plans for a preparse function to be provided by B<DBI>, but this has
+not reached fruition yet.
+Meantime, if you want to know how ugly it can get, try looking at the
+C<dbd_ix_preparse()> in B<DBD::Informix> F<dbdimp.ec> and the related
+functions in F<iustoken.c> and F<sqltoken.c>.
+
+=head3 The dbd_st_fetch method
+
+This function fetches a row of data. The row is stored in in an array,
+of C<SV>'s that B<DBI> prepares for you. This has two advantages: it is fast
+(you even reuse the C<SV>'s, so they don't have to be created after the
+first C<fetchrow()>), and it guarantees that B<DBI> handles C<bind_cols()> for
+you.
+
+What you do is the following:
+
+ AV* av;
+ int numFields = DBIc_NUM_FIELDS(imp_sth); /* Correct, if NUM_FIELDS
+ is constant for this statement. There are drivers where this is
+ not the case! */
+ int chopBlanks = DBIc_is(imp_sth, DBIcf_ChopBlanks);
+ int i;
+
+ if (!fetch_new_row_of_data(...)) {
+ ... /* check for error or end-of-data */
+ DBIc_ACTIVE_off(imp_sth); /* turn off Active flag automatically */
+ return Nullav;
+ }
+ /* get the fbav (field buffer array value) for this row */
+ /* it is very important to only call this after you know */
+ /* that you have a row of data to return. */
+ av = DBIc_DBISTATE(imp_sth)->get_fbav(imp_sth);
+ for (i = 0; i < numFields; i++) {
+ SV* sv = fetch_a_field(..., i);
+ if (chopBlanks && SvOK(sv) && type_is_blank_padded(field_type[i])) {
+ /* Remove white space from end (only) of sv */
+ }
+ sv_setsv(AvARRAY(av)[i], sv); /* Note: (re)use! */
+ }
+ return av;
+
+There's no need to use a C<fetch_a_field()> function returning an C<SV*>.
+It's more common to use your database API functions to fetch the
+data as character strings and use code like this:
+
+ sv_setpvn(AvARRAY(av)[i], char_ptr, char_count);
+
+C<NULL> values must be returned as C<undef>. You can use code like this:
+
+ SvOK_off(AvARRAY(av)[i]);
+
+The function returns the C<AV> prepared by B<DBI> for success or C<Nullav>
+otherwise.
+
+ *FIX ME* Discuss what happens when there's no more data to fetch.
+ Are errors permitted if another fetch occurs after the first fetch
+ that reports no more data. (Permitted, not required.)
+
+If an error occurs which leaves the I<$sth> in a state where remaining
+rows can't be fetched then I<Active> should be turned off before the
+method returns.
+
+=head3 The dbd_st_finish3 method
+
+The C<$sth-E<gt>finish()> method can be called if the user wishes to
+indicate that no more rows will be fetched even if the database has more
+rows to offer, and the B<DBI> code can call the function when handles are
+being destroyed. See the B<DBI> specification for more background details.
+
+In both circumstances, the B<DBI> code ends up calling the
+C<dbd_st_finish3()> method (if you provide a mapping for
+C<dbd_st_finish3()> in F<dbdimp.h>), or C<dbd_st_finish()> otherwise.
+The difference is that C<dbd_st_finish3()> takes a third argument which
+is an C<int> with the value 1 if it is being called from a C<destroy()>
+method and 0 otherwise.
+
+Note that B<DBI> v1.32 and earlier test on C<dbd_db_finish3()> to call
+C<dbd_st_finish3()>; if you provide C<dbd_st_finish3()>, either define
+C<dbd_db_finish3()> too, or insist on B<DBI> v1.33 or later.
+
+All it I<needs> to do is turn off the I<Active> flag for the I<sth>.
+It will only be called by F<Driver.xst> code, if the driver has set I<ACTIVE>
+to on for the I<sth>.
+
+Outline example:
+
+ int dbd_st_finish3(SV* sth, imp_sth_t* imp_sth, int from_destroy) {
+ if (DBIc_ACTIVE(imp_sth))
+ {
+ /* close cursor or equivalent action */
+ DBIc_ACTIVE_off(imp_sth);
+ }
+ return 1;
+ }
+
+The from_destroy parameter is true if C<dbd_st_finish3()> is being called
+from C<DESTROY()> - and so the statement is about to be destroyed.
+For many drivers there is no point in doing anything more than turning off
+the I<Active> flag in this case.
+
+The function returns I<TRUE> for success, I<FALSE> otherwise, but there isn't
+a lot anyone can do to recover if there is an error.
+
+=head3 The dbd_st_destroy method
+
+This function is the private part of the statement handle destructor.
+
+ void dbd_st_destroy(SV* sth, imp_sth_t* imp_sth) {
+ ... /* any clean-up that's needed */
+ DBIc_IMPSET_off(imp_sth); /* let DBI know we've done it */
+ }
+
+The B<DBI> F<Driver.xst> code will call C<dbd_st_finish()> for you, if the
+I<sth> has the I<ACTIVE> flag set, before calling C<dbd_st_destroy()>.
+
+=head3 The dbd_st_STORE_attrib and dbd_st_FETCH_attrib methods
+
+These functions correspond to C<dbd_db_STORE()> and C<dbd_db_FETCH()> attrib
+above, except that they are for statement handles.
+See above.
+
+ int dbd_st_STORE_attrib(SV* sth, imp_sth_t* imp_sth, SV* keysv,
+ SV* valuesv);
+ SV* dbd_st_FETCH_attrib(SV* sth, imp_sth_t* imp_sth, SV* keysv);
+
+=head3 The dbd_bind_ph method
+
+This function is internally used by the C<bind_param()> method, the
+C<bind_param_inout()> method and by the B<DBI> F<Driver.xst> code if
+C<execute()> is called with any bind parameters.
+
+ int dbd_bind_ph (SV *sth, imp_sth_t *imp_sth, SV *param,
+ SV *value, IV sql_type, SV *attribs,
+ int is_inout, IV maxlen);
+
+The I<param> argument holds an C<IV> with the parameter number (1, 2, ...).
+The I<value> argument is the parameter value and I<sql_type> is its type.
+
+If your driver does not support C<bind_param_inout()> then you should
+ignore I<maxlen> and croak if I<is_inout> is I<TRUE>.
+
+If your driver I<does> support C<bind_param_inout()> then you should
+note that I<value> is the C<SV> I<after> dereferencing the reference
+passed to C<bind_param_inout()>.
+
+In drivers of simple databases the function will, for example, store
+the value in a parameter array and use it later in C<dbd_st_execute()>.
+See the B<DBD::mysql> driver for an example.
+
+=head3 Implementing bind_param_inout support
+
+To provide support for parameters bound by reference rather than by
+value, the driver must do a number of things. First, and most
+importantly, it must note the references and stash them in its own
+driver structure. Secondly, when a value is bound to a column, the
+driver must discard any previous reference bound to the column. On
+each execute, the driver must evaluate the references and internally
+bind the values resulting from the references. This is only applicable
+if the user writes:
+
+ $sth->execute;
+
+If the user writes:
+
+ $sth->execute(@values);
+
+then B<DBI> automatically calls the binding code for each element of
+I<@values>. These calls are indistinguishable from explicit user calls to
+C<bind_param()>.
+
+=head2 C/XS version of Makefile.PL
+
+The F<Makefile.PL> file for a C/XS driver is similar to the code needed
+for a pure Perl driver, but there are a number of extra bits of
+information needed by the build system.
+
+For example, the attributes list passed to C<WriteMakefile()> needs
+to specify the object files that need to be compiled and built into
+the shared object (DLL). This is often, but not necessarily, just
+F<dbdimp.o> (unless that should be F<dbdimp.obj> because you're building
+on MS Windows).
+
+Note that you can reliably determine the extension of the object files
+from the I<$Config{obj_ext}> values, and there are many other useful pieces
+of configuration information lurking in that hash.
+You get access to it with:
+
+ use Config;
+
+=head2 Methods which do not need to be written
+
+The B<DBI> code implements the majority of the methods which are accessed
+using the notation C<DBI-E<gt>function()>, the only exceptions being
+C<DBI-E<gt>connect()> and C<DBI-E<gt>data_sources()> which require
+support from the driver.
+
+The B<DBI> code implements the following documented driver, database and
+statement functions which do not need to be written by the B<DBD> driver
+writer.
+
+=over 4
+
+=item $dbh->do()
+
+The default implementation of this function prepares, executes and
+destroys the statement. This can be replaced if there is a better
+way to implement this, such as C<EXECUTE IMMEDIATE> which can
+sometimes be used if there are no parameters.
+
+=item $h->errstr()
+
+=item $h->err()
+
+=item $h->state()
+
+=item $h->trace()
+
+The B<DBD> driver does not need to worry about these routines at all.
+
+=item $h->{ChopBlanks}
+
+This attribute needs to be honored during C<fetch()> operations, but does
+not need to be handled by the attribute handling code.
+
+=item $h->{RaiseError}
+
+The B<DBD> driver does not need to worry about this attribute at all.
+
+=item $h->{PrintError}
+
+The B<DBD> driver does not need to worry about this attribute at all.
+
+=item $sth->bind_col()
+
+Assuming the driver uses the C<DBIc_DBISTATE(imp_xxh)-E<gt>get_fbav()>
+function (C drivers, see below), or the C<$sth-E<gt>_set_fbav($data)>
+method (Perl drivers) the driver does not need to do anything about this
+routine.
+
+=item $sth->bind_columns()
+
+Regardless of whether the driver uses
+C<DBIc_DBISTATE(imp_xxh)-E<gt>get_fbav()>, the driver does not need
+to do anything about this routine as it simply iteratively calls
+C<$sth-E<gt>bind_col()>.
+
+=back
+
+The B<DBI> code implements a default implementation of the following
+functions which do not need to be written by the B<DBD> driver writer
+unless the default implementation is incorrect for the Driver.
+
+=over 4
+
+=item $dbh->quote()
+
+This should only be written if the database does not accept the ANSI
+SQL standard for quoting strings, with the string enclosed in single
+quotes and any embedded single quotes replaced by two consecutive
+single quotes.
+
+For the two argument form of quote, you need to implement the
+C<type_info()> method to provide the information that quote needs.
+
+=item $dbh->ping()
+
+This should be implemented as a simple efficient way to determine
+whether the connection to the database is still alive. Typically
+code like this:
+
+ sub ping {
+ my $dbh = shift;
+ $sth = $dbh->prepare_cached(q{
+ select * from A_TABLE_NAME where 1=0
+ }) or return 0;
+ $sth->execute or return 0;
+ $sth->finish;
+ return 1;
+ }
+
+where I<A_TABLE_NAME> is the name of a table that always exists (such as a
+database system catalogue).
+
+=item $drh->default_user
+
+The default implementation of default_user will get the database
+username and password fields from C<$ENV{DBI_USER}> and
+C<$ENV{DBI_PASS}>. You can override this method. It is called as
+follows:
+
+ ($user, $pass) = $drh->default_user($user, $pass, $attr)
+
+=back
+
+=head1 METADATA METHODS
+
+The exposition above ignores the B<DBI> MetaData methods.
+The metadata methods are all associated with a database handle.
+
+=head2 Using DBI::DBD::Metadata
+
+The B<DBI::DBD::Metadata> module is a good semi-automatic way for the
+developer of a B<DBD> module to write the C<get_info()> and C<type_info()>
+functions quickly and accurately.
+
+=head3 Generating the get_info method
+
+Prior to B<DBI> v1.33, this existed as the method C<write_getinfo_pm()>
+in the B<DBI::DBD> module. From B<DBI> v1.33, it exists as the method
+C<write_getinfo_pm()> in the B<DBI::DBD::Metadata> module. This
+discussion assumes you have B<DBI> v1.33 or later.
+
+You examine the documentation for C<write_getinfo_pm()> using:
+
+ perldoc DBI::DBD::Metadata
+
+To use it, you need a Perl B<DBI> driver for your database which implements
+the C<get_info()> method. In practice, this means you need to install
+B<DBD::ODBC>, an ODBC driver manager, and an ODBC driver for your
+database.
+
+With the pre-requisites in place, you might type:
+
+ perl -MDBI::DBD::Metadata -we \
+ "write_getinfo_pm (qw{ dbi:ODBC:foo_db username password Driver })"
+
+The procedure writes to standard output the code that should be added to
+your F<Driver.pm> file and the code that should be written to
+F<lib/DBD/Driver/GetInfo.pm>.
+
+You should review the output to ensure that it is sensible.
+
+=head3 Generating the type_info method
+
+Given the idea of the C<write_getinfo_pm()> method, it was not hard
+to devise a parallel method, C<write_typeinfo_pm()>, which does the
+analogous job for the B<DBI> C<type_info_all()> metadata method. The
+C<write_typeinfo_pm()> method was added to B<DBI> v1.33.
+
+You examine the documentation for C<write_typeinfo_pm()> using:
+
+ perldoc DBI::DBD::Metadata
+
+The setup is exactly analogous to the mechanism described in
+L</Generating the get_info method>.
+
+With the pre-requisites in place, you might type:
+
+ perl -MDBI::DBD::Metadata -we \
+ "write_typeinfo (qw{ dbi:ODBC:foo_db username password Driver })"
+
+The procedure writes to standard output the code that should be added to
+your F<Driver.pm> file and the code that should be written to
+F<lib/DBD/Driver/TypeInfo.pm>.
+
+You should review the output to ensure that it is sensible.
+
+=head2 Writing DBD::Driver::db::get_info
+
+If you use the B<DBI::DBD::Metadata> module, then the code you need is
+generated for you.
+
+If you decide not to use the B<DBI::DBD::Metadata> module, you
+should probably borrow the code from a driver that has done so (eg
+B<DBD::Informix> from version 1.05 onwards) and crib the code from
+there, or look at the code that generates that module and follow
+that. The method in F<Driver.pm> will be very simple; the method in
+F<lib/DBD/Driver/GetInfo.pm> is not very much more complex unless your
+DBMS itself is much more complex.
+
+Note that some of the B<DBI> utility methods rely on information from the
+C<get_info()> method to perform their operations correctly. See, for
+example, the C<quote_identifier()> and quote methods, discussed below.
+
+=head2 Writing DBD::Driver::db::type_info_all
+
+If you use the C<DBI::DBD::Metadata> module, then the code you need is
+generated for you.
+
+If you decide not to use the C<DBI::DBD::Metadata> module, you
+should probably borrow the code from a driver that has done so (eg
+C<DBD::Informix> from version 1.05 onwards) and crib the code from
+there, or look at the code that generates that module and follow
+that. The method in F<Driver.pm> will be very simple; the method in
+F<lib/DBD/Driver/TypeInfo.pm> is not very much more complex unless your
+DBMS itself is much more complex.
+
+=head2 Writing DBD::Driver::db::type_info
+
+The guidelines on writing this method are still not really clear.
+No sample implementation is available.
+
+=head2 Writing DBD::Driver::db::table_info
+
+ *FIX ME* The guidelines on writing this method have not been written yet.
+ No sample implementation is available.
+
+=head2 Writing DBD::Driver::db::column_info
+
+ *FIX ME* The guidelines on writing this method have not been written yet.
+ No sample implementation is available.
+
+=head2 Writing DBD::Driver::db::primary_key_info
+
+ *FIX ME* The guidelines on writing this method have not been written yet.
+ No sample implementation is available.
+
+=head2 Writing DBD::Driver::db::primary_key
+
+ *FIX ME* The guidelines on writing this method have not been written yet.
+ No sample implementation is available.
+
+=head2 Writing DBD::Driver::db::foreign_key_info
+
+ *FIX ME* The guidelines on writing this method have not been written yet.
+ No sample implementation is available.
+
+=head2 Writing DBD::Driver::db::tables
+
+This method generates an array of names in a format suitable for being
+embedded in SQL statements in places where a table name is expected.
+
+If your database hews close enough to the SQL standard or if you have
+implemented an appropriate C<table_info()> function and and the appropriate
+C<quote_identifier()> function, then the B<DBI> default version of this method
+will work for your driver too.
+
+Otherwise, you have to write a function yourself, such as:
+
+ sub tables
+ {
+ my($dbh, $cat, $sch, $tab, $typ) = @_;
+ my(@res);
+ my($sth) = $dbh->table_info($cat, $sch, $tab, $typ);
+ my(@arr);
+ while (@arr = $sth->fetchrow_array)
+ {
+ push @res, $dbh->quote_identifier($arr[0], $arr[1], $arr[2]);
+ }
+ return @res;
+ }
+
+See also the default implementation in F<DBI.pm>.
+
+=head2 Writing DBD::Driver::db::quote
+
+This method takes a value and converts it into a string suitable for
+embedding in an SQL statement as a string literal.
+
+If your DBMS accepts the SQL standard notation for strings (single
+quotes around the string as a whole with any embedded single quotes
+doubled up), then you do not need to write this method as B<DBI> provides a
+default method that does it for you.
+
+If your DBMS uses an alternative notation or escape mechanism, then you
+need to provide an equivalent function. For example, suppose your DBMS
+used C notation with double quotes around the string and backslashes
+escaping both double quotes and backslashes themselves. Then you might
+write the function as:
+
+ sub quote
+ {
+ my($dbh, $str) = @_;
+ $str =~ s/["\\]/\\$&/gmo;
+ return qq{"$str"};
+ }
+
+Handling newlines and other control characters is left as an exercise
+for the reader.
+
+This sample method ignores the I<$data_type> indicator which is the
+optional second argument to the method.
+
+=head2 Writing DBD::Driver::db::quote_identifier
+
+This method is called to ensure that the name of the given table (or
+other database object) can be embedded into an SQL statement without
+danger of misinterpretation. The result string should be usable in the
+text of an SQL statement as the identifier for a table.
+
+If your DBMS accepts the SQL standard notation for quoted identifiers
+(which uses double quotes around the identifier as a whole, with any
+embedded double quotes doubled up) and accepts I<"schema"."identifier">
+(and I<"catalog"."schema"."identifier"> when a catalog is specified), then
+you do not need to write this method as B<DBI> provides a default method
+that does it for you.
+
+In fact, even if your DBMS does not handle exactly that notation but
+you have implemented the C<get_info()> method and it gives the correct
+responses, then it will work for you. If your database is fussier, then
+you need to implement your own version of the function.
+
+For example, B<DBD::Informix> has to deal with an environment variable
+I<DELIMIDENT>. If it is not set, then the DBMS treats names enclosed in
+double quotes as strings rather than names, which is usually a syntax
+error. Additionally, the catalog portion of the name is separated from
+the schema and table by a different delimiter (colon instead of dot),
+and the catalog portion is never enclosed in quotes. (Fortunately,
+valid strings for the catalog will never contain weird characters that
+might need to be escaped, unless you count dots, dashes, slashes and
+at-signs as weird.) Finally, an Informix database can contain objects
+that cannot be accessed because they were created by a user with the
+I<DELIMIDENT> environment variable set, but the current user does not
+have it set. By design choice, the C<quote_identifier()> method encloses
+those identifiers in double quotes anyway, which generally triggers a
+syntax error, and the metadata methods which generate lists of tables
+etc omit those identifiers from the result sets.
+
+ sub quote_identifier
+ {
+ my($dbh, $cat, $sch, $obj) = @_;
+ my($rv) = "";
+ my($qq) = (defined $ENV{DELIMIDENT}) ? '"' : '';
+ $rv .= qq{$cat:} if (defined $cat);
+ if (defined $sch)
+ {
+ if ($sch !~ m/^\w+$/o)
+ {
+ $qq = '"';
+ $sch =~ s/$qq/$qq$qq/gm;
+ }
+ $rv .= qq{$qq$sch$qq.};
+ }
+ if (defined $obj)
+ {
+ if ($obj !~ m/^\w+$/o)
+ {
+ $qq = '"';
+ $obj =~ s/$qq/$qq$qq/gm;
+ }
+ $rv .= qq{$qq$obj$qq};
+ }
+ return $rv;
+ }
+
+Handling newlines and other control characters is left as an exercise
+for the reader.
+
+Note that there is an optional fourth parameter to this function which
+is a reference to a hash of attributes; this sample implementation
+ignores that.
+
+This sample implementation also ignores the single-argument variant of
+the method.
+
+=head1 TRACING
+
+Tracing in DBI is controlled with a combination of a trace level and a
+set of flags which together are known as the trace settings. The trace
+settings are stored in a single integer and divided into levels and
+flags by a set of masks (C<DBIc_TRACE_LEVEL_MASK> and
+C<DBIc_TRACE_FLAGS_MASK>).
+
+Each handle has it's own trace settings and so does the DBI. When you
+call a method the DBI merges the handles settings into its own for the
+duration of the call: the trace flags of the handle are OR'd into the
+trace flags of the DBI, and if the handle has a higher trace level
+then the DBI trace level is raised to match it. The previous DBI trace
+settings are restored when the called method returns.
+
+=head2 Trace Level
+
+The trace level is the first 4 bits of the trace settings (masked by
+C<DBIc_TRACE_FLAGS_MASK>) and represents trace levels of 1 to 15. Do
+not output anything at trace levels less than 3 as they are reserved
+for DBI.
+
+For advice on what to output at each level see "Trace Levels" in
+L<DBI>.
+
+To test for a trace level you can use the C<DBIc_TRACE_LEVEL> macro
+like this:
+
+ if (DBIc_TRACE_LEVEL(imp_xxh) >= 2) {
+ PerlIO_printf(DBIc_LOGPIO(imp_xxh), "foobar");
+ }
+
+Also B<note> the use of PerlIO_printf which you should always use for
+tracing and never the C C<stdio.h> I/O functions.
+
+=head2 Trace Flags
+
+Trace flags are used to enable tracing of specific activities within
+the DBI and drivers. The DBI defines some trace flags and drivers can
+define others. DBI trace flag names begin with a capital letter and
+driver specific names begin with a lowercase letter. For a list of DBI
+defined trace flags see "Trace Flags" in L<DBI>.
+
+If you want to use private trace flags you'll probably want to be able
+to set them by name. Drivers are expected to override the
+parse_trace_flag (note the singular) and check if $trace_flag_name is
+a driver specific trace flags and, if not, then call the DBIs default
+parse_trace_flag(). To do that you'll need to define a
+parse_trace_flag() method like this:
+
+ sub parse_trace_flag {
+ my ($h, $name) = @_;
+ return 0x01000000 if $name eq 'foo';
+ return 0x02000000 if $name eq 'bar';
+ return 0x04000000 if $name eq 'baz';
+ return 0x08000000 if $name eq 'boo';
+ return 0x10000000 if $name eq 'bop';
+ return $h->SUPER::parse_trace_flag($name);
+ }
+
+All private flag names must be lowercase, and all private flags must
+be in the top 8 of the 32 bits of C<DBIc_TRACE_FLAGS(imp)> i.e.,
+0xFF000000.
+
+If you've defined a parse_trace_flag() method in ::db you'll also want
+it in ::st, so just alias it in:
+
+ *parse_trace_flag = \&DBD::foo:db::parse_trace_flag;
+
+You may want to act on the current 'SQL' trace flag that DBI defines
+to output SQL prepared/executed as DBI currently does not do SQL
+tracing.
+
+=head2 Trace Macros
+
+Access to the trace level and trace flags is via a set of macros.
+
+ DBIc_TRACE_SETTINGS(imp) returns the trace settings
+ DBIc_TRACE_LEVEL(imp) returns the trace level
+ DBIc_TRACE_FLAGS(imp) returns the trace flags
+ DBIc_TRACE(imp, flags, flaglevel, level)
+
+ e.g.,
+
+ DBIc_TRACE(imp, 0, 0, 4)
+ if level >= 4
+
+ DBIc_TRACE(imp, DBDtf_FOO, 2, 4)
+ if tracing DBDtf_FOO & level>=2 or level>=4
+
+ DBIc_TRACE(imp, DBDtf_FOO, 2, 0)
+ as above but never trace just due to level
+
+=head1 WRITING AN EMULATION LAYER FOR AN OLD PERL INTERFACE
+
+Study F<Oraperl.pm> (supplied with B<DBD::Oracle>) and F<Ingperl.pm> (supplied
+with B<DBD::Ingres>) and the corresponding I<dbdimp.c> files for ideas.
+
+Note that the emulation code sets C<$dbh-E<gt>{CompatMode} = 1;> for each
+connection so that the internals of the driver can implement behaviour
+compatible with the old interface when dealing with those handles.
+
+=head2 Setting emulation perl variables
+
+For example, ingperl has a I<$sql_rowcount> variable. Rather than try
+to manually update this in F<Ingperl.pm> it can be done faster in C code.
+In C<dbd_init()>:
+
+ sql_rowcount = perl_get_sv("Ingperl::sql_rowcount", GV_ADDMULTI);
+
+In the relevant places do:
+
+ if (DBIc_COMPAT(imp_sth)) /* only do this for compatibility mode handles */
+ sv_setiv(sql_rowcount, the_row_count);
+
+=head1 OTHER MISCELLANEOUS INFORMATION
+
+=head2 The imp_xyz_t types
+
+Any handle has a corresponding C structure filled with private data.
+Some of this data is reserved for use by B<DBI> (except for using the
+DBIc macros below), some is for you. See the description of the
+F<dbdimp.h> file above for examples. Most functions in F<dbdimp.c>
+are passed both the handle C<xyz> and a pointer to C<imp_xyz>. In
+rare cases, however, you may use the following macros:
+
+=over 4
+
+=item D_imp_dbh(dbh)
+
+Given a function argument I<dbh>, declare a variable I<imp_dbh> and
+initialize it with a pointer to the handles private data. Note: This
+must be a part of the function header, because it declares a variable.
+
+=item D_imp_sth(sth)
+
+Likewise for statement handles.
+
+=item D_imp_xxx(h)
+
+Given any handle, declare a variable I<imp_xxx> and initialize it
+with a pointer to the handles private data. It is safe, for example,
+to cast I<imp_xxx> to C<imp_dbh_t*>, if C<DBIc_TYPE(imp_xxx) == DBIt_DB>.
+(You can also call C<sv_derived_from(h, "DBI::db")>, but that's much
+slower.)
+
+=item D_imp_dbh_from_sth
+
+Given a I<imp_sth>, declare a variable I<imp_dbh> and initialize it with a
+pointer to the parent database handle's implementors structure.
+
+=back
+
+=head2 Using DBIc_IMPSET_on
+
+The driver code which initializes a handle should use C<DBIc_IMPSET_on()>
+as soon as its state is such that the cleanup code must be called.
+When this happens is determined by your driver code.
+
+B<Failure to call this can lead to corruption of data structures.>
+
+For example, B<DBD::Informix> maintains a linked list of database
+handles in the driver, and within each handle, a linked list of
+statements. Once a statement is added to the linked list, it is crucial
+that it is cleaned up (removed from the list). When I<DBIc_IMPSET_on()>
+was being called too late, it was able to cause all sorts of problems.
+
+=head2 Using DBIc_is(), DBIc_has(), DBIc_on() and DBIc_off()
+
+Once upon a long time ago, the only way of handling the internal B<DBI>
+boolean flags/attributes was through macros such as:
+
+ DBIc_WARN DBIc_WARN_on DBIc_WARN_off
+ DBIc_COMPAT DBIc_COMPAT_on DBIc_COMPAT_off
+
+Each of these took an I<imp_xxh> pointer as an argument.
+
+Since then, new attributes have been added such as I<ChopBlanks>,
+I<RaiseError> and I<PrintError>, and these do not have the full set of
+macros. The approved method for handling these is now the four macros:
+
+ DBIc_is(imp, flag)
+ DBIc_has(imp, flag) an alias for DBIc_is
+ DBIc_on(imp, flag)
+ DBIc_off(imp, flag)
+ DBIc_set(imp, flag, on) set if on is true, else clear
+
+Consequently, the C<DBIc_XXXXX> family of macros is now mostly deprecated
+and new drivers should avoid using them, even though the older drivers
+will probably continue to do so for quite a while yet. However...
+
+There is an I<important exception> to that. The I<ACTIVE> and I<IMPSET>
+flags should be set via the C<DBIc_ACTIVE_on()> and C<DBIc_IMPSET_on()> macros,
+and unset via the C<DBIc_ACTIVE_off()> and C<DBIc_IMPSET_off()> macros.
+
+=head2 Using the get_fbav() method
+
+B<THIS IS CRITICAL for C/XS drivers>.
+
+The C<$sth-E<gt>bind_col()> and C<$sth-E<gt>bind_columns()> documented
+in the B<DBI> specification do not have to be implemented by the driver
+writer because B<DBI> takes care of the details for you.
+
+However, the key to ensuring that bound columns work is to call the
+function C<DBIc_DBISTATE(imp_xxh)-E<gt>get_fbav()> in the code which
+fetches a row of data.
+
+This returns an C<AV>, and each element of the C<AV> contains the C<SV> which
+should be set to contain the returned data.
+
+The pure Perl equivalent is the C<$sth-E<gt>_set_fbav($data)> method, as
+described in the part on pure Perl drivers.
+
+=head2 Casting strings to Perl types based on a SQL type
+
+DBI from 1.611 (and DBIXS_REVISION 13606) defines the
+sql_type_cast_svpv method which may be used to cast a string
+representation of a value to a more specific Perl type based on a SQL
+type. You should consider using this method when processing bound
+column data as it provides some support for the TYPE bind_col
+attribute which is rarely used in drivers.
+
+ int sql_type_cast_svpv(pTHX_ SV *sv, int sql_type, U32 flags, void *v)
+
+C<sv> is what you would like cast, C<sql_type> is one of the DBI defined
+SQL types (e.g., C<SQL_INTEGER>) and C<flags> is a bitmask as follows:
+
+=over
+
+=item DBIstcf_STRICT
+
+If set this indicates you want an error state returned if the cast
+cannot be performed.
+
+=item DBIstcf_DISCARD_STRING
+
+If set and the pv portion of the C<sv> is cast then this will cause
+sv's pv to be freed up.
+
+=back
+
+sql_type_cast_svpv returns the following states:
+
+ -2 sql_type is not handled - sv not changed
+ -1 sv is undef, sv not changed
+ 0 sv could not be cast cleanly and DBIstcf_STRICT was specified
+ 1 sv could not be case cleanly and DBIstcf_STRICT was not specified
+ 2 sv was cast ok
+
+The current implementation of sql_type_cast_svpv supports
+C<SQL_INTEGER>, C<SQL_DOUBLE> and C<SQL_NUMERIC>. C<SQL_INTEGER> uses
+sv_2iv and hence may set IV, UV or NV depending on the
+number. C<SQL_DOUBLE> uses sv_2nv so may set NV and C<SQL_NUMERIC>
+will set IV or UV or NV.
+
+DBIstcf_STRICT should be implemented as the StrictlyTyped attribute
+and DBIstcf_DISCARD_STRING implemented as the DiscardString attribute
+to the bind_col method and both default to off.
+
+See DBD::Oracle for an example of how this is used.
+
+=head1 SUBCLASSING DBI DRIVERS
+
+This is definitely an open subject. It can be done, as demonstrated by
+the B<DBD::File> driver, but it is not as simple as one might think.
+
+(Note that this topic is different from subclassing the B<DBI>. For an
+example of that, see the F<t/subclass.t> file supplied with the B<DBI>.)
+
+The main problem is that the I<dbh>'s and I<sth>'s that your C<connect()> and
+C<prepare()> methods return are not instances of your B<DBD::Driver::db>
+or B<DBD::Driver::st> packages, they are not even derived from it.
+Instead they are instances of the B<DBI::db> or B<DBI::st> classes or
+a derived subclass. Thus, if you write a method C<mymethod()> and do a
+
+ $dbh->mymethod()
+
+then the autoloader will search for that method in the package B<DBI::db>.
+Of course you can instead to a
+
+ $dbh->func('mymethod')
+
+and that will indeed work, even if C<mymethod()> is inherited, but not
+without additional work. Setting I<@ISA> is not sufficient.
+
+=head2 Overwriting methods
+
+The first problem is, that the C<connect()> method has no idea of
+subclasses. For example, you cannot implement base class and subclass
+in the same file: The C<install_driver()> method wants to do a
+
+ require DBD::Driver;
+
+In particular, your subclass B<has> to be a separate driver, from
+the view of B<DBI>, and you cannot share driver handles.
+
+Of course that's not much of a problem. You should even be able
+to inherit the base classes C<connect()> method. But you cannot
+simply overwrite the method, unless you do something like this,
+quoted from B<DBD::CSV>:
+
+ sub connect ($$;$$$) {
+ my ($drh, $dbname, $user, $auth, $attr) = @_;
+
+ my $this = $drh->DBD::File::dr::connect($dbname, $user, $auth, $attr);
+ if (!exists($this->{csv_tables})) {
+ $this->{csv_tables} = {};
+ }
+
+ $this;
+ }
+
+Note that we cannot do a
+
+ $drh->SUPER::connect($dbname, $user, $auth, $attr);
+
+as we would usually do in a an OO environment, because I<$drh> is an instance
+of B<DBI::dr>. And note, that the C<connect()> method of B<DBD::File> is
+able to handle subclass attributes. See the description of Pure Perl
+drivers above.
+
+It is essential that you always call superclass method in the above
+manner. However, that should do.
+
+=head2 Attribute handling
+
+Fortunately the B<DBI> specifications allow a simple, but still
+performant way of handling attributes. The idea is based on the
+convention that any driver uses a prefix I<driver_> for its private
+methods. Thus it's always clear whether to pass attributes to the super
+class or not. For example, consider this C<STORE()> method from the
+B<DBD::CSV> class:
+
+ sub STORE {
+ my ($dbh, $attr, $val) = @_;
+ if ($attr !~ /^driver_/) {
+ return $dbh->DBD::File::db::STORE($attr, $val);
+ }
+ if ($attr eq 'driver_foo') {
+ ...
+ }
+
+=cut
+
+use Exporter ();
+use Config qw(%Config);
+use Carp;
+use Cwd;
+use File::Spec;
+use strict;
+use vars qw(
+ @ISA @EXPORT
+ $is_dbi
+);
+
+BEGIN {
+ if ($^O eq 'VMS') {
+ require vmsish;
+ import vmsish;
+ require VMS::Filespec;
+ import VMS::Filespec;
+ }
+ else {
+ *vmsify = sub { return $_[0] };
+ *unixify = sub { return $_[0] };
+ }
+}
+
+@ISA = qw(Exporter);
+
+@EXPORT = qw(
+ dbd_dbi_dir
+ dbd_dbi_arch_dir
+ dbd_edit_mm_attribs
+ dbd_postamble
+);
+
+BEGIN {
+ $is_dbi = (-r 'DBI.pm' && -r 'DBI.xs' && -r 'DBIXS.h');
+ require DBI unless $is_dbi;
+}
+
+my $done_inst_checks;
+
+sub _inst_checks {
+ return if $done_inst_checks++;
+ my $cwd = cwd();
+ if ($cwd =~ /\Q$Config{path_sep}/) {
+ warn "*** Warning: Path separator characters (`$Config{path_sep}') ",
+ "in the current directory path ($cwd) may cause problems\a\n\n";
+ sleep 2;
+ }
+ if ($cwd =~ /\s/) {
+ warn "*** Warning: whitespace characters ",
+ "in the current directory path ($cwd) may cause problems\a\n\n";
+ sleep 2;
+ }
+ if ( $^O eq 'MSWin32'
+ && $Config{cc} eq 'cl'
+ && !(exists $ENV{'LIB'} && exists $ENV{'INCLUDE'}))
+ {
+ die <<EOT;
+*** You're using Microsoft Visual C++ compiler or similar but
+ the LIB and INCLUDE environment variables are not both set.
+
+ You need to run the VCVARS32.BAT batch file that was supplied
+ with the compiler before you can use it.
+
+ A copy of vcvars32.bat can typically be found in the following
+ directories under your Visual Studio install directory:
+ Visual C++ 6.0: vc98\\bin
+ Visual Studio .NET: vc7\\bin
+
+ Find it, run it, then retry this.
+
+ If you think this error is not correct then just set the LIB and
+ INCLUDE environment variables to some value to disable the check.
+EOT
+ }
+}
+
+sub dbd_edit_mm_attribs {
+ # this both edits the attribs in-place and returns the flattened attribs
+ my $mm_attr = shift;
+ my $dbd_attr = shift || {};
+ croak "dbd_edit_mm_attribs( \%makemaker [, \%other ]): too many parameters"
+ if @_;
+ _inst_checks();
+
+ # what can be done
+ my %test_variants = (
+ p => { name => "DBI::PurePerl",
+ match => qr/^\d/,
+ add => [ '$ENV{DBI_PUREPERL} = 2',
+ 'END { delete $ENV{DBI_PUREPERL}; }' ],
+ },
+ g => { name => "DBD::Gofer",
+ match => qr/^\d/,
+ add => [ q{$ENV{DBI_AUTOPROXY} = 'dbi:Gofer:transport=null;policy=pedantic'},
+ q|END { delete $ENV{DBI_AUTOPROXY}; }| ],
+ },
+ n => { name => "DBI::SQL::Nano",
+ match => qr/^(?:48dbi_dbd_sqlengine|49dbd_file|5\ddbm_\w+|85gofer)\.t$/,
+ add => [ q{$ENV{DBI_SQL_NANO} = 1},
+ q|END { delete $ENV{DBI_SQL_NANO}; }| ],
+ },
+ # mx => { name => "DBD::Multiplex",
+ # add => [ q{local $ENV{DBI_AUTOPROXY} = 'dbi:Multiplex:';} ],
+ # }
+ # px => { name => "DBD::Proxy",
+ # need mechanism for starting/stopping the proxy server
+ # add => [ q{local $ENV{DBI_AUTOPROXY} = 'dbi:Proxy:XXX';} ],
+ # }
+ );
+
+ # decide what needs doing
+ $dbd_attr->{create_pp_tests} or delete $test_variants{p};
+ $dbd_attr->{create_nano_tests} or delete $test_variants{n};
+ $dbd_attr->{create_gap_tests} or delete $test_variants{g};
+
+ # expand for all combinations
+ my @all_keys = my @tv_keys = sort keys %test_variants;
+ while( @tv_keys ) {
+ my $cur_key = shift @tv_keys;
+ last if( 1 < length $cur_key );
+ my @new_keys;
+ foreach my $remain (@tv_keys) {
+ push @new_keys, $cur_key . $remain unless $remain =~ /$cur_key/;
+ }
+ push @tv_keys, @new_keys;
+ push @all_keys, @new_keys;
+ }
+
+ my %uniq_keys;
+ foreach my $key (@all_keys) {
+ @tv_keys = sort split //, $key;
+ my $ordered = join( '', @tv_keys );
+ $uniq_keys{$ordered} = 1;
+ }
+ @all_keys = sort { length $a <=> length $b or $a cmp $b } keys %uniq_keys;
+
+ # do whatever needs doing
+ if( keys %test_variants ) {
+ # XXX need to convert this to work within the generated Makefile
+ # so 'make' creates them and 'make clean' deletes them
+ opendir DIR, 't' or die "Can't read 't' directory: $!";
+ my @tests = grep { /\.t$/ } readdir DIR;
+ closedir DIR;
+
+ foreach my $test_combo (@all_keys) {
+ @tv_keys = split //, $test_combo;
+ my @test_names = map { $test_variants{$_}->{name} } @tv_keys;
+ printf "Creating test wrappers for " . join( " + ", @test_names ) . ":\n";
+ my @test_matches = map { $test_variants{$_}->{match} } @tv_keys;
+ my @test_adds;
+ foreach my $test_add ( map { $test_variants{$_}->{add} } @tv_keys) {
+ push @test_adds, @$test_add;
+ }
+ my $v_type = $test_combo;
+ $v_type = 'x' . $v_type if length( $v_type ) > 1;
+
+ TEST:
+ foreach my $test (sort @tests) {
+ foreach my $match (@test_matches) {
+ next TEST if $test !~ $match;
+ }
+ my $usethr = ($test =~ /(\d+|\b)thr/ && $] >= 5.008 && $Config{useithreads});
+ my $v_test = "t/zv${v_type}_$test";
+ my $v_perl = ($test =~ /taint/) ? "perl -wT" : "perl -w";
+ printf "%s %s\n", $v_test, ($usethr) ? "(use threads)" : "";
+ open PPT, ">$v_test" or warn "Can't create $v_test: $!";
+ print PPT "#!$v_perl\n";
+ print PPT "use threads;\n" if $usethr;
+ print PPT "$_;\n" foreach @test_adds;
+ print PPT "require './t/$test'; # or warn \$!;\n";
+ close PPT or warn "Error writing $v_test: $!";
+ }
+ }
+ }
+ return %$mm_attr;
+}
+
+sub dbd_dbi_dir {
+ _inst_checks();
+ return '.' if $is_dbi;
+ my $dbidir = $INC{'DBI.pm'} || die "DBI.pm not in %INC!";
+ $dbidir =~ s:/DBI\.pm$::;
+ return $dbidir;
+}
+
+sub dbd_dbi_arch_dir {
+ _inst_checks();
+ return '$(INST_ARCHAUTODIR)' if $is_dbi;
+ my $dbidir = dbd_dbi_dir();
+ my %seen;
+ my @try = grep { not $seen{$_}++ } map { vmsify( unixify($_) . "/auto/DBI/" ) } @INC;
+ my @xst = grep { -f vmsify( unixify($_) . "/Driver.xst" ) } @try;
+ Carp::croak("Unable to locate Driver.xst in @try") unless @xst;
+ Carp::carp( "Multiple copies of Driver.xst found in: @xst") if @xst > 1;
+ print "Using DBI $DBI::VERSION (for perl $] on $Config{archname}) installed in $xst[0]\n";
+ return File::Spec->canonpath($xst[0]);
+}
+
+sub dbd_postamble {
+ my $self = shift;
+ _inst_checks();
+ my $dbi_instarch_dir = ($is_dbi) ? "." : dbd_dbi_arch_dir();
+ my $dbi_driver_xst= File::Spec->catfile($dbi_instarch_dir, 'Driver.xst');
+ my $xstf_h = File::Spec->catfile($dbi_instarch_dir, 'Driver_xst.h');
+
+ # we must be careful of quotes, expecially for Win32 here.
+ return '
+# --- This section was generated by DBI::DBD::dbd_postamble()
+DBI_INSTARCH_DIR='.$dbi_instarch_dir.'
+DBI_DRIVER_XST='.$dbi_driver_xst.'
+
+# The main dependancy (technically correct but probably not used)
+$(BASEEXT).c: $(BASEEXT).xsi
+
+# This dependancy is needed since MakeMaker uses the .xs.o rule
+$(BASEEXT)$(OBJ_EXT): $(BASEEXT).xsi
+
+$(BASEEXT).xsi: $(DBI_DRIVER_XST) '.$xstf_h.'
+ $(PERL) -p -e "s/~DRIVER~/$(BASEEXT)/g" $(DBI_DRIVER_XST) > $(BASEEXT).xsi
+
+# ---
+';
+}
+
+package DBDI; # just to reserve it via PAUSE for the future
+
+1;
+
+__END__
+
+=head1 AUTHORS
+
+Jonathan Leffler <jleffler@us.ibm.com> (previously <jleffler@informix.com>),
+Jochen Wiedmann <joe@ispsoft.de>,
+Steffen Goeldner <sgoeldner@cpan.org>,
+and Tim Bunce <dbi-users@perl.org>.
+
+=cut
diff --git a/lib/DBI/DBD/Metadata.pm b/lib/DBI/DBD/Metadata.pm
new file mode 100644
index 0000000..75f5b89
--- /dev/null
+++ b/lib/DBI/DBD/Metadata.pm
@@ -0,0 +1,493 @@
+package DBI::DBD::Metadata;
+
+# $Id: Metadata.pm 14213 2010-06-30 19:29:18Z mjevans $
+#
+# Copyright (c) 1997-2003 Jonathan Leffler, Jochen Wiedmann,
+# Steffen Goeldner and Tim Bunce
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use Exporter ();
+use Carp;
+
+use DBI;
+use DBI::Const::GetInfoType qw(%GetInfoType);
+
+# Perl 5.005_03 does not recognize 'our'
+@ISA = qw(Exporter);
+@EXPORT = qw(write_getinfo_pm write_typeinfo_pm);
+
+$VERSION = sprintf("2.%06d", q$Revision: 14213 $ =~ /(\d+)/o);
+
+
+use strict;
+
+=head1 NAME
+
+DBI::DBD::Metadata - Generate the code and data for some DBI metadata methods
+
+=head1 SYNOPSIS
+
+The idea is to extract metadata information from a good quality
+ODBC driver and use it to generate code and data to use in your own
+DBI driver for the same database.
+
+To generate code to support the get_info method:
+
+ perl -MDBI::DBD::Metadata -e "write_getinfo_pm('dbi:ODBC:dsn-name','user','pass','Driver')"
+
+ perl -MDBI::DBD::Metadata -e write_getinfo_pm dbi:ODBC:foo_db username password Driver
+
+To generate code to support the type_info method:
+
+ perl -MDBI::DBD::Metadata -e "write_typeinfo_pm('dbi:ODBC:dsn-name','user','pass','Driver')"
+
+ perl -MDBI::DBD::Metadata -e write_typeinfo_pm dbi:ODBC:dsn-name user pass Driver
+
+Where C<dbi:ODBC:dsn-name> is the connection to use to extract the
+data, and C<Driver> is the name of the driver you want the code
+generated for (the driver name gets embedded into the output in
+numerous places).
+
+=head1 Generating a GetInfo package for a driver
+
+The C<write_getinfo_pm> in the DBI::DBD::Metadata module generates a
+DBD::Driver::GetInfo package on standard output.
+
+This method generates a DBD::Driver::GetInfo package from the data
+source you specified in the parameter list or in the environment
+variable DBI_DSN.
+DBD::Driver::GetInfo should help a DBD author implement the DBI
+get_info() method.
+Because you are just creating this package, it is very unlikely that
+DBD::Driver already provides a good implementation for get_info().
+Thus you will probably connect via DBD::ODBC.
+
+Once you are sure that it is producing reasonably sane data, you should
+typically redirect the standard output to lib/DBD/Driver/GetInfo.pm, and
+then hand edit the result.
+Do not forget to update your Makefile.PL and MANIFEST to include this as
+an extra PM file that should be installed.
+
+If you connect via DBD::ODBC, you should use version 0.38 or greater;
+
+Please take a critical look at the data returned!
+ODBC drivers vary dramatically in their quality.
+
+The generator assumes that most values are static and places these
+values directly in the %info hash.
+A few examples show the use of CODE references and the implementation
+via subroutines.
+It is very likely that you will have to write additional subroutines for
+values depending on the session state or server version, e.g.
+SQL_DBMS_VER.
+
+A possible implementation of DBD::Driver::db::get_info() may look like:
+
+ sub get_info {
+ my($dbh, $info_type) = @_;
+ require DBD::Driver::GetInfo;
+ my $v = $DBD::Driver::GetInfo::info{int($info_type)};
+ $v = $v->($dbh) if ref $v eq 'CODE';
+ return $v;
+ }
+
+Please replace Driver (or "<foo>") with the name of your driver.
+Note that this stub function is generated for you by write_getinfo_pm
+function, but you must manually transfer the code to Driver.pm.
+
+=cut
+
+sub write_getinfo_pm
+{
+ my ($dsn, $user, $pass, $driver) = @_ ? @_ : @ARGV;
+ my $dbh = DBI->connect($dsn, $user, $pass, {RaiseError=>1});
+ $driver = "<foo>" unless defined $driver;
+
+ print <<PERL;
+
+# Transfer this to ${driver}.pm
+
+# The get_info function was automatically generated by
+# DBI::DBD::Metadata::write_getinfo_pm v$DBI::DBD::Metadata::VERSION.
+
+package DBD::${driver}::db; # This line can be removed once transferred.
+
+ sub get_info {
+ my(\$dbh, \$info_type) = \@_;
+ require DBD::${driver}::GetInfo;
+ my \$v = \$DBD::${driver}::GetInfo::info{int(\$info_type)};
+ \$v = \$v->(\$dbh) if ref \$v eq 'CODE';
+ return \$v;
+ }
+
+# Transfer this to lib/DBD/${driver}/GetInfo.pm
+
+# The \%info hash was automatically generated by
+# DBI::DBD::Metadata::write_getinfo_pm v$DBI::DBD::Metadata::VERSION.
+
+package DBD::${driver}::GetInfo;
+
+use strict;
+use DBD::${driver};
+
+# Beware: not officially documented interfaces...
+# use DBI::Const::GetInfoType qw(\%GetInfoType);
+# use DBI::Const::GetInfoReturn qw(\%GetInfoReturnTypes \%GetInfoReturnValues);
+
+my \$sql_driver = '${driver}';
+my \$sql_ver_fmt = '%02d.%02d.%04d'; # ODBC version string: ##.##.#####
+my \$sql_driver_ver = sprintf \$sql_ver_fmt, split (/\\./, \$DBD::${driver}::VERSION);
+PERL
+
+my $kw_map = 0;
+{
+# Informix CLI (ODBC) v3.81.0000 does not return a list of keywords.
+ local $\ = "\n";
+ local $, = "\n";
+ my ($kw) = $dbh->get_info($GetInfoType{SQL_KEYWORDS});
+ if ($kw)
+ {
+ print "\nmy \@Keywords = qw(\n";
+ print sort split /,/, $kw;
+ print ");\n\n";
+ print "sub sql_keywords {\n";
+ print q% return join ',', @Keywords;%;
+ print "\n}\n\n";
+ $kw_map = 1;
+ }
+}
+
+ print <<'PERL';
+
+sub sql_data_source_name {
+ my $dbh = shift;
+ return "dbi:$sql_driver:" . $dbh->{Name};
+}
+
+sub sql_user_name {
+ my $dbh = shift;
+ # CURRENT_USER is a non-standard attribute, probably undef
+ # Username is a standard DBI attribute
+ return $dbh->{CURRENT_USER} || $dbh->{Username};
+}
+
+PERL
+
+ print "\nour \%info = (\n";
+ foreach my $key (sort keys %GetInfoType)
+ {
+ my $num = $GetInfoType{$key};
+ my $val = eval { $dbh->get_info($num); };
+ if ($key eq 'SQL_DATA_SOURCE_NAME') {
+ $val = '\&sql_data_source_name';
+ }
+ elsif ($key eq 'SQL_KEYWORDS') {
+ $val = ($kw_map) ? '\&sql_keywords' : 'undef';
+ }
+ elsif ($key eq 'SQL_DRIVER_NAME') {
+ $val = "\$INC{'DBD/$driver.pm'}";
+ }
+ elsif ($key eq 'SQL_DRIVER_VER') {
+ $val = '$sql_driver_ver';
+ }
+ elsif ($key eq 'SQL_USER_NAME') {
+ $val = '\&sql_user_name';
+ }
+ elsif (not defined $val) {
+ $val = 'undef';
+ }
+ elsif ($val eq '') {
+ $val = "''";
+ }
+ elsif ($val =~ /\D/) {
+ $val =~ s/\\/\\\\/g;
+ $val =~ s/'/\\'/g;
+ $val = "'$val'";
+ }
+ printf "%s %5d => %-30s # %s\n", (($val eq 'undef') ? '#' : ' '), $num, "$val,", $key;
+ }
+ print ");\n\n1;\n\n__END__\n";
+}
+
+
+
+=head1 Generating a TypeInfo package for a driver
+
+The C<write_typeinfo_pm> function in the DBI::DBD::Metadata module generates
+on standard output the data needed for a driver's type_info_all method.
+It also provides default implementations of the type_info_all
+method for inclusion in the driver's main implementation file.
+
+The driver parameter is the name of the driver for which the methods
+will be generated; for the sake of examples, this will be "Driver".
+Typically, the dsn parameter will be of the form "dbi:ODBC:odbc_dsn",
+where the odbc_dsn is a DSN for one of the driver's databases.
+The user and pass parameters are the other optional connection
+parameters that will be provided to the DBI connect method.
+
+Once you are sure that it is producing reasonably sane data, you should
+typically redirect the standard output to lib/DBD/Driver/TypeInfo.pm,
+and then hand edit the result if necessary.
+Do not forget to update your Makefile.PL and MANIFEST to include this as
+an extra PM file that should be installed.
+
+Please take a critical look at the data returned!
+ODBC drivers vary dramatically in their quality.
+
+The generator assumes that all the values are static and places these
+values directly in the %info hash.
+
+A possible implementation of DBD::Driver::type_info_all() may look like:
+
+ sub type_info_all {
+ my ($dbh) = @_;
+ require DBD::Driver::TypeInfo;
+ return [ @$DBD::Driver::TypeInfo::type_info_all ];
+ }
+
+Please replace Driver (or "<foo>") with the name of your driver.
+Note that this stub function is generated for you by the write_typeinfo_pm
+function, but you must manually transfer the code to Driver.pm.
+
+=cut
+
+
+# These two are used by fmt_value...
+my %dbi_inv;
+my %sql_type_inv;
+
+#-DEBUGGING-#
+#sub print_hash
+#{
+# my ($name, %hash) = @_;
+# print "Hash: $name\n";
+# foreach my $key (keys %hash)
+# {
+# print "$key => $hash{$key}\n";
+# }
+#}
+#-DEBUGGING-#
+
+sub inverse_hash
+{
+ my (%hash) = @_;
+ my (%inv);
+ foreach my $key (keys %hash)
+ {
+ my $val = $hash{$key};
+ die "Double mapping for key value $val ($inv{$val}, $key)!"
+ if (defined $inv{$val});
+ $inv{$val} = $key;
+ }
+ return %inv;
+}
+
+sub fmt_value
+{
+ my ($num, $val) = @_;
+ if (!defined $val)
+ {
+ $val = "undef";
+ }
+ elsif ($val !~ m/^[-+]?\d+$/)
+ {
+ # All the numbers in type_info_all are integers!
+ # Anything that isn't an integer is a string.
+ # Ensure that no double quotes screw things up.
+ $val =~ s/"/\\"/g if ($val =~ m/"/o);
+ $val = qq{"$val"};
+ }
+ elsif ($dbi_inv{$num} =~ m/^(SQL_)?DATA_TYPE$/)
+ {
+ # All numeric...
+ $val = $sql_type_inv{$val}
+ if (defined $sql_type_inv{$val});
+ }
+ return $val;
+}
+
+sub write_typeinfo_pm
+{
+ my ($dsn, $user, $pass, $driver) = @_ ? @_ : @ARGV;
+ my $dbh = DBI->connect($dsn, $user, $pass, {AutoCommit=>1, RaiseError=>1});
+ $driver = "<foo>" unless defined $driver;
+
+ print <<PERL;
+
+# Transfer this to ${driver}.pm
+
+# The type_info_all function was automatically generated by
+# DBI::DBD::Metadata::write_typeinfo_pm v$DBI::DBD::Metadata::VERSION.
+
+package DBD::${driver}::db; # This line can be removed once transferred.
+
+ sub type_info_all
+ {
+ my (\$dbh) = \@_;
+ require DBD::${driver}::TypeInfo;
+ return [ \@\$DBD::${driver}::TypeInfo::type_info_all ];
+ }
+
+# Transfer this to lib/DBD/${driver}/TypeInfo.pm.
+# Don't forget to add version and intellectual property control information.
+
+# The \%type_info_all hash was automatically generated by
+# DBI::DBD::Metadata::write_typeinfo_pm v$DBI::DBD::Metadata::VERSION.
+
+package DBD::${driver}::TypeInfo;
+
+{
+ require Exporter;
+ require DynaLoader;
+ \@ISA = qw(Exporter DynaLoader);
+ \@EXPORT = qw(type_info_all);
+ use DBI qw(:sql_types);
+
+PERL
+
+ # Generate SQL type name mapping hashes.
+ # See code fragment in DBI specification.
+ my %sql_type_map;
+ foreach (@{$DBI::EXPORT_TAGS{sql_types}})
+ {
+ no strict 'refs';
+ $sql_type_map{$_} = &{"DBI::$_"}();
+ $sql_type_inv{$sql_type_map{$_}} = $_;
+ }
+ #-DEBUG-# print_hash("sql_type_map", %sql_type_map);
+ #-DEBUG-# print_hash("sql_type_inv", %sql_type_inv);
+
+ my %dbi_map =
+ (
+ TYPE_NAME => 0,
+ DATA_TYPE => 1,
+ COLUMN_SIZE => 2,
+ LITERAL_PREFIX => 3,
+ LITERAL_SUFFIX => 4,
+ CREATE_PARAMS => 5,
+ NULLABLE => 6,
+ CASE_SENSITIVE => 7,
+ SEARCHABLE => 8,
+ UNSIGNED_ATTRIBUTE => 9,
+ FIXED_PREC_SCALE => 10,
+ AUTO_UNIQUE_VALUE => 11,
+ LOCAL_TYPE_NAME => 12,
+ MINIMUM_SCALE => 13,
+ MAXIMUM_SCALE => 14,
+ SQL_DATA_TYPE => 15,
+ SQL_DATETIME_SUB => 16,
+ NUM_PREC_RADIX => 17,
+ INTERVAL_PRECISION => 18,
+ );
+
+ #-DEBUG-# print_hash("dbi_map", %dbi_map);
+
+ %dbi_inv = inverse_hash(%dbi_map);
+
+ #-DEBUG-# print_hash("dbi_inv", %dbi_inv);
+
+ my $maxlen = 0;
+ foreach my $key (keys %dbi_map)
+ {
+ $maxlen = length($key) if length($key) > $maxlen;
+ }
+
+ # Print the name/value mapping entry in the type_info_all array;
+ my $fmt = " \%-${maxlen}s => \%2d,\n";
+ my $numkey = 0;
+ my $maxkey = 0;
+ print " \$type_info_all = [\n {\n";
+ foreach my $i (sort { $a <=> $b } keys %dbi_inv)
+ {
+ printf($fmt, $dbi_inv{$i}, $i);
+ $numkey++;
+ $maxkey = $i;
+ }
+ print " },\n";
+
+ print STDERR "### WARNING - Non-dense set of keys ($numkey keys, $maxkey max key)\n"
+ unless $numkey = $maxkey + 1;
+
+ my $h = $dbh->type_info_all;
+ my @tia = @$h;
+ my %odbc_map = map { uc $_ => $tia[0]->{$_} } keys %{$tia[0]};
+ shift @tia; # Remove the mapping reference.
+ my $numtyp = $#tia;
+
+ #-DEBUG-# print_hash("odbc_map", %odbc_map);
+
+ # In theory, the key/number mapping sequence for %dbi_map
+ # should be the same as the one from the ODBC driver. However, to
+ # prevent the possibility of mismatches, and to deal with older
+ # missing attributes or unexpected new ones, we chase back through
+ # the %dbi_inv and %odbc_map hashes, generating @dbi_to_odbc
+ # to map our new key number to the old one.
+ # Report if @dbi_to_odbc is not an identity mapping.
+ my @dbi_to_odbc;
+ foreach my $num (sort { $a <=> $b } keys %dbi_inv)
+ {
+ # Find the name in %dbi_inv that matches this index number.
+ my $dbi_key = $dbi_inv{$num};
+ #-DEBUG-# print "dbi_key = $dbi_key\n";
+ #-DEBUG-# print "odbc_key = $odbc_map{$dbi_key}\n";
+ # Find the index in %odbc_map that has this key.
+ $dbi_to_odbc[$num] = (defined $odbc_map{$dbi_key}) ? $odbc_map{$dbi_key} : undef;
+ }
+
+ # Determine the length of the longest formatted value in each field
+ my @len;
+ for (my $i = 0; $i <= $numtyp; $i++)
+ {
+ my @odbc_val = @{$tia[$i]};
+ for (my $num = 0; $num <= $maxkey; $num++)
+ {
+ # Find the value of the entry in the @odbc_val array.
+ my $val = (defined $dbi_to_odbc[$num]) ? $odbc_val[$dbi_to_odbc[$num]] : undef;
+ $val = fmt_value($num, $val);
+ #-DEBUG-# print "val = $val\n";
+ $val = "$val,";
+ $len[$num] = length($val) if !defined $len[$num] || length($val) > $len[$num];
+ }
+ }
+
+ # Generate format strings to left justify each string in maximum field width.
+ my @fmt;
+ for (my $i = 0; $i <= $maxkey; $i++)
+ {
+ $fmt[$i] = "%-$len[$i]s";
+ #-DEBUG-# print "fmt[$i] = $fmt[$i]\n";
+ }
+
+ # Format the data from type_info_all
+ for (my $i = 0; $i <= $numtyp; $i++)
+ {
+ my @odbc_val = @{$tia[$i]};
+ print " [ ";
+ for (my $num = 0; $num <= $maxkey; $num++)
+ {
+ # Find the value of the entry in the @odbc_val array.
+ my $val = (defined $dbi_to_odbc[$num]) ? $odbc_val[$dbi_to_odbc[$num]] : undef;
+ $val = fmt_value($num, $val);
+ printf $fmt[$num], "$val,";
+ }
+ print " ],\n";
+ }
+
+ print " ];\n\n 1;\n}\n\n__END__\n";
+
+}
+
+1;
+
+__END__
+
+=head1 AUTHORS
+
+Jonathan Leffler <jleffler@us.ibm.com> (previously <jleffler@informix.com>),
+Jochen Wiedmann <joe@ispsoft.de>,
+Steffen Goeldner <sgoeldner@cpan.org>,
+and Tim Bunce <dbi-users@perl.org>.
+
+=cut
diff --git a/lib/DBI/DBD/SqlEngine.pm b/lib/DBI/DBD/SqlEngine.pm
new file mode 100644
index 0000000..ae5c115
--- /dev/null
+++ b/lib/DBI/DBD/SqlEngine.pm
@@ -0,0 +1,1232 @@
+# -*- perl -*-
+#
+# DBI::DBD::SqlEngine - A base class for implementing DBI drivers that
+# have not an own SQL engine
+#
+# This module is currently maintained by
+#
+# H.Merijn Brand & Jens Rehsack
+#
+# The original author is Jochen Wiedmann.
+#
+# Copyright (C) 2009,2010 by H.Merijn Brand & Jens Rehsack
+# Copyright (C) 2004 by Jeff Zucker
+# Copyright (C) 1998 by Jochen Wiedmann
+#
+# All rights reserved.
+#
+# You may distribute this module under the terms of either the GNU
+# General Public License or the Artistic License, as specified in
+# the Perl README file.
+
+require 5.008;
+
+use strict;
+
+use DBI ();
+require DBI::SQL::Nano;
+
+package DBI::DBD::SqlEngine;
+
+use strict;
+
+use Carp;
+use vars qw( @ISA $VERSION $drh %methods_installed);
+
+$VERSION = "0.03";
+
+$drh = undef; # holds driver handle(s) once initialized
+
+DBI->setup_driver("DBI::DBD::SqlEngine"); # only needed once but harmless to repeat
+
+my %accessors = ( versions => "get_driver_versions", );
+
+sub driver ($;$)
+{
+ my ( $class, $attr ) = @_;
+
+ # Drivers typically use a singleton object for the $drh
+ # We use a hash here to have one singleton per subclass.
+ # (Otherwise DBD::CSV and DBD::DBM, for example, would
+ # share the same driver object which would cause problems.)
+ # An alternative would be not not cache the $drh here at all
+ # and require that subclasses do that. Subclasses should do
+ # their own caching, so caching here just provides extra safety.
+ $drh->{$class} and return $drh->{$class};
+
+ $attr ||= {};
+ {
+ no strict "refs";
+ unless ( $attr->{Attribution} )
+ {
+ $class eq "DBI::DBD::SqlEngine"
+ and $attr->{Attribution} = "$class by Jens Rehsack";
+ $attr->{Attribution} ||= ${ $class . "::ATTRIBUTION" }
+ || "oops the author of $class forgot to define this";
+ }
+ $attr->{Version} ||= ${ $class . "::VERSION" };
+ $attr->{Name} or ( $attr->{Name} = $class ) =~ s/^DBD\:\://;
+ }
+
+ $drh->{$class} = DBI::_new_drh( $class . "::dr", $attr );
+ $drh->{$class}->STORE( ShowErrorStatement => 1 );
+
+ my $prefix = DBI->driver_prefix($class);
+ if ($prefix)
+ {
+ my $dbclass = $class . "::db";
+ while ( my ( $accessor, $funcname ) = each %accessors )
+ {
+ my $method = $prefix . $accessor;
+ $dbclass->can($method) and next;
+ my $inject = sprintf <<'EOI', $dbclass, $method, $dbclass, $funcname;
+sub %s::%s
+{
+ my $func = %s->can (q{%s});
+ goto &$func;
+ }
+EOI
+ eval $inject;
+ $dbclass->install_method($method);
+ }
+ }
+
+ # XXX inject DBD::XXX::Statement unless exists
+
+ my $stclass = $class . "::st";
+ $stclass->install_method("sql_get_colnames") unless ( $methods_installed{$class}++ );
+
+ return $drh->{$class};
+} # driver
+
+sub CLONE
+{
+ undef $drh;
+} # CLONE
+
+# ====== DRIVER ================================================================
+
+package DBI::DBD::SqlEngine::dr;
+
+use strict;
+use warnings;
+
+use vars qw(@ISA $imp_data_size);
+
+$imp_data_size = 0;
+
+sub connect ($$;$$$)
+{
+ my ( $drh, $dbname, $user, $auth, $attr ) = @_;
+
+ # create a 'blank' dbh
+ my $dbh = DBI::_new_dbh(
+ $drh,
+ {
+ Name => $dbname,
+ USER => $user,
+ CURRENT_USER => $user,
+ }
+ );
+
+ if ($dbh)
+ {
+ # must be done first, because setting flags implicitly calls $dbdname::db->STORE
+ $dbh->func( 0, "init_default_attributes" );
+ my $two_phased_init;
+ defined $dbh->{sql_init_phase} and $two_phased_init = ++$dbh->{sql_init_phase};
+ my %second_phase_attrs;
+
+ my ( $var, $val );
+ while ( length $dbname )
+ {
+ if ( $dbname =~ s/^((?:[^\\;]|\\.)*?);//s )
+ {
+ $var = $1;
+ }
+ else
+ {
+ $var = $dbname;
+ $dbname = "";
+ }
+ if ( $var =~ m/^(.+?)=(.*)/s )
+ {
+ $var = $1;
+ ( $val = $2 ) =~ s/\\(.)/$1/g;
+ if ($two_phased_init)
+ {
+ eval { $dbh->STORE( $var, $val ); };
+ $@ and $second_phase_attrs{$var} = $val;
+ }
+ else
+ {
+ $dbh->STORE( $var, $val );
+ }
+ }
+ elsif ( $var =~ m/^(.+?)=>(.*)/s )
+ {
+ $var = $1;
+ ( $val = $2 ) =~ s/\\(.)/$1/g;
+ my $ref = eval $val;
+ $dbh->$var($ref);
+ }
+ }
+
+ if ($two_phased_init)
+ {
+ foreach $a (qw(Profile RaiseError PrintError AutoCommit))
+ { # do these first
+ exists $attr->{$a} or next;
+ eval {
+ $dbh->{$a} = $attr->{$a};
+ delete $attr->{$a};
+ };
+ $@ and $second_phase_attrs{$a} = delete $attr->{$a};
+ }
+ while ( my ( $a, $v ) = each %$attr )
+ {
+ eval { $dbh->{$a} = $v };
+ $@ and $second_phase_attrs{$a} = $v;
+ }
+
+ $dbh->func( 1, "init_default_attributes" );
+ %$attr = %second_phase_attrs;
+ }
+
+ $dbh->func("init_done");
+
+ $dbh->STORE( Active => 1 );
+ }
+
+ return $dbh;
+} # connect
+
+sub disconnect_all
+{
+} # disconnect_all
+
+sub DESTROY
+{
+ undef;
+} # DESTROY
+
+# ====== DATABASE ==============================================================
+
+package DBI::DBD::SqlEngine::db;
+
+use strict;
+use warnings;
+
+use vars qw(@ISA $imp_data_size);
+
+use Carp;
+
+if ( eval { require Clone; } )
+{
+ Clone->import("clone");
+}
+else
+{
+ require Storable; # in CORE since 5.7.3
+ *clone = \&Storable::dclone;
+}
+
+$imp_data_size = 0;
+
+sub ping
+{
+ ( $_[0]->FETCH("Active") ) ? 1 : 0;
+} # ping
+
+sub prepare ($$;@)
+{
+ my ( $dbh, $statement, @attribs ) = @_;
+
+ # create a 'blank' sth
+ my $sth = DBI::_new_sth( $dbh, { Statement => $statement } );
+
+ if ($sth)
+ {
+ my $class = $sth->FETCH("ImplementorClass");
+ $class =~ s/::st$/::Statement/;
+ my $stmt;
+
+ # if using SQL::Statement version > 1
+ # cache the parser object if the DBD supports parser caching
+ # SQL::Nano and older SQL::Statements don't support this
+
+ if ( $class->isa("SQL::Statement") )
+ {
+ my $parser = $dbh->{sql_parser_object};
+ $parser ||= eval { $dbh->func("sql_parser_object") };
+ if ($@)
+ {
+ $stmt = eval { $class->new($statement) };
+ }
+ else
+ {
+ $stmt = eval { $class->new( $statement, $parser ) };
+ }
+ }
+ else
+ {
+ $stmt = eval { $class->new($statement) };
+ }
+ if ($@ || $stmt->{errstr})
+ {
+ $dbh->set_err( $DBI::stderr, $@ || $stmt->{errstr} );
+ undef $sth;
+ }
+ else
+ {
+ $sth->STORE( "sql_stmt", $stmt );
+ $sth->STORE( "sql_params", [] );
+ $sth->STORE( "NUM_OF_PARAMS", scalar( $stmt->params() ) );
+ my @colnames = $sth->sql_get_colnames();
+ $sth->STORE( "NUM_OF_FIELDS", scalar @colnames );
+ }
+ }
+ return $sth;
+} # prepare
+
+sub set_versions
+{
+ my $dbh = $_[0];
+ $dbh->{sql_engine_version} = $DBI::DBD::SqlEngine::VERSION;
+ for (qw( nano_version statement_version ))
+ {
+ defined $DBI::SQL::Nano::versions->{$_} or next;
+ $dbh->{"sql_$_"} = $DBI::SQL::Nano::versions->{$_};
+ }
+ $dbh->{sql_handler} =
+ $dbh->{sql_statement_version}
+ ? "SQL::Statement"
+ : "DBI::SQL::Nano";
+
+ return $dbh;
+} # set_versions
+
+sub init_valid_attributes
+{
+ my $dbh = $_[0];
+
+ $dbh->{sql_valid_attrs} = {
+ sql_engine_version => 1, # DBI::DBD::SqlEngine version
+ sql_handler => 1, # Nano or S:S
+ sql_nano_version => 1, # Nano version
+ sql_statement_version => 1, # S:S version
+ sql_flags => 1, # flags for SQL::Parser
+ sql_dialect => 1, # dialect for SQL::Parser
+ sql_quoted_identifier_case => 1, # case for quoted identifiers
+ sql_identifier_case => 1, # case for non-quoted identifiers
+ sql_parser_object => 1, # SQL::Parser instance
+ sql_sponge_driver => 1, # Sponge driver for table_info ()
+ sql_valid_attrs => 1, # SQL valid attributes
+ sql_readonly_attrs => 1, # SQL readonly attributes
+ sql_init_phase => 1, # Only during initialization
+ };
+ $dbh->{sql_readonly_attrs} = {
+ sql_engine_version => 1, # DBI::DBD::SqlEngine version
+ sql_handler => 1, # Nano or S:S
+ sql_nano_version => 1, # Nano version
+ sql_statement_version => 1, # S:S version
+ sql_quoted_identifier_case => 1, # case for quoted identifiers
+ sql_parser_object => 1, # SQL::Parser instance
+ sql_sponge_driver => 1, # Sponge driver for table_info ()
+ sql_valid_attrs => 1, # SQL valid attributes
+ sql_readonly_attrs => 1, # SQL readonly attributes
+ };
+
+ return $dbh;
+} # init_valid_attributes
+
+sub init_default_attributes
+{
+ my ( $dbh, $phase ) = @_;
+ my $given_phase = $phase;
+
+ unless ( defined($phase) )
+ {
+ # we have an "old" driver here
+ $phase = defined $dbh->{sql_init_phase};
+ $phase and $phase = $dbh->{sql_init_phase};
+ }
+
+ if ( 0 == $phase )
+ {
+ # must be done first, because setting flags implicitly calls $dbdname::db->STORE
+ $dbh->func("init_valid_attributes");
+
+ $dbh->func("set_versions");
+
+ $dbh->{sql_identifier_case} = 2; # SQL_IC_LOWER
+ $dbh->{sql_quoted_identifier_case} = 3; # SQL_IC_SENSITIVE
+
+ $dbh->{sql_dialect} = "CSV";
+
+ $dbh->{sql_init_phase} = $given_phase;
+
+ # complete derived attributes, if required
+ ( my $drv_class = $dbh->{ImplementorClass} ) =~ s/::db$//;
+ my $drv_prefix = DBI->driver_prefix($drv_class);
+ my $valid_attrs = $drv_prefix . "valid_attrs";
+ my $ro_attrs = $drv_prefix . "readonly_attrs";
+
+ my @comp_attrs = qw(valid_attrs version readonly_attrs);
+
+ foreach my $comp_attr (@comp_attrs)
+ {
+ my $attr = $drv_prefix . $comp_attr;
+ defined $dbh->{$valid_attrs}
+ and !defined $dbh->{$valid_attrs}{$attr}
+ and $dbh->{$valid_attrs}{$attr} = 1;
+ defined $dbh->{$ro_attrs}
+ and !defined $dbh->{$ro_attrs}{$attr}
+ and $dbh->{$ro_attrs}{$attr} = 1;
+ }
+ }
+
+ return $dbh;
+} # init_default_attributes
+
+sub init_done
+{
+ defined $_[0]->{sql_init_phase} and delete $_[0]->{sql_init_phase};
+ delete $_[0]->{sql_valid_attrs}->{sql_init_phase};
+ return;
+}
+
+sub sql_parser_object
+{
+ my $dbh = $_[0];
+ my $dialect = $dbh->{sql_dialect} || "CSV";
+ my $parser = {
+ RaiseError => $dbh->FETCH("RaiseError"),
+ PrintError => $dbh->FETCH("PrintError"),
+ };
+ my $sql_flags = $dbh->FETCH("sql_flags") || {};
+ %$parser = ( %$parser, %$sql_flags );
+ $parser = SQL::Parser->new( $dialect, $parser );
+ $dbh->{sql_parser_object} = $parser;
+ return $parser;
+} # sql_parser_object
+
+sub sql_sponge_driver
+{
+ my $dbh = $_[0];
+ my $dbh2 = $dbh->{sql_sponge_driver};
+ unless ($dbh2)
+ {
+ $dbh2 = $dbh->{sql_sponge_driver} = DBI->connect("DBI:Sponge:");
+ unless ($dbh2)
+ {
+ $dbh->set_err( $DBI::stderr, $DBI::errstr );
+ return;
+ }
+ }
+}
+
+sub disconnect ($)
+{
+ $_[0]->STORE( Active => 0 );
+ return 1;
+} # disconnect
+
+sub validate_FETCH_attr
+{
+ my ( $dbh, $attrib ) = @_;
+
+ return $attrib;
+}
+
+sub FETCH ($$)
+{
+ my ( $dbh, $attrib ) = @_;
+ $attrib eq "AutoCommit"
+ and return 1;
+
+ # Driver private attributes are lower cased
+ if ( $attrib eq ( lc $attrib ) )
+ {
+ # first let the implementation deliver an alias for the attribute to fetch
+ # after it validates the legitimation of the fetch request
+ $attrib = $dbh->func( $attrib, "validate_FETCH_attr" ) or return;
+
+ my $attr_prefix;
+ $attrib =~ m/^([a-z]+_)/ and $attr_prefix = $1;
+ unless ($attr_prefix)
+ {
+ ( my $drv_class = $dbh->{ImplementorClass} ) =~ s/::db$//;
+ $attr_prefix = DBI->driver_prefix($drv_class);
+ $attrib = $attr_prefix . $attrib;
+ }
+ my $valid_attrs = $attr_prefix . "valid_attrs";
+ my $ro_attrs = $attr_prefix . "readonly_attrs";
+
+ exists $dbh->{$valid_attrs}
+ and ( $dbh->{$valid_attrs}{$attrib}
+ or return $dbh->set_err( $DBI::stderr, "Invalid attribute '$attrib'" ) );
+ exists $dbh->{$ro_attrs}
+ and $dbh->{$ro_attrs}{$attrib}
+ and defined $dbh->{$attrib}
+ and refaddr( $dbh->{$attrib} )
+ and return clone( $dbh->{$attrib} );
+
+ return $dbh->{$attrib};
+ }
+ # else pass up to DBI to handle
+ return $dbh->SUPER::FETCH($attrib);
+} # FETCH
+
+sub validate_STORE_attr
+{
+ my ( $dbh, $attrib, $value ) = @_;
+
+ if ( $attrib eq "sql_identifier_case" || $attrib eq "sql_quoted_identifier_case"
+ and $value < 1 || $value > 4 )
+ {
+ croak "attribute '$attrib' must have a value from 1 .. 4 (SQL_IC_UPPER .. SQL_IC_MIXED)";
+ # XXX correctly a remap of all entries in f_meta/f_meta_map is required here
+ }
+
+ return ( $attrib, $value );
+}
+
+# the ::db::STORE method is what gets called when you set
+# a lower-cased database handle attribute such as $dbh->{somekey}=$someval;
+#
+# STORE should check to make sure that "somekey" is a valid attribute name
+# but only if it is really one of our attributes (starts with dbm_ or foo_)
+# You can also check for valid values for the attributes if needed
+# and/or perform other operations
+#
+sub STORE ($$$)
+{
+ my ( $dbh, $attrib, $value ) = @_;
+
+ if ( $attrib eq "AutoCommit" )
+ {
+ $value and return 1; # is already set
+ croak "Can't disable AutoCommit";
+ }
+
+ if ( $attrib eq lc $attrib )
+ {
+ # Driver private attributes are lower cased
+
+ my $attr_prefix;
+ $attrib =~ m/^([a-z]+_)/ and $attr_prefix = $1;
+ unless ($attr_prefix)
+ {
+ ( my $drv_class = $dbh->{ImplementorClass} ) =~ s/::db$//;
+ $attr_prefix = DBI->driver_prefix($drv_class);
+ $attrib = $attr_prefix . $attrib;
+ }
+ my $valid_attrs = $attr_prefix . "valid_attrs";
+ my $ro_attrs = $attr_prefix . "readonly_attrs";
+
+ ( $attrib, $value ) = $dbh->func( $attrib, $value, "validate_STORE_attr" );
+ $attrib or return;
+
+ exists $dbh->{$valid_attrs}
+ and ( $dbh->{$valid_attrs}{$attrib}
+ or return $dbh->set_err( $DBI::stderr, "Invalid attribute '$attrib'" ) );
+ exists $dbh->{$ro_attrs}
+ and $dbh->{$ro_attrs}{$attrib}
+ and defined $dbh->{$attrib}
+ and return $dbh->set_err( $DBI::stderr,
+ "attribute '$attrib' is readonly and must not be modified" );
+
+ $dbh->{$attrib} = $value;
+ return 1;
+ }
+
+ return $dbh->SUPER::STORE( $attrib, $value );
+} # STORE
+
+sub get_driver_versions
+{
+ my ( $dbh, $table ) = @_;
+ my %vsn = (
+ OS => "$^O ($Config::Config{osvers})",
+ Perl => "$] ($Config::Config{archname})",
+ DBI => $DBI::VERSION,
+ );
+ my %vmp;
+
+ my $sql_engine_verinfo =
+ join " ",
+ $dbh->{sql_engine_version}, "using", $dbh->{sql_handler},
+ $dbh->{sql_handler} eq "SQL::Statement"
+ ? $dbh->{sql_statement_version}
+ : $dbh->{sql_nano_version};
+
+ my $indent = 0;
+ my @deriveds = ( $dbh->{ImplementorClass} );
+ while (@deriveds)
+ {
+ my $derived = shift @deriveds;
+ $derived eq "DBI::DBD::SqlEngine::db" and last;
+ $derived->isa("DBI::DBD::SqlEngine::db") or next;
+ #no strict 'refs';
+ eval "push \@deriveds, \@${derived}::ISA";
+ #use strict;
+ ( my $drv_class = $derived ) =~ s/::db$//;
+ my $drv_prefix = DBI->driver_prefix($drv_class);
+ my $ddgv = $dbh->{ImplementorClass}->can("get_${drv_prefix}versions");
+ my $drv_version = $ddgv ? &$ddgv( $dbh, $table ) : $dbh->{ $drv_prefix . "version" };
+ $drv_version ||= eval { $derived->VERSION() }; # XXX access $drv_class::VERSION via symbol table
+ $vsn{$drv_class} = $drv_version;
+ $indent and $vmp{$drv_class} = " " x $indent . $drv_class;
+ $indent += 2;
+ }
+
+ $vsn{"DBI::DBD::SqlEngine"} = $sql_engine_verinfo;
+ $indent and $vmp{"DBI::DBD::SqlEngine"} = " " x $indent . "DBI::DBD::SqlEngine";
+
+ $DBI::PurePerl and $vsn{"DBI::PurePerl"} = $DBI::PurePerl::VERSION;
+
+ $indent += 20;
+ my @versions = map { sprintf "%-${indent}s %s", $vmp{$_} || $_, $vsn{$_} }
+ sort {
+ $a->isa($b) and return -1;
+ $b->isa($a) and return 1;
+ $a->isa("DBI::DBD::SqlEngine") and return -1;
+ $b->isa("DBI::DBD::SqlEngine") and return 1;
+ return $a cmp $b;
+ } keys %vsn;
+
+ return wantarray ? @versions : join "\n", @versions;
+} # get_versions
+
+sub DESTROY ($)
+{
+ my $dbh = shift;
+ $dbh->SUPER::FETCH("Active") and $dbh->disconnect;
+ undef $dbh->{sql_parser_object};
+} # DESTROY
+
+sub type_info_all ($)
+{
+ [
+ {
+ TYPE_NAME => 0,
+ DATA_TYPE => 1,
+ PRECISION => 2,
+ LITERAL_PREFIX => 3,
+ LITERAL_SUFFIX => 4,
+ CREATE_PARAMS => 5,
+ NULLABLE => 6,
+ CASE_SENSITIVE => 7,
+ SEARCHABLE => 8,
+ UNSIGNED_ATTRIBUTE => 9,
+ MONEY => 10,
+ AUTO_INCREMENT => 11,
+ LOCAL_TYPE_NAME => 12,
+ MINIMUM_SCALE => 13,
+ MAXIMUM_SCALE => 14,
+ },
+ [ "VARCHAR", DBI::SQL_VARCHAR(), undef, "'", "'", undef, 0, 1, 1, 0, 0, 0, undef, 1, 999999, ],
+ [ "CHAR", DBI::SQL_CHAR(), undef, "'", "'", undef, 0, 1, 1, 0, 0, 0, undef, 1, 999999, ],
+ [ "INTEGER", DBI::SQL_INTEGER(), undef, "", "", undef, 0, 0, 1, 0, 0, 0, undef, 0, 0, ],
+ [ "REAL", DBI::SQL_REAL(), undef, "", "", undef, 0, 0, 1, 0, 0, 0, undef, 0, 0, ],
+ [ "BLOB", DBI::SQL_LONGVARBINARY(), undef, "'", "'", undef, 0, 1, 1, 0, 0, 0, undef, 1, 999999, ],
+ [ "BLOB", DBI::SQL_LONGVARBINARY(), undef, "'", "'", undef, 0, 1, 1, 0, 0, 0, undef, 1, 999999, ],
+ [ "TEXT", DBI::SQL_LONGVARCHAR(), undef, "'", "'", undef, 0, 1, 1, 0, 0, 0, undef, 1, 999999, ],
+ ];
+} # type_info_all
+
+sub get_avail_tables
+{
+ my $dbh = $_[0];
+ my @tables = ();
+
+ if ( $dbh->{sql_handler} eq "SQL::Statement" and $dbh->{sql_ram_tables} )
+ {
+ foreach my $table ( keys %{ $dbh->{sql_ram_tables} } )
+ {
+ push @tables, [ undef, undef, $table, "TABLE", "TEMP" ];
+ }
+ }
+
+ return @tables;
+} # get_avail_tables
+
+{
+ my $names = [qw( TABLE_QUALIFIER TABLE_OWNER TABLE_NAME TABLE_TYPE REMARKS )];
+
+ sub table_info ($)
+ {
+ my $dbh = shift;
+
+ my @tables = $dbh->func("get_avail_tables");
+
+ # Temporary kludge: DBD::Sponge dies if @tables is empty. :-(
+ @tables or return;
+
+ my $dbh2 = $dbh->func("sql_sponge_driver");
+ my $sth = $dbh2->prepare(
+ "TABLE_INFO",
+ {
+ rows => \@tables,
+ NAMES => $names,
+ }
+ );
+ $sth or $dbh->set_err( $DBI::stderr, $dbh2->errstr );
+ return $sth;
+ } # table_info
+}
+
+sub list_tables ($)
+{
+ my $dbh = shift;
+ my @table_list;
+
+ my @tables = $dbh->func("get_avail_tables") or return;
+ foreach my $ref (@tables)
+ {
+ # rt69260 and rt67223 - the same issue in 2 different queues
+ push @table_list, $ref->[2];
+ }
+
+ return @table_list;
+} # list_tables
+
+sub quote ($$;$)
+{
+ my ( $self, $str, $type ) = @_;
+ defined $str or return "NULL";
+ defined $type && ( $type == DBI::SQL_NUMERIC()
+ || $type == DBI::SQL_DECIMAL()
+ || $type == DBI::SQL_INTEGER()
+ || $type == DBI::SQL_SMALLINT()
+ || $type == DBI::SQL_FLOAT()
+ || $type == DBI::SQL_REAL()
+ || $type == DBI::SQL_DOUBLE()
+ || $type == DBI::SQL_TINYINT() )
+ and return $str;
+
+ $str =~ s/\\/\\\\/sg;
+ $str =~ s/\0/\\0/sg;
+ $str =~ s/\'/\\\'/sg;
+ $str =~ s/\n/\\n/sg;
+ $str =~ s/\r/\\r/sg;
+ return "'$str'";
+} # quote
+
+sub commit ($)
+{
+ my $dbh = shift;
+ $dbh->FETCH("Warn")
+ and carp "Commit ineffective while AutoCommit is on", -1;
+ return 1;
+} # commit
+
+sub rollback ($)
+{
+ my $dbh = shift;
+ $dbh->FETCH("Warn")
+ and carp "Rollback ineffective while AutoCommit is on", -1;
+ return 0;
+} # rollback
+
+# ====== STATEMENT =============================================================
+
+package DBI::DBD::SqlEngine::st;
+
+use strict;
+use warnings;
+
+use vars qw(@ISA $imp_data_size);
+
+$imp_data_size = 0;
+
+sub bind_param ($$$;$)
+{
+ my ( $sth, $pNum, $val, $attr ) = @_;
+ if ( $attr && defined $val )
+ {
+ my $type = ref $attr eq "HASH" ? $attr->{TYPE} : $attr;
+ if ( $type == DBI::SQL_BIGINT()
+ || $type == DBI::SQL_INTEGER()
+ || $type == DBI::SQL_SMALLINT()
+ || $type == DBI::SQL_TINYINT() )
+ {
+ $val += 0;
+ }
+ elsif ( $type == DBI::SQL_DECIMAL()
+ || $type == DBI::SQL_DOUBLE()
+ || $type == DBI::SQL_FLOAT()
+ || $type == DBI::SQL_NUMERIC()
+ || $type == DBI::SQL_REAL() )
+ {
+ $val += 0.;
+ }
+ else
+ {
+ $val = "$val";
+ }
+ }
+ $sth->{sql_params}[ $pNum - 1 ] = $val;
+ return 1;
+} # bind_param
+
+sub execute
+{
+ my $sth = shift;
+ my $params = @_ ? ( $sth->{sql_params} = [@_] ) : $sth->{sql_params};
+
+ $sth->finish;
+ my $stmt = $sth->{sql_stmt};
+ unless ( $sth->{sql_params_checked}++ )
+ {
+ # bug in SQL::Statement 1.20 and below causes breakage
+ # on all but the first call
+ unless ( ( my $req_prm = $stmt->params() ) == ( my $nparm = @$params ) )
+ {
+ my $msg = "You passed $nparm parameters where $req_prm required";
+ $sth->set_err( $DBI::stderr, $msg );
+ return;
+ }
+ }
+ my @err;
+ my $result;
+ eval {
+ local $SIG{__WARN__} = sub { push @err, @_ };
+ $result = $stmt->execute( $sth, $params );
+ };
+ unless ( defined $result )
+ {
+ $sth->set_err( $DBI::stderr, $@ || $stmt->{errstr} || $err[0] );
+ return;
+ }
+
+ if ( $stmt->{NUM_OF_FIELDS} )
+ { # is a SELECT statement
+ $sth->STORE( Active => 1 );
+ $sth->FETCH("NUM_OF_FIELDS")
+ or $sth->STORE( "NUM_OF_FIELDS", $stmt->{NUM_OF_FIELDS} );
+ }
+ return $result;
+} # execute
+
+sub finish
+{
+ my $sth = $_[0];
+ $sth->SUPER::STORE( Active => 0 );
+ delete $sth->{sql_stmt}{data};
+ return 1;
+} # finish
+
+sub fetch ($)
+{
+ my $sth = $_[0];
+ my $data = $sth->{sql_stmt}{data};
+ if ( !$data || ref $data ne "ARRAY" )
+ {
+ $sth->set_err(
+ $DBI::stderr,
+ "Attempt to fetch row without a preceeding execute () call or from a non-SELECT statement"
+ );
+ return;
+ }
+ my $dav = shift @$data;
+ unless ($dav)
+ {
+ $sth->finish;
+ return;
+ }
+ if ( $sth->FETCH("ChopBlanks") ) # XXX: (TODO) Only chop on CHAR fields,
+ { # not on VARCHAR or NUMERIC (see DBI docs)
+ $_ && $_ =~ s/ +$// for @$dav;
+ }
+ return $sth->_set_fbav($dav);
+} # fetch
+
+no warnings 'once';
+*fetchrow_arrayref = \&fetch;
+
+use warnings;
+
+sub sql_get_colnames
+{
+ my $sth = $_[0];
+ # Being a bit dirty here, as neither SQL::Statement::Structure nor
+ # DBI::SQL::Nano::Statement_ does not offer an interface to the
+ # required data
+ my @colnames;
+ if ( $sth->{sql_stmt}->{NAME} and "ARRAY" eq ref( $sth->{sql_stmt}->{NAME} ) )
+ {
+ @colnames = @{ $sth->{sql_stmt}->{NAME} };
+ }
+ elsif ( $sth->{sql_stmt}->isa('SQL::Statement') )
+ {
+ my $stmt = $sth->{sql_stmt} || {};
+ my @coldefs = @{ $stmt->{column_defs} || [] };
+ @colnames = map { $_->{name} || $_->{value} } @coldefs;
+ }
+ @colnames = $sth->{sql_stmt}->column_names() unless (@colnames);
+
+ @colnames = () if ( grep { m/\*/ } @colnames );
+
+ return @colnames;
+}
+
+sub FETCH ($$)
+{
+ my ( $sth, $attrib ) = @_;
+
+ $attrib eq "NAME" and return [ $sth->sql_get_colnames() ];
+
+ $attrib eq "TYPE" and return [ (DBI::SQL_VARCHAR()) x scalar $sth->sql_get_colnames() ];
+ $attrib eq "TYPE_NAME" and return [ ("VARCHAR") x scalar $sth->sql_get_colnames() ];
+ $attrib eq "PRECISION" and return [ (0) x scalar $sth->sql_get_colnames() ];
+ $attrib eq "NULLABLE" and return [ (1) x scalar $sth->sql_get_colnames() ];
+
+ if ( $attrib eq lc $attrib )
+ {
+ # Private driver attributes are lower cased
+ return $sth->{$attrib};
+ }
+
+ # else pass up to DBI to handle
+ return $sth->SUPER::FETCH($attrib);
+} # FETCH
+
+sub STORE ($$$)
+{
+ my ( $sth, $attrib, $value ) = @_;
+ if ( $attrib eq lc $attrib ) # Private driver attributes are lower cased
+ {
+ $sth->{$attrib} = $value;
+ return 1;
+ }
+ return $sth->SUPER::STORE( $attrib, $value );
+} # STORE
+
+sub DESTROY ($)
+{
+ my $sth = shift;
+ $sth->SUPER::FETCH("Active") and $sth->finish;
+ undef $sth->{sql_stmt};
+ undef $sth->{sql_params};
+} # DESTROY
+
+sub rows ($)
+{
+ return $_[0]->{sql_stmt}{NUM_OF_ROWS};
+} # rows
+
+# ====== SQL::STATEMENT ========================================================
+
+package DBI::DBD::SqlEngine::Statement;
+
+use strict;
+use warnings;
+
+use Carp;
+
+@DBI::DBD::SqlEngine::Statement::ISA = qw(DBI::SQL::Nano::Statement);
+
+# ====== SQL::TABLE ============================================================
+
+package DBI::DBD::SqlEngine::Table;
+
+use strict;
+use warnings;
+
+@DBI::DBD::SqlEngine::Table::ISA = qw(DBI::SQL::Nano::Table);
+
+=pod
+
+=head1 NAME
+
+DBI::DBD::SqlEngine - Base class for DBI drivers without their own SQL engine
+
+=head1 SYNOPSIS
+
+ package DBD::myDriver;
+
+ use base qw(DBI::DBD::SqlEngine);
+
+ sub driver
+ {
+ ...
+ my $drh = $proto->SUPER::driver($attr);
+ ...
+ return $drh->{class};
+ }
+
+ package DBD::myDriver::dr;
+
+ @ISA = qw(DBI::DBD::SqlEngine::dr);
+
+ sub data_sources { ... }
+ ...
+
+ package DBD::myDriver::db;
+
+ @ISA = qw(DBI::DBD::SqlEngine::db);
+
+ sub init_valid_attributes { ... }
+ sub init_default_attributes { ... }
+ sub set_versions { ... }
+ sub validate_STORE_attr { my ($dbh, $attrib, $value) = @_; ... }
+ sub validate_FETCH_attr { my ($dbh, $attrib) = @_; ... }
+ sub get_myd_versions { ... }
+ sub get_avail_tables { ... }
+
+ package DBD::myDriver::st;
+
+ @ISA = qw(DBI::DBD::SqlEngine::st);
+
+ sub FETCH { ... }
+ sub STORE { ... }
+
+ package DBD::myDriver::Statement;
+
+ @ISA = qw(DBI::DBD::SqlEngine::Statement);
+
+ sub open_table { ... }
+
+ package DBD::myDriver::Table;
+
+ @ISA = qw(DBI::DBD::SqlEngine::Table);
+
+ sub new { ... }
+
+=head1 DESCRIPTION
+
+DBI::DBD::SqlEngine abstracts the usage of SQL engines from the
+DBD. DBD authors can concentrate on the data retrieval they want to
+provide.
+
+It is strongly recommended that you read L<DBD::File::Developers> and
+L<DBD::File::Roadmap>, because many of the DBD::File API is provided
+by DBI::DBD::SqlEngine.
+
+Currently the API of DBI::DBD::SqlEngine is experimental and will
+likely change in the near future to provide the table meta data basics
+like DBD::File.
+
+=head2 Metadata
+
+The following attributes are handled by DBI itself and not by
+DBI::DBD::SqlEngine, thus they all work as expected:
+
+ Active
+ ActiveKids
+ CachedKids
+ CompatMode (Not used)
+ InactiveDestroy
+ AutoInactiveDestroy
+ Kids
+ PrintError
+ RaiseError
+ Warn (Not used)
+
+=head3 The following DBI attributes are handled by DBI::DBD::SqlEngine:
+
+=head4 AutoCommit
+
+Always on.
+
+=head4 ChopBlanks
+
+Works.
+
+=head4 NUM_OF_FIELDS
+
+Valid after C<< $sth->execute >>.
+
+=head4 NUM_OF_PARAMS
+
+Valid after C<< $sth->prepare >>.
+
+=head4 NAME
+
+Valid after C<< $sth->execute >>; probably undef for Non-Select statements.
+
+=head4 NULLABLE
+
+Not really working, always returns an array ref of ones, as DBD::CSV
+does not verify input data. Valid after C<< $sth->execute >>; undef for
+non-select statements.
+
+=head3 The following DBI attributes and methods are not supported:
+
+=over 4
+
+=item bind_param_inout
+
+=item CursorName
+
+=item LongReadLen
+
+=item LongTruncOk
+
+=back
+
+=head3 DBI::DBD::SqlEngine specific attributes
+
+In addition to the DBI attributes, you can use the following dbh
+attributes:
+
+=head4 sql_engine_version
+
+Contains the module version of this driver (B<readonly>)
+
+=head4 sql_nano_version
+
+Contains the module version of DBI::SQL::Nano (B<readonly>)
+
+=head4 sql_statement_version
+
+Contains the module version of SQL::Statement, if available (B<readonly>)
+
+=head4 sql_handler
+
+Contains the SQL Statement engine, either DBI::SQL::Nano or SQL::Statement
+(B<readonly>).
+
+=head4 sql_parser_object
+
+Contains an instantiated instance of SQL::Parser (B<readonly>).
+This is filled when used first time (only when used with SQL::Statement).
+
+=head4 sql_sponge_driver
+
+Contains an internally used DBD::Sponge handle (B<readonly>).
+
+=head4 sql_valid_attrs
+
+Contains the list of valid attributes for each DBI::DBD::SqlEngine based
+driver (B<readonly>).
+
+=head4 sql_readonly_attrs
+
+Contains the list of those attributes which are readonly (B<readonly>).
+
+=head4 sql_identifier_case
+
+Contains how DBI::DBD::SqlEngine deals with non-quoted SQL identifiers:
+
+ * SQL_IC_UPPER (1) means all identifiers are internally converted
+ into upper-cased pendants
+ * SQL_IC_LOWER (2) means all identifiers are internally converted
+ into lower-cased pendants
+ * SQL_IC_MIXED (4) means all identifiers are taken as they are
+
+These conversions happen if (and only if) no existing identifier matches.
+Once existing identifier is used as known.
+
+The SQL statement execution classes doesn't have to care, so don't expect
+C<sql_identifier_case> affects column names in statements like
+
+ SELECT * FROM foo
+
+=head4 sql_quoted_identifier_case
+
+Contains how DBI::DBD::SqlEngine deals with quoted SQL identifiers
+(B<readonly>). It's fixated to SQL_IC_SENSITIVE (3), which is interpreted
+as SQL_IC_MIXED.
+
+=head4 sql_flags
+
+Contains additional flags to instantiate an SQL::Parser. Because an
+SQL::Parser is instantiated only once, it's recommended to set this flag
+before any statement is executed.
+
+=head4 sql_dialect
+
+Controls the dialect understood by SQL::Parser. Possible values (delivery
+state of SQL::Statement):
+
+ * ANSI
+ * CSV
+ * AnyData
+
+Defaults to "CSV". Because an SQL::Parser is instantiated only once and
+SQL::Parser doesn't allow to modify the dialect once instantiated,
+it's strongly recommended to set this flag before any statement is
+executed (best place is connect attribute hash).
+
+=head1 SUPPORT
+
+You can find documentation for this module with the perldoc command.
+
+ perldoc DBI::DBD::SqlEngine
+
+You can also look for information at:
+
+=over 4
+
+=item * RT: CPAN's request tracker
+
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=DBI>
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=SQL-Statement>
+
+=item * AnnoCPAN: Annotated CPAN documentation
+
+L<http://annocpan.org/dist/DBI>
+L<http://annocpan.org/dist/SQL-Statement>
+
+=item * CPAN Ratings
+
+L<http://cpanratings.perl.org/d/DBI>
+
+=item * Search CPAN
+
+L<http://search.cpan.org/dist/DBI/>
+
+=back
+
+=head2 Where can I go for more help?
+
+For questions about installation or usage, please ask on the
+dbi-dev@perl.org mailing list.
+
+If you have a bug report, patch or suggestion, please open
+a new report ticket on CPAN, if there is not already one for
+the issue you want to report. Of course, you can mail any of the
+module maintainers, but it is less likely to be missed if
+it is reported on RT.
+
+Report tickets should contain a detailed description of the bug or
+enhancement request you want to report and at least an easy way to
+verify/reproduce the issue and any supplied fix. Patches are always
+welcome, too.
+
+=head1 ACKNOWLEDGEMENTS
+
+Thanks to Tim Bunce, Martin Evans and H.Merijn Brand for their continued
+support while developing DBD::File, DBD::DBM and DBD::AnyData.
+Their support, hints and feedback helped to design and implement this
+module.
+
+=head1 AUTHOR
+
+This module is currently maintained by
+
+H.Merijn Brand < h.m.brand at xs4all.nl > and
+Jens Rehsack < rehsack at googlemail.com >
+
+The original authors are Jochen Wiedmann and Jeff Zucker.
+
+=head1 COPYRIGHT AND LICENSE
+
+ Copyright (C) 2009-2010 by H.Merijn Brand & Jens Rehsack
+ Copyright (C) 2004-2009 by Jeff Zucker
+ Copyright (C) 1998-2004 by Jochen Wiedmann
+
+All rights reserved.
+
+You may freely distribute and/or modify this module under the terms of
+either the GNU General Public License (GPL) or the Artistic License, as
+specified in the Perl README file.
+
+=head1 SEE ALSO
+
+L<DBI>, L<DBD::File>, L<DBD::AnyData> and L<DBD::Sys>.
+
+=cut
diff --git a/lib/DBI/DBD/SqlEngine/Developers.pod b/lib/DBI/DBD/SqlEngine/Developers.pod
new file mode 100644
index 0000000..2ee3a5f
--- /dev/null
+++ b/lib/DBI/DBD/SqlEngine/Developers.pod
@@ -0,0 +1,422 @@
+=head1 NAME
+
+DBI::DBD::SqlEngine::Developers - Developers documentation for DBI::DBD::SqlEngine
+
+=head1 SYNOPSIS
+
+ package DBD::myDriver;
+
+ use base qw(DBI::DBD::SqlEngine);
+
+ sub driver
+ {
+ ...
+ my $drh = $proto->SUPER::driver($attr);
+ ...
+ return $drh->{class};
+ }
+
+ sub CLONE { ... }
+
+ package DBD::myDriver::dr;
+
+ @ISA = qw(DBI::DBD::SqlEngine::dr);
+
+ sub data_sources { ... }
+ ...
+
+ package DBD::myDriver::db;
+
+ @ISA = qw(DBI::DBD::SqlEngine::db);
+
+ sub init_valid_attributes { ... }
+ sub init_default_attributes { ... }
+ sub set_versions { ... }
+ sub validate_STORE_attr { my ($dbh, $attrib, $value) = @_; ... }
+ sub validate_FETCH_attr { my ($dbh, $attrib) = @_; ... }
+ sub get_myd_versions { ... }
+ sub get_avail_tables { ... }
+
+ package DBD::myDriver::st;
+
+ @ISA = qw(DBI::DBD::SqlEngine::st);
+
+ sub FETCH { ... }
+ sub STORE { ... }
+
+ package DBD::myDriver::Statement;
+
+ @ISA = qw(DBI::DBD::SqlEngine::Statement);
+
+ sub open_table { ... }
+
+ package DBD::myDriver::Table;
+
+ @ISA = qw(DBI::DBD::SqlEngine::Table);
+
+ sub new { ... }
+
+ sub fetch_row { ... }
+ sub push_row { ... }
+ sub push_names { ... }
+ sub seek { ... }
+ sub truncate { ... }
+ sub drop { ... }
+
+ # optimize the SQL engine by add one or more of
+ sub update_current_row { ... }
+ # or
+ sub update_specific_row { ... }
+ # or
+ sub update_one_row { ... }
+ # or
+ sub insert_new_row { ... }
+ # or
+ sub delete_current_row { ... }
+ # or
+ sub delete_one_row { ... }
+
+=head1 DESCRIPTION
+
+This document describes the interface of DBI::DBD::SqlEngine for DBD
+developers who write DBI::DBD::SqlEngine based DBI drivers. It supplements
+L<DBI::DBD> and L<DBI::DBD::SqlEngine::HowTo>, which you should read first.
+
+=head1 CLASSES
+
+Each DBI driver must provide a package global C<< driver >> method and
+three DBI related classes:
+
+=over 4
+
+=item DBI::DBD::SqlEngine::dr
+
+Driver package, contains the methods DBI calls indirectly via DBI
+interface:
+
+ DBI->connect ('DBI:DBM:', undef, undef, {})
+
+ # invokes
+ package DBD::DBM::dr;
+ @DBD::DBM::dr::ISA = qw(DBI::DBD::SqlEngine::dr);
+
+ sub connect ($$;$$$)
+ {
+ ...
+ }
+
+Similar for C<< data_sources () >> and C<< disconnect_all() >>.
+
+Pure Perl DBI drivers derived from DBI::DBD::SqlEngine do not usually need to
+override any of the methods provided through the DBD::XXX::dr package
+however if you need additional initialization in the connect method
+you may need to.
+
+=item DBI::DBD::SqlEngine::db
+
+Contains the methods which are called through DBI database handles
+(C<< $dbh >>). e.g.,
+
+ $sth = $dbh->prepare ("select * from foo");
+ # returns the f_encoding setting for table foo
+ $dbh->csv_get_meta ("foo", "f_encoding");
+
+DBI::DBD::SqlEngine provides the typical methods required here. Developers who
+write DBI drivers based on DBI::DBD::SqlEngine need to override the methods
+C<< set_versions >> and C<< init_valid_attributes >>.
+
+=item DBI::DBD::SqlEngine::st
+
+Contains the methods to deal with prepared statement handles. e.g.,
+
+ $sth->execute () or die $sth->errstr;
+
+=back
+
+=head2 DBI::DBD::SqlEngine
+
+This is the main package containing the routines to initialize
+DBI::DBD::SqlEngine based DBI drivers. Primarily the
+C<< DBI::DBD::SqlEngine::driver >> method is invoked, either directly
+from DBI when the driver is initialized or from the derived class.
+
+ package DBD::DBM;
+
+ use base qw( DBI::DBD::SqlEngine );
+
+ sub driver
+ {
+ my ( $class, $attr ) = @_;
+ ...
+ my $drh = $class->SUPER::driver( $attr );
+ ...
+ return $drh;
+ }
+
+It is not necessary to implement your own driver method as long as
+additional initialization (e.g. installing more private driver
+methods) is not required. You do not need to call C<< setup_driver >>
+as DBI::DBD::SqlEngine takes care of it.
+
+=head2 DBI::DBD::SqlEngine::dr
+
+The driver package contains the methods DBI calls indirectly via the DBI
+interface (see L<DBI/DBI Class Methods>).
+
+DBI::DBD::SqlEngine based DBI drivers usually do not need to implement anything here,
+it is enough to do the basic initialization:
+
+ package DBD:XXX::dr;
+
+ @DBD::XXX::dr::ISA = qw (DBI::DBD::SqlEngine::dr);
+ $DBD::XXX::dr::imp_data_size = 0;
+ $DBD::XXX::dr::data_sources_attr = undef;
+ $DBD::XXX::ATTRIBUTION = "DBD::XXX $DBD::XXX::VERSION by Hans Mustermann";
+
+=head2 DBI::DBD::SqlEngine::db
+
+This package defines the database methods, which are called via the DBI
+database handle C<< $dbh >>.
+
+Methods provided by DBI::DBD::SqlEngine:
+
+=over 4
+
+=item ping
+
+Simply returns the content of the C<< Active >> attribute. Override
+when your driver needs more complicated actions here.
+
+=item prepare
+
+Prepares a new SQL statement to execute. Returns a statement handle,
+C<< $sth >> - instance of the DBD:XXX::st. It is neither required nor
+recommended to override this method.
+
+=item FETCH
+
+Fetches an attribute of a DBI database object. Private handle attributes
+must have a prefix (this is mandatory). If a requested attribute is
+detected as a private attribute without a valid prefix, the driver prefix
+(written as C<$drv_prefix>) is added.
+
+The driver prefix is extracted from the attribute name and verified against
+C<< $dbh->{ $drv_prefix . "valid_attrs" } >> (when it exists). If the
+requested attribute value is not listed as a valid attribute, this method
+croaks. If the attribute is valid and readonly (listed in C<< $dbh->{
+$drv_prefix . "readonly_attrs" } >> when it exists), a real copy of the
+attribute value is returned. So it's not possible to modify
+C<f_valid_attrs> from outside of DBI::DBD::SqlEngine::db or a derived class.
+
+=item STORE
+
+Stores a database private attribute. Private handle attributes must have a
+prefix (this is mandatory). If a requested attribute is detected as a private
+attribute without a valid prefix, the driver prefix (written as
+C<$drv_prefix>) is added. If the database handle has an attribute
+C<${drv_prefix}_valid_attrs> - for attribute names which are not listed in
+that hash, this method croaks. If the database handle has an attribute
+C<${drv_prefix}_readonly_attrs>, only attributes which are not listed there
+can be stored (once they are initialized). Trying to overwrite such an
+immutable attribute forces this method to croak.
+
+An example of a valid attributes list can be found in
+C<< DBI::DBD::SqlEngine::db::init_valid_attributes >>.
+
+=item set_versions
+
+This method sets the attributes C<< f_version >>, C<< sql_nano_version >>,
+C<< sql_statement_version >> and (if not prohibited by a restrictive
+C<< ${prefix}_valid_attrs >>) C<< ${prefix}_version >>.
+
+This method is called at the end of the C<< connect () >> phase.
+
+When overriding this method, do not forget to invoke the superior one.
+
+=item init_valid_attributes
+
+This method is called after the database handle is instantiated as the
+first attribute initialization.
+
+C<< DBI::DBD::SqlEngine::db::init_valid_attributes >> initializes the
+attributes C<sql_valid_attrs> and C<sql_readonly_attrs>.
+
+When overriding this method, do not forget to invoke the superior one,
+preferably before doing anything else.
+
+=item init_default_attributes
+
+This method is called after the database handle is instantiated to
+initialize the default attributes.
+
+C<< DBI::DBD::SqlEngine::db::init_default_attributes >> initializes the
+attributes C<sql_identifier_case>, C<sql_quoted_identifier_case>,
+C<sql_handler>, C<sql_engine_version>, C<sql_nano_version> and
+C<sql_statement_version> when L<SQL::Statement> is available.
+
+When the derived implementor class provides the attribute to validate
+attributes (e.g. C<< $dbh->{dbm_valid_attrs} = {...}; >>) or the attribute
+containing the immutable attributes (e.g. C<< $dbh->{dbm_readonly_attrs}
+= {...}; >>), the attributes C<drv_valid_attrs>, C<drv_readonly_attrs> and
+C<drv_version> are added (when available) to the list of valid and
+immutable attributes (where C<drv_> is interpreted as the driver prefix).
+
+=item get_versions
+
+This method is called by the code injected into the instantiated driver to
+provide the user callable driver method C<< ${prefix}versions >> (e.g.
+C<< dbm_versions >>, C<< csv_versions >>, ...).
+
+The DBI::DBD::SqlEngine implementation returns all version information known by
+DBI::DBD::SqlEngine (e.g. DBI version, Perl version, DBI::DBD::SqlEngine version and
+the SQL handler version).
+
+C<get_versions> takes the C<$dbh> as the first argument and optionally a
+second argument containing a table name. The second argument is not
+evaluated in C<< DBI::DBD::SqlEngine::db::get_versions >> itself - but
+might be in the future.
+
+If the derived implementor class provides a method named
+C<get_${drv_prefix}versions>, this is invoked and the return value of
+it is associated to the derived driver name:
+
+ if (my $dgv = $dbh->{ImplementorClass}->can ("get_" . $drv_prefix . "versions") {
+ (my $derived_driver = $dbh->{ImplementorClass}) =~ s/::db$//;
+ $versions{$derived_driver} = &$dgv ($dbh, $table);
+ }
+
+Override it to add more version information about your module, (e.g.
+some kind of parser version in case of DBD::CSV, ...), if one line is not
+enough room to provide all relevant information.
+
+=item sql_parser_object
+
+Returns a L<SQL::Parser> instance, when C<< sql_handler >> is set to
+"SQL::Statement". The parser instance is stored in C<< sql_parser_object >>.
+
+It is not recommended to override this method.
+
+=item disconnect
+
+Disconnects from a database. All local table information is discarded and
+the C<< Active >> attribute is set to 0.
+
+=item type_info_all
+
+Returns information about all the types supported by DBI::DBD::SqlEngine.
+
+=item table_info
+
+Returns a statement handle which is prepared to deliver information about
+all known tables.
+
+=item list_tables
+
+Returns a list of all known table names.
+
+=item quote
+
+Quotes a string for use in SQL statements.
+
+=item commit
+
+Warns about a useless call (if warnings enabled) and returns.
+DBI::DBD::SqlEngine is typically a driver which commits every action instantly when
+executed.
+
+=item rollback
+
+Warns about a useless call (if warnings enabled) and returns.
+DBI::DBD::SqlEngine is typically a driver which commits every action instantly when
+executed.
+
+=back
+
+=head2 DBI::DBD::SqlEngine::st
+
+Contains the methods to deal with prepared statement handles:
+
+=over 4
+
+=item bind_param
+
+Common routine to bind placeholders to a statement for execution. It
+is dangerous to override this method without detailed knowledge about
+the DBI::DBD::SqlEngine internal storage structure.
+
+=item execute
+
+Executes a previously prepared statement (with placeholders, if any).
+
+=item finish
+
+Finishes a statement handle, discards all buffered results. The prepared
+statement is not discarded so the statement can be executed again.
+
+=item fetch
+
+Fetches the next row from the result-set. This method may be rewritten
+in a later version and if it's overridden in a derived class, the
+derived implementation should not rely on the storage details.
+
+=item fetchrow_arrayref
+
+Alias for C<< fetch >>.
+
+=item FETCH
+
+Fetches statement handle attributes. Supported attributes (for full overview
+see L<DBI/Statement Handle Attributes>) are C<NAME>, C<TYPE>, C<PRECISION>
+and C<NULLABLE>. Each column is returned as C<NULLABLE> which might be wrong
+depending on the derived backend storage. If the statement handle has
+private attributes, they can be fetched using this method, too. B<Note> that
+statement attributes are not associated with any table used in this statement.
+
+This method usually requires extending in a derived implementation.
+See L<DBD::CSV> or L<DBD::DBM> for some example.
+
+=item STORE
+
+Allows storing of statement private attributes. No special handling is
+currently implemented here.
+
+=item rows
+
+Returns the number of rows affected by the last execute. This method might
+return C<undef>.
+
+=back
+
+=head2 DBI::DBD::SqlEngine::Statement
+
+Derives from DBI::SQL::Nano::Statement for unified naming when deriving
+new drivers. No additional feature is provided from here.
+
+=head2 DBI::DBD::SqlEngine::Table
+
+Derives from DBI::SQL::Nano::Table for unified naming when deriving
+new drivers. No additional feature is provided from here.
+
+You should consult the documentation of C<< SQL::Eval::Table >> (see
+L<SQL::Eval>) to get more information about the abstract methods of the
+table's base class you have to override and a description of the table
+meta information expected by the SQL engines.
+
+=head1 AUTHOR
+
+The module DBI::DBD::SqlEngine is currently maintained by
+
+H.Merijn Brand < h.m.brand at xs4all.nl > and
+Jens Rehsack < rehsack at googlemail.com >
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2010 by H.Merijn Brand & Jens Rehsack
+
+All rights reserved.
+
+You may freely distribute and/or modify this module under the terms of
+either the GNU General Public License (GPL) or the Artistic License, as
+specified in the Perl README file.
+
+=cut
diff --git a/lib/DBI/DBD/SqlEngine/HowTo.pod b/lib/DBI/DBD/SqlEngine/HowTo.pod
new file mode 100644
index 0000000..764dd08
--- /dev/null
+++ b/lib/DBI/DBD/SqlEngine/HowTo.pod
@@ -0,0 +1,218 @@
+=head1 NAME
+
+DBI::DBD::SqlEngine::HowTo - Guide to create DBI::DBD::SqlEngine based driver
+
+=head1 SYNOPSIS
+
+ perldoc DBI::DBD::SqlEngine::HowTo
+ perldoc DBI
+ perldoc DBI::DBD
+ perldoc DBI::DBD::SqlEngine::Developers
+ perldoc SQL::Eval
+ perldoc DBI::DBD::SqlEngine
+ perldoc DBI::DBD::SqlEngine::HowTo
+ perldoc SQL::Statement::Embed
+
+=head1 DESCRIPTION
+
+This document provides a step-by-step guide, how to create a new
+C<DBI::DBD::SqlEngine> based DBD. It expects that you carefully read the
+L<DBI> documentation and that you're familiar with L<DBI::DBD> and had
+read and understood L<DBD::ExampleP>.
+
+This document addresses experienced developers who are really sure that
+they need to invest time when writing a new DBI Driver. Writing a DBI
+Driver is neither a weekend project nor an easy job for hobby coders
+after work. Expect one or two man-month of time for the first start.
+
+Those who are still reading, should be able to sing the rules of
+L<DBI::DBD/CREATING A NEW DRIVER>.
+
+=head1 CREATING DRIVER CLASSES
+
+Do you have an entry in DBI's DBD registry? For this guide, a prefix of
+C<foo_> is assumed.
+
+=head2 Sample Skeleton
+
+ package DBD::Foo;
+
+ use strict;
+ use warnings;
+ use vars qw($VERSION);
+ use base qw(DBI::DBD::SqlEngine);
+
+ use DBI ();
+
+ $VERSION = "0.001";
+
+ package DBD::Foo::dr;
+
+ use vars qw(@ISA $imp_data_size);
+
+ @ISA = qw(DBI::DBD::SqlEngine::dr);
+ $imp_data_size = 0;
+
+ package DBD::Foo::db;
+
+ use vars qw(@ISA $imp_data_size);
+
+ @ISA = qw(DBI::DBD::SqlEngine::db);
+ $imp_data_size = 0;
+
+ package DBD::Foo::st;
+
+ use vars qw(@ISA $imp_data_size);
+
+ @ISA = qw(DBI::DBD::SqlEngine::st);
+ $imp_data_size = 0;
+
+ package DBD::Foo::Statement;
+
+ use vars qw(@ISA);
+
+ @ISA = qw(DBI::DBD::SqlEngine::Statement);
+
+ package DBD::Foo::Table;
+
+ use vars qw(@ISA);
+
+ @ISA = qw(DBI::DBD::SqlEngine::Table);
+
+ 1;
+
+Tiny, eh? And all you have now is a DBD named foo which will is able to
+deal with temporary tables, as long as you use L<SQL::Statement>. In
+L<DBI::SQL::Nano> environments, this DBD can do nothing.
+
+=head2 Deal with own attributes
+
+Before we start doing usable stuff with our DBI driver, we need to think
+about what we want to do and how we want to do it.
+
+Do we need tunable knobs accessible by users? Do we need status
+information? All this is handled in attributes of the database handles (be
+careful when your DBD is running "behind" a L<DBD::Gofer> proxy).
+
+How come the attributes into the DBD and how are they fetchable by the
+user? Good question, but you should know because you've read the L<DBI>
+documentation.
+
+C<DBI::DBD::SqlEngine::db::FETCH> and C<DBI::DBD::SqlEngine::db::STORE>
+taking care for you - all they need to know is which attribute names
+are valid and mutable or immutable. Tell them by adding
+C<init_valid_attributes> to your db class:
+
+ sub init_valid_attributes
+ {
+ my $dbh = $_[0];
+
+ $dbh->SUPER::init_valid_attributes ();
+
+ $dbh->{foo_valid_attrs} = {
+ foo_version => 1, # contains version of this driver
+ foo_valid_attrs => 1, # contains the valid attributes of foo drivers
+ foo_readonly_attrs => 1, # contains immutable attributes of foo drivers
+ foo_bar => 1, # contains the bar attribute
+ foo_baz => 1, # contains the baz attribute
+ foo_manager => 1, # contains the manager of the driver instance
+ foo_manager_type => 1, # contains the manager class of the driver instance
+ };
+ $dbh->{foo_readonly_attrs} = {
+ foo_version => 1, # ensure no-one modifies the driver version
+ foo_valid_attrs => 1, # do not permit to add more valid attributes ...
+ foo_readonly_attrs => 1, # ... or make the immutable mutable
+ foo_manager => 1, # manager is set internally only
+ };
+
+ return $dbh;
+ }
+
+Woooho - but now the user cannot assign new managers? This is intended,
+overwrite C<STORE> to handle it!
+
+ sub STORE ($$$)
+ {
+ my ( $dbh, $attrib, $value ) = @_;
+
+ $dbh->SUPER::STORE( $attrib, $value );
+
+ # we're still alive, so no exception is thrown ...
+ # by DBI::DBD::SqlEngine::db::STORE
+ if ( $attrib eq "foo_manager_type" )
+ {
+ $dbh->{foo_manager} = $dbh->{foo_manager_type}->new();
+ # ... probably correct some states based on the new
+ # foo_manager_type - see DBD::Sys for an example
+ }
+ }
+
+But ... my driver runs without a manager until someone first assignes
+a C<foo_manager_type>. Well, no - there're two places where you can
+initialize defaults:
+
+ sub init_default_attributes
+ {
+ my ($dbh, $phase) = @_;
+
+ $dbh->SUPER::init_default_attributes($phase);
+
+ if( 0 == $phase )
+ {
+ # init all attributes which have no knowledge about
+ # user settings from DSN or the attribute hash
+ $dbh->{foo_manager_type} = "DBD::Foo::Manager";
+ }
+ elsif( 1 == $phase )
+ {
+ # init phase with more knowledge from DSN or attribute
+ # hash
+ $dbh->{foo_manager} = $dbh->{foo_manager_type}->new();
+ }
+
+ return $dbh;
+ }
+
+So far we can prevent the users to use our database driver as data
+storage for anything and everything. We care only about the real important
+stuff for peace on earth and alike attributes. But in fact, the driver
+still can't do anything. It can do less than nothing - meanwhile it's
+not a stupid storage area anymore.
+
+=head2 Dealing with Tables
+
+Let's put some life into it - it's going to be time for it.
+
+This is a good point where a quick side step to L<SQL::Statement::Embed>
+will help to shorten the next paragraph. The documentation in
+SQL::Statement::Embed regarding embedding in own DBD's works pretty
+fine with SQL::Statement and DBI::SQL::Nano.
+
+=head2 Testing
+
+Now you should have your first own DBD. Was easy, wasn't it? But does
+it work well? Prove it by writing tests and remember to use
+dbd_edit_mm_attribs from L<DBI::DBD> to ensure testing even rare cases.
+
+=head1 AUTHOR
+
+This guide is written by Jens Rehsack. DBI::DBD::SqlEngine is written by
+Jens Rehsack using code from DBD::File originally written by Jochen
+Wiedmann and Jeff Zucker.
+
+The module DBI::DBD::SqlEngine is currently maintained by
+
+H.Merijn Brand < h.m.brand at xs4all.nl > and
+Jens Rehsack < rehsack at googlemail.com >
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2010 by H.Merijn Brand & Jens Rehsack
+
+All rights reserved.
+
+You may freely distribute and/or modify this module under the terms of
+either the GNU General Public License (GPL) or the Artistic License, as
+specified in the Perl README file.
+
+=cut
diff --git a/lib/DBI/FAQ.pm b/lib/DBI/FAQ.pm
new file mode 100644
index 0000000..1ad760b
--- /dev/null
+++ b/lib/DBI/FAQ.pm
@@ -0,0 +1,966 @@
+###
+### $Id: FAQ.pm 14934 2011-09-14 10:02:25Z timbo $
+###
+### DBI Frequently Asked Questions POD
+###
+### Copyright section reproduced from below.
+###
+### This document is Copyright (c)1994-2000 Alligator Descartes, with portions
+### Copyright (c)1994-2000 their original authors. This module is released under
+### the 'Artistic' license which you can find in the perl distribution.
+###
+### This document is Copyright (c)1997-2000 Alligator Descartes. All rights reserved.
+### Permission to distribute this document, in full or in part, via email,
+### Usenet, ftp archives or http is granted providing that no charges are involved,
+### reasonable attempt is made to use the most current version and all credits
+### and copyright notices are retained ( the I<AUTHOR> and I<COPYRIGHT> sections ).
+### Requests for other distribution rights, including incorporation into
+### commercial products, such as books, magazine articles or CD-ROMs should be
+### made to Alligator Descartes.
+###
+
+package DBI::FAQ;
+
+our $VERSION = sprintf("1.%06d", q$Revision: 14934 $ =~ /(\d+)/o);
+
+
+=head1 NAME
+
+DBI::FAQ -- The Frequently Asked Questions for the Perl5 Database Interface
+
+=for html
+<BODY BGCOLOR="#ffffff" TEXT="#000000" LINK="#3a15ff" ALINK="#ff0000" VLINK="#ff282d">
+<!--#include virtual="/technology/perl/DBI/templatetop.html" -->
+<CENTER>
+<FONT SIZE="+2">
+DBI Frequently Asked Questions v.0.38
+</FONT>
+<BR>
+<FONT SIZE="-1">
+<I>Last updated: February 8th, 2000</I>
+</FONT>
+</CENTER>
+<P>
+
+=head1 SYNOPSIS
+
+ perldoc DBI::FAQ
+
+=head1 VERSION
+
+This document is currently at version I<0.38>, as of I<February 8th, 2000>.
+
+That's B<very> old. A newer FAQ can be found at L<http://faq.dbi-support.com/>
+
+Neither this document nor that web site are actively maintained.
+Volunteers are welcome.
+
+=head1 DESCRIPTION
+
+This document serves to answer the most frequently asked questions on both
+the DBI Mailing Lists and personally to members of the DBI development team.
+
+=head1 Basic Information & Information Sources
+
+=head2 1.1 What is DBI, DBperl, Oraperl and *perl?
+
+To quote Tim Bunce, the architect and author of DBI:
+
+ DBI is a database access Application Programming Interface (API)
+ for the Perl Language. The DBI API Specification defines a set
+ of functions, variables and conventions that provide a consistent
+ database interface independent of the actual database being used.
+
+In simple language, the DBI interface allows users to access multiple database
+types transparently. So, if you connecting to an Oracle, Informix, mSQL, Sybase
+or whatever database, you don't need to know the underlying mechanics of the
+3GL layer. The API defined by DBI will work on I<all> these database types.
+
+A similar benefit is gained by the ability to connect to two I<different>
+databases of different vendor within the one perl script, I<ie>, I want
+to read data from an Oracle database and insert it back into an Informix
+database all within one program. The DBI layer allows you to do this simply
+and powerfully.
+
+
+=for html
+Here's a diagram that demonstrates the principle:
+<P>
+<CENTER>
+<IMG SRC="img/dbiarch.gif" WIDTH=451 HEIGHT=321 ALT="[ DBI Architecture ]">
+</CENTER>
+<P>
+
+I<DBperl> is the old name for the interface specification. It's usually
+now used to denote perlI<4> modules on database interfacing, such as,
+I<oraperl>, I<isqlperl>, I<ingperl> and so on. These interfaces
+didn't have a standard API and are generally I<not> supported.
+
+Here's a list of DBperl modules, their corresponding DBI counterparts and
+support information. I<Please note>, the author's listed here generally
+I<do not> maintain the DBI module for the same database. These email
+addresses are unverified and should only be used for queries concerning the
+perl4 modules listed below. DBI driver queries should be directed to the
+I<dbi-users> mailing list.
+
+ Module Name Database Required Author DBI
+ ----------- ----------------- ------ ---
+ Sybperl Sybase Michael Peppler DBD::Sybase
+ <mpeppler@itf.ch>
+ Oraperl Oracle 6 & 7 Kevin Stock DBD::Oracle
+ <dbi-users@perl.org>
+ Ingperl Ingres Tim Bunce & DBD::Ingres
+ Ted Lemon
+ <dbi-users@perl.org>
+ Interperl Interbase Buzz Moschetti DBD::Interbase
+ <buzz@bear.com>
+ Uniperl Unify 5.0 Rick Wargo None
+ <rickers@coe.drexel.edu>
+ Pgperl Postgres Igor Metz DBD::Pg
+ <metz@iam.unibe.ch>
+ Btreeperl NDBM John Conover SDBM?
+ <john@johncon.com>
+ Ctreeperl C-Tree John Conover None
+ <john@johncon.com>
+ Cisamperl Informix C-ISAM Mathias Koerber None
+ <mathias@unicorn.swi.com.sg>
+ Duaperl X.500 Directory Eric Douglas None
+ User Agent
+
+However, some DBI modules have DBperl emulation layers, so, I<DBD::Oracle>
+comes with an Oraperl emulation layer, which allows you to run legacy oraperl
+scripts without modification. The emulation layer translates the oraperl API
+calls into DBI calls and executes them through the DBI switch.
+
+Here's a table of emulation layer information:
+
+ Module Emulation Layer Status
+ ------ --------------- ------
+ DBD::Oracle Oraperl Complete
+ DBD::Informix Isqlperl Under development
+ DBD::Ingres Ingperl Complete?
+ DBD::Sybase Sybperl Working? ( Needs verification )
+ DBD::mSQL Msqlperl Experimentally released with
+ DBD::mSQL-0.61
+
+The I<Msqlperl> emulation is a special case. I<Msqlperl> is a perl5 driver
+for I<mSQL> databases, but does not conform to the DBI Specification. It's
+use is being deprecated in favour of I<DBD::mSQL>. I<Msqlperl> may be downloaded
+from CPAN I<via>:
+
+ http://www.perl.com/cgi-bin/cpan_mod?module=Msqlperl
+
+=head2 1.2. Where can I get it from?
+
+The Comprehensive Perl Archive Network
+resources should be used for retrieving up-to-date versions of the DBI
+and drivers. CPAN may be accessed I<via> Tom Christiansen's splendid
+I<CPAN multiplexer> program located at:
+
+ http://www.perl.com/CPAN/
+
+For more specific version information and exact URLs of drivers, please see
+the DBI drivers list and the DBI module pages which can be found on:
+
+ http://dbi.perl.org/
+
+This list is automatically generated on a nightly basis from CPAN and should
+be up-to-date.
+
+=head2 1.3. Where can I get more information?
+
+There are a few information sources on DBI.
+
+=over 4
+
+=item I<"Programming the Perl DBI">
+
+"Programming the Perl DBI" is the I<official> book on the DBI written by
+Alligator Descartes and Tim Bunce and published by O'Reilly & Associates.
+The book was released on February 9th, 2000.
+
+The table of contents is:
+
+ Preface
+ 1. Introduction
+ From Mainframes to Workstations
+ Perl
+ DBI in the Real World
+ A Historical Interlude and Standing Stones
+ 2. Basic Non-DBI Databases
+ Storage Managers and Layers
+ Query Languages and Data Functions
+ Standing Stones and the Sample Database
+ Flat-File Databases
+ Putting Complex Data into Flat Files
+ Concurrent Database Access and Locking
+ DBM Files and the Berkeley Database Manager
+ The MLDBM Module
+ Summary
+ 3. SQL and Relational Databases
+ The Relational Database Methodology
+ Datatypes and NULL Values
+ Querying Data
+ Modifying Data Within Tables
+ Creating and Destroying Tables
+ 4. Programming with the DBI
+ DBI Architecture
+ Handles
+ Data Source Names
+ Connection and Disconnection
+ Error Handling
+ Utility Methods and Functions
+ 5. Interacting with the Database
+ Issuing Simple Queries
+ Executing Non-SELECT Statements
+ Binding Parameters to Statements
+ Binding Output Columns
+ do() Versus prepare()
+ Atomic and Batch Fetching
+ 6. Advanced DBI
+ Handle Attributes and Metadata
+ Handling LONG/LOB Data
+ Transactions, Locking, and Isolation
+ 7. ODBC and the DBI
+ ODBC -- Embraced and Extended
+ DBI -- Thrashed and Mutated
+ The Nuts and Bolts of ODBC
+ ODBC from Perl
+ The Marriage of DBI and ODBC
+ Questions and Choices
+ Moving Between Win32::ODBC and the DBI
+ And What About ADO?
+ 8. DBI Shell and Database Proxying
+ dbish -- The DBI Shell
+ Database Proxying
+ A. DBI Specification
+ B. Driver and Database Characteristics
+ C. ASLaN Sacred Site Charter
+ Index
+
+The book should be available from all good bookshops and can be ordered online
+either <I>via</I> O'Reilly & Associates
+
+ http://www.oreilly.com/catalog/perldbi
+
+or Amazon
+
+ http://www.amazon.com/exec/obidos/ASIN/1565926994/dbi
+
+=item I<POD documentation>
+
+I<POD>s are chunks of documentation usually embedded within perl programs
+that document the code ``I<in place>'', providing a useful resource for
+programmers and users of modules. POD for DBI and drivers is beginning to
+become more commonplace, and documentation for these modules can be read
+with the C<perldoc> program included with Perl.
+
+=over 4
+
+=item The DBI Specification
+
+The POD for the DBI Specification can be read with the:
+
+ perldoc DBI
+
+command. The Specification also forms Appendix A of "Programming the Perl
+DBI".
+
+=item Oraperl
+
+Users of the Oraperl emulation layer bundled with I<DBD::Oracle>, may read
+up on how to program with the Oraperl interface by typing:
+
+ perldoc Oraperl
+
+This will produce an updated copy of the original oraperl man page written by
+Kevin Stock for perl4. The oraperl API is fully listed and described there.
+
+=item Drivers
+
+Users of the DBD modules may read about some of the private functions
+and quirks of that driver by typing:
+
+ perldoc <driver>
+
+For example, the I<DBD::mSQL> driver is bundled with driver-specific
+documentation that can be accessed by typing
+
+ perldoc DBD::mSQL
+
+=item Frequently Asked Questions
+
+This document, the I<Frequently Asked Questions> is also available as POD
+documentation! You can read this on your own system by typing:
+
+ perldoc DBI::FAQ
+
+This may be more convenient to persons not permanently, or conveniently,
+connected to the Internet. The I<DBI::FAQ> module should be downloaded and
+installed for the more up-to-date version.
+
+The version of I<DBI::FAQ> shipped with the C<DBI> module may be slightly out
+of date.
+
+=item POD in general
+
+Information on writing POD, and on the philosophy of POD in general, can be
+read by typing:
+
+ perldoc perlpod
+
+Users with the Tk module installed may be interested to learn there is a
+Tk-based POD reader available called C<tkpod>, which formats POD in a convenient
+and readable way. This is available I<via> CPAN as the module called
+I<Tk::POD> and is highly recommended.
+
+=back
+
+=item I<Driver and Database Characteristics>
+
+The driver summaries that were produced for Appendix B of "Programming the
+Perl DBI" are available online at:
+
+ http://dbi.perl.org/
+
+in the driver information table. These summaries contain standardised
+information on each driver and database which should aid you in selecting
+a database to use. It will also inform you quickly of any issues within
+drivers or whether a driver is not fully compliant with the DBI Specification.
+
+=item I<Rambles, Tidbits and Observations>
+
+ http://dbi.perl.org/tidbits
+
+There are a series of occasional rambles from various people on the
+DBI mailing lists who, in an attempt to clear up a simple point, end up
+drafting fairly comprehensive documents. These are quite often varying in
+quality, but do provide some insights into the workings of the interfaces.
+
+=item I<Articles>
+
+A list of articles discussing the DBI can be found on the DBI WWW page at:
+
+ http://dbi.perl.org/
+
+These articles are of varying quality and age, from the original Perl Journal
+article written by Alligator and Tim, to more recent debacles published online
+from about.com.
+
+=item I<README files>
+
+The I<README> files included with each driver occasionally contains
+some useful information ( no, really! ) that may be pertinent to the user.
+Please read them. It makes our worthless existences more bearable. These
+can all be read from the main DBI WWW page at:
+
+ http://dbi.perl.org/
+
+=item I<Mailing Lists>
+
+There are three mailing lists for DBI:
+
+ dbi-announce@perl.org -- for announcements, very low traffic
+ dbi-users@perl.org -- general user support
+ dbi-dev@perl.org -- for driver developers (no user support)
+
+For information on how to subscribe, set digest mode etc, and unsubscribe,
+send an email message (the content will be ignored) to:
+
+ dbi-announce-help@perl.org
+ dbi-users-help@perl.org
+ dbi-dev-help@perl.org
+
+=item I<Mailing List Archives>
+
+=over 4
+
+=item I<US Mailing List Archives>
+
+ http://outside.organic.com/mail-archives/dbi-users/
+
+Searchable hypermail archives of the three mailing lists, and some of the
+much older traffic have been set up for users to browse.
+
+=item I<European Mailing List Archives>
+
+ http://www.rosat.mpe-garching.mpg.de/mailing-lists/PerlDB-Interest
+
+As per the US archive above.
+
+=back
+
+=back
+
+=head1 Compilation Problems
+
+=head2 2.1. Compilation problems or "It fails the test!"
+
+First off, consult the README for that driver in case there is useful
+information about the problem. It may be a known problem for your given
+architecture and operating system or database. You can check the README
+files for each driver in advance online at:
+
+ http://dbi.perl.org/
+
+If it's a known problem, you'll probably have to wait till it gets fixed. If
+you're I<really> needing it fixed, try the following:
+
+=over 4
+
+=item I<Attempt to fix it yourself>
+
+This technique is generally I<not> recommended to the faint-hearted.
+If you do think you have managed to fix it, then, send a patch file
+( context diff ) to the author with an explanation of:
+
+=over 4
+
+=item *
+
+What the problem was, and test cases, if possible.
+
+=item *
+
+What you needed to do to fix it. Please make sure you mention everything.
+
+=item *
+
+Platform information, database version, perl version, module version and
+DBI version.
+
+=back
+
+=item I<Email the author> Do I<NOT> whinge!
+
+Please email the address listed in the WWW pages for whichever driver you
+are having problems with. Do I<not> directly email the author at a
+known address unless it corresponds with the one listed.
+
+We tend to have real jobs to do, and we do read the mailing lists for
+problems. Besides, we may not have access to <I<insert your
+favourite brain-damaged platform here>> and couldn't be of any
+assistance anyway! Apologies for sounding harsh, but that's the way of it!
+
+However, you might catch one of these creative genii at 3am when we're
+doing this sort of stuff anyway, and get a patch within 5 minutes. The
+atmosphere in the DBI circle is that we I<do> appreciate the users'
+problems, since we work in similar environments.
+
+If you are planning to email the author, please furnish as much information
+as possible, I<ie>:
+
+=over 4
+
+=item *
+
+I<ALL> the information asked for in the README file in
+the problematic module. And we mean I<ALL> of it. We don't
+put lines like that in documentation for the good of our health, or
+to meet obscure README file standards of length.
+
+=item *
+
+If you have a core dump, try the I<Devel::CoreStack> module for
+generating a stack trace from the core dump. Send us that too.
+I<Devel::CoreStack> can be found on CPAN at:
+
+ http://www.perl.com/cgi-bin/cpan_mod?module=Devel::CoreStack
+
+=item *
+
+Module versions, perl version, test cases, operating system versions
+and I<any other pertinent information>.
+
+=back
+
+Remember, the more information you send us, the quicker we can track
+problems down. If you send us no useful information, expect nothing back.
+
+Finally, please be aware that some authors, including Tim Bunce, specifically
+request that you do I<not> mail them directly. Please respect their wishes and
+use the email addresses listed in the appropriate module C<README> file.
+
+=item I<Email the dbi-users Mailing List>
+
+It's usually a fairly intelligent idea to I<cc> the mailing list
+anyway with problems. The authors all read the lists, so you lose nothing
+by mailing there.
+
+=back
+
+=head1 Platform and Driver Issues
+
+=head2 3.1 What's the difference between ODBC and DBI?
+
+In terms of architecture - not much: Both define programming
+interfaces. Both allow multiple drivers to be loaded to do the
+actual work.
+
+In terms of ease of use - much: The DBI is a 'high level' interface
+that, like Perl itself, strives to make the simple things easy while
+still making the hard things possible. The ODBC is a 'low level'
+interface. All nuts-bolts-knobs-and-dials.
+
+Now there's an ODBC driver for the DBI (DBD::ODBC) the "What's the
+difference" question is more usefully rephrased as:
+
+Chapter 7 of "Programming the Perl DBI" covers this topic in far more
+detail and should be consulted.
+
+=head2 3.2 What's the difference between Win32::ODBC and DBD::ODBC?
+
+The DBI, and thus DBD::ODBC, has a different philosophy from the
+Win32::ODBC module:
+
+The Win32::ODBC module is a 'thin' layer over the low-level ODBC API.
+The DBI defines a simpler 'higher level' interface.
+
+The Win32::ODBC module gives you access to more of the ODBC API.
+The DBI and DBD::ODBC give you access to only the essentials.
+(But, unlike Win32::ODBC, the DBI and DBD::ODBC do support parameter
+binding and multiple prepared statements which reduces the load on
+the database server and can dramatically increase performance.)
+
+The Win32::ODBC module only works on Win32 systems.
+The DBI and DBD::ODBC are very portable and work on Win32 and Unix.
+
+The DBI and DBD::ODBC modules are supplied as a standard part of the
+Perl 5.004 binary distribution for Win32 (they don't work with the
+older, non-standard, ActiveState port).
+
+Scripts written with the DBI and DBD::ODBC are faster than Win32::ODBC
+on Win32 and are trivially portable to other supported database types.
+
+The DBI offers optional automatic printing or die()ing on errors which
+makes applications simpler and more robust.
+
+The current DBD::ODBC driver version 0.16 is new and not yet fully stable.
+A new release is due soon [relative to the date of the next TPJ issue :-]
+and will be much improved and offer more ODBC functionality.
+
+To summarise: The Win32::ODBC module is your best choice if you need
+access to more of the ODBC API than the DBI gives you. Otherwise, the
+DBI and DBD::ODBC combination may be your best bet.
+
+Chapter 7 of "Programming the Perl DBI" covers this topic in far more
+detail and should be consulted.
+
+=head2 3.3 Is DBI supported under Windows 95 / NT platforms?
+
+Finally, yes! Jeff Urlwin has been working diligently on building
+I<DBI> and I<DBD::ODBC> under these platforms, and, with the
+advent of a stabler perl and a port of I<MakeMaker>, the project has
+come on by great leaps and bounds.
+
+The I<DBI> and I<DBD::Oracle> Win32 ports are now a standard part of DBI,
+so, downloading I<DBI> of version higher than I<0.81> should work fine as
+should using the most recent I<DBD::Oracle> version.
+
+=head2 3.4 Can I access Microsoft Access or SQL-Server databases with DBI?
+
+Yes, use the I<DBD::ODBC> driver.
+
+=head2 3.5 Is there a DBD for <I<insert favourite database here>>?
+
+First check if a driver is available on CPAN by searching for the name of the
+database (including common abbreviations and aliases).
+
+Here's a general query that'll match all distributions:
+
+ http://search.cpan.org/search?query=DBD&mode=dist
+
+If you can't find a driver that way, you could check if the database supports
+ODBC drivers. If so then you could probably use the DBD::ODBC driver:
+
+ http://search.cpan.org/dist/DBD-ODBC/
+
+If not, then try asking on the dbi-users mailing list.
+
+=head2 3.6 What's DBM? And why should I use DBI instead?
+
+Extracted from ``I<DBI - The Database Interface for Perl 5>'':
+
+ ``UNIX was originally blessed with simple file-based ``databases'', namely
+ the dbm system. dbm lets you store data in files, and retrieve
+ that data quickly. However, it also has serious drawbacks.
+
+ File Locking
+
+ The dbm systems did not allow particularly robust file locking
+ capabilities, nor any capability for correcting problems arising through
+ simultaneous writes [ to the database ].
+
+ Arbitrary Data Structures
+
+ The dbm systems only allows a single fixed data structure:
+ key-value pairs. That value could be a complex object, such as a
+ [ C ] struct, but the key had to be unique. This was a large
+ limitation on the usefulness of dbm systems.
+
+ However, dbm systems still provide a useful function for users with
+ simple datasets and limited resources, since they are fast, robust and
+ extremely well-tested. Perl modules to access dbm systems have now
+ been integrated into the core Perl distribution via the
+ AnyDBM_File module.''
+
+To sum up, DBM is a perfectly satisfactory solution for essentially read-only
+databases, or small and simple datasets. However, for more
+scaleable dataset handling, not to mention robust transactional locking,
+users are recommended to use a more powerful database engine I<via> I<DBI>.
+
+Chapter 2 of "Programming the Perl DBI" discusses DBM files in detail.
+
+=head2 3.7 What database do you recommend me using?
+
+This is a particularly thorny area in which an objective answer is difficult
+to come by, since each dataset, proposed usage and system configuration
+differs from person to person.
+
+From the current author's point of view, if the dataset is relatively
+small, being tables of less than 1 million rows, and less than 1000 tables
+in a given database, then I<mSQL> is a perfectly acceptable solution
+to your problem. This database is extremely cheap, is wonderfully robust
+and has excellent support. More information is available on the Hughes
+Technology WWW site at:
+
+ http://www.hughes.com.au
+
+You may also wish to look at MySQL which is a more powerful database engine
+that has a similar feel to mSQL.
+
+ http://www.tcx.se
+
+If the dataset is larger than 1 million row tables or 1000 tables, or if you
+have either more money, or larger machines, I would recommend I<Oracle RDBMS>.
+Oracle's WWW site is an excellent source of more information.
+
+ http://www.oracle.com
+
+I<Informix> is another high-end RDBMS that is worth considering. There are
+several differences between Oracle and Informix which are too complex for
+this document to detail. Information on Informix can be found on their
+WWW site at:
+
+ http://www.informix.com
+
+In the case of WWW fronted applications, I<mSQL> may be a better option
+due to slow connection times between a CGI script and the Oracle RDBMS and
+also the amount of resource each Oracle connection will consume. I<mSQL>
+is lighter resource-wise and faster.
+
+These views are not necessarily representative of anyone else's opinions,
+and do not reflect any corporate sponsorship or views. They are provided
+I<as-is>.
+
+=head2 3.8 Is <I<insert feature here>> supported in DBI?
+
+Given that we're making the assumption that the feature you have requested
+is a non-standard database-specific feature, then the answer will be I<no>.
+
+DBI reflects a I<generic> API that will work for most databases, and has
+no database-specific functionality.
+
+However, driver authors may, if they so desire, include hooks to database-specific
+functionality through the C<func()> method defined in the DBI API.
+Script developers should note that use of functionality provided I<via>
+the C<func()> methods is very unlikely to be portable across databases.
+
+=head1 Programming Questions
+
+=head2 4.1 Is DBI any use for CGI programming?
+
+In a word, yes! DBI is hugely useful for CGI programming! In fact, I would
+tentatively say that CGI programming is one of two top uses for DBI.
+
+DBI confers the ability to CGI programmers to power WWW-fronted databases
+to their users, which provides users with vast quantities of ordered
+data to play with. DBI also provides the possibility that, if a site is
+receiving far too much traffic than their database server can cope with, they
+can upgrade the database server behind the scenes with no alterations to
+the CGI scripts.
+
+=head2 4.2 How do I get faster connection times with DBD::Oracle and CGI?
+
+ Contributed by John D. Groenveld
+
+The Apache C<httpd> maintains a pool of C<httpd> children to service client
+requests.
+
+Using the Apache I<mod_perl> module by I<Doug MacEachern>, the perl
+interpreter is embedded with the C<httpd> children. The CGI, DBI, and your
+other favorite modules can be loaded at the startup of each child. These
+modules will not be reloaded unless changed on disk.
+
+For more information on Apache, see the Apache Project's WWW site:
+
+ http://www.apache.org
+
+The I<mod_perl> module can be downloaded from CPAN I<via>:
+
+ http://www.perl.com/cgi-bin/cpan_mod?module=Apache
+
+=head2 4.3 How do I get persistent connections with DBI and CGI?
+
+ Contributed by John D. Groenveld
+
+Using Edmund Mergl's I<Apache::DBI> module, database logins are stored in a
+hash with each of these C<httpd> child. If your application is based on a
+single database user, this connection can be started with each child.
+Currently, database connections cannot be shared between C<httpd> children.
+
+I<Apache::DBI> can be downloaded from CPAN I<via>:
+
+ http://www.perl.com/cgi-bin/cpan_mod?module=Apache::DBI
+
+=head2 4.4 ``When I run a perl script from the command line, it works, but, when I run it under the C<httpd>, it fails!'' Why?
+
+Basically, a good chance this is occurring is due to the fact that the user
+that you ran it from the command line as has a correctly configured set of
+environment variables, in the case of I<DBD::Oracle>, variables like
+C<ORACLE_HOME>, C<ORACLE_SID> or C<TWO_TASK>.
+
+The C<httpd> process usually runs under the user id of C<nobody>,
+which implies there is no configured environment. Any scripts attempting to
+execute in this situation will correctly fail.
+
+One way to solve this problem is to set the environment for your database in a
+C<BEGIN { }> block at the top of your script. Another technique is to configure
+your WWW server to pass-through certain environment variables to your CGI
+scripts.
+
+Similarly, you should check your C<httpd> error logfile for any clues,
+as well as the ``Idiot's Guide To Solving Perl / CGI Problems'' and
+``Perl CGI Programming FAQ'' for further information. It is
+unlikely the problem is DBI-related.
+
+The ``Idiot's Guide To Solving Perl / CGI Problems'' can be located at:
+
+ http://www.perl.com/perl/faq/index.html
+
+as can the ``Perl CGI Programming FAQ''. Read I<BOTH> these documents
+carefully!
+
+=head2 4.5 How do I get the number of rows returned from a C<SELECT> statement?
+
+Count them. Read the DBI docs for the C<rows()> method.
+
+=head1 Miscellaneous Questions
+
+=head2 5.1 Can I do multi-threading with DBI?
+
+Perl version 5.005 and later can be built to support multi-threading.
+The DBI, as of version 1.02, does not yet support multi-threading
+so it would be unsafe to let more than one thread enter the DBI at
+the same time.
+
+It is expected that some future version of the DBI will at least be
+thread-safe (but not thread-hot) by automatically blocking threads
+intering the DBI while it's already in use.
+
+=head2 5.2 How do I handle BLOB data with DBI?
+
+Handling BLOB data with the DBI is very straight-forward. BLOB columns are
+specified in a SELECT statement as per normal columns. However, you also
+need to specify a maximum BLOB size that the <I>database handle</I> can
+fetch using the C<LongReadLen> attribute.
+
+For example:
+
+ ### $dbh is a connected database handle
+ $sth = $dbh->prepare( "SELECT blob_column FROM blobby_table" );
+ $sth->execute;
+
+would fail.
+
+ ### $dbh is a connected database handle
+ ### Set the maximum BLOB size...
+ $dbh->{LongReadLen} = 16384; ### 16Kb...Not much of a BLOB!
+
+ $sth = $dbh->prepare( "..." );
+
+would succeed <I>provided no column values were larger than the specified
+value</I>.
+
+If the BLOB data is longer than the value of C<LongReadLen>, then an
+error will occur. However, the DBI provides an additional piece of
+functionality that will automatically truncate the fetched BLOB to the
+size of C<LongReadLen> if it is longer. This does not cause an error to
+occur, but may make your fetched BLOB data useless.
+
+This behaviour is regulated by the C<LongTruncOk> attribute which is
+defaultly set to a false value ( thus making overlong BLOB fetches fail ).
+
+ ### Set BLOB handling such that it's 16Kb and can be truncated
+ $dbh->{LongReadLen} = 16384;
+ $dbh->{LongTruncOk} = 1;
+
+Truncation of BLOB data may not be a big deal in cases where the BLOB
+contains run-length encoded data, but data containing checksums at the end,
+for example, a ZIP file, would be rendered useless.
+
+=head2 5.3 How can I invoke stored procedures with DBI?
+
+The DBI does not define a database-independent way of calling stored procedures.
+
+However, most database that support them also provide a way to call
+them from SQL statements - and the DBI certainly supports that.
+
+So, assuming that you have created a stored procedure within the target
+database, I<eg>, an Oracle database, you can use C<$dbh>->C<do()> to
+immediately execute the procedure. For example,
+
+ $dbh->do( "BEGIN someProcedure; END;" ); # Oracle-specific
+
+You should also be able to C<prepare> and C<execute>, which is
+the recommended way if you'll be calling the procedure often.
+
+=head2 5.4 How can I get return values from stored procedures with DBI?
+
+ Contributed by Jeff Urlwin
+
+ $sth = $dbh->prepare( "BEGIN foo(:1, :2, :3); END;" );
+ $sth->bind_param(1, $a);
+ $sth->bind_param_inout(2, \$path, 2000);
+ $sth->bind_param_inout(3, \$success, 2000);
+ $sth->execute;
+
+Remember to perform error checking, though! ( Or use the C<RaiseError>
+attribute ).
+
+=head2 5.5 How can I create or drop a database with DBI?
+
+Database creation and deletion are concepts that are entirely too abstract
+to be adequately supported by DBI. For example, Oracle does not support the
+concept of dropping a database at all! Also, in Oracle, the database
+I<server> essentially I<is> the database, whereas in mSQL, the
+server process runs happily without any databases created in it. The
+problem is too disparate to attack in a worthwhile way.
+
+Some drivers, therefore, support database creation and deletion through
+the private C<func()> methods. You should check the documentation for
+the drivers you are using to see if they support this mechanism.
+
+=head2 5.6 How can I C<commit> or C<rollback> a statement with DBI?
+
+See the C<commit()> and C<rollback()> methods in the DBI Specification.
+
+Chapter 6 of "Programming the Perl DBI" discusses transaction handling within
+the context of DBI in more detail.
+
+=head2 5.7 How are C<NULL> values handled by DBI?
+
+C<NULL> values in DBI are specified to be treated as the value C<undef>.
+C<NULL>s can be inserted into databases as C<NULL>, for example:
+
+ $rv = $dbh->do( "INSERT INTO table VALUES( NULL )" );
+
+but when queried back, the C<NULL>s should be tested against C<undef>.
+This is standard across all drivers.
+
+=head2 5.8 What are these C<func()> methods all about?
+
+The C<func()> method is defined within DBI as being an entry point
+for database-specific functionality, I<eg>, the ability to create or
+drop databases. Invoking these driver-specific methods is simple, for example,
+to invoke a C<createDatabase> method that has one argument, we would
+write:
+
+ $rv =$dbh->func( 'argument', 'createDatabase' );
+
+Software developers should note that the C<func()> methods are
+non-portable between databases.
+
+=head2 5.9 Is DBI Year 2000 Compliant?
+
+DBI has no knowledge of understanding of what dates are. Therefore, DBI
+itself does not have a Year 2000 problem. Individual drivers may use date
+handling code internally and therefore be potentially susceptible to the
+Year 2000 problem, but this is unlikely.
+
+You may also wish to read the ``Does Perl have a Year 2000 problem?'' section
+of the Perl FAQ at:
+
+ http://www.perl.com/CPAN/doc/FAQs/FAQ/PerlFAQ.html
+
+=head1 Support and Training
+
+The Perl5 Database Interface is I<FREE> software. IT COMES WITHOUT WARRANTY
+OF ANY KIND. See the DBI README for more details.
+
+However, some organizations are providing either technical support or
+training programs on DBI. The present author has no knowledge as
+to the quality of these services. The links are included for reference
+purposes only and should not be regarded as recommendations in any way.
+I<Caveat emptor>.
+
+=head2 Commercial Support
+
+=over 4
+
+=item The Perl Clinic
+
+The Perl Clinic provides commercial support for I<Perl> and Perl
+related problems, including the I<DBI> and its drivers. Support is
+provided by the company with whom Tim Bunce, author of I<DBI> and
+I<DBD::Oracle>, works and ActiveState. For more information on their
+services, please see:
+
+ http://www.perlclinic.com
+
+=back
+
+=head2 Training
+
+=over 4
+
+=item Westlake Solutions
+
+A hands-on class for experienced Perl CGI developers that teaches
+how to write database-connected CGI scripts using Perl and DBI.pm. This
+course, along with four other courses on CGI scripting with Perl, is
+taught in Washington, DC; Arlington, Virginia; and on-site worldwide upon
+request.
+
+See:
+
+ http://www.westlake.com/training
+
+for more details.
+
+=back
+
+=head1 Other References
+
+In this section, we present some miscellaneous WWW links that may be of
+some interest to DBI users. These are not verified and may result in
+unknown sites or missing documents.
+
+ http://www-ccs.cs.umass.edu/db.html
+ http://www.odmg.org/odmg93/updates_dbarry.html
+ http://www.jcc.com/sql_stnd.html
+
+=head1 AUTHOR
+
+Alligator Descartes.
+Portions are Copyright their original stated authors.
+
+=head1 COPYRIGHT
+
+This document is Copyright (c)1994-2000 Alligator Descartes, with portions
+Copyright (c)1994-2000 their original authors. This module is released under
+the 'Artistic' license which you can find in the perl distribution.
+
+This document is Copyright (c)1997-2000 Alligator Descartes. All rights reserved.
+Permission to distribute this document, in full or in part, via email,
+Usenet, ftp archives or http is granted providing that no charges are involved,
+reasonable attempt is made to use the most current version and all credits
+and copyright notices are retained ( the I<AUTHOR> and I<COPYRIGHT> sections ).
+Requests for other distribution rights, including incorporation into
+commercial products, such as books, magazine articles or CD-ROMs should be
+made to Alligator Descartes.
+
+=for html
+<!--#include virtual="/technology/perl/DBI/templatebottom.html" -->
+</BODY>
+</HTML>
diff --git a/lib/DBI/Gofer/Execute.pm b/lib/DBI/Gofer/Execute.pm
new file mode 100644
index 0000000..7d75df2
--- /dev/null
+++ b/lib/DBI/Gofer/Execute.pm
@@ -0,0 +1,900 @@
+package DBI::Gofer::Execute;
+
+# $Id: Execute.pm 14282 2010-07-26 00:12:54Z theory $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+use Carp;
+
+use DBI qw(dbi_time);
+use DBI::Gofer::Request;
+use DBI::Gofer::Response;
+
+use base qw(DBI::Util::_accessor);
+
+our $VERSION = sprintf("0.%06d", q$Revision: 14282 $ =~ /(\d+)/o);
+
+our @all_dbh_methods = sort map { keys %$_ } $DBI::DBI_methods{db}, $DBI::DBI_methods{common};
+our %all_dbh_methods = map { $_ => (DBD::_::db->can($_)||undef) } @all_dbh_methods;
+
+our $local_log = $ENV{DBI_GOFER_LOCAL_LOG}; # do extra logging to stderr
+
+our $current_dbh; # the dbh we're using for this request
+
+
+# set trace for server-side gofer
+# Could use DBI_TRACE env var when it's an unrelated separate process
+# but using DBI_GOFER_TRACE makes testing easier for subprocesses (eg stream)
+DBI->trace(split /=/, $ENV{DBI_GOFER_TRACE}, 2) if $ENV{DBI_GOFER_TRACE};
+
+
+# define valid configuration attributes (args to new())
+# the values here indicate the basic type of values allowed
+my %configuration_attributes = (
+ gofer_execute_class => 1,
+ default_connect_dsn => 1,
+ forced_connect_dsn => 1,
+ default_connect_attributes => {},
+ forced_connect_attributes => {},
+ track_recent => 1,
+ check_request_sub => sub {},
+ check_response_sub => sub {},
+ forced_single_resultset => 1,
+ max_cached_dbh_per_drh => 1,
+ max_cached_sth_per_dbh => 1,
+ forced_response_attributes => {},
+ forced_gofer_random => 1,
+ stats => {},
+);
+
+__PACKAGE__->mk_accessors(
+ keys %configuration_attributes
+);
+
+
+
+sub new {
+ my ($self, $args) = @_;
+ $args->{default_connect_attributes} ||= {};
+ $args->{forced_connect_attributes} ||= {};
+ $args->{max_cached_sth_per_dbh} ||= 1000;
+ $args->{stats} ||= {};
+ return $self->SUPER::new($args);
+}
+
+
+sub valid_configuration_attributes {
+ my $self = shift;
+ return { %configuration_attributes };
+}
+
+
+my %extra_attr = (
+ # Only referenced if the driver doesn't support private_attribute_info method.
+ # What driver-specific attributes should be returned for the driver being used?
+ # keyed by $dbh->{Driver}{Name}
+ # XXX for sth should split into attr specific to resultsets (where NUM_OF_FIELDS > 0) and others
+ # which would reduce processing/traffic for non-select statements
+ mysql => {
+ dbh => [qw(
+ mysql_errno mysql_error mysql_hostinfo mysql_info mysql_insertid
+ mysql_protoinfo mysql_serverinfo mysql_stat mysql_thread_id
+ )],
+ sth => [qw(
+ mysql_is_blob mysql_is_key mysql_is_num mysql_is_pri_key mysql_is_auto_increment
+ mysql_length mysql_max_length mysql_table mysql_type mysql_type_name mysql_insertid
+ )],
+ # XXX this dbh_after_sth stuff is a temporary, but important, hack.
+ # should be done via hash instead of arrays where the hash value contains
+ # flags that can indicate which attributes need to be handled in this way
+ dbh_after_sth => [qw(
+ mysql_insertid
+ )],
+ },
+ Pg => {
+ dbh => [qw(
+ pg_protocol pg_lib_version pg_server_version
+ pg_db pg_host pg_port pg_default_port
+ pg_options pg_pid
+ )],
+ sth => [qw(
+ pg_size pg_type pg_oid_status pg_cmd_status
+ )],
+ },
+ Sybase => {
+ dbh => [qw(
+ syb_dynamic_supported syb_oc_version syb_server_version syb_server_version_string
+ )],
+ sth => [qw(
+ syb_types syb_proc_status syb_result_type
+ )],
+ },
+ SQLite => {
+ dbh => [qw(
+ sqlite_version
+ )],
+ sth => [qw(
+ )],
+ },
+ ExampleP => {
+ dbh => [qw(
+ examplep_private_dbh_attrib
+ )],
+ sth => [qw(
+ examplep_private_sth_attrib
+ )],
+ dbh_after_sth => [qw(
+ examplep_insertid
+ )],
+ },
+);
+
+
+sub _connect {
+ my ($self, $request) = @_;
+
+ my $stats = $self->{stats};
+
+ # discard CachedKids from time to time
+ if (++$stats->{_requests_served} % 1000 == 0 # XXX config?
+ and my $max_cached_dbh_per_drh = $self->{max_cached_dbh_per_drh}
+ ) {
+ my %drivers = DBI->installed_drivers();
+ while ( my ($driver, $drh) = each %drivers ) {
+ next unless my $CK = $drh->{CachedKids};
+ next unless keys %$CK > $max_cached_dbh_per_drh;
+ next if $driver eq 'Gofer'; # ie transport=null when testing
+ DBI->trace_msg(sprintf "Clearing %d cached dbh from $driver",
+ scalar keys %$CK, $self->{max_cached_dbh_per_drh});
+ $_->{Active} && $_->disconnect for values %$CK;
+ %$CK = ();
+ }
+ }
+
+ # local $ENV{...} can leak, so only do it if required
+ local $ENV{DBI_AUTOPROXY} if $ENV{DBI_AUTOPROXY};
+
+ my ($connect_method, $dsn, $username, $password, $attr) = @{ $request->dbh_connect_call };
+ $connect_method ||= 'connect_cached';
+ $stats->{method_calls_dbh}->{$connect_method}++;
+
+ # delete attributes we don't want to affect the server-side
+ # (Could just do this on client-side and trust the client. DoS?)
+ delete @{$attr}{qw(Profile InactiveDestroy AutoInactiveDestroy HandleError HandleSetErr TraceLevel Taint TaintIn TaintOut)};
+
+ $dsn = $self->forced_connect_dsn || $dsn || $self->default_connect_dsn
+ or die "No forced_connect_dsn, requested dsn, or default_connect_dsn for request";
+
+ my $random = $self->{forced_gofer_random} || $ENV{DBI_GOFER_RANDOM} || '';
+
+ my $connect_attr = {
+
+ # the configured default attributes, if any
+ %{ $self->default_connect_attributes },
+
+ # pass username and password as attributes
+ # then they can be overridden by forced_connect_attributes
+ Username => $username,
+ Password => $password,
+
+ # the requested attributes
+ %$attr,
+
+ # force some attributes the way we'd like them
+ PrintWarn => $local_log,
+ PrintError => $local_log,
+
+ # the configured default attributes, if any
+ %{ $self->forced_connect_attributes },
+
+ # RaiseError must be enabled
+ RaiseError => 1,
+
+ # reset Executed flag (of the cached handle) so we can use it to tell
+ # if errors happened before the main part of the request was executed
+ Executed => 0,
+
+ # ensure this connect_cached doesn't have the same args as the client
+ # because that causes subtle issues if in the same process (ie transport=null)
+ # include pid to avoid problems with forking (ie null transport in mod_perl)
+ # include gofer-random to avoid random behaviour leaking to other handles
+ dbi_go_execute_unique => join("|", __PACKAGE__, $$, $random),
+ };
+
+ # XXX implement our own private connect_cached method? (with rate-limited ping)
+ my $dbh = DBI->$connect_method($dsn, undef, undef, $connect_attr);
+
+ $dbh->{ShowErrorStatement} = 1 if $local_log;
+
+ # XXX should probably just be a Callbacks => arg to connect_cached
+ # with a cache of pre-built callback hooks (memoized, without $self)
+ if (my $random = $self->{forced_gofer_random} || $ENV{DBI_GOFER_RANDOM}) {
+ $self->_install_rand_callbacks($dbh, $random);
+ }
+
+ my $CK = $dbh->{CachedKids};
+ if ($CK && keys %$CK > $self->{max_cached_sth_per_dbh}) {
+ %$CK = (); # clear all statement handles
+ }
+
+ #$dbh->trace(0);
+ $current_dbh = $dbh;
+ return $dbh;
+}
+
+
+sub reset_dbh {
+ my ($self, $dbh) = @_;
+ $dbh->set_err(undef, undef); # clear any error state
+}
+
+
+sub new_response_with_err {
+ my ($self, $rv, $eval_error, $dbh) = @_;
+ # this is the usual way to create a response for both success and failure
+ # capture err+errstr etc and merge in $eval_error ($@)
+
+ my ($err, $errstr, $state) = ($DBI::err, $DBI::errstr, $DBI::state);
+
+ if ($eval_error) {
+ $err ||= $DBI::stderr || 1; # ensure err is true
+ if ($errstr) {
+ $eval_error =~ s/(?: : \s)? \Q$errstr//x if $errstr;
+ chomp $errstr;
+ $errstr .= "; $eval_error";
+ }
+ else {
+ $errstr = $eval_error;
+ }
+ }
+ chomp $errstr if $errstr;
+
+ my $flags;
+ # (XXX if we ever add transaction support then we'll need to take extra
+ # steps because the commit/rollback would reset Executed before we get here)
+ $flags |= GOf_RESPONSE_EXECUTED if $dbh && $dbh->{Executed};
+
+ my $response = DBI::Gofer::Response->new({
+ rv => $rv,
+ err => $err,
+ errstr => $errstr,
+ state => $state,
+ flags => $flags,
+ });
+
+ return $response;
+}
+
+
+sub execute_request {
+ my ($self, $request) = @_;
+ # should never throw an exception
+
+ DBI->trace_msg("-----> execute_request\n");
+
+ my @warnings;
+ local $SIG{__WARN__} = sub {
+ push @warnings, @_;
+ warn @_ if $local_log;
+ };
+
+ my $response = eval {
+
+ if (my $check_request_sub = $self->check_request_sub) {
+ $request = $check_request_sub->($request, $self)
+ or die "check_request_sub failed";
+ }
+
+ my $version = $request->version || 0;
+ die ref($request)." version $version is not supported"
+ if $version < 0.009116 or $version >= 1;
+
+ ($request->is_sth_request)
+ ? $self->execute_sth_request($request)
+ : $self->execute_dbh_request($request);
+ };
+ $response ||= $self->new_response_with_err(undef, $@, $current_dbh);
+
+ if (my $check_response_sub = $self->check_response_sub) {
+ # not protected with an eval so it can choose to throw an exception
+ my $new = $check_response_sub->($response, $self, $request);
+ $response = $new if ref $new;
+ }
+
+ undef $current_dbh;
+
+ $response->warnings(\@warnings) if @warnings;
+ DBI->trace_msg("<----- execute_request\n");
+ return $response;
+}
+
+
+sub execute_dbh_request {
+ my ($self, $request) = @_;
+ my $stats = $self->{stats};
+
+ my $dbh;
+ my $rv_ref = eval {
+ $dbh = $self->_connect($request);
+ my $args = $request->dbh_method_call; # [ wantarray, 'method_name', @args ]
+ my $wantarray = shift @$args;
+ my $meth = shift @$args;
+ $stats->{method_calls_dbh}->{$meth}++;
+ my @rv = ($wantarray)
+ ? $dbh->$meth(@$args)
+ : scalar $dbh->$meth(@$args);
+ \@rv;
+ } || [];
+ my $response = $self->new_response_with_err($rv_ref, $@, $dbh);
+
+ return $response if not $dbh;
+
+ # does this request also want any dbh attributes returned?
+ if (my $dbh_attributes = $request->dbh_attributes) {
+ $response->dbh_attributes( $self->gather_dbh_attributes($dbh, $dbh_attributes) );
+ }
+
+ if ($rv_ref and my $lid_args = $request->dbh_last_insert_id_args) {
+ $stats->{method_calls_dbh}->{last_insert_id}++;
+ my $id = $dbh->last_insert_id( @$lid_args );
+ $response->last_insert_id( $id );
+ }
+
+ if ($rv_ref and UNIVERSAL::isa($rv_ref->[0],'DBI::st')) {
+ # dbh_method_call was probably a metadata method like table_info
+ # that returns a statement handle, so turn the $sth into resultset
+ my $sth = $rv_ref->[0];
+ $response->sth_resultsets( $self->gather_sth_resultsets($sth, $request, $response) );
+ $response->rv("(sth)"); # don't try to return actual sth
+ }
+
+ # we're finished with this dbh for this request
+ $self->reset_dbh($dbh);
+
+ return $response;
+}
+
+
+sub gather_dbh_attributes {
+ my ($self, $dbh, $dbh_attributes) = @_;
+ my @req_attr_names = @$dbh_attributes;
+ if ($req_attr_names[0] eq '*') { # auto include std + private
+ shift @req_attr_names;
+ push @req_attr_names, @{ $self->_std_response_attribute_names($dbh) };
+ }
+ my %dbh_attr_values;
+ @dbh_attr_values{@req_attr_names} = $dbh->FETCH_many(@req_attr_names);
+
+ # XXX piggyback installed_methods onto dbh_attributes for now
+ $dbh_attr_values{dbi_installed_methods} = { DBI->installed_methods };
+
+ # XXX piggyback default_methods onto dbh_attributes for now
+ $dbh_attr_values{dbi_default_methods} = _get_default_methods($dbh);
+
+ return \%dbh_attr_values;
+}
+
+
+sub _std_response_attribute_names {
+ my ($self, $h) = @_;
+ $h = tied(%$h) || $h; # switch to inner handle
+
+ # cache the private_attribute_info data for each handle
+ # XXX might be better to cache it in the executor
+ # as it's unlikely to change
+ # or perhaps at least cache it in the dbh even for sth
+ # as the sth are typically very short lived
+
+ my ($dbh, $h_type, $driver_name, @attr_names);
+
+ if ($dbh = $h->{Database}) { # is an sth
+
+ # does the dbh already have the answer cached?
+ return $dbh->{private_gofer_std_attr_names_sth} if $dbh->{private_gofer_std_attr_names_sth};
+
+ ($h_type, $driver_name) = ('sth', $dbh->{Driver}{Name});
+ push @attr_names, qw(NUM_OF_PARAMS NUM_OF_FIELDS NAME TYPE NULLABLE PRECISION SCALE);
+ }
+ else { # is a dbh
+ return $h->{private_gofer_std_attr_names_dbh} if $h->{private_gofer_std_attr_names_dbh};
+
+ ($h_type, $driver_name, $dbh) = ('dbh', $h->{Driver}{Name}, $h);
+ # explicitly add these because drivers may have different defaults
+ # add Name so the client gets the real Name of the connection
+ push @attr_names, qw(ChopBlanks LongReadLen LongTruncOk ReadOnly Name);
+ }
+
+ if (my $pai = $h->private_attribute_info) {
+ push @attr_names, keys %$pai;
+ }
+ else {
+ push @attr_names, @{ $extra_attr{ $driver_name }{$h_type} || []};
+ }
+ if (my $fra = $self->{forced_response_attributes}) {
+ push @attr_names, @{ $fra->{ $driver_name }{$h_type} || []}
+ }
+ $dbh->trace_msg("_std_response_attribute_names for $driver_name $h_type: @attr_names\n");
+
+ # cache into the dbh even for sth, as the dbh is usually longer lived
+ return $dbh->{"private_gofer_std_attr_names_$h_type"} = \@attr_names;
+}
+
+
+sub execute_sth_request {
+ my ($self, $request) = @_;
+ my $dbh;
+ my $sth;
+ my $last_insert_id;
+ my $stats = $self->{stats};
+
+ my $rv = eval {
+ $dbh = $self->_connect($request);
+
+ my $args = $request->dbh_method_call; # [ wantarray, 'method_name', @args ]
+ shift @$args; # discard wantarray
+ my $meth = shift @$args;
+ $stats->{method_calls_sth}->{$meth}++;
+ $sth = $dbh->$meth(@$args);
+ my $last = '(sth)'; # a true value (don't try to return actual sth)
+
+ # execute methods on the sth, e.g., bind_param & execute
+ if (my $calls = $request->sth_method_calls) {
+ for my $meth_call (@$calls) {
+ my $method = shift @$meth_call;
+ $stats->{method_calls_sth}->{$method}++;
+ $last = $sth->$method(@$meth_call);
+ }
+ }
+
+ if (my $lid_args = $request->dbh_last_insert_id_args) {
+ $stats->{method_calls_sth}->{last_insert_id}++;
+ $last_insert_id = $dbh->last_insert_id( @$lid_args );
+ }
+
+ $last;
+ };
+ my $response = $self->new_response_with_err($rv, $@, $dbh);
+
+ return $response if not $dbh;
+
+ $response->last_insert_id( $last_insert_id )
+ if defined $last_insert_id;
+
+ # even if the eval failed we still want to try to gather attribute values
+ # (XXX would be nice to be able to support streaming of results.
+ # which would reduce memory usage and latency for large results)
+ if ($sth) {
+ $response->sth_resultsets( $self->gather_sth_resultsets($sth, $request, $response) );
+ $sth->finish;
+ }
+
+ # does this request also want any dbh attributes returned?
+ my $dbh_attr_set;
+ if (my $dbh_attributes = $request->dbh_attributes) {
+ $dbh_attr_set = $self->gather_dbh_attributes($dbh, $dbh_attributes);
+ }
+ # XXX needs to be integrated with private_attribute_info() etc
+ if (my $dbh_attr = $extra_attr{$dbh->{Driver}{Name}}{dbh_after_sth}) {
+ @{$dbh_attr_set}{@$dbh_attr} = $dbh->FETCH_many(@$dbh_attr);
+ }
+ $response->dbh_attributes($dbh_attr_set) if $dbh_attr_set && %$dbh_attr_set;
+
+ $self->reset_dbh($dbh);
+
+ return $response;
+}
+
+
+sub gather_sth_resultsets {
+ my ($self, $sth, $request, $response) = @_;
+ my $resultsets = eval {
+
+ my $attr_names = $self->_std_response_attribute_names($sth);
+ my $sth_attr = {};
+ $sth_attr->{$_} = 1 for @$attr_names;
+
+ # let the client add/remove sth atributes
+ if (my $sth_result_attr = $request->sth_result_attr) {
+ $sth_attr->{$_} = $sth_result_attr->{$_}
+ for keys %$sth_result_attr;
+ }
+ my @sth_attr = grep { $sth_attr->{$_} } keys %$sth_attr;
+
+ my $row_count = 0;
+ my $rs_list = [];
+ while (1) {
+ my $rs = $self->fetch_result_set($sth, \@sth_attr);
+ push @$rs_list, $rs;
+ if (my $rows = $rs->{rowset}) {
+ $row_count += @$rows;
+ }
+ last if $self->{forced_single_resultset};
+ last if !($sth->more_results || $sth->{syb_more_results});
+ }
+
+ my $stats = $self->{stats};
+ $stats->{rows_returned_total} += $row_count;
+ $stats->{rows_returned_max} = $row_count
+ if $row_count > ($stats->{rows_returned_max}||0);
+
+ $rs_list;
+ };
+ $response->add_err(1, $@) if $@;
+ return $resultsets;
+}
+
+
+sub fetch_result_set {
+ my ($self, $sth, $sth_attr) = @_;
+ my %meta;
+ eval {
+ @meta{ @$sth_attr } = $sth->FETCH_many(@$sth_attr);
+ # we assume @$sth_attr contains NUM_OF_FIELDS
+ $meta{rowset} = $sth->fetchall_arrayref()
+ if (($meta{NUM_OF_FIELDS}||0) > 0); # is SELECT
+ # the fetchall_arrayref may fail with a 'not executed' kind of error
+ # because gather_sth_resultsets/fetch_result_set are called even if
+ # execute() failed, or even if there was no execute() call at all.
+ # The corresponding error goes into the resultset err, not the top-level
+ # response err, so in most cases this resultset err is never noticed.
+ };
+ if ($@) {
+ chomp $@;
+ $meta{err} = $DBI::err || 1;
+ $meta{errstr} = $DBI::errstr || $@;
+ $meta{state} = $DBI::state;
+ }
+ return \%meta;
+}
+
+
+sub _get_default_methods {
+ my ($dbh) = @_;
+ # returns a ref to a hash of dbh method names for methods which the driver
+ # hasn't overridden i.e., quote(). These don't need to be forwarded via gofer.
+ my $ImplementorClass = $dbh->{ImplementorClass} or die;
+ my %default_methods;
+ for my $method (@all_dbh_methods) {
+ my $dbi_sub = $all_dbh_methods{$method} || 42;
+ my $imp_sub = $ImplementorClass->can($method) || 42;
+ next if $imp_sub != $dbi_sub;
+ #warn("default $method\n");
+ $default_methods{$method} = 1;
+ }
+ return \%default_methods;
+}
+
+
+# XXX would be nice to make this a generic DBI module
+sub _install_rand_callbacks {
+ my ($self, $dbh, $dbi_gofer_random) = @_;
+
+ my $callbacks = $dbh->{Callbacks} || {};
+ my $prev = $dbh->{private_gofer_rand_fail_callbacks} || {};
+
+ # return if we've already setup this handle with callbacks for these specs
+ return if (($callbacks->{_dbi_gofer_random_spec}||'') eq $dbi_gofer_random);
+ #warn "$dbh # $callbacks->{_dbi_gofer_random_spec}";
+ $callbacks->{_dbi_gofer_random_spec} = $dbi_gofer_random;
+
+ my ($fail_percent, $fail_err, $delay_percent, $delay_duration, %spec_part, @spec_note);
+ my @specs = split /,/, $dbi_gofer_random;
+ for my $spec (@specs) {
+ if ($spec =~ m/^fail=(-?[.\d]+)%?$/) {
+ $fail_percent = $1;
+ $spec_part{fail} = $spec;
+ next;
+ }
+ if ($spec =~ m/^err=(-?\d+)$/) {
+ $fail_err = $1;
+ $spec_part{err} = $spec;
+ next;
+ }
+ if ($spec =~ m/^delay([.\d]+)=(-?[.\d]+)%?$/) {
+ $delay_duration = $1;
+ $delay_percent = $2;
+ $spec_part{delay} = $spec;
+ next;
+ }
+ elsif ($spec !~ m/^(\w+|\*)$/) {
+ warn "Ignored DBI_GOFER_RANDOM item '$spec' which isn't a config or a dbh method name";
+ next;
+ }
+
+ my $method = $spec;
+ if ($callbacks->{$method} && $prev->{$method} && $callbacks->{$method} != $prev->{$method}) {
+ warn "Callback for $method method already installed so DBI_GOFER_RANDOM callback not installed\n";
+ next;
+ }
+ unless (defined $fail_percent or defined $delay_percent) {
+ warn "Ignored DBI_GOFER_RANDOM item '$spec' because not preceeded by 'fail=N' and/or 'delayN=N'";
+ next;
+ }
+
+ push @spec_note, join(",", values(%spec_part), $method);
+ $callbacks->{$method} = $self->_mk_rand_callback($method, $fail_percent, $delay_percent, $delay_duration, $fail_err);
+ }
+ warn "DBI_GOFER_RANDOM failures/delays enabled: @spec_note\n"
+ if @spec_note;
+ $dbh->{Callbacks} = $callbacks;
+ $dbh->{private_gofer_rand_fail_callbacks} = $callbacks;
+}
+
+my %_mk_rand_callback_seqn;
+
+sub _mk_rand_callback {
+ my ($self, $method, $fail_percent, $delay_percent, $delay_duration, $fail_err) = @_;
+ my ($fail_modrate, $delay_modrate);
+ $fail_percent ||= 0; $fail_modrate = int(1/(-$fail_percent )*100) if $fail_percent;
+ $delay_percent ||= 0; $delay_modrate = int(1/(-$delay_percent)*100) if $delay_percent;
+ # note that $method may be "*" but that's not recommended or documented or wise
+ return sub {
+ my ($h) = @_;
+ my $seqn = ++$_mk_rand_callback_seqn{$method};
+ my $delay = ($delay_percent > 0) ? rand(100) < $delay_percent :
+ ($delay_percent < 0) ? !($seqn % $delay_modrate): 0;
+ my $fail = ($fail_percent > 0) ? rand(100) < $fail_percent :
+ ($fail_percent < 0) ? !($seqn % $fail_modrate) : 0;
+ #no warnings 'uninitialized';
+ #warn "_mk_rand_callback($fail_percent:$fail_modrate, $delay_percent:$delay_modrate): seqn=$seqn fail=$fail delay=$delay";
+ if ($delay) {
+ my $msg = "DBI_GOFER_RANDOM delaying execution of $method() by $delay_duration seconds\n";
+ # Note what's happening in a trace message. If the delay percent is an even
+ # number then use warn() instead so it's sent back to the client.
+ ($delay_percent % 2 == 1) ? warn($msg) : $h->trace_msg($msg);
+ select undef, undef, undef, $delay_duration; # allows floating point value
+ }
+ if ($fail) {
+ undef $_; # tell DBI to not call the method
+ # the "induced by DBI_GOFER_RANDOM" is special and must be included in errstr
+ # as it's checked for in a few places, such as the gofer retry logic
+ return $h->set_err($fail_err || $DBI::stderr,
+ "fake error from $method method induced by DBI_GOFER_RANDOM env var ($fail_percent%)");
+ }
+ return;
+ }
+}
+
+
+sub update_stats {
+ my ($self,
+ $request, $response,
+ $frozen_request, $frozen_response,
+ $time_received,
+ $store_meta, $other_meta,
+ ) = @_;
+
+ # should always have a response object here
+ carp("No response object provided") unless $request;
+
+ my $stats = $self->{stats};
+ $stats->{frozen_request_max_bytes} = length($frozen_request)
+ if $frozen_request
+ && length($frozen_request) > ($stats->{frozen_request_max_bytes}||0);
+ $stats->{frozen_response_max_bytes} = length($frozen_response)
+ if $frozen_response
+ && length($frozen_response) > ($stats->{frozen_response_max_bytes}||0);
+
+ my $recent;
+ if (my $track_recent = $self->{track_recent}) {
+ $recent = {
+ request => $frozen_request,
+ response => $frozen_response,
+ time_received => $time_received,
+ duration => dbi_time()-$time_received,
+ # for any other info
+ ($store_meta) ? (meta => $store_meta) : (),
+ };
+ $recent->{request_object} = $request
+ if !$frozen_request && $request;
+ $recent->{response_object} = $response
+ if !$frozen_response;
+ my @queues = ($stats->{recent_requests} ||= []);
+ push @queues, ($stats->{recent_errors} ||= [])
+ if !$response or $response->err;
+ for my $queue (@queues) {
+ push @$queue, $recent;
+ shift @$queue if @$queue > $track_recent;
+ }
+ }
+ return $recent;
+}
+
+
+1;
+__END__
+
+=head1 NAME
+
+DBI::Gofer::Execute - Executes Gofer requests and returns Gofer responses
+
+=head1 SYNOPSIS
+
+ $executor = DBI::Gofer::Execute->new( { ...config... });
+
+ $response = $executor->execute_request( $request );
+
+=head1 DESCRIPTION
+
+Accepts a DBI::Gofer::Request object, executes the requested DBI method calls,
+and returns a DBI::Gofer::Response object.
+
+Any error, including any internal 'fatal' errors are caught and converted into
+a DBI::Gofer::Response object.
+
+This module is usually invoked by a 'server-side' Gofer transport module.
+They usually have names in the "C<DBI::Gofer::Transport::*>" namespace.
+Examples include: L<DBI::Gofer::Transport::stream> and L<DBI::Gofer::Transport::mod_perl>.
+
+=head1 CONFIGURATION
+
+=head2 check_request_sub
+
+If defined, it must be a reference to a subroutine that will 'check' the request.
+It is passed the request object and the executor as its only arguments.
+
+The subroutine can either return the original request object or die with a
+suitable error message (which will be turned into a Gofer response).
+
+It can also construct and return a new request that should be executed instead
+of the original request.
+
+=head2 check_response_sub
+
+If defined, it must be a reference to a subroutine that will 'check' the response.
+It is passed the response object, the executor, and the request object.
+The sub may alter the response object and return undef, or return a new response object.
+
+This mechanism can be used to, for example, terminate the service if specific
+database errors are seen.
+
+=head2 forced_connect_dsn
+
+If set, this DSN is always used instead of the one in the request.
+
+=head2 default_connect_dsn
+
+If set, this DSN is used if C<forced_connect_dsn> is not set and the request does not contain a DSN itself.
+
+=head2 forced_connect_attributes
+
+A reference to a hash of connect() attributes. Individual attributes in
+C<forced_connect_attributes> will take precedence over corresponding attributes
+in the request.
+
+=head2 default_connect_attributes
+
+A reference to a hash of connect() attributes. Individual attributes in the
+request take precedence over corresponding attributes in C<default_connect_attributes>.
+
+=head2 max_cached_dbh_per_drh
+
+If set, the loaded drivers will be checked to ensure they don't have more than
+this number of cached connections. There is no default value. This limit is not
+enforced for every request.
+
+=head2 max_cached_sth_per_dbh
+
+If set, all the cached statement handles will be cleared once the number of
+cached statement handles rises above this limit. The default is 1000.
+
+=head2 forced_single_resultset
+
+If true, then only the first result set will be fetched and returned in the response.
+
+=head2 forced_response_attributes
+
+A reference to a data structure that can specify extra attributes to be returned in responses.
+
+ forced_response_attributes => {
+ DriverName => {
+ dbh => [ qw(dbh_attrib_name) ],
+ sth => [ qw(sth_attrib_name) ],
+ },
+ },
+
+This can be useful in cases where the driver has not implemented the
+private_attribute_info() method and DBI::Gofer::Execute's own fallback list of
+private attributes doesn't include the driver or attributes you need.
+
+=head2 track_recent
+
+If set, specifies the number of recent requests and responses that should be
+kept by the update_stats() method for diagnostics. See L<DBI::Gofer::Transport::mod_perl>.
+
+Note that this setting can significantly increase memory use. Use with caution.
+
+=head2 forced_gofer_random
+
+Enable forced random failures and/or delays for testing. See L</DBI_GOFER_RANDOM> below.
+
+=head1 DRIVER-SPECIFIC ISSUES
+
+Gofer needs to know about any driver-private attributes that should have their
+values sent back to the client.
+
+If the driver doesn't support private_attribute_info() method, and very few do,
+then the module fallsback to using some hard-coded details, if available, for
+the driver being used. Currently hard-coded details are available for the
+mysql, Pg, Sybase, and SQLite drivers.
+
+=head1 TESTING
+
+DBD::Gofer, DBD::Execute and related packages are well tested by executing the
+DBI test suite with DBI_AUTOPROXY configured to route all DBI calls via DBD::Gofer.
+
+Because Gofer includes timeout and 'retry on error' mechanisms there is a need
+for some way to trigger delays and/or errors. This can be done via the
+C<forced_gofer_random> configuration item, or else the DBI_GOFER_RANDOM environment
+variable.
+
+=head2 DBI_GOFER_RANDOM
+
+The value of the C<forced_gofer_random> configuration item (or else the
+DBI_GOFER_RANDOM environment variable) is treated as a series of tokens
+separated by commas.
+
+The tokens can be one of three types:
+
+=over 4
+
+=item fail=R%
+
+Set the current failure rate to R where R is a percentage.
+The value R can be floating point, e.g., C<fail=0.05%>.
+Negative values for R have special meaning, see below.
+
+=item err=N
+
+Sets the current failure err value to N (instead of the DBI's default 'standard
+err value' of 2000000000). This is useful when you want to simulate a
+specific error.
+
+=item delayN=R%
+
+Set the current random delay rate to R where R is a percentage, and set the
+current delay duration to N seconds. The values of R and N can be floating point,
+e.g., C<delay0.5=0.2%>. Negative values for R have special meaning, see below.
+
+If R is an odd number (R % 2 == 1) then a message is logged via warn() which
+will be returned to, and echoed at, the client.
+
+=item methodname
+
+Applies the current fail, err, and delay values to the named method.
+If neither a fail nor delay have been set yet then a warning is generated.
+
+=back
+
+For example:
+
+ $executor = DBI::Gofer::Execute->new( {
+ forced_gofer_random => "fail=0.01%,do,delay60=1%,execute",
+ });
+
+will cause the do() method to fail for 0.01% of calls, and the execute() method to
+fail 0.01% of calls and be delayed by 60 seconds on 1% of calls.
+
+If the percentage value (C<R>) is negative then instead of the failures being
+triggered randomly (via the rand() function) they are triggered via a sequence
+number. In other words "C<fail=-20%>" will mean every fifth call will fail.
+Each method has a distinct sequence number.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=cut
diff --git a/lib/DBI/Gofer/Request.pm b/lib/DBI/Gofer/Request.pm
new file mode 100644
index 0000000..d6464a6
--- /dev/null
+++ b/lib/DBI/Gofer/Request.pm
@@ -0,0 +1,200 @@
+package DBI::Gofer::Request;
+
+# $Id: Request.pm 12536 2009-02-24 22:37:09Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+
+use DBI qw(neat neat_list);
+
+use base qw(DBI::Util::_accessor);
+
+our $VERSION = sprintf("0.%06d", q$Revision: 12536 $ =~ /(\d+)/o);
+
+use constant GOf_REQUEST_IDEMPOTENT => 0x0001;
+use constant GOf_REQUEST_READONLY => 0x0002;
+
+our @EXPORT = qw(GOf_REQUEST_IDEMPOTENT GOf_REQUEST_READONLY);
+
+
+__PACKAGE__->mk_accessors(qw(
+ version
+ flags
+ dbh_connect_call
+ dbh_method_call
+ dbh_attributes
+ dbh_last_insert_id_args
+ sth_method_calls
+ sth_result_attr
+));
+__PACKAGE__->mk_accessors_using(make_accessor_autoviv_hashref => qw(
+ meta
+));
+
+
+sub new {
+ my ($self, $args) = @_;
+ $args->{version} ||= $VERSION;
+ return $self->SUPER::new($args);
+}
+
+
+sub reset {
+ my ($self, $flags) = @_;
+ # remove everything except connect and version
+ %$self = (
+ version => $self->{version},
+ dbh_connect_call => $self->{dbh_connect_call},
+ );
+ $self->{flags} = $flags if $flags;
+}
+
+
+sub init_request {
+ my ($self, $method_and_args, $dbh) = @_;
+ $self->reset( $dbh->{ReadOnly} ? GOf_REQUEST_READONLY : 0 );
+ $self->dbh_method_call($method_and_args);
+}
+
+
+sub is_sth_request {
+ return shift->{sth_result_attr};
+}
+
+
+sub statements {
+ my $self = shift;
+ my @statements;
+ if (my $dbh_method_call = $self->dbh_method_call) {
+ my $statement_method_regex = qr/^(?:do|prepare)$/;
+ my (undef, $method, $arg1) = @$dbh_method_call;
+ push @statements, $arg1 if $method && $method =~ $statement_method_regex;
+ }
+ return @statements;
+}
+
+
+sub is_idempotent {
+ my $self = shift;
+
+ if (my $flags = $self->flags) {
+ return 1 if $flags & (GOf_REQUEST_IDEMPOTENT|GOf_REQUEST_READONLY);
+ }
+
+ # else check if all statements are SELECT statement that don't include FOR UPDATE
+ my @statements = $self->statements;
+ # XXX this is very minimal for now, doesn't even allow comments before the select
+ # (and can't ever work for "exec stored_procedure_name" kinds of statements)
+ # XXX it also doesn't deal with multiple statements: prepare("select foo; update bar")
+ return 1 if @statements == grep {
+ m/^ \s* SELECT \b /xmsi && !m/ \b FOR \s+ UPDATE \b /xmsi
+ } @statements;
+
+ return 0;
+}
+
+
+sub summary_as_text {
+ my $self = shift;
+ my ($context) = @_;
+ my @s = '';
+
+ if ($context && %$context) {
+ my @keys = sort keys %$context;
+ push @s, join(", ", map { "$_=>".$context->{$_} } @keys);
+ }
+
+ my ($method, $dsn, $user, $pass, $attr) = @{ $self->dbh_connect_call };
+ $method ||= 'connect_cached';
+ $pass = '***' if defined $pass;
+ my $tmp = '';
+ if ($attr) {
+ $tmp = { %{$attr||{}} }; # copy so we can edit
+ $tmp->{Password} = '***' if exists $tmp->{Password};
+ $tmp = "{ ".neat_list([ %$tmp ])." }";
+ }
+ push @s, sprintf "dbh= $method(%s, %s)", neat_list([$dsn, $user, $pass]), $tmp;
+
+ if (my $flags = $self->flags) {
+ push @s, sprintf "flags: 0x%x", $flags;
+ }
+
+ if (my $dbh_attr = $self->dbh_attributes) {
+ push @s, sprintf "dbh->FETCH: %s", @$dbh_attr
+ if @$dbh_attr;
+ }
+
+ my ($wantarray, $meth, @args) = @{ $self->dbh_method_call };
+ my $args = neat_list(\@args);
+ $args =~ s/\n+/ /g;
+ push @s, sprintf "dbh->%s(%s)", $meth, $args;
+
+ if (my $lii_args = $self->dbh_last_insert_id_args) {
+ push @s, sprintf "dbh->last_insert_id(%s)", neat_list($lii_args);
+ }
+
+ for my $call (@{ $self->sth_method_calls || [] }) {
+ my ($meth, @args) = @$call;
+ ($args = neat_list(\@args)) =~ s/\n+/ /g;
+ push @s, sprintf "sth->%s(%s)", $meth, $args;
+ }
+
+ if (my $sth_attr = $self->sth_result_attr) {
+ push @s, sprintf "sth->FETCH: %s", %$sth_attr
+ if %$sth_attr;
+ }
+
+ return join("\n\t", @s) . "\n";
+}
+
+
+sub outline_as_text { # one-line version of summary_as_text
+ my $self = shift;
+ my @s = '';
+ my $neatlen = 80;
+
+ if (my $flags = $self->flags) {
+ push @s, sprintf "flags=0x%x", $flags;
+ }
+
+ my (undef, $meth, @args) = @{ $self->dbh_method_call };
+ push @s, sprintf "%s(%s)", $meth, neat_list(\@args, $neatlen);
+
+ for my $call (@{ $self->sth_method_calls || [] }) {
+ my ($meth, @args) = @$call;
+ push @s, sprintf "%s(%s)", $meth, neat_list(\@args, $neatlen);
+ }
+
+ my ($method, $dsn) = @{ $self->dbh_connect_call };
+ push @s, "$method($dsn,...)"; # dsn last as it's usually less interesting
+
+ (my $outline = join("; ", @s)) =~ s/\s+/ /g; # squish whitespace, incl newlines
+ return $outline;
+}
+
+1;
+
+=head1 NAME
+
+DBI::Gofer::Request - Encapsulate a request from DBD::Gofer to DBI::Gofer::Execute
+
+=head1 DESCRIPTION
+
+This is an internal class.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=cut
diff --git a/lib/DBI/Gofer/Response.pm b/lib/DBI/Gofer/Response.pm
new file mode 100644
index 0000000..b09782e
--- /dev/null
+++ b/lib/DBI/Gofer/Response.pm
@@ -0,0 +1,218 @@
+package DBI::Gofer::Response;
+
+# $Id: Response.pm 11565 2008-07-22 20:17:33Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+
+use Carp;
+use DBI qw(neat neat_list);
+
+use base qw(DBI::Util::_accessor Exporter);
+
+our $VERSION = sprintf("0.%06d", q$Revision: 11565 $ =~ /(\d+)/o);
+
+use constant GOf_RESPONSE_EXECUTED => 0x0001;
+
+our @EXPORT = qw(GOf_RESPONSE_EXECUTED);
+
+
+__PACKAGE__->mk_accessors(qw(
+ version
+ rv
+ err
+ errstr
+ state
+ flags
+ last_insert_id
+ dbh_attributes
+ sth_resultsets
+ warnings
+));
+__PACKAGE__->mk_accessors_using(make_accessor_autoviv_hashref => qw(
+ meta
+));
+
+
+sub new {
+ my ($self, $args) = @_;
+ $args->{version} ||= $VERSION;
+ chomp $args->{errstr} if $args->{errstr};
+ return $self->SUPER::new($args);
+}
+
+
+sub err_errstr_state {
+ my $self = shift;
+ return @{$self}{qw(err errstr state)};
+}
+
+sub executed_flag_set {
+ my $flags = shift->flags
+ or return 0;
+ return $flags & GOf_RESPONSE_EXECUTED;
+}
+
+
+sub add_err {
+ my ($self, $err, $errstr, $state, $trace) = @_;
+
+ # acts like the DBI's set_err method.
+ # this code copied from DBI::PurePerl's set_err method.
+
+ chomp $errstr if $errstr;
+ $state ||= '';
+ carp ref($self)."->add_err($err, $errstr, $state)"
+ if $trace and defined($err) || $errstr;
+
+ my ($r_err, $r_errstr, $r_state) = ($self->{err}, $self->{errstr}, $self->{state});
+
+ if ($r_errstr) {
+ $r_errstr .= sprintf " [err was %s now %s]", $r_err, $err
+ if $r_err && $err && $r_err ne $err;
+ $r_errstr .= sprintf " [state was %s now %s]", $r_state, $state
+ if $r_state and $r_state ne "S1000" && $state && $r_state ne $state;
+ $r_errstr .= "\n$errstr" if $r_errstr ne $errstr;
+ }
+ else {
+ $r_errstr = $errstr;
+ }
+
+ # assign if higher priority: err > "0" > "" > undef
+ my $err_changed;
+ if ($err # new error: so assign
+ or !defined $r_err # no existing warn/info: so assign
+ # new warn ("0" len 1) > info ("" len 0): so assign
+ or defined $err && length($err) > length($r_err)
+ ) {
+ $r_err = $err;
+ ++$err_changed;
+ }
+
+ $r_state = ($state eq "00000") ? "" : $state
+ if $state && $err_changed;
+
+ ($self->{err}, $self->{errstr}, $self->{state}) = ($r_err, $r_errstr, $r_state);
+
+ return undef;
+}
+
+
+sub summary_as_text {
+ my $self = shift;
+ my ($context) = @_;
+
+ my ($rv, $err, $errstr, $state) = ($self->{rv}, $self->{err}, $self->{errstr}, $self->{state});
+
+ my @s = sprintf("\trv=%s", (ref $rv) ? "[".neat_list($rv)."]" : neat($rv));
+ $s[-1] .= sprintf(", err=%s, errstr=%s", $err, neat($errstr))
+ if defined $err;
+ $s[-1] .= sprintf(", flags=0x%x", $self->{flags})
+ if defined $self->{flags};
+
+ push @s, "last_insert_id=%s", $self->last_insert_id
+ if defined $self->last_insert_id;
+
+ if (my $dbh_attr = $self->dbh_attributes) {
+ my @keys = sort keys %$dbh_attr;
+ push @s, sprintf "dbh= { %s }", join(", ", map { "$_=>".neat($dbh_attr->{$_},100) } @keys)
+ if @keys;
+ }
+
+ for my $rs (@{$self->sth_resultsets || []}) {
+ my ($rowset, $err, $errstr, $state)
+ = @{$rs}{qw(rowset err errstr state)};
+ my $summary = "rowset: ";
+ my $NUM_OF_FIELDS = $rs->{NUM_OF_FIELDS} || 0;
+ my $rows = $rowset ? @$rowset : 0;
+ if ($rowset || $NUM_OF_FIELDS > 0) {
+ $summary .= sprintf "%d rows, %d columns", $rows, $NUM_OF_FIELDS;
+ }
+ $summary .= sprintf ", err=%s, errstr=%s", $err, neat($errstr) if defined $err;
+ if ($rows) {
+ my $NAME = $rs->{NAME};
+ # generate
+ my @colinfo = map { "$NAME->[$_]=".neat($rowset->[0][$_], 30) } 0..@{$NAME}-1;
+ $summary .= sprintf " [%s]", join ", ", @colinfo;
+ $summary .= ",..." if $rows > 1;
+ # we can be a little more helpful for Sybase/MSSQL user
+ $summary .= " syb_result_type=$rs->{syb_result_type}"
+ if $rs->{syb_result_type} and $rs->{syb_result_type} != 4040;
+ }
+ push @s, $summary;
+ }
+ for my $w (@{$self->warnings || []}) {
+ chomp $w;
+ push @s, "warning: $w";
+ }
+ if ($context && %$context) {
+ my @keys = sort keys %$context;
+ push @s, join(", ", map { "$_=>".$context->{$_} } @keys);
+ }
+ return join("\n\t", @s). "\n";
+}
+
+
+sub outline_as_text { # one-line version of summary_as_text
+ my $self = shift;
+ my ($context) = @_;
+
+ my ($rv, $err, $errstr, $state) = ($self->{rv}, $self->{err}, $self->{errstr}, $self->{state});
+
+ my $s = sprintf("rv=%s", (ref $rv) ? "[".neat_list($rv)."]" : neat($rv));
+ $s .= sprintf(", err=%s %s", $err, neat($errstr))
+ if defined $err;
+ $s .= sprintf(", flags=0x%x", $self->{flags})
+ if $self->{flags};
+
+ if (my $sth_resultsets = $self->sth_resultsets) {
+ $s .= sprintf(", %d resultsets ", scalar @$sth_resultsets);
+
+ my @rs;
+ for my $rs (@{$self->sth_resultsets || []}) {
+ my $summary = "";
+ my ($rowset, $err, $errstr)
+ = @{$rs}{qw(rowset err errstr)};
+ my $NUM_OF_FIELDS = $rs->{NUM_OF_FIELDS} || 0;
+ my $rows = $rowset ? @$rowset : 0;
+ if ($rowset || $NUM_OF_FIELDS > 0) {
+ $summary .= sprintf "%dr x %dc", $rows, $NUM_OF_FIELDS;
+ }
+ $summary .= sprintf "%serr %s %s", ($summary?", ":""), $err, neat($errstr)
+ if defined $err;
+ push @rs, $summary;
+ }
+ $s .= join "; ", map { "[$_]" } @rs;
+ }
+
+ return $s;
+}
+
+
+1;
+
+=head1 NAME
+
+DBI::Gofer::Response - Encapsulate a response from DBI::Gofer::Execute to DBD::Gofer
+
+=head1 DESCRIPTION
+
+This is an internal class.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=cut
+
diff --git a/lib/DBI/Gofer/Serializer/Base.pm b/lib/DBI/Gofer/Serializer/Base.pm
new file mode 100644
index 0000000..53fc7e7
--- /dev/null
+++ b/lib/DBI/Gofer/Serializer/Base.pm
@@ -0,0 +1,64 @@
+package DBI::Gofer::Serializer::Base;
+
+# $Id: Base.pm 9949 2007-09-18 09:38:15Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+=head1 NAME
+
+DBI::Gofer::Serializer::Base - base class for Gofer serialization
+
+=head1 SYNOPSIS
+
+ $serializer = $serializer_class->new();
+
+ $string = $serializer->serialize( $data );
+ ($string, $deserializer_class) = $serializer->serialize( $data );
+
+ $data = $serializer->deserialize( $string );
+
+=head1 DESCRIPTION
+
+DBI::Gofer::Serializer::* classes implement a very minimal subset of the L<Data::Serializer> API.
+
+Gofer serializers are expected to be very fast and are not required to deal
+with anything other than non-blessed references to arrays and hashes, and plain scalars.
+
+=cut
+
+
+use strict;
+use warnings;
+
+use Carp qw(croak);
+
+our $VERSION = sprintf("0.%06d", q$Revision: 9949 $ =~ /(\d+)/o);
+
+
+sub new {
+ my $class = shift;
+ my $deserializer_class = $class->deserializer_class;
+ return bless { deserializer_class => $deserializer_class } => $class;
+}
+
+sub deserializer_class {
+ my $self = shift;
+ my $class = ref($self) || $self;
+ $class =~ s/^DBI::Gofer::Serializer:://;
+ return $class;
+}
+
+sub serialize {
+ my $self = shift;
+ croak ref($self)." has not implemented the serialize method";
+}
+
+sub deserialize {
+ my $self = shift;
+ croak ref($self)." has not implemented the deserialize method";
+}
+
+1;
diff --git a/lib/DBI/Gofer/Serializer/DataDumper.pm b/lib/DBI/Gofer/Serializer/DataDumper.pm
new file mode 100644
index 0000000..c6fc3a1
--- /dev/null
+++ b/lib/DBI/Gofer/Serializer/DataDumper.pm
@@ -0,0 +1,53 @@
+package DBI::Gofer::Serializer::DataDumper;
+
+use strict;
+use warnings;
+
+our $VERSION = sprintf("0.%06d", q$Revision: 9949 $ =~ /(\d+)/o);
+
+# $Id: DataDumper.pm 9949 2007-09-18 09:38:15Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+=head1 NAME
+
+DBI::Gofer::Serializer::DataDumper - Gofer serialization using DataDumper
+
+=head1 SYNOPSIS
+
+ $serializer = DBI::Gofer::Serializer::DataDumper->new();
+
+ $string = $serializer->serialize( $data );
+
+=head1 DESCRIPTION
+
+Uses DataDumper to serialize. Deserialization is not supported.
+The output of this class is only meant for human consumption.
+
+See also L<DBI::Gofer::Serializer::Base>.
+
+=cut
+
+use Data::Dumper;
+
+use base qw(DBI::Gofer::Serializer::Base);
+
+
+sub serialize {
+ my $self = shift;
+ local $Data::Dumper::Indent = 1;
+ local $Data::Dumper::Terse = 1;
+ local $Data::Dumper::Useqq = 0; # enabling this disables xs
+ local $Data::Dumper::Sortkeys = 1;
+ local $Data::Dumper::Quotekeys = 0;
+ local $Data::Dumper::Deparse = 0;
+ local $Data::Dumper::Purity = 0;
+ my $frozen = Data::Dumper::Dumper(shift);
+ return $frozen unless wantarray;
+ return ($frozen, $self->{deserializer_class});
+}
+
+1;
diff --git a/lib/DBI/Gofer/Serializer/Storable.pm b/lib/DBI/Gofer/Serializer/Storable.pm
new file mode 100644
index 0000000..9a571bd
--- /dev/null
+++ b/lib/DBI/Gofer/Serializer/Storable.pm
@@ -0,0 +1,59 @@
+package DBI::Gofer::Serializer::Storable;
+
+use strict;
+use warnings;
+
+use base qw(DBI::Gofer::Serializer::Base);
+
+# $Id: Storable.pm 9949 2007-09-18 09:38:15Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+=head1 NAME
+
+DBI::Gofer::Serializer::Storable - Gofer serialization using Storable
+
+=head1 SYNOPSIS
+
+ $serializer = DBI::Gofer::Serializer::Storable->new();
+
+ $string = $serializer->serialize( $data );
+ ($string, $deserializer_class) = $serializer->serialize( $data );
+
+ $data = $serializer->deserialize( $string );
+
+=head1 DESCRIPTION
+
+Uses Storable::nfreeze() to serialize and Storable::thaw() to deserialize.
+
+The serialize() method sets local $Storable::forgive_me = 1; so it doesn't
+croak if it encounters any data types that can't be serialized, such as code refs.
+
+See also L<DBI::Gofer::Serializer::Base>.
+
+=cut
+
+use Storable qw(nfreeze thaw);
+
+our $VERSION = sprintf("0.%06d", q$Revision: 9949 $ =~ /(\d+)/o);
+
+use base qw(DBI::Gofer::Serializer::Base);
+
+
+sub serialize {
+ my $self = shift;
+ local $Storable::forgive_me = 1; # for CODE refs etc
+ my $frozen = nfreeze(shift);
+ return $frozen unless wantarray;
+ return ($frozen, $self->{deserializer_class});
+}
+
+sub deserialize {
+ my $self = shift;
+ return thaw(shift);
+}
+
+1;
diff --git a/lib/DBI/Gofer/Transport/Base.pm b/lib/DBI/Gofer/Transport/Base.pm
new file mode 100644
index 0000000..b688689
--- /dev/null
+++ b/lib/DBI/Gofer/Transport/Base.pm
@@ -0,0 +1,176 @@
+package DBI::Gofer::Transport::Base;
+
+# $Id: Base.pm 12536 2009-02-24 22:37:09Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+use DBI;
+
+use base qw(DBI::Util::_accessor);
+
+use DBI::Gofer::Serializer::Storable;
+use DBI::Gofer::Serializer::DataDumper;
+
+
+our $VERSION = sprintf("0.%06d", q$Revision: 12536 $ =~ /(\d+)/o);
+
+
+__PACKAGE__->mk_accessors(qw(
+ trace
+ keep_meta_frozen
+ serializer_obj
+));
+
+
+# see also $ENV{DBI_GOFER_TRACE} in DBI::Gofer::Execute
+sub _init_trace { (split(/=/,$ENV{DBI_GOFER_TRACE}||0))[0] }
+
+
+sub new {
+ my ($class, $args) = @_;
+ $args->{trace} ||= $class->_init_trace;
+ $args->{serializer_obj} ||= DBI::Gofer::Serializer::Storable->new();
+ my $self = bless {}, $class;
+ $self->$_( $args->{$_} ) for keys %$args;
+ $self->trace_msg("$class->new({ @{[ %$args ]} })\n") if $self->trace;
+ return $self;
+}
+
+my $packet_header_text = "GoFER1:";
+my $packet_header_regex = qr/^GoFER(\d+):/;
+
+
+sub _freeze_data {
+ my ($self, $data, $serializer, $skip_trace) = @_;
+ my $frozen = eval {
+ $self->_dump("freezing $self->{trace} ".ref($data), $data)
+ if !$skip_trace and $self->trace;
+
+ local $data->{meta}; # don't include meta in serialization
+ $serializer ||= $self->{serializer_obj};
+ my ($data, $deserializer_class) = $serializer->serialize($data);
+
+ $packet_header_text . $data;
+ };
+ if ($@) {
+ chomp $@;
+ die "Error freezing ".ref($data)." object: $@";
+ }
+
+ # stash the frozen data into the data structure itself
+ # to make life easy for the client caching code in DBD::Gofer::Transport::Base
+ $data->{meta}{frozen} = $frozen if $self->keep_meta_frozen;
+
+ return $frozen;
+}
+# public aliases used by subclasses
+*freeze_request = \&_freeze_data;
+*freeze_response = \&_freeze_data;
+
+
+sub _thaw_data {
+ my ($self, $frozen_data, $serializer, $skip_trace) = @_;
+ my $data;
+ eval {
+ # check for and extract our gofer header and the info it contains
+ (my $frozen = $frozen_data) =~ s/$packet_header_regex//o
+ or die "does not have gofer header\n";
+ my ($t_version) = $1;
+ $serializer ||= $self->{serializer_obj};
+ $data = $serializer->deserialize($frozen);
+ die ref($serializer)."->deserialize didn't return a reference"
+ unless ref $data;
+ $data->{_transport}{version} = $t_version;
+
+ $data->{meta}{frozen} = $frozen_data if $self->keep_meta_frozen;
+ };
+ if ($@) {
+ chomp(my $err = $@);
+ # remove extra noise from Storable
+ $err =~ s{ at \S+?/Storable.pm \(autosplit into \S+?/Storable/thaw.al\) line \d+(, \S+ line \d+)?}{};
+ my $msg = sprintf "Error thawing: %s (data=%s)", $err, DBI::neat($frozen_data,50);
+ Carp::cluck("$msg, pid $$ stack trace follows:"); # XXX if $self->trace;
+ die $msg;
+ }
+ $self->_dump("thawing $self->{trace} ".ref($data), $data)
+ if !$skip_trace and $self->trace;
+
+ return $data;
+}
+# public aliases used by subclasses
+*thaw_request = \&_thaw_data;
+*thaw_response = \&_thaw_data;
+
+
+# this should probably live in the request and response classes
+# and the tace level passed in
+sub _dump {
+ my ($self, $label, $data) = @_;
+
+ # don't dump the binary
+ local $data->{meta}{frozen} if $data->{meta} && $data->{meta}{frozen};
+
+ my $trace_level = $self->trace;
+ my $summary;
+ if ($trace_level >= 4) {
+ require Data::Dumper;
+ local $Data::Dumper::Indent = 1;
+ local $Data::Dumper::Terse = 1;
+ local $Data::Dumper::Useqq = 0;
+ local $Data::Dumper::Sortkeys = 1;
+ local $Data::Dumper::Quotekeys = 0;
+ local $Data::Dumper::Deparse = 0;
+ local $Data::Dumper::Purity = 0;
+ $summary = Data::Dumper::Dumper($data);
+ }
+ elsif ($trace_level >= 2) {
+ $summary = eval { $data->summary_as_text } || $@ || "no summary available\n";
+ }
+ else {
+ $summary = eval { $data->outline_as_text."\n" } || $@ || "no summary available\n";
+ }
+ $self->trace_msg("$label: $summary");
+}
+
+
+sub trace_msg {
+ my ($self, $msg, $min_level) = @_;
+ $min_level = 1 unless defined $min_level;
+ # transport trace level can override DBI's trace level
+ $min_level = 0 if $self->trace >= $min_level;
+ return DBI->trace_msg("gofer ".$msg, $min_level);
+}
+
+1;
+
+=head1 NAME
+
+DBI::Gofer::Transport::Base - Base class for Gofer transports
+
+=head1 DESCRIPTION
+
+This is the base class for server-side Gofer transports.
+
+It's also the base class for the client-side base class L<DBD::Gofer::Transport::Base>.
+
+This is an internal class.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=cut
+
diff --git a/lib/DBI/Gofer/Transport/pipeone.pm b/lib/DBI/Gofer/Transport/pipeone.pm
new file mode 100644
index 0000000..d79c2eb
--- /dev/null
+++ b/lib/DBI/Gofer/Transport/pipeone.pm
@@ -0,0 +1,61 @@
+package DBI::Gofer::Transport::pipeone;
+
+# $Id: pipeone.pm 12536 2009-02-24 22:37:09Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+use DBI::Gofer::Execute;
+
+use base qw(DBI::Gofer::Transport::Base Exporter);
+
+our $VERSION = sprintf("0.%06d", q$Revision: 12536 $ =~ /(\d+)/o);
+
+our @EXPORT = qw(run_one_stdio);
+
+my $executor = DBI::Gofer::Execute->new();
+
+sub run_one_stdio {
+
+ my $transport = DBI::Gofer::Transport::pipeone->new();
+
+ my $frozen_request = do { local $/; <STDIN> };
+
+ my $response = $executor->execute_request( $transport->thaw_request($frozen_request) );
+
+ my $frozen_response = $transport->freeze_response($response);
+
+ print $frozen_response;
+
+ # no point calling $executor->update_stats(...) for pipeONE
+}
+
+1;
+__END__
+
+=head1 NAME
+
+DBI::Gofer::Transport::pipeone - DBD::Gofer server-side transport for pipeone
+
+=head1 SYNOPSIS
+
+See L<DBD::Gofer::Transport::pipeone>.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=cut
+
diff --git a/lib/DBI/Gofer/Transport/stream.pm b/lib/DBI/Gofer/Transport/stream.pm
new file mode 100644
index 0000000..49de550
--- /dev/null
+++ b/lib/DBI/Gofer/Transport/stream.pm
@@ -0,0 +1,76 @@
+package DBI::Gofer::Transport::stream;
+
+# $Id: stream.pm 12536 2009-02-24 22:37:09Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+use DBI qw(dbi_time);
+use DBI::Gofer::Execute;
+
+use base qw(DBI::Gofer::Transport::pipeone Exporter);
+
+our $VERSION = sprintf("0.%06d", q$Revision: 12536 $ =~ /(\d+)/o);
+
+our @EXPORT = qw(run_stdio_hex);
+
+my $executor = DBI::Gofer::Execute->new();
+
+sub run_stdio_hex {
+
+ my $transport = DBI::Gofer::Transport::stream->new();
+ local $| = 1;
+
+ DBI->trace_msg("$0 started (pid $$)\n");
+
+ local $\; # OUTPUT_RECORD_SEPARATOR
+ local $/ = "\012"; # INPUT_RECORD_SEPARATOR
+ while ( defined( my $encoded_request = <STDIN> ) ) {
+ my $time_received = dbi_time();
+ $encoded_request =~ s/\015?\012$//;
+
+ my $frozen_request = pack "H*", $encoded_request;
+ my $request = $transport->thaw_request( $frozen_request );
+
+ my $response = $executor->execute_request( $request );
+
+ my $frozen_response = $transport->freeze_response($response);
+ my $encoded_response = unpack "H*", $frozen_response;
+
+ print $encoded_response, "\015\012"; # autoflushed due to $|=1
+
+ # there's no way to access the stats currently
+ # so this just serves as a basic test and illustration of update_stats()
+ $executor->update_stats($request, $response, $frozen_request, $frozen_response, $time_received, 1);
+ }
+ DBI->trace_msg("$0 ending (pid $$)\n");
+}
+
+1;
+__END__
+
+=head1 NAME
+
+DBI::Gofer::Transport::stream - DBD::Gofer server-side transport for stream
+
+=head1 SYNOPSIS
+
+See L<DBD::Gofer::Transport::stream>.
+
+=head1 AUTHOR
+
+Tim Bunce, L<http://www.tim.bunce.name>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+=cut
diff --git a/lib/DBI/Profile.pm b/lib/DBI/Profile.pm
new file mode 100644
index 0000000..a468c05
--- /dev/null
+++ b/lib/DBI/Profile.pm
@@ -0,0 +1,949 @@
+package DBI::Profile;
+
+=head1 NAME
+
+DBI::Profile - Performance profiling and benchmarking for the DBI
+
+=head1 SYNOPSIS
+
+The easiest way to enable DBI profiling is to set the DBI_PROFILE
+environment variable to 2 and then run your code as usual:
+
+ DBI_PROFILE=2 prog.pl
+
+This will profile your program and then output a textual summary
+grouped by query when the program exits. You can also enable profiling by
+setting the Profile attribute of any DBI handle:
+
+ $dbh->{Profile} = 2;
+
+Then the summary will be printed when the handle is destroyed.
+
+Many other values apart from are possible - see L<"ENABLING A PROFILE"> below.
+
+=head1 DESCRIPTION
+
+The DBI::Profile module provides a simple interface to collect and
+report performance and benchmarking data from the DBI.
+
+For a more elaborate interface, suitable for larger programs, see
+L<DBI::ProfileDumper|DBI::ProfileDumper> and L<dbiprof|dbiprof>.
+For Apache/mod_perl applications see
+L<DBI::ProfileDumper::Apache|DBI::ProfileDumper::Apache>.
+
+=head1 OVERVIEW
+
+Performance data collection for the DBI is built around several
+concepts which are important to understand clearly.
+
+=over 4
+
+=item Method Dispatch
+
+Every method call on a DBI handle passes through a single 'dispatch'
+function which manages all the common aspects of DBI method calls,
+such as handling the RaiseError attribute.
+
+=item Data Collection
+
+If profiling is enabled for a handle then the dispatch code takes
+a high-resolution timestamp soon after it is entered. Then, after
+calling the appropriate method and just before returning, it takes
+another high-resolution timestamp and calls a function to record
+the information. That function is passed the two timestamps
+plus the DBI handle and the name of the method that was called.
+That data about a single DBI method call is called a I<profile sample>.
+
+=item Data Filtering
+
+If the method call was invoked by the DBI or by a driver then the call is
+ignored for profiling because the time spent will be accounted for by the
+original 'outermost' call for your code.
+
+For example, the calls that the selectrow_arrayref() method makes
+to prepare() and execute() etc. are not counted individually
+because the time spent in those methods is going to be allocated
+to the selectrow_arrayref() method when it returns. If this was not
+done then it would be very easy to double count time spent inside
+the DBI.
+
+=item Data Storage Tree
+
+The profile data is accumulated as 'leaves on a tree'. The 'path' through the
+branches of the tree to a particular leaf is determined dynamically for each sample.
+This is a key feature of DBI profiling.
+
+For each profiled method call the DBI walks along the Path and uses each value
+in the Path to step into and grow the Data tree.
+
+For example, if the Path is
+
+ [ 'foo', 'bar', 'baz' ]
+
+then the new profile sample data will be I<merged> into the tree at
+
+ $h->{Profile}->{Data}->{foo}->{bar}->{baz}
+
+But it's not very useful to merge all the call data into one leaf node (except
+to get an overall 'time spent inside the DBI' total). It's more common to want
+the Path to include dynamic values such as the current statement text and/or
+the name of the method called to show what the time spent inside the DBI was for.
+
+The Path can contain some 'magic cookie' values that are automatically replaced
+by corresponding dynamic values when they're used. These magic cookies always
+start with a punctuation character.
+
+For example a value of 'C<!MethodName>' in the Path causes the corresponding
+entry in the Data to be the name of the method that was called.
+For example, if the Path was:
+
+ [ 'foo', '!MethodName', 'bar' ]
+
+and the selectall_arrayref() method was called, then the profile sample data
+for that call will be merged into the tree at:
+
+ $h->{Profile}->{Data}->{foo}->{selectall_arrayref}->{bar}
+
+=item Profile Data
+
+Profile data is stored at the 'leaves' of the tree as references
+to an array of numeric values. For example:
+
+ [
+ 106, # 0: count of samples at this node
+ 0.0312958955764771, # 1: total duration
+ 0.000490069389343262, # 2: first duration
+ 0.000176072120666504, # 3: shortest duration
+ 0.00140702724456787, # 4: longest duration
+ 1023115819.83019, # 5: time of first sample
+ 1023115819.86576, # 6: time of last sample
+ ]
+
+After the first sample, later samples always update elements 0, 1, and 6, and
+may update 3 or 4 depending on the duration of the sampled call.
+
+=back
+
+=head1 ENABLING A PROFILE
+
+Profiling is enabled for a handle by assigning to the Profile
+attribute. For example:
+
+ $h->{Profile} = DBI::Profile->new();
+
+The Profile attribute holds a blessed reference to a hash object
+that contains the profile data and attributes relating to it.
+
+The class the Profile object is blessed into is expected to
+provide at least a DESTROY method which will dump the profile data
+to the DBI trace file handle (STDERR by default).
+
+All these examples have the same effect as each other:
+
+ $h->{Profile} = 0;
+ $h->{Profile} = "/DBI::Profile";
+ $h->{Profile} = DBI::Profile->new();
+ $h->{Profile} = {};
+ $h->{Profile} = { Path => [] };
+
+Similarly, these examples have the same effect as each other:
+
+ $h->{Profile} = 6;
+ $h->{Profile} = "6/DBI::Profile";
+ $h->{Profile} = "!Statement:!MethodName/DBI::Profile";
+ $h->{Profile} = { Path => [ '!Statement', '!MethodName' ] };
+
+If a non-blessed hash reference is given then the DBI::Profile
+module is automatically C<require>'d and the reference is blessed
+into that class.
+
+If a string is given then it is processed like this:
+
+ ($path, $module, $args) = split /\//, $string, 3
+
+ @path = split /:/, $path
+ @args = split /:/, $args
+
+ eval "require $module" if $module
+ $module ||= "DBI::Profile"
+
+ $module->new( Path => \@Path, @args )
+
+So the first value is used to select the Path to be used (see below).
+The second value, if present, is used as the name of a module which
+will be loaded and it's C<new> method called. If not present it
+defaults to DBI::Profile. Any other values are passed as arguments
+to the C<new> method. For example: "C<2/DBIx::OtherProfile/Foo:42>".
+
+Numbers can be used as a shorthand way to enable common Path values.
+The simplest way to explain how the values are interpreted is to show the code:
+
+ push @Path, "DBI" if $path_elem & 0x01;
+ push @Path, "!Statement" if $path_elem & 0x02;
+ push @Path, "!MethodName" if $path_elem & 0x04;
+ push @Path, "!MethodClass" if $path_elem & 0x08;
+ push @Path, "!Caller2" if $path_elem & 0x10;
+
+So "2" is the same as "!Statement" and "6" (2+4) is the same as
+"!Statement:!Method". Those are the two most commonly used values. Using a
+negative number will reverse the path. Thus "-6" will group by method name then
+statement.
+
+The splitting and parsing of string values assigned to the Profile
+attribute may seem a little odd, but there's a good reason for it.
+Remember that attributes can be embedded in the Data Source Name
+string which can be passed in to a script as a parameter. For
+example:
+
+ dbi:DriverName(Profile=>2):dbname
+ dbi:DriverName(Profile=>{Username}:!Statement/MyProfiler/Foo:42):dbname
+
+And also, if the C<DBI_PROFILE> environment variable is set then
+The DBI arranges for every driver handle to share the same profile
+object. When perl exits a single profile summary will be generated
+that reflects (as nearly as practical) the total use of the DBI by
+the application.
+
+
+=head1 THE PROFILE OBJECT
+
+The DBI core expects the Profile attribute value to be a hash
+reference and if the following values don't exist it will create
+them as needed:
+
+=head2 Data
+
+A reference to a hash containing the collected profile data.
+
+=head2 Path
+
+The Path value is a reference to an array. Each element controls the
+value to use at the corresponding level of the profile Data tree.
+
+If the value of Path is anything other than an array reference,
+it is treated as if it was:
+
+ [ '!Statement' ]
+
+The elements of Path array can be one of the following types:
+
+=head3 Special Constant
+
+B<!Statement>
+
+Use the current Statement text. Typically that's the value of the Statement
+attribute for the handle the method was called with. Some methods, like
+commit() and rollback(), are unrelated to a particular statement. For those
+methods !Statement records an empty string.
+
+For statement handles this is always simply the string that was
+given to prepare() when the handle was created. For database handles
+this is the statement that was last prepared or executed on that
+database handle. That can lead to a little 'fuzzyness' because, for
+example, calls to the quote() method to build a new statement will
+typically be associated with the previous statement. In practice
+this isn't a significant issue and the dynamic Path mechanism can
+be used to setup your own rules.
+
+B<!MethodName>
+
+Use the name of the DBI method that the profile sample relates to.
+
+B<!MethodClass>
+
+Use the fully qualified name of the DBI method, including
+the package, that the profile sample relates to. This shows you
+where the method was implemented. For example:
+
+ 'DBD::_::db::selectrow_arrayref' =>
+ 0.022902s
+ 'DBD::mysql::db::selectrow_arrayref' =>
+ 2.244521s / 99 = 0.022445s avg (first 0.022813s, min 0.022051s, max 0.028932s)
+
+The "DBD::_::db::selectrow_arrayref" shows that the driver has
+inherited the selectrow_arrayref method provided by the DBI.
+
+But you'll note that there is only one call to
+DBD::_::db::selectrow_arrayref but another 99 to
+DBD::mysql::db::selectrow_arrayref. Currently the first
+call doesn't record the true location. That may change.
+
+B<!Caller>
+
+Use a string showing the filename and line number of the code calling the method.
+
+B<!Caller2>
+
+Use a string showing the filename and line number of the code calling the
+method, as for !Caller, but also include filename and line number of the code
+that called that. Calls from DBI:: and DBD:: packages are skipped.
+
+B<!File>
+
+Same as !Caller above except that only the filename is included, not the line number.
+
+B<!File2>
+
+Same as !Caller2 above except that only the filenames are included, not the line number.
+
+B<!Time>
+
+Use the current value of time(). Rarely used. See the more useful C<!Time~N> below.
+
+B<!Time~N>
+
+Where C<N> is an integer. Use the current value of time() but with reduced precision.
+The value used is determined in this way:
+
+ int( time() / N ) * N
+
+This is a useful way to segregate a profile into time slots. For example:
+
+ [ '!Time~60', '!Statement' ]
+
+=head3 Code Reference
+
+The subroutine is passed the handle it was called on and the DBI method name.
+The current Statement is in $_. The statement string should not be modified,
+so most subs start with C<local $_ = $_;>.
+
+The list of values it returns is used at that point in the Profile Path.
+
+The sub can 'veto' (reject) a profile sample by including a reference to undef
+in the returned list. That can be useful when you want to only profile
+statements that match a certain pattern, or only profile certain methods.
+
+=head3 Subroutine Specifier
+
+A Path element that begins with 'C<&>' is treated as the name of a subroutine
+in the DBI::ProfileSubs namespace and replaced with the corresponding code reference.
+
+Currently this only works when the Path is specified by the C<DBI_PROFILE>
+environment variable.
+
+Also, currently, the only subroutine in the DBI::ProfileSubs namespace is
+C<'&norm_std_n3'>. That's a very handy subroutine when profiling code that
+doesn't use placeholders. See L<DBI::ProfileSubs> for more information.
+
+=head3 Attribute Specifier
+
+A string enclosed in braces, such as 'C<{Username}>', specifies that the current
+value of the corresponding database handle attribute should be used at that
+point in the Path.
+
+=head3 Reference to a Scalar
+
+Specifies that the current value of the referenced scalar be used at that point
+in the Path. This provides an efficient way to get 'contextual' values into
+your profile.
+
+=head3 Other Values
+
+Any other values are stringified and used literally.
+
+(References, and values that begin with punctuation characters are reserved.)
+
+
+=head1 REPORTING
+
+=head2 Report Format
+
+The current accumulated profile data can be formatted and output using
+
+ print $h->{Profile}->format;
+
+To discard the profile data and start collecting fresh data
+you can do:
+
+ $h->{Profile}->{Data} = undef;
+
+
+The default results format looks like this:
+
+ DBI::Profile: 0.001015s 42.7% (5 calls) programname @ YYYY-MM-DD HH:MM:SS
+ '' =>
+ 0.000024s / 2 = 0.000012s avg (first 0.000015s, min 0.000009s, max 0.000015s)
+ 'SELECT mode,size,name FROM table' =>
+ 0.000991s / 3 = 0.000330s avg (first 0.000678s, min 0.000009s, max 0.000678s)
+
+Which shows the total time spent inside the DBI, with a count of
+the total number of method calls and the name of the script being
+run, then a formatted version of the profile data tree.
+
+If the results are being formatted when the perl process is exiting
+(which is usually the case when the DBI_PROFILE environment variable
+is used) then the percentage of time the process spent inside the
+DBI is also shown. If the process is not exiting then the percentage is
+calculated using the time between the first and last call to the DBI.
+
+In the example above the paths in the tree are only one level deep and
+use the Statement text as the value (that's the default behaviour).
+
+The merged profile data at the 'leaves' of the tree are presented
+as total time spent, count, average time spent (which is simply total
+time divided by the count), then the time spent on the first call,
+the time spent on the fastest call, and finally the time spent on
+the slowest call.
+
+The 'avg', 'first', 'min' and 'max' times are not particularly
+useful when the profile data path only contains the statement text.
+Here's an extract of a more detailed example using both statement
+text and method name in the path:
+
+ 'SELECT mode,size,name FROM table' =>
+ 'FETCH' =>
+ 0.000076s
+ 'fetchrow_hashref' =>
+ 0.036203s / 108 = 0.000335s avg (first 0.000490s, min 0.000152s, max 0.002786s)
+
+Here you can see the 'avg', 'first', 'min' and 'max' for the
+108 calls to fetchrow_hashref() become rather more interesting.
+Also the data for FETCH just shows a time value because it was only
+called once.
+
+Currently the profile data is output sorted by branch names. That
+may change in a later version so the leaf nodes are sorted by total
+time per leaf node.
+
+
+=head2 Report Destination
+
+The default method of reporting is for the DESTROY method of the
+Profile object to format the results and write them using:
+
+ DBI->trace_msg($results, 0); # see $ON_DESTROY_DUMP below
+
+to write them to the DBI trace() filehandle (which defaults to
+STDERR). To direct the DBI trace filehandle to write to a file
+without enabling tracing the trace() method can be called with a
+trace level of 0. For example:
+
+ DBI->trace(0, $filename);
+
+The same effect can be achieved without changing the code by
+setting the C<DBI_TRACE> environment variable to C<0=filename>.
+
+The $DBI::Profile::ON_DESTROY_DUMP variable holds a code ref
+that's called to perform the output of the formatted results.
+The default value is:
+
+ $ON_DESTROY_DUMP = sub { DBI->trace_msg($results, 0) };
+
+Apart from making it easy to send the dump elsewhere, it can also
+be useful as a simple way to disable dumping results.
+
+=head1 CHILD HANDLES
+
+Child handles inherit a reference to the Profile attribute value
+of their parent. So if profiling is enabled for a database handle
+then by default the statement handles created from it all contribute
+to the same merged profile data tree.
+
+
+=head1 PROFILE OBJECT METHODS
+
+=head2 format
+
+See L</REPORTING>.
+
+=head2 as_node_path_list
+
+ @ary = $dbh->{Profile}->as_node_path_list();
+ @ary = $dbh->{Profile}->as_node_path_list($node, $path);
+
+Returns the collected data ($dbh->{Profile}{Data}) restructured into a list of
+array refs, one for each leaf node in the Data tree. This 'flat' structure is
+often much simpler for applications to work with.
+
+The first element of each array ref is a reference to the leaf node.
+The remaining elements are the 'path' through the data tree to that node.
+
+For example, given a data tree like this:
+
+ {key1a}{key2a}[node1]
+ {key1a}{key2b}[node2]
+ {key1b}{key2a}{key3a}[node3]
+
+The as_node_path_list() method will return this list:
+
+ [ [node1], 'key1a', 'key2a' ]
+ [ [node2], 'key1a', 'key2b' ]
+ [ [node3], 'key1b', 'key2a', 'key3a' ]
+
+The nodes are ordered by key, depth-first.
+
+The $node argument can be used to focus on a sub-tree.
+If not specified it defaults to $dbh->{Profile}{Data}.
+
+The $path argument can be used to specify a list of path elements that will be
+added to each element of the returned list. If not specified it defaults to a a
+ref to an empty array.
+
+=head2 as_text
+
+ @txt = $dbh->{Profile}->as_text();
+ $txt = $dbh->{Profile}->as_text({
+ node => undef,
+ path => [],
+ separator => " > ",
+ format => '%1$s: %11$fs / %10$d = %2$fs avg (first %12$fs, min %13$fs, max %14$fs)'."\n";
+ sortsub => sub { ... },
+ );
+
+Returns the collected data ($dbh->{Profile}{Data}) reformatted into a list of formatted strings.
+In scalar context the list is returned as a single concatenated string.
+
+A hashref can be used to pass in arguments, the default values are shown in the example above.
+
+The C<node> and <path> arguments are passed to as_node_path_list().
+
+The C<separator> argument is used to join the elements of the path for each leaf node.
+
+The C<sortsub> argument is used to pass in a ref to a sub that will order the list.
+The subroutine will be passed a reference to the array returned by
+as_node_path_list() and should sort the contents of the array in place.
+The return value from the sub is ignored. For example, to sort the nodes by the
+second level key you could use:
+
+ sortsub => sub { my $ary=shift; @$ary = sort { $a->[2] cmp $b->[2] } @$ary }
+
+The C<format> argument is a C<sprintf> format string that specifies the format
+to use for each leaf node. It uses the explicit format parameter index
+mechanism to specify which of the arguments should appear where in the string.
+The arguments to sprintf are:
+
+ 1: path to node, joined with the separator
+ 2: average duration (total duration/count)
+ (3 thru 9 are currently unused)
+ 10: count
+ 11: total duration
+ 12: first duration
+ 13: smallest duration
+ 14: largest duration
+ 15: time of first call
+ 16: time of first call
+
+=head1 CUSTOM DATA MANIPULATION
+
+Recall that C<< $h->{Profile}->{Data} >> is a reference to the collected data.
+Either to a 'leaf' array (when the Path is empty, i.e., DBI_PROFILE env var is 1),
+or a reference to hash containing values that are either further hash
+references or leaf array references.
+
+Sometimes it's useful to be able to summarise some or all of the collected data.
+The dbi_profile_merge_nodes() function can be used to merge leaf node values.
+
+=head2 dbi_profile_merge_nodes
+
+ use DBI qw(dbi_profile_merge_nodes);
+
+ $time_in_dbi = dbi_profile_merge_nodes(my $totals=[], @$leaves);
+
+Merges profile data node. Given a reference to a destination array, and zero or
+more references to profile data, merges the profile data into the destination array.
+For example:
+
+ $time_in_dbi = dbi_profile_merge_nodes(
+ my $totals=[],
+ [ 10, 0.51, 0.11, 0.01, 0.22, 1023110000, 1023110010 ],
+ [ 15, 0.42, 0.12, 0.02, 0.23, 1023110005, 1023110009 ],
+ );
+
+$totals will then contain
+
+ [ 25, 0.93, 0.11, 0.01, 0.23, 1023110000, 1023110010 ]
+
+and $time_in_dbi will be 0.93;
+
+The second argument need not be just leaf nodes. If given a reference to a hash
+then the hash is recursively searched for for leaf nodes and all those found
+are merged.
+
+For example, to get the time spent 'inside' the DBI during an http request,
+your logging code run at the end of the request (i.e. mod_perl LogHandler)
+could use:
+
+ my $time_in_dbi = 0;
+ if (my $Profile = $dbh->{Profile}) { # if DBI profiling is enabled
+ $time_in_dbi = dbi_profile_merge_nodes(my $total=[], $Profile->{Data});
+ $Profile->{Data} = {}; # reset the profile data
+ }
+
+If profiling has been enabled then $time_in_dbi will hold the time spent inside
+the DBI for that handle (and any other handles that share the same profile data)
+since the last request.
+
+Prior to DBI 1.56 the dbi_profile_merge_nodes() function was called dbi_profile_merge().
+That name still exists as an alias.
+
+=head1 CUSTOM DATA COLLECTION
+
+=head2 Using The Path Attribute
+
+ XXX example to be added later using a selectall_arrayref call
+ XXX nested inside a fetch loop where the first column of the
+ XXX outer loop is bound to the profile Path using
+ XXX bind_column(1, \${ $dbh->{Profile}->{Path}->[0] })
+ XXX so you end up with separate profiles for each loop
+ XXX (patches welcome to add this to the docs :)
+
+=head2 Adding Your Own Samples
+
+The dbi_profile() function can be used to add extra sample data
+into the profile data tree. For example:
+
+ use DBI;
+ use DBI::Profile (dbi_profile dbi_time);
+
+ my $t1 = dbi_time(); # floating point high-resolution time
+
+ ... execute code you want to profile here ...
+
+ my $t2 = dbi_time();
+ dbi_profile($h, $statement, $method, $t1, $t2);
+
+The $h parameter is the handle the extra profile sample should be
+associated with. The $statement parameter is the string to use where
+the Path specifies !Statement. If $statement is undef
+then $h->{Statement} will be used. Similarly $method is the string
+to use if the Path specifies !MethodName. There is no
+default value for $method.
+
+The $h->{Profile}{Path} attribute is processed by dbi_profile() in
+the usual way.
+
+The $h parameter is usually a DBI handle but it can also be a reference to a
+hash, in which case the dbi_profile() acts on each defined value in the hash.
+This is an efficient way to update multiple profiles with a single sample,
+and is used by the L<DashProfiler> module.
+
+=head1 SUBCLASSING
+
+Alternate profile modules must subclass DBI::Profile to help ensure
+they work with future versions of the DBI.
+
+
+=head1 CAVEATS
+
+Applications which generate many different statement strings
+(typically because they don't use placeholders) and profile with
+!Statement in the Path (the default) will consume memory
+in the Profile Data structure for each statement. Use a code ref
+in the Path to return an edited (simplified) form of the statement.
+
+If a method throws an exception itself (not via RaiseError) then
+it won't be counted in the profile.
+
+If a HandleError subroutine throws an exception (rather than returning
+0 and letting RaiseError do it) then the method call won't be counted
+in the profile.
+
+Time spent in DESTROY is added to the profile of the parent handle.
+
+Time spent in DBI->*() methods is not counted. The time spent in
+the driver connect method, $drh->connect(), when it's called by
+DBI->connect is counted if the DBI_PROFILE environment variable is set.
+
+Time spent fetching tied variables, $DBI::errstr, is counted.
+
+Time spent in FETCH for $h->{Profile} is not counted, so getting the profile
+data doesn't alter it.
+
+DBI::PurePerl does not support profiling (though it could in theory).
+
+For asynchronous queries, time spent while the query is running on the
+backend is not counted.
+
+A few platforms don't support the gettimeofday() high resolution
+time function used by the DBI (and available via the dbi_time() function).
+In which case you'll get integer resolution time which is mostly useless.
+
+On Windows platforms the dbi_time() function is limited to millisecond
+resolution. Which isn't sufficiently fine for our needs, but still
+much better than integer resolution. This limited resolution means
+that fast method calls will often register as taking 0 time. And
+timings in general will have much more 'jitter' depending on where
+within the 'current millisecond' the start and and timing was taken.
+
+This documentation could be more clear. Probably needs to be reordered
+to start with several examples and build from there. Trying to
+explain the concepts first seems painful and to lead to just as
+many forward references. (Patches welcome!)
+
+=cut
+
+
+use strict;
+use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
+use Exporter ();
+use UNIVERSAL ();
+use Carp;
+
+use DBI qw(dbi_time dbi_profile dbi_profile_merge_nodes dbi_profile_merge);
+
+$VERSION = sprintf("2.%06d", q$Revision: 15064 $ =~ /(\d+)/o);
+
+
+@ISA = qw(Exporter);
+@EXPORT = qw(
+ DBIprofile_Statement
+ DBIprofile_MethodName
+ DBIprofile_MethodClass
+ dbi_profile
+ dbi_profile_merge_nodes
+ dbi_profile_merge
+ dbi_time
+);
+@EXPORT_OK = qw(
+ format_profile_thingy
+);
+
+use constant DBIprofile_Statement => '!Statement';
+use constant DBIprofile_MethodName => '!MethodName';
+use constant DBIprofile_MethodClass => '!MethodClass';
+
+our $ON_DESTROY_DUMP = sub { DBI->trace_msg(shift, 0) };
+our $ON_FLUSH_DUMP = sub { DBI->trace_msg(shift, 0) };
+
+sub new {
+ my $class = shift;
+ my $profile = { @_ };
+ return bless $profile => $class;
+}
+
+
+sub _auto_new {
+ my $class = shift;
+ my ($arg) = @_;
+
+ # This sub is called by DBI internals when a non-hash-ref is
+ # assigned to the Profile attribute. For example
+ # dbi:mysql(RaiseError=>1,Profile=>!Statement:!MethodName/DBIx::MyProfile/arg1:arg2):dbname
+ # This sub works out what to do and returns a suitable hash ref.
+
+ $arg =~ s/^DBI::/2\/DBI::/
+ and carp "Automatically changed old-style DBI::Profile specification to $arg";
+
+ # it's a path/module/k1:v1:k2:v2:... list
+ my ($path, $package, $args) = split /\//, $arg, 3;
+ my @args = (defined $args) ? split(/:/, $args, -1) : ();
+ my @Path;
+
+ for my $element (split /:/, $path) {
+ if (DBI::looks_like_number($element)) {
+ my $reverse = ($element < 0) ? ($element=-$element, 1) : 0;
+ my @p;
+ # a single "DBI" is special-cased in format()
+ push @p, "DBI" if $element & 0x01;
+ push @p, DBIprofile_Statement if $element & 0x02;
+ push @p, DBIprofile_MethodName if $element & 0x04;
+ push @p, DBIprofile_MethodClass if $element & 0x08;
+ push @p, '!Caller2' if $element & 0x10;
+ push @Path, ($reverse ? reverse @p : @p);
+ }
+ elsif ($element =~ m/^&(\w.*)/) {
+ my $name = "DBI::ProfileSubs::$1"; # capture $1 early
+ require DBI::ProfileSubs;
+ my $code = do { no strict; *{$name}{CODE} };
+ if (defined $code) {
+ push @Path, $code;
+ }
+ else {
+ warn "$name: subroutine not found\n";
+ push @Path, $element;
+ }
+ }
+ else {
+ push @Path, $element;
+ }
+ }
+
+ eval "require $package" if $package; # sliently ignores errors
+ $package ||= $class;
+
+ return $package->new(Path => \@Path, @args);
+}
+
+
+sub empty { # empty out profile data
+ my $self = shift;
+ DBI->trace_msg("profile data discarded\n",0) if $self->{Trace};
+ $self->{Data} = undef;
+}
+
+sub filename { # baseclass method, see DBI::ProfileDumper
+ return undef;
+}
+
+sub flush_to_disk { # baseclass method, see DBI::ProfileDumper & DashProfiler::Core
+ my $self = shift;
+ return unless $ON_FLUSH_DUMP;
+ return unless $self->{Data};
+ my $detail = $self->format();
+ $ON_FLUSH_DUMP->($detail) if $detail;
+}
+
+
+sub as_node_path_list {
+ my ($self, $node, $path) = @_;
+ # convert the tree into an array of arrays
+ # from
+ # {key1a}{key2a}[node1]
+ # {key1a}{key2b}[node2]
+ # {key1b}{key2a}{key3a}[node3]
+ # to
+ # [ [node1], 'key1a', 'key2a' ]
+ # [ [node2], 'key1a', 'key2b' ]
+ # [ [node3], 'key1b', 'key2a', 'key3a' ]
+
+ $node ||= $self->{Data} or return;
+ $path ||= [];
+ if (ref $node eq 'HASH') { # recurse
+ $path = [ @$path, undef ];
+ return map {
+ $path->[-1] = $_;
+ ($node->{$_}) ? $self->as_node_path_list($node->{$_}, $path) : ()
+ } sort keys %$node;
+ }
+ return [ $node, @$path ];
+}
+
+
+sub as_text {
+ my ($self, $args_ref) = @_;
+ my $separator = $args_ref->{separator} || " > ";
+ my $format_path_element = $args_ref->{format_path_element}
+ || "%s"; # or e.g., " key%2$d='%s'"
+ my $format = $args_ref->{format}
+ || '%1$s: %11$fs / %10$d = %2$fs avg (first %12$fs, min %13$fs, max %14$fs)'."\n";
+
+ my @node_path_list = $self->as_node_path_list(undef, $args_ref->{path});
+
+ $args_ref->{sortsub}->(\@node_path_list) if $args_ref->{sortsub};
+
+ my $eval = "qr/".quotemeta($separator)."/";
+ my $separator_re = eval($eval) || quotemeta($separator);
+ #warn "[$eval] = [$separator_re]";
+ my @text;
+ my @spare_slots = (undef) x 7;
+ for my $node_path (@node_path_list) {
+ my ($node, @path) = @$node_path;
+ my $idx = 0;
+ for (@path) {
+ s/[\r\n]+/ /g;
+ s/$separator_re/ /g;
+ $_ = sprintf $format_path_element, $_, ++$idx;
+ }
+ push @text, sprintf $format,
+ join($separator, @path), # 1=path
+ ($node->[0] ? $node->[1]/$node->[0] : 0), # 2=avg
+ @spare_slots,
+ @$node; # 10=count, 11=dur, 12=first_dur, 13=min, 14=max, 15=first_called, 16=last_called
+ }
+ return @text if wantarray;
+ return join "", @text;
+}
+
+
+sub format {
+ my $self = shift;
+ my $class = ref($self) || $self;
+
+ my $prologue = "$class: ";
+ my $detail = $self->format_profile_thingy(
+ $self->{Data}, 0, " ",
+ my $path = [],
+ my $leaves = [],
+ )."\n";
+
+ if (@$leaves) {
+ dbi_profile_merge_nodes(my $totals=[], @$leaves);
+ my ($count, $time_in_dbi, undef, undef, undef, $t1, $t2) = @$totals;
+ (my $progname = $0) =~ s:.*/::;
+ if ($count) {
+ $prologue .= sprintf "%fs ", $time_in_dbi;
+ my $perl_time = ($DBI::PERL_ENDING) ? time() - $^T : $t2-$t1;
+ $prologue .= sprintf "%.2f%% ", $time_in_dbi/$perl_time*100 if $perl_time;
+ my @lt = localtime(time);
+ my $ts = sprintf "%d-%02d-%02d %02d:%02d:%02d",
+ 1900+$lt[5], $lt[4]+1, @lt[3,2,1,0];
+ $prologue .= sprintf "(%d calls) $progname \@ $ts\n", $count;
+ }
+ if (@$leaves == 1 && ref($self->{Data}) eq 'HASH' && $self->{Data}->{DBI}) {
+ $detail = ""; # hide the "DBI" from DBI_PROFILE=1
+ }
+ }
+ return ($prologue, $detail) if wantarray;
+ return $prologue.$detail;
+}
+
+
+sub format_profile_leaf {
+ my ($self, $thingy, $depth, $pad, $path, $leaves) = @_;
+ croak "format_profile_leaf called on non-leaf ($thingy)"
+ unless UNIVERSAL::isa($thingy,'ARRAY');
+
+ push @$leaves, $thingy if $leaves;
+ my ($count, $total_time, $first_time, $min, $max, $first_called, $last_called) = @$thingy;
+ return sprintf "%s%fs\n", ($pad x $depth), $total_time
+ if $count <= 1;
+ return sprintf "%s%fs / %d = %fs avg (first %fs, min %fs, max %fs)\n",
+ ($pad x $depth), $total_time, $count, $count ? $total_time/$count : 0,
+ $first_time, $min, $max;
+}
+
+
+sub format_profile_branch {
+ my ($self, $thingy, $depth, $pad, $path, $leaves) = @_;
+ croak "format_profile_branch called on non-branch ($thingy)"
+ unless UNIVERSAL::isa($thingy,'HASH');
+ my @chunk;
+ my @keys = sort keys %$thingy;
+ while ( @keys ) {
+ my $k = shift @keys;
+ my $v = $thingy->{$k};
+ push @$path, $k;
+ push @chunk, sprintf "%s'%s' =>\n%s",
+ ($pad x $depth), $k,
+ $self->format_profile_thingy($v, $depth+1, $pad, $path, $leaves);
+ pop @$path;
+ }
+ return join "", @chunk;
+}
+
+
+sub format_profile_thingy {
+ my ($self, $thingy, $depth, $pad, $path, $leaves) = @_;
+ return "undef" if not defined $thingy;
+ return $self->format_profile_leaf( $thingy, $depth, $pad, $path, $leaves)
+ if UNIVERSAL::isa($thingy,'ARRAY');
+ return $self->format_profile_branch($thingy, $depth, $pad, $path, $leaves)
+ if UNIVERSAL::isa($thingy,'HASH');
+ return "$thingy\n";
+}
+
+
+sub on_destroy {
+ my $self = shift;
+ return unless $ON_DESTROY_DUMP;
+ return unless $self->{Data};
+ my $detail = $self->format();
+ $ON_DESTROY_DUMP->($detail) if $detail;
+ $self->{Data} = undef;
+}
+
+sub DESTROY {
+ my $self = shift;
+ local $@;
+ DBI->trace_msg("profile data DESTROY\n",0)
+ if (($self->{Trace}||0) >= 2);
+ eval { $self->on_destroy };
+ if ($@) {
+ chomp $@;
+ my $class = ref($self) || $self;
+ DBI->trace_msg("$class on_destroy failed: $@", 0);
+ }
+}
+
+1;
+
diff --git a/lib/DBI/ProfileData.pm b/lib/DBI/ProfileData.pm
new file mode 100644
index 0000000..b2db087
--- /dev/null
+++ b/lib/DBI/ProfileData.pm
@@ -0,0 +1,737 @@
+package DBI::ProfileData;
+use strict;
+
+=head1 NAME
+
+DBI::ProfileData - manipulate DBI::ProfileDumper data dumps
+
+=head1 SYNOPSIS
+
+The easiest way to use this module is through the dbiprof frontend
+(see L<dbiprof> for details):
+
+ dbiprof --number 15 --sort count
+
+This module can also be used to roll your own profile analysis:
+
+ # load data from dbi.prof
+ $prof = DBI::ProfileData->new(File => "dbi.prof");
+
+ # get a count of the records (unique paths) in the data set
+ $count = $prof->count();
+
+ # sort by longest overall time
+ $prof->sort(field => "longest");
+
+ # sort by longest overall time, least to greatest
+ $prof->sort(field => "longest", reverse => 1);
+
+ # exclude records with key2 eq 'disconnect'
+ $prof->exclude(key2 => 'disconnect');
+
+ # exclude records with key1 matching /^UPDATE/i
+ $prof->exclude(key1 => qr/^UPDATE/i);
+
+ # remove all records except those where key1 matches /^SELECT/i
+ $prof->match(key1 => qr/^SELECT/i);
+
+ # produce a formatted report with the given number of items
+ $report = $prof->report(number => 10);
+
+ # clone the profile data set
+ $clone = $prof->clone();
+
+ # get access to hash of header values
+ $header = $prof->header();
+
+ # get access to sorted array of nodes
+ $nodes = $prof->nodes();
+
+ # format a single node in the same style as report()
+ $text = $prof->format($nodes->[0]);
+
+ # get access to Data hash in DBI::Profile format
+ $Data = $prof->Data();
+
+=head1 DESCRIPTION
+
+This module offers the ability to read, manipulate and format
+DBI::ProfileDumper profile data.
+
+Conceptually, a profile consists of a series of records, or nodes,
+each of each has a set of statistics and set of keys. Each record
+must have a unique set of keys, but there is no requirement that every
+record have the same number of keys.
+
+=head1 METHODS
+
+The following methods are supported by DBI::ProfileData objects.
+
+=cut
+
+
+our $VERSION = sprintf("2.%06d", q$Revision: 10007 $ =~ /(\d+)/o);
+
+use Carp qw(croak);
+use Symbol;
+use Fcntl qw(:flock);
+
+use DBI::Profile qw(dbi_profile_merge);
+
+# some constants for use with node data arrays
+sub COUNT () { 0 };
+sub TOTAL () { 1 };
+sub FIRST () { 2 };
+sub SHORTEST () { 3 };
+sub LONGEST () { 4 };
+sub FIRST_AT () { 5 };
+sub LAST_AT () { 6 };
+sub PATH () { 7 };
+
+
+my $HAS_FLOCK = (defined $ENV{DBI_PROFILE_FLOCK})
+ ? $ENV{DBI_PROFILE_FLOCK}
+ : do { local $@; eval { flock STDOUT, 0; 1 } };
+
+
+=head2 $prof = DBI::ProfileData->new(File => "dbi.prof")
+
+=head2 $prof = DBI::ProfileData->new(File => "dbi.prof", Filter => sub { ... })
+
+=head2 $prof = DBI::ProfileData->new(Files => [ "dbi.prof.1", "dbi.prof.2" ])
+
+Creates a a new DBI::ProfileData object. Takes either a single file
+through the File option or a list of Files in an array ref. If
+multiple files are specified then the header data from the first file
+is used.
+
+=head3 Files
+
+Reference to an array of file names to read.
+
+=head3 File
+
+Name of file to read. Takes precedence over C<Files>.
+
+=head3 DeleteFiles
+
+If true, the files are deleted after being read.
+
+Actually the files are renamed with a C.deleteme> suffix before being read,
+and then, after reading all the files, they're all deleted together.
+
+The files are locked while being read which, combined with the rename, makes it
+safe to 'consume' files that are still being generated by L<DBI::ProfileDumper>.
+
+=head3 Filter
+
+The C<Filter> parameter can be used to supply a code reference that can
+manipulate the profile data as it is being read. This is most useful for
+editing SQL statements so that slightly different statements in the raw data
+will be merged and aggregated in the loaded data. For example:
+
+ Filter => sub {
+ my ($path_ref, $data_ref) = @_;
+ s/foo = '.*?'/foo = '...'/ for @$path_ref;
+ }
+
+Here's an example that performs some normalization on the SQL. It converts all
+numbers to C<N> and all quoted strings to C<S>. It can also convert digits to
+N within names. Finally, it summarizes long "IN (...)" clauses.
+
+It's aggressive and simplistic, but it's often sufficient, and serves as an
+example that you can tailor to suit your own needs:
+
+ Filter => sub {
+ my ($path_ref, $data_ref) = @_;
+ local $_ = $path_ref->[0]; # whichever element contains the SQL Statement
+ s/\b\d+\b/N/g; # 42 -> N
+ s/\b0x[0-9A-Fa-f]+\b/N/g; # 0xFE -> N
+ s/'.*?'/'S'/g; # single quoted strings (doesn't handle escapes)
+ s/".*?"/"S"/g; # double quoted strings (doesn't handle escapes)
+ # convert names like log_20001231 into log_NNNNNNNN, controlled by $opt{n}
+ s/([a-z_]+)(\d{$opt{n},})/$1.('N' x length($2))/ieg if $opt{n};
+ # abbreviate massive "in (...)" statements and similar
+ s!(([NS],){100,})!sprintf("$2,{repeated %d times}",length($1)/2)!eg;
+ }
+
+It's often better to perform this kinds of normalization in the DBI while the
+data is being collected, to avoid too much memory being used by storing profile
+data for many different SQL statement. See L<DBI::Profile>.
+
+=cut
+
+sub new {
+ my $pkg = shift;
+ my $self = {
+ Files => [ "dbi.prof" ],
+ Filter => undef,
+ DeleteFiles => 0,
+ LockFile => $HAS_FLOCK,
+ _header => {},
+ _nodes => [],
+ _node_lookup => {},
+ _sort => 'none',
+ @_
+ };
+ bless $self, $pkg;
+
+ # File (singular) overrides Files (plural)
+ $self->{Files} = [ $self->{File} ] if exists $self->{File};
+
+ $self->_read_files();
+ return $self;
+}
+
+# read files into _header and _nodes
+sub _read_files {
+ my $self = shift;
+ my $files = $self->{Files};
+ my $read_header = 0;
+ my @files_to_delete;
+
+ my $fh = gensym;
+ foreach (@$files) {
+ my $filename = $_;
+
+ if ($self->{DeleteFiles}) {
+ my $newfilename = $filename . ".deleteme";
+ if ($^O eq 'VMS') {
+ # VMS default filesystem can only have one period
+ $newfilename = $filename . 'deleteme';
+ }
+ # will clobber an existing $newfilename
+ rename($filename, $newfilename)
+ or croak "Can't rename($filename, $newfilename): $!";
+ # On a versioned filesystem we want old versions to be removed
+ 1 while (unlink $filename);
+ $filename = $newfilename;
+ }
+
+ open($fh, "<", $filename)
+ or croak("Unable to read profile file '$filename': $!");
+
+ # lock the file in case it's still being written to
+ # (we'll be foced to wait till the write is complete)
+ flock($fh, LOCK_SH) if $self->{LockFile};
+
+ if (-s $fh) { # not empty
+ $self->_read_header($fh, $filename, $read_header ? 0 : 1);
+ $read_header = 1;
+ $self->_read_body($fh, $filename);
+ }
+ close($fh); # and release lock
+
+ push @files_to_delete, $filename
+ if $self->{DeleteFiles};
+ }
+ for (@files_to_delete){
+ # for versioned file systems
+ 1 while (unlink $_);
+ if(-e $_){
+ warn "Can't delete '$_': $!";
+ }
+ }
+
+ # discard node_lookup now that all files are read
+ delete $self->{_node_lookup};
+}
+
+# read the header from the given $fh named $filename. Discards the
+# data unless $keep.
+sub _read_header {
+ my ($self, $fh, $filename, $keep) = @_;
+
+ # get profiler module id
+ my $first = <$fh>;
+ chomp $first;
+ $self->{_profiler} = $first if $keep;
+
+ # collect variables from the header
+ local $_;
+ while (<$fh>) {
+ chomp;
+ last unless length $_;
+ /^(\S+)\s*=\s*(.*)/
+ or croak("Syntax error in header in $filename line $.: $_");
+ # XXX should compare new with existing (from previous file)
+ # and warn if they differ (diferent program or path)
+ $self->{_header}{$1} = unescape_key($2) if $keep;
+ }
+}
+
+
+sub unescape_key { # inverse of escape_key() in DBI::ProfileDumper
+ local $_ = shift;
+ s/(?<!\\)\\n/\n/g; # expand \n, unless it's a \\n
+ s/(?<!\\)\\r/\r/g; # expand \r, unless it's a \\r
+ s/\\\\/\\/g; # \\ to \
+ return $_;
+}
+
+
+# reads the body of the profile data
+sub _read_body {
+ my ($self, $fh, $filename) = @_;
+ my $nodes = $self->{_nodes};
+ my $lookup = $self->{_node_lookup};
+ my $filter = $self->{Filter};
+
+ # build up node array
+ my @path = ("");
+ my (@data, $path_key);
+ local $_;
+ while (<$fh>) {
+ chomp;
+ if (/^\+\s+(\d+)\s?(.*)/) {
+ # it's a key
+ my ($key, $index) = ($2, $1 - 1);
+
+ $#path = $index; # truncate path to new length
+ $path[$index] = unescape_key($key); # place new key at end
+
+ }
+ elsif (s/^=\s+//) {
+ # it's data - file in the node array with the path in index 0
+ # (the optional minus is to make it more robust against systems
+ # with unstable high-res clocks - typically due to poor NTP config
+ # of kernel SMP behaviour, i.e. min time may be -0.000008))
+
+ @data = split / /, $_;
+
+ # corrupt data?
+ croak("Invalid number of fields in $filename line $.: $_")
+ unless @data == 7;
+ croak("Invalid leaf node characters $filename line $.: $_")
+ unless m/^[-+ 0-9eE\.]+$/;
+
+ # hook to enable pre-processing of the data - such as mangling SQL
+ # so that slightly different statements get treated as the same
+ # and so merged in the results
+ $filter->(\@path, \@data) if $filter;
+
+ # elements of @path can't have NULLs in them, so this
+ # forms a unique string per @path. If there's some way I
+ # can get this without arbitrarily stripping out a
+ # character I'd be happy to hear it!
+ $path_key = join("\0",@path);
+
+ # look for previous entry
+ if (exists $lookup->{$path_key}) {
+ # merge in the new data
+ dbi_profile_merge($nodes->[$lookup->{$path_key}], \@data);
+ } else {
+ # insert a new node - nodes are arrays with data in 0-6
+ # and path data after that
+ push(@$nodes, [ @data, @path ]);
+
+ # record node in %seen
+ $lookup->{$path_key} = $#$nodes;
+ }
+ }
+ else {
+ croak("Invalid line type syntax error in $filename line $.: $_");
+ }
+ }
+}
+
+
+
+=head2 $copy = $prof->clone();
+
+Clone a profile data set creating a new object.
+
+=cut
+
+sub clone {
+ my $self = shift;
+
+ # start with a simple copy
+ my $clone = bless { %$self }, ref($self);
+
+ # deep copy nodes
+ $clone->{_nodes} = [ map { [ @$_ ] } @{$self->{_nodes}} ];
+
+ # deep copy header
+ $clone->{_header} = { %{$self->{_header}} };
+
+ return $clone;
+}
+
+=head2 $header = $prof->header();
+
+Returns a reference to a hash of header values. These are the key
+value pairs included in the header section of the DBI::ProfileDumper
+data format. For example:
+
+ $header = {
+ Path => [ '!Statement', '!MethodName' ],
+ Program => 't/42profile_data.t',
+ };
+
+Note that modifying this hash will modify the header data stored
+inside the profile object.
+
+=cut
+
+sub header { shift->{_header} }
+
+
+=head2 $nodes = $prof->nodes()
+
+Returns a reference the sorted nodes array. Each element in the array
+is a single record in the data set. The first seven elements are the
+same as the elements provided by DBI::Profile. After that each key is
+in a separate element. For example:
+
+ $nodes = [
+ [
+ 2, # 0, count
+ 0.0312958955764771, # 1, total duration
+ 0.000490069389343262, # 2, first duration
+ 0.000176072120666504, # 3, shortest duration
+ 0.00140702724456787, # 4, longest duration
+ 1023115819.83019, # 5, time of first event
+ 1023115819.86576, # 6, time of last event
+ 'SELECT foo FROM bar' # 7, key1
+ 'execute' # 8, key2
+ # 6+N, keyN
+ ],
+ # ...
+ ];
+
+Note that modifying this array will modify the node data stored inside
+the profile object.
+
+=cut
+
+sub nodes { shift->{_nodes} }
+
+
+=head2 $count = $prof->count()
+
+Returns the number of items in the profile data set.
+
+=cut
+
+sub count { scalar @{shift->{_nodes}} }
+
+
+=head2 $prof->sort(field => "field")
+
+=head2 $prof->sort(field => "field", reverse => 1)
+
+Sorts data by the given field. Available fields are:
+
+ longest
+ total
+ count
+ shortest
+
+The default sort is greatest to smallest, which is the opposite of the
+normal Perl meaning. This, however, matches the expected behavior of
+the dbiprof frontend.
+
+=cut
+
+
+# sorts data by one of the available fields
+{
+ my %FIELDS = (
+ longest => LONGEST,
+ total => TOTAL,
+ count => COUNT,
+ shortest => SHORTEST,
+ key1 => PATH+0,
+ key2 => PATH+1,
+ key3 => PATH+2,
+ );
+ sub sort {
+ my $self = shift;
+ my $nodes = $self->{_nodes};
+ my %opt = @_;
+
+ croak("Missing required field option.") unless $opt{field};
+
+ my $index = $FIELDS{$opt{field}};
+
+ croak("Unrecognized sort field '$opt{field}'.")
+ unless defined $index;
+
+ # sort over index
+ if ($opt{reverse}) {
+ @$nodes = sort {
+ $a->[$index] <=> $b->[$index]
+ } @$nodes;
+ } else {
+ @$nodes = sort {
+ $b->[$index] <=> $a->[$index]
+ } @$nodes;
+ }
+
+ # remember how we're sorted
+ $self->{_sort} = $opt{field};
+
+ return $self;
+ }
+}
+
+
+=head2 $count = $prof->exclude(key2 => "disconnect")
+
+=head2 $count = $prof->exclude(key2 => "disconnect", case_sensitive => 1)
+
+=head2 $count = $prof->exclude(key1 => qr/^SELECT/i)
+
+Removes records from the data set that match the given string or
+regular expression. This method modifies the data in a permanent
+fashion - use clone() first to maintain the original data after
+exclude(). Returns the number of nodes left in the profile data set.
+
+=cut
+
+sub exclude {
+ my $self = shift;
+ my $nodes = $self->{_nodes};
+ my %opt = @_;
+
+ # find key index number
+ my ($index, $val);
+ foreach (keys %opt) {
+ if (/^key(\d+)$/) {
+ $index = PATH + $1 - 1;
+ $val = $opt{$_};
+ last;
+ }
+ }
+ croak("Missing required keyN option.") unless $index;
+
+ if (UNIVERSAL::isa($val,"Regexp")) {
+ # regex match
+ @$nodes = grep {
+ $#$_ < $index or $_->[$index] !~ /$val/
+ } @$nodes;
+ } else {
+ if ($opt{case_sensitive}) {
+ @$nodes = grep {
+ $#$_ < $index or $_->[$index] ne $val;
+ } @$nodes;
+ } else {
+ $val = lc $val;
+ @$nodes = grep {
+ $#$_ < $index or lc($_->[$index]) ne $val;
+ } @$nodes;
+ }
+ }
+
+ return scalar @$nodes;
+}
+
+
+=head2 $count = $prof->match(key2 => "disconnect")
+
+=head2 $count = $prof->match(key2 => "disconnect", case_sensitive => 1)
+
+=head2 $count = $prof->match(key1 => qr/^SELECT/i)
+
+Removes records from the data set that do not match the given string
+or regular expression. This method modifies the data in a permanent
+fashion - use clone() first to maintain the original data after
+match(). Returns the number of nodes left in the profile data set.
+
+=cut
+
+sub match {
+ my $self = shift;
+ my $nodes = $self->{_nodes};
+ my %opt = @_;
+
+ # find key index number
+ my ($index, $val);
+ foreach (keys %opt) {
+ if (/^key(\d+)$/) {
+ $index = PATH + $1 - 1;
+ $val = $opt{$_};
+ last;
+ }
+ }
+ croak("Missing required keyN option.") unless $index;
+
+ if (UNIVERSAL::isa($val,"Regexp")) {
+ # regex match
+ @$nodes = grep {
+ $#$_ >= $index and $_->[$index] =~ /$val/
+ } @$nodes;
+ } else {
+ if ($opt{case_sensitive}) {
+ @$nodes = grep {
+ $#$_ >= $index and $_->[$index] eq $val;
+ } @$nodes;
+ } else {
+ $val = lc $val;
+ @$nodes = grep {
+ $#$_ >= $index and lc($_->[$index]) eq $val;
+ } @$nodes;
+ }
+ }
+
+ return scalar @$nodes;
+}
+
+
+=head2 $Data = $prof->Data()
+
+Returns the same Data hash structure as seen in DBI::Profile. This
+structure is not sorted. The nodes() structure probably makes more
+sense for most analysis.
+
+=cut
+
+sub Data {
+ my $self = shift;
+ my (%Data, @data, $ptr);
+
+ foreach my $node (@{$self->{_nodes}}) {
+ # traverse to key location
+ $ptr = \%Data;
+ foreach my $key (@{$node}[PATH .. $#$node - 1]) {
+ $ptr->{$key} = {} unless exists $ptr->{$key};
+ $ptr = $ptr->{$key};
+ }
+
+ # slice out node data
+ $ptr->{$node->[-1]} = [ @{$node}[0 .. 6] ];
+ }
+
+ return \%Data;
+}
+
+
+=head2 $text = $prof->format($nodes->[0])
+
+Formats a single node into a human-readable block of text.
+
+=cut
+
+sub format {
+ my ($self, $node) = @_;
+ my $format;
+
+ # setup keys
+ my $keys = "";
+ for (my $i = PATH; $i <= $#$node; $i++) {
+ my $key = $node->[$i];
+
+ # remove leading and trailing space
+ $key =~ s/^\s+//;
+ $key =~ s/\s+$//;
+
+ # if key has newlines or is long take special precautions
+ if (length($key) > 72 or $key =~ /\n/) {
+ $keys .= " Key " . ($i - PATH + 1) . " :\n\n$key\n\n";
+ } else {
+ $keys .= " Key " . ($i - PATH + 1) . " : $key\n";
+ }
+ }
+
+ # nodes with multiple runs get the long entry format, nodes with
+ # just one run get a single count.
+ if ($node->[COUNT] > 1) {
+ $format = <<END;
+ Count : %d
+ Total Time : %3.6f seconds
+ Longest Time : %3.6f seconds
+ Shortest Time : %3.6f seconds
+ Average Time : %3.6f seconds
+END
+ return sprintf($format, @{$node}[COUNT,TOTAL,LONGEST,SHORTEST],
+ $node->[TOTAL] / $node->[COUNT]) . $keys;
+ } else {
+ $format = <<END;
+ Count : %d
+ Time : %3.6f seconds
+END
+
+ return sprintf($format, @{$node}[COUNT,TOTAL]) . $keys;
+
+ }
+}
+
+
+=head2 $text = $prof->report(number => 10)
+
+Produces a report with the given number of items.
+
+=cut
+
+sub report {
+ my $self = shift;
+ my $nodes = $self->{_nodes};
+ my %opt = @_;
+
+ croak("Missing required number option") unless exists $opt{number};
+
+ $opt{number} = @$nodes if @$nodes < $opt{number};
+
+ my $report = $self->_report_header($opt{number});
+ for (0 .. $opt{number} - 1) {
+ $report .= sprintf("#" x 5 . "[ %d ]". "#" x 59 . "\n",
+ $_ + 1);
+ $report .= $self->format($nodes->[$_]);
+ $report .= "\n";
+ }
+ return $report;
+}
+
+# format the header for report()
+sub _report_header {
+ my ($self, $number) = @_;
+ my $nodes = $self->{_nodes};
+ my $node_count = @$nodes;
+
+ # find total runtime and method count
+ my ($time, $count) = (0,0);
+ foreach my $node (@$nodes) {
+ $time += $node->[TOTAL];
+ $count += $node->[COUNT];
+ }
+
+ my $header = <<END;
+
+DBI Profile Data ($self->{_profiler})
+
+END
+
+ # output header fields
+ while (my ($key, $value) = each %{$self->{_header}}) {
+ $header .= sprintf(" %-13s : %s\n", $key, $value);
+ }
+
+ # output summary data fields
+ $header .= sprintf(<<END, $node_count, $number, $self->{_sort}, $count, $time);
+ Total Records : %d (showing %d, sorted by %s)
+ Total Count : %d
+ Total Runtime : %3.6f seconds
+
+END
+
+ return $header;
+}
+
+
+1;
+
+__END__
+
+=head1 AUTHOR
+
+Sam Tregar <sam@tregar.com>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2002 Sam Tregar
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl 5 itself.
+
+=cut
diff --git a/lib/DBI/ProfileDumper.pm b/lib/DBI/ProfileDumper.pm
new file mode 100644
index 0000000..89bb884
--- /dev/null
+++ b/lib/DBI/ProfileDumper.pm
@@ -0,0 +1,351 @@
+package DBI::ProfileDumper;
+use strict;
+
+=head1 NAME
+
+DBI::ProfileDumper - profile DBI usage and output data to a file
+
+=head1 SYNOPSIS
+
+To profile an existing program using DBI::ProfileDumper, set the
+DBI_PROFILE environment variable and run your program as usual. For
+example, using bash:
+
+ DBI_PROFILE=2/DBI::ProfileDumper program.pl
+
+Then analyze the generated file (F<dbi.prof>) with L<dbiprof|dbiprof>:
+
+ dbiprof
+
+You can also activate DBI::ProfileDumper from within your code:
+
+ use DBI;
+
+ # profile with default path (2) and output file (dbi.prof)
+ $dbh->{Profile} = "!Statement/DBI::ProfileDumper";
+
+ # same thing, spelled out
+ $dbh->{Profile} = "!Statement/DBI::ProfileDumper/File:dbi.prof";
+
+ # another way to say it
+ use DBI::ProfileDumper;
+ $dbh->{Profile} = DBI::ProfileDumper->new(
+ Path => [ '!Statement' ],
+ File => 'dbi.prof' );
+
+ # using a custom path
+ $dbh->{Profile} = DBI::ProfileDumper->new(
+ Path => [ "foo", "bar" ],
+ File => 'dbi.prof',
+ );
+
+
+=head1 DESCRIPTION
+
+DBI::ProfileDumper is a subclass of L<DBI::Profile|DBI::Profile> which
+dumps profile data to disk instead of printing a summary to your
+screen. You can then use L<dbiprof|dbiprof> to analyze the data in
+a number of interesting ways, or you can roll your own analysis using
+L<DBI::ProfileData|DBI::ProfileData>.
+
+B<NOTE:> For Apache/mod_perl applications, use
+L<DBI::ProfileDumper::Apache|DBI::ProfileDumper::Apache>.
+
+=head1 USAGE
+
+One way to use this module is just to enable it in your C<$dbh>:
+
+ $dbh->{Profile} = "1/DBI::ProfileDumper";
+
+This will write out profile data by statement into a file called
+F<dbi.prof>. If you want to modify either of these properties, you
+can construct the DBI::ProfileDumper object yourself:
+
+ use DBI::ProfileDumper;
+ $dbh->{Profile} = DBI::ProfileDumper->new(
+ Path => [ '!Statement' ],
+ File => 'dbi.prof'
+ );
+
+The C<Path> option takes the same values as in
+L<DBI::Profile>. The C<File> option gives the name of the
+file where results will be collected. If it already exists it will be
+overwritten.
+
+You can also activate this module by setting the DBI_PROFILE
+environment variable:
+
+ $ENV{DBI_PROFILE} = "!Statement/DBI::ProfileDumper";
+
+This will cause all DBI handles to share the same profiling object.
+
+=head1 METHODS
+
+The following methods are available to be called using the profile
+object. You can get access to the profile object from the Profile key
+in any DBI handle:
+
+ my $profile = $dbh->{Profile};
+
+=head2 flush_to_disk
+
+ $profile->flush_to_disk()
+
+Flushes all collected profile data to disk and empties the Data hash. Returns
+the filename writen to. If no profile data has been collected then the file is
+not written and flush_to_disk() returns undef.
+
+The file is locked while it's being written. A process 'consuming' the files
+while they're being written to, should rename the file first, then lock it,
+then read it, then close and delete it. The C<DeleteFiles> option to
+L<DBI::ProfileData> does the right thing.
+
+This method may be called multiple times during a program run.
+
+=head2 empty
+
+ $profile->empty()
+
+Clears the Data hash without writing to disk.
+
+=head2 filename
+
+ $filename = $profile->filename();
+
+Get or set the filename.
+
+The filename can be specified as a CODE reference, in which case the referenced
+code should return the filename to be used. The code will be called with the
+profile object as its first argument.
+
+=head1 DATA FORMAT
+
+The data format written by DBI::ProfileDumper starts with a header
+containing the version number of the module used to generate it. Then
+a block of variable declarations describes the profile. After two
+newlines, the profile data forms the body of the file. For example:
+
+ DBI::ProfileDumper 2.003762
+ Path = [ '!Statement', '!MethodName' ]
+ Program = t/42profile_data.t
+
+ + 1 SELECT name FROM users WHERE id = ?
+ + 2 prepare
+ = 1 0.0312958955764771 0.000490069389343262 0.000176072120666504 0.00140702724456787 1023115819.83019 1023115819.86576
+ + 2 execute
+ 1 0.0312958955764771 0.000490069389343262 0.000176072120666504 0.00140702724456787 1023115819.83019 1023115819.86576
+ + 2 fetchrow_hashref
+ = 1 0.0312958955764771 0.000490069389343262 0.000176072120666504 0.00140702724456787 1023115819.83019 1023115819.86576
+ + 1 UPDATE users SET name = ? WHERE id = ?
+ + 2 prepare
+ = 1 0.0312958955764771 0.000490069389343262 0.000176072120666504 0.00140702724456787 1023115819.83019 1023115819.86576
+ + 2 execute
+ = 1 0.0312958955764771 0.000490069389343262 0.000176072120666504 0.00140702724456787 1023115819.83019 1023115819.86576
+
+The lines beginning with C<+> signs signify keys. The number after
+the C<+> sign shows the nesting level of the key. Lines beginning
+with C<=> are the actual profile data, in the same order as
+in DBI::Profile.
+
+Note that the same path may be present multiple times in the data file
+since C<format()> may be called more than once. When read by
+DBI::ProfileData the data points will be merged to produce a single
+data set for each distinct path.
+
+The key strings are transformed in three ways. First, all backslashes
+are doubled. Then all newlines and carriage-returns are transformed
+into C<\n> and C<\r> respectively. Finally, any NULL bytes (C<\0>)
+are entirely removed. When DBI::ProfileData reads the file the first
+two transformations will be reversed, but NULL bytes will not be
+restored.
+
+=head1 AUTHOR
+
+Sam Tregar <sam@tregar.com>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2002 Sam Tregar
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl 5 itself.
+
+=cut
+
+# inherit from DBI::Profile
+use DBI::Profile;
+
+our @ISA = ("DBI::Profile");
+
+our $VERSION = sprintf("2.%06d", q$Revision: 15324 $ =~ /(\d+)/o);
+
+use Carp qw(croak);
+use Fcntl qw(:flock);
+use Symbol;
+
+my $HAS_FLOCK = (defined $ENV{DBI_PROFILE_FLOCK})
+ ? $ENV{DBI_PROFILE_FLOCK}
+ : do { local $@; eval { flock STDOUT, 0; 1 } };
+
+my $program_header;
+
+
+# validate params and setup default
+sub new {
+ my $pkg = shift;
+ my $self = $pkg->SUPER::new(
+ LockFile => $HAS_FLOCK,
+ @_,
+ );
+
+ # provide a default filename
+ $self->filename("dbi.prof") unless $self->filename;
+
+ DBI->trace_msg("$self: @{[ %$self ]}\n",0)
+ if $self->{Trace} && $self->{Trace} >= 2;
+
+ return $self;
+}
+
+
+# get/set filename to use
+sub filename {
+ my $self = shift;
+ $self->{File} = shift if @_;
+ my $filename = $self->{File};
+ $filename = $filename->($self) if ref($filename) eq 'CODE';
+ return $filename;
+}
+
+
+# flush available data to disk
+sub flush_to_disk {
+ my $self = shift;
+ my $class = ref $self;
+ my $filename = $self->filename;
+ my $data = $self->{Data};
+
+ if (1) { # make an option
+ if (not $data or ref $data eq 'HASH' && !%$data) {
+ DBI->trace_msg("flush_to_disk skipped for empty profile\n",0) if $self->{Trace};
+ return undef;
+ }
+ }
+
+ my $fh = gensym;
+ if (($self->{_wrote_header}||'') eq $filename) {
+ # append more data to the file
+ # XXX assumes that Path hasn't changed
+ open($fh, ">>", $filename)
+ or croak("Unable to open '$filename' for $class output: $!");
+ } else {
+ # create new file (or overwrite existing)
+ if (-f $filename) {
+ my $bak = $filename.'.prev';
+ unlink($bak);
+ rename($filename, $bak)
+ or warn "Error renaming $filename to $bak: $!\n";
+ }
+ open($fh, ">", $filename)
+ or croak("Unable to open '$filename' for $class output: $!");
+ }
+ # lock the file (before checking size and writing the header)
+ flock($fh, LOCK_EX) if $self->{LockFile};
+ # write header if file is empty - typically because we just opened it
+ # in '>' mode, or perhaps we used '>>' but the file had been truncated externally.
+ if (-s $fh == 0) {
+ DBI->trace_msg("flush_to_disk wrote header to $filename\n",0) if $self->{Trace};
+ $self->write_header($fh);
+ $self->{_wrote_header} = $filename;
+ }
+
+ my $lines = $self->write_data($fh, $self->{Data}, 1);
+ DBI->trace_msg("flush_to_disk wrote $lines lines to $filename\n",0) if $self->{Trace};
+
+ close($fh) # unlocks the file
+ or croak("Error closing '$filename': $!");
+
+ $self->empty();
+
+
+ return $filename;
+}
+
+
+# write header to a filehandle
+sub write_header {
+ my ($self, $fh) = @_;
+
+ # isolate us against globals which effect print
+ local($\, $,);
+
+ # $self->VERSION can return undef during global destruction
+ my $version = $self->VERSION || $VERSION;
+
+ # module name and version number
+ print $fh ref($self)." $version\n";
+
+ # print out Path (may contain CODE refs etc)
+ my @path_words = map { escape_key($_) } @{ $self->{Path} || [] };
+ print $fh "Path = [ ", join(', ', @path_words), " ]\n";
+
+ # print out $0 and @ARGV
+ if (!$program_header) {
+ # XXX should really quote as well as escape
+ $program_header = "Program = "
+ . join(" ", map { escape_key($_) } $0, @ARGV)
+ . "\n";
+ }
+ print $fh $program_header;
+
+ # all done
+ print $fh "\n";
+}
+
+
+# write data in the proscribed format
+sub write_data {
+ my ($self, $fh, $data, $level) = @_;
+
+ # XXX it's valid for $data to be an ARRAY ref, i.e., Path is empty.
+ # produce an empty profile for invalid $data
+ return 0 unless $data and UNIVERSAL::isa($data,'HASH');
+
+ # isolate us against globals which affect print
+ local ($\, $,);
+
+ my $lines = 0;
+ while (my ($key, $value) = each(%$data)) {
+ # output a key
+ print $fh "+ $level ". escape_key($key). "\n";
+ if (UNIVERSAL::isa($value,'ARRAY')) {
+ # output a data set for a leaf node
+ print $fh "= ".join(' ', @$value)."\n";
+ $lines += 1;
+ } else {
+ # recurse through keys - this could be rewritten to use a
+ # stack for some small performance gain
+ $lines += $self->write_data($fh, $value, $level + 1);
+ }
+ }
+ return $lines;
+}
+
+
+# escape a key for output
+sub escape_key {
+ my $key = shift;
+ $key =~ s!\\!\\\\!g;
+ $key =~ s!\n!\\n!g;
+ $key =~ s!\r!\\r!g;
+ $key =~ s!\0!!g;
+ return $key;
+}
+
+
+# flush data to disk when profile object goes out of scope
+sub on_destroy {
+ shift->flush_to_disk();
+}
+
+1;
diff --git a/lib/DBI/ProfileDumper/Apache.pm b/lib/DBI/ProfileDumper/Apache.pm
new file mode 100644
index 0000000..1f58926
--- /dev/null
+++ b/lib/DBI/ProfileDumper/Apache.pm
@@ -0,0 +1,219 @@
+package DBI::ProfileDumper::Apache;
+
+use strict;
+
+=head1 NAME
+
+DBI::ProfileDumper::Apache - capture DBI profiling data from Apache/mod_perl
+
+=head1 SYNOPSIS
+
+Add this line to your F<httpd.conf>:
+
+ PerlSetEnv DBI_PROFILE 2/DBI::ProfileDumper::Apache
+
+(If you're using mod_perl2, see L</When using mod_perl2> for some additional notes.)
+
+Then restart your server. Access the code you wish to test using a
+web browser, then shutdown your server. This will create a set of
+F<dbi.prof.*> files in your Apache log directory.
+
+Get a profiling report with L<dbiprof|dbiprof>:
+
+ dbiprof /path/to/your/apache/logs/dbi.prof.*
+
+When you're ready to perform another profiling run, delete the old files and start again.
+
+=head1 DESCRIPTION
+
+This module interfaces DBI::ProfileDumper to Apache/mod_perl. Using
+this module you can collect profiling data from mod_perl applications.
+It works by creating a DBI::ProfileDumper data file for each Apache
+process. These files are created in your Apache log directory. You
+can then use the dbiprof utility to analyze the profile files.
+
+=head1 USAGE
+
+=head2 LOADING THE MODULE
+
+The easiest way to use this module is just to set the DBI_PROFILE
+environment variable in your F<httpd.conf>:
+
+ PerlSetEnv DBI_PROFILE 2/DBI::ProfileDumper::Apache
+
+The DBI will look after loading and using the module when the first DBI handle
+is created.
+
+It's also possible to use this module by setting the Profile attribute
+of any DBI handle:
+
+ $dbh->{Profile} = "2/DBI::ProfileDumper::Apache";
+
+See L<DBI::ProfileDumper> for more possibilities, and L<DBI::Profile> for full
+details of the DBI's profiling mechanism.
+
+=head2 WRITING PROFILE DATA
+
+The profile data files will be written to your Apache log directory by default.
+
+The user that the httpd processes run as will need write access to the
+directory. So, for example, if you're running the child httpds as user 'nobody'
+and using chronolog to write to the logs directory, then you'll need to change
+the default.
+
+You can change the destination directory either by specifying a C<Dir> value
+when creating the profile (like C<File> in the L<DBI::ProfileDumper> docs),
+or you can use the C<DBI_PROFILE_APACHE_LOG_DIR> env var to change that. For example:
+
+ PerlSetEnv DBI_PROFILE_APACHE_LOG_DIR /server_root/logs
+
+=head3 When using mod_perl2
+
+Under mod_perl2 you'll need to either set the C<DBI_PROFILE_APACHE_LOG_DIR> env var,
+or enable the mod_perl2 C<GlobalRequest> option, like this:
+
+ PerlOptions +GlobalRequest
+
+to the global config section you're about test with DBI::ProfileDumper::Apache.
+If you don't do one of those then you'll see messages in your error_log similar to:
+
+ DBI::ProfileDumper::Apache on_destroy failed: Global $r object is not available. Set:
+ PerlOptions +GlobalRequest in httpd.conf at ..../DBI/ProfileDumper/Apache.pm line 144
+
+=head3 Naming the files
+
+The default file name is inherited from L<DBI::ProfileDumper> via the
+filename() method, but DBI::ProfileDumper::Apache appends the parent pid and
+the current pid, separated by dots, to that name.
+
+=head3 Silencing the log
+
+By default a message is written to STDERR (i.e., the apache error_log file)
+when flush_to_disk() is called (either explicitly, or implicitly via DESTROY).
+
+That's usually very useful. If you don't want the log message you can silence
+it by setting the C<Quiet> attribute true.
+
+ PerlSetEnv DBI_PROFILE 2/DBI::ProfileDumper::Apache/Quiet:1
+
+ $dbh->{Profile} = "!Statement/DBI::ProfileDumper/Quiet:1";
+
+ $dbh->{Profile} = DBI::ProfileDumper->new(
+ Path => [ '!Statement' ]
+ Quiet => 1
+ );
+
+
+=head2 GATHERING PROFILE DATA
+
+Once you have the module loaded, use your application as you normally
+would. Stop the webserver when your tests are complete. Profile data
+files will be produced when Apache exits and you'll see something like
+this in your error_log:
+
+ DBI::ProfileDumper::Apache writing to /usr/local/apache/logs/dbi.prof.2604.2619
+
+Now you can use dbiprof to examine the data:
+
+ dbiprof /usr/local/apache/logs/dbi.prof.2604.*
+
+By passing dbiprof a list of all generated files, dbiprof will
+automatically merge them into one result set. You can also pass
+dbiprof sorting and querying options, see L<dbiprof> for details.
+
+=head2 CLEANING UP
+
+Once you've made some code changes, you're ready to start again.
+First, delete the old profile data files:
+
+ rm /usr/local/apache/logs/dbi.prof.*
+
+Then restart your server and get back to work.
+
+=head1 OTHER ISSUES
+
+=head2 Memory usage
+
+DBI::Profile can use a lot of memory for very active applications because it
+collects profiling data in memory for each distinct query run.
+Calling C<flush_to_disk()> will write the current data to disk and free the
+memory it's using. For example:
+
+ $dbh->{Profile}->flush_to_disk() if $dbh->{Profile};
+
+or, rather than flush every time, you could flush less often:
+
+ $dbh->{Profile}->flush_to_disk()
+ if $dbh->{Profile} and ++$i % 100;
+
+=head1 AUTHOR
+
+Sam Tregar <sam@tregar.com>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2002 Sam Tregar
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl 5 itself.
+
+=cut
+
+our $VERSION = sprintf("2.%06d", q$Revision: 14120 $ =~ /(\d+)/o);
+
+our @ISA = qw(DBI::ProfileDumper);
+
+use DBI::ProfileDumper;
+use File::Spec;
+
+my $initial_pid = $$;
+
+use constant MP2 => ($ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} == 2) ? 1 : 0;
+
+my $server_root_dir;
+
+if (MP2) {
+ require Apache2::ServerUtil;
+ $server_root_dir = Apache2::ServerUtil::server_root();
+}
+else {
+ require Apache;
+ $server_root_dir = eval { Apache->server_root_relative('') } || "/tmp";
+}
+
+
+sub _dirname {
+ my $self = shift;
+ return $self->{Dir} ||= $ENV{DBI_PROFILE_APACHE_LOG_DIR}
+ || File::Spec->catdir($server_root_dir, "logs");
+}
+
+
+sub filename {
+ my $self = shift;
+ my $filename = $self->SUPER::filename(@_);
+ return $filename if not $filename; # not set yet
+
+ # to be able to identify groups of profile files from the same set of
+ # apache processes, we include the parent pid in the file name
+ # as well as the pid.
+ my $group_pid = ($$ eq $initial_pid) ? $$ : getppid();
+ $filename .= ".$group_pid.$$";
+
+ return $filename if File::Spec->file_name_is_absolute($filename);
+ return File::Spec->catfile($self->_dirname, $filename);
+}
+
+
+sub flush_to_disk {
+ my $self = shift;
+
+ my $filename = $self->SUPER::flush_to_disk(@_);
+
+ print STDERR ref($self)." pid$$ written to $filename\n"
+ if $filename && not $self->{Quiet};
+
+ return $filename;
+}
+
+1;
diff --git a/lib/DBI/ProfileSubs.pm b/lib/DBI/ProfileSubs.pm
new file mode 100644
index 0000000..02ca64d
--- /dev/null
+++ b/lib/DBI/ProfileSubs.pm
@@ -0,0 +1,50 @@
+package DBI::ProfileSubs;
+
+our $VERSION = sprintf("0.%06d", q$Revision: 9395 $ =~ /(\d+)/o);
+
+=head1 NAME
+
+DBI::ProfileSubs - Subroutines for dynamic profile Path
+
+=head1 SYNOPSIS
+
+ DBI_PROFILE='&norm_std_n3' prog.pl
+
+This is new and still experimental.
+
+=head1 TO DO
+
+Define come kind of naming convention for the subs.
+
+=cut
+
+use strict;
+use warnings;
+
+
+# would be good to refactor these regex into separate subs and find some
+# way to compose them in various combinations into multiple subs.
+# Perhaps via AUTOLOAD where \&auto_X_Y_Z creates a sub that does X, Y, and Z.
+# The final subs always need to be very fast.
+#
+
+sub norm_std_n3 {
+ # my ($h, $method_name) = @_;
+ local $_ = $_;
+
+ s/\b\d+\b/<N>/g; # 42 -> <N>
+ s/\b0x[0-9A-Fa-f]+\b/<N>/g; # 0xFE -> <N>
+
+ s/'.*?'/'<S>'/g; # single quoted strings (doesn't handle escapes)
+ s/".*?"/"<S>"/g; # double quoted strings (doesn't handle escapes)
+
+ # convert names like log20001231 into log<N>
+ s/([a-z_]+)(\d{3,})\b/${1}<N>/ig;
+
+ # abbreviate massive "in (...)" statements and similar
+ s!((\s*<[NS]>\s*,\s*){100,})!sprintf("$2,<repeated %d times>",length($1)/2)!eg;
+
+ return $_;
+}
+
+1;
diff --git a/lib/DBI/ProxyServer.pm b/lib/DBI/ProxyServer.pm
new file mode 100644
index 0000000..89e2de6
--- /dev/null
+++ b/lib/DBI/ProxyServer.pm
@@ -0,0 +1,890 @@
+# $Header: /home/timbo/dbi/lib/DBI/RCS/ProxyServer.pm,v 11.9 2003/05/14 11:08:17 timbo Exp $
+# -*- perl -*-
+#
+# DBI::ProxyServer - a proxy server for DBI drivers
+#
+# Copyright (c) 1997 Jochen Wiedmann
+#
+# The DBD::Proxy module is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself. In particular permission
+# is granted to Tim Bunce for distributing this as a part of the DBI.
+#
+#
+# Author: Jochen Wiedmann
+# Am Eisteich 9
+# 72555 Metzingen
+# Germany
+#
+# Email: joe@ispsoft.de
+# Phone: +49 7123 14881
+#
+#
+##############################################################################
+
+
+require 5.004;
+use strict;
+
+use RPC::PlServer 0.2001;
+require DBI;
+require Config;
+
+
+package DBI::ProxyServer;
+
+
+
+############################################################################
+#
+# Constants
+#
+############################################################################
+
+use vars qw($VERSION @ISA);
+
+$VERSION = "0.3005";
+@ISA = qw(RPC::PlServer DBI);
+
+
+# Most of the options below are set to default values, we note them here
+# just for the sake of documentation.
+my %DEFAULT_SERVER_OPTIONS;
+{
+ my $o = \%DEFAULT_SERVER_OPTIONS;
+ $o->{'chroot'} = undef, # To be used in the initfile,
+ # after loading the required
+ # DBI drivers.
+ $o->{'clients'} =
+ [ { 'mask' => '.*',
+ 'accept' => 1,
+ 'cipher' => undef
+ }
+ ];
+ $o->{'configfile'} = '/etc/dbiproxy.conf' if -f '/etc/dbiproxy.conf';
+ $o->{'debug'} = 0;
+ $o->{'facility'} = 'daemon';
+ $o->{'group'} = undef;
+ $o->{'localaddr'} = undef; # Bind to any local IP number
+ $o->{'localport'} = undef; # Must set port number on the
+ # command line.
+ $o->{'logfile'} = undef; # Use syslog or EventLog.
+
+ # XXX don't restrict methods that can be called (trust users once connected)
+ $o->{'XXX_methods'} = {
+ 'DBI::ProxyServer' => {
+ 'Version' => 1,
+ 'NewHandle' => 1,
+ 'CallMethod' => 1,
+ 'DestroyHandle' => 1
+ },
+ 'DBI::ProxyServer::db' => {
+ 'prepare' => 1,
+ 'commit' => 1,
+ 'rollback' => 1,
+ 'STORE' => 1,
+ 'FETCH' => 1,
+ 'func' => 1,
+ 'quote' => 1,
+ 'type_info_all' => 1,
+ 'table_info' => 1,
+ 'disconnect' => 1,
+ },
+ 'DBI::ProxyServer::st' => {
+ 'execute' => 1,
+ 'STORE' => 1,
+ 'FETCH' => 1,
+ 'func' => 1,
+ 'fetch' => 1,
+ 'finish' => 1
+ }
+ };
+ if ($Config::Config{'usethreads'} eq 'define') {
+ $o->{'mode'} = 'threads';
+ } elsif ($Config::Config{'d_fork'} eq 'define') {
+ $o->{'mode'} = 'fork';
+ } else {
+ $o->{'mode'} = 'single';
+ }
+ # No pidfile by default, configuration must provide one if needed
+ $o->{'pidfile'} = 'none';
+ $o->{'user'} = undef;
+};
+
+
+############################################################################
+#
+# Name: Version
+#
+# Purpose: Return version string
+#
+# Inputs: $class - This class
+#
+# Result: Version string; suitable for printing by "--version"
+#
+############################################################################
+
+sub Version {
+ my $version = $DBI::ProxyServer::VERSION;
+ "DBI::ProxyServer $version, Copyright (C) 1998, Jochen Wiedmann";
+}
+
+
+############################################################################
+#
+# Name: AcceptApplication
+#
+# Purpose: Verify DBI DSN
+#
+# Inputs: $self - This instance
+# $dsn - DBI dsn
+#
+# Returns: TRUE for a valid DSN, FALSE otherwise
+#
+############################################################################
+
+sub AcceptApplication {
+ my $self = shift; my $dsn = shift;
+ $dsn =~ /^dbi:\w+:/i;
+}
+
+
+############################################################################
+#
+# Name: AcceptVersion
+#
+# Purpose: Verify requested DBI version
+#
+# Inputs: $self - Instance
+# $version - DBI version being requested
+#
+# Returns: TRUE for ok, FALSE otherwise
+#
+############################################################################
+
+sub AcceptVersion {
+ my $self = shift; my $version = shift;
+ require DBI;
+ DBI::ProxyServer->init_rootclass();
+ $DBI::VERSION >= $version;
+}
+
+
+############################################################################
+#
+# Name: AcceptUser
+#
+# Purpose: Verify user and password by connecting to the client and
+# creating a database connection
+#
+# Inputs: $self - Instance
+# $user - User name
+# $password - Password
+#
+############################################################################
+
+sub AcceptUser {
+ my $self = shift; my $user = shift; my $password = shift;
+ return 0 if (!$self->SUPER::AcceptUser($user, $password));
+ my $dsn = $self->{'application'};
+ $self->Debug("Connecting to $dsn as $user");
+ local $ENV{DBI_AUTOPROXY} = ''; # :-)
+ $self->{'dbh'} = eval {
+ DBI::ProxyServer->connect($dsn, $user, $password,
+ { 'PrintError' => 0,
+ 'Warn' => 0,
+ 'RaiseError' => 1,
+ 'HandleError' => sub {
+ my $err = $_[1]->err;
+ my $state = $_[1]->state || '';
+ $_[0] .= " [err=$err,state=$state]";
+ return 0;
+ } })
+ };
+ if ($@) {
+ $self->Error("Error while connecting to $dsn as $user: $@");
+ return 0;
+ }
+ [1, $self->StoreHandle($self->{'dbh'}) ];
+}
+
+
+sub CallMethod {
+ my $server = shift;
+ my $dbh = $server->{'dbh'};
+ # We could store the private_server attribute permanently in
+ # $dbh. However, we'd have a reference loop in that case and
+ # I would be concerned about garbage collection. :-(
+ $dbh->{'private_server'} = $server;
+ $server->Debug("CallMethod: => " . do { local $^W; join(",", @_)});
+ my @result = eval { $server->SUPER::CallMethod(@_) };
+ my $msg = $@;
+ undef $dbh->{'private_server'};
+ if ($msg) {
+ $server->Debug("CallMethod died with: $@");
+ die $msg;
+ } else {
+ $server->Debug("CallMethod: <= " . do { local $^W; join(",", @result) });
+ }
+ @result;
+}
+
+
+sub main {
+ my $server = DBI::ProxyServer->new(\%DEFAULT_SERVER_OPTIONS, \@_);
+ $server->Bind();
+}
+
+
+############################################################################
+#
+# The DBI part of the proxyserver is implemented as a DBI subclass.
+# Thus we can reuse some of the DBI methods and overwrite only
+# those that need additional handling.
+#
+############################################################################
+
+package DBI::ProxyServer::dr;
+
+@DBI::ProxyServer::dr::ISA = qw(DBI::dr);
+
+
+package DBI::ProxyServer::db;
+
+@DBI::ProxyServer::db::ISA = qw(DBI::db);
+
+sub prepare {
+ my($dbh, $statement, $attr, $params, $proto_ver) = @_;
+ my $server = $dbh->{'private_server'};
+ if (my $client = $server->{'client'}) {
+ if ($client->{'sql'}) {
+ if ($statement =~ /^\s*(\S+)/) {
+ my $st = $1;
+ if (!($statement = $client->{'sql'}->{$st})) {
+ die "Unknown SQL query: $st";
+ }
+ } else {
+ die "Cannot parse restricted SQL statement: $statement";
+ }
+ }
+ }
+ my $sth = $dbh->SUPER::prepare($statement, $attr);
+ my $handle = $server->StoreHandle($sth);
+
+ if ( $proto_ver and $proto_ver > 1 ) {
+ $sth->{private_proxyserver_described} = 0;
+ return $handle;
+
+ } else {
+ # The difference between the usual prepare and ours is that we implement
+ # a combined prepare/execute. The DBD::Proxy driver doesn't call us for
+ # prepare. Only if an execute happens, then we are called with method
+ # "prepare". Further execute's are called as "execute".
+ my @result = $sth->execute($params);
+ my ($NAME, $TYPE);
+ my $NUM_OF_FIELDS = $sth->{NUM_OF_FIELDS};
+ if ($NUM_OF_FIELDS) { # is a SELECT
+ $NAME = $sth->{NAME};
+ $TYPE = $sth->{TYPE};
+ }
+ ($handle, $NUM_OF_FIELDS, $sth->{'NUM_OF_PARAMS'},
+ $NAME, $TYPE, @result);
+ }
+}
+
+sub table_info {
+ my $dbh = shift;
+ my $sth = $dbh->SUPER::table_info();
+ my $numFields = $sth->{'NUM_OF_FIELDS'};
+ my $names = $sth->{'NAME'};
+ my $types = $sth->{'TYPE'};
+
+ # We wouldn't need to send all the rows at this point, instead we could
+ # make use of $rsth->fetch() on the client as usual.
+ # The problem is that some drivers (namely DBD::ExampleP, DBD::mysql and
+ # DBD::mSQL) are returning foreign sth's here, thus an instance of
+ # DBI::st and not DBI::ProxyServer::st. We could fix this by permitting
+ # the client to execute method DBI::st, but I don't like this.
+ my @rows;
+ while (my ($row) = $sth->fetch()) {
+ last unless defined $row;
+ push(@rows, [@$row]);
+ }
+ ($numFields, $names, $types, @rows);
+}
+
+
+package DBI::ProxyServer::st;
+
+@DBI::ProxyServer::st::ISA = qw(DBI::st);
+
+sub execute {
+ my $sth = shift; my $params = shift; my $proto_ver = shift;
+ my @outParams;
+ if ($params) {
+ for (my $i = 0; $i < @$params;) {
+ my $param = $params->[$i++];
+ if (!ref($param)) {
+ $sth->bind_param($i, $param);
+ }
+ else {
+ if (!ref(@$param[0])) {#It's not a reference
+ $sth->bind_param($i, @$param);
+ }
+ else {
+ $sth->bind_param_inout($i, @$param);
+ my $ref = shift @$param;
+ push(@outParams, $ref);
+ }
+ }
+ }
+ }
+ my $rows = $sth->SUPER::execute();
+ if ( $proto_ver and $proto_ver > 1 and not $sth->{private_proxyserver_described} ) {
+ my ($NAME, $TYPE);
+ my $NUM_OF_FIELDS = $sth->{NUM_OF_FIELDS};
+ if ($NUM_OF_FIELDS) { # is a SELECT
+ $NAME = $sth->{NAME};
+ $TYPE = $sth->{TYPE};
+ }
+ $sth->{private_proxyserver_described} = 1;
+ # First execution, we ship back description.
+ return ($rows, $NUM_OF_FIELDS, $sth->{'NUM_OF_PARAMS'}, $NAME, $TYPE, @outParams);
+ }
+ ($rows, @outParams);
+}
+
+sub fetch {
+ my $sth = shift; my $numRows = shift || 1;
+ my($ref, @rows);
+ while ($numRows-- && ($ref = $sth->SUPER::fetch())) {
+ push(@rows, [@$ref]);
+ }
+ @rows;
+}
+
+
+1;
+
+
+__END__
+
+=head1 NAME
+
+DBI::ProxyServer - a server for the DBD::Proxy driver
+
+=head1 SYNOPSIS
+
+ use DBI::ProxyServer;
+ DBI::ProxyServer::main(@ARGV);
+
+=head1 DESCRIPTION
+
+DBI::Proxy Server is a module for implementing a proxy for the DBI proxy
+driver, DBD::Proxy. It allows access to databases over the network if the
+DBMS does not offer networked operations. But the proxy server might be
+useful for you, even if you have a DBMS with integrated network
+functionality: It can be used as a DBI proxy in a firewalled environment.
+
+DBI::ProxyServer runs as a daemon on the machine with the DBMS or on the
+firewall. The client connects to the agent using the DBI driver DBD::Proxy,
+thus in the exactly same way than using DBD::mysql, DBD::mSQL or any other
+DBI driver.
+
+The agent is implemented as a RPC::PlServer application. Thus you have
+access to all the possibilities of this module, in particular encryption
+and a similar configuration file. DBI::ProxyServer adds the possibility of
+query restrictions: You can define a set of queries that a client may
+execute and restrict access to those. (Requires a DBI driver that supports
+parameter binding.) See L</CONFIGURATION FILE>.
+
+The provided driver script, L<dbiproxy>, may either be used as it is or
+used as the basis for a local version modified to meet your needs.
+
+=head1 OPTIONS
+
+When calling the DBI::ProxyServer::main() function, you supply an
+array of options. These options are parsed by the Getopt::Long module.
+The ProxyServer inherits all of RPC::PlServer's and hence Net::Daemon's
+options and option handling, in particular the ability to read
+options from either the command line or a config file. See
+L<RPC::PlServer>. See L<Net::Daemon>. Available options include
+
+=over 4
+
+=item I<chroot> (B<--chroot=dir>)
+
+(UNIX only) After doing a bind(), change root directory to the given
+directory by doing a chroot(). This is useful for security, but it
+restricts the environment a lot. For example, you need to load DBI
+drivers in the config file or you have to create hard links to Unix
+sockets, if your drivers are using them. For example, with MySQL, a
+config file might contain the following lines:
+
+ my $rootdir = '/var/dbiproxy';
+ my $unixsockdir = '/tmp';
+ my $unixsockfile = 'mysql.sock';
+ foreach $dir ($rootdir, "$rootdir$unixsockdir") {
+ mkdir 0755, $dir;
+ }
+ link("$unixsockdir/$unixsockfile",
+ "$rootdir$unixsockdir/$unixsockfile");
+ require DBD::mysql;
+
+ {
+ 'chroot' => $rootdir,
+ ...
+ }
+
+If you don't know chroot(), think of an FTP server where you can see a
+certain directory tree only after logging in. See also the --group and
+--user options.
+
+=item I<clients>
+
+An array ref with a list of clients. Clients are hash refs, the attributes
+I<accept> (0 for denying access and 1 for permitting) and I<mask>, a Perl
+regular expression for the clients IP number or its host name.
+
+=item I<configfile> (B<--configfile=file>)
+
+Config files are assumed to return a single hash ref that overrides the
+arguments of the new method. However, command line arguments in turn take
+precedence over the config file. See the L<"CONFIGURATION FILE"> section
+below for details on the config file.
+
+=item I<debug> (B<--debug>)
+
+Turn debugging mode on. Mainly this asserts that logging messages of
+level "debug" are created.
+
+=item I<facility> (B<--facility=mode>)
+
+(UNIX only) Facility to use for L<Sys::Syslog>. The default is
+B<daemon>.
+
+=item I<group> (B<--group=gid>)
+
+After doing a bind(), change the real and effective GID to the given.
+This is useful, if you want your server to bind to a privileged port
+(<1024), but don't want the server to execute as root. See also
+the --user option.
+
+GID's can be passed as group names or numeric values.
+
+=item I<localaddr> (B<--localaddr=ip>)
+
+By default a daemon is listening to any IP number that a machine
+has. This attribute allows to restrict the server to the given
+IP number.
+
+=item I<localport> (B<--localport=port>)
+
+This attribute sets the port on which the daemon is listening. It
+must be given somehow, as there's no default.
+
+=item I<logfile> (B<--logfile=file>)
+
+Be default logging messages will be written to the syslog (Unix) or
+to the event log (Windows NT). On other operating systems you need to
+specify a log file. The special value "STDERR" forces logging to
+stderr. See L<Net::Daemon::Log> for details.
+
+=item I<mode> (B<--mode=modename>)
+
+The server can run in three different modes, depending on the environment.
+
+If you are running Perl 5.005 and did compile it for threads, then the
+server will create a new thread for each connection. The thread will
+execute the server's Run() method and then terminate. This mode is the
+default, you can force it with "--mode=threads".
+
+If threads are not available, but you have a working fork(), then the
+server will behave similar by creating a new process for each connection.
+This mode will be used automatically in the absence of threads or if
+you use the "--mode=fork" option.
+
+Finally there's a single-connection mode: If the server has accepted a
+connection, he will enter the Run() method. No other connections are
+accepted until the Run() method returns (if the client disconnects).
+This operation mode is useful if you have neither threads nor fork(),
+for example on the Macintosh. For debugging purposes you can force this
+mode with "--mode=single".
+
+=item I<pidfile> (B<--pidfile=file>)
+
+(UNIX only) If this option is present, a PID file will be created at the
+given location. Default is to not create a pidfile.
+
+=item I<user> (B<--user=uid>)
+
+After doing a bind(), change the real and effective UID to the given.
+This is useful, if you want your server to bind to a privileged port
+(<1024), but don't want the server to execute as root. See also
+the --group and the --chroot options.
+
+UID's can be passed as group names or numeric values.
+
+=item I<version> (B<--version>)
+
+Suppresses startup of the server; instead the version string will
+be printed and the program exits immediately.
+
+=back
+
+=head1 SHUTDOWN
+
+DBI::ProxyServer is built on L<RPC::PlServer> which is, in turn, built on L<Net::Daemon>.
+
+You should refer to L<Net::Daemon> for how to shutdown the server, except that
+you can't because it's not currently documented there (as of v0.43).
+The bottom-line is that it seems that there's no support for graceful shutdown.
+
+=head1 CONFIGURATION FILE
+
+The configuration file is just that of I<RPC::PlServer> or I<Net::Daemon>
+with some additional attributes in the client list.
+
+The config file is a Perl script. At the top of the file you may include
+arbitrary Perl source, for example load drivers at the start (useful
+to enhance performance), prepare a chroot environment and so on.
+
+The important thing is that you finally return a hash ref of option
+name/value pairs. The possible options are listed above.
+
+All possibilities of Net::Daemon and RPC::PlServer apply, in particular
+
+=over 4
+
+=item Host and/or User dependent access control
+
+=item Host and/or User dependent encryption
+
+=item Changing UID and/or GID after binding to the port
+
+=item Running in a chroot() environment
+
+=back
+
+Additionally the server offers you query restrictions. Suggest the
+following client list:
+
+ 'clients' => [
+ { 'mask' => '^admin\.company\.com$',
+ 'accept' => 1,
+ 'users' => [ 'root', 'wwwrun' ],
+ },
+ {
+ 'mask' => '^admin\.company\.com$',
+ 'accept' => 1,
+ 'users' => [ 'root', 'wwwrun' ],
+ 'sql' => {
+ 'select' => 'SELECT * FROM foo',
+ 'insert' => 'INSERT INTO foo VALUES (?, ?, ?)'
+ }
+ }
+
+then only the users root and wwwrun may connect from admin.company.com,
+executing arbitrary queries, but only wwwrun may connect from other
+hosts and is restricted to
+
+ $sth->prepare("select");
+
+or
+
+ $sth->prepare("insert");
+
+which in fact are "SELECT * FROM foo" or "INSERT INTO foo VALUES (?, ?, ?)".
+
+
+=head1 Proxyserver Configuration file (bigger example)
+
+This section tells you how to restrict a DBI-Proxy: Not every user from
+every workstation shall be able to execute every query.
+
+There is a perl program "dbiproxy" which runs on a machine which is able
+to connect to all the databases we wish to reach. All Perl-DBD-drivers must
+be installed on this machine. You can also reach databases for which drivers
+are not available on the machine where you run the program querying the
+database, e.g. ask MS-Access-database from Linux.
+
+Create a configuration file "proxy_oracle.cfg" at the dbproxy-server:
+
+ {
+ # This shall run in a shell or a DOS-window
+ # facility => 'daemon',
+ pidfile => 'your_dbiproxy.pid',
+ logfile => 1,
+ debug => 0,
+ mode => 'single',
+ localport => '12400',
+
+ # Access control, the first match in this list wins!
+ # So the order is important
+ clients => [
+ # hint to organize:
+ # the most specialized rules for single machines/users are 1st
+ # then the denying rules
+ # the the rules about whole networks
+
+ # rule: internal_webserver
+ # desc: to get statistical information
+ {
+ # this IP-address only is meant
+ mask => '^10\.95\.81\.243$',
+ # accept (not defer) connections like this
+ accept => 1,
+ # only users from this list
+ # are allowed to log on
+ users => [ 'informationdesk' ],
+ # only this statistical query is allowed
+ # to get results for a web-query
+ sql => {
+ alive => 'select count(*) from dual',
+ statistic_area => 'select count(*) from e01admin.e01e203 where geb_bezei like ?',
+ }
+ },
+
+ # rule: internal_bad_guy_1
+ {
+ mask => '^10\.95\.81\.1$',
+ accept => 0,
+ },
+
+ # rule: employee_workplace
+ # desc: get detailled information
+ {
+ # any IP-address is meant here
+ mask => '^10\.95\.81\.(\d+)$',
+ # accept (not defer) connections like this
+ accept => 1,
+ # only users from this list
+ # are allowed to log on
+ users => [ 'informationdesk', 'lippmann' ],
+ # all these queries are allowed:
+ sql => {
+ search_city => 'select ort_nr, plz, ort from e01admin.e01e200 where plz like ?',
+ search_area => 'select gebiettyp, geb_bezei from e01admin.e01e203 where geb_bezei like ? or geb_bezei like ?',
+ }
+ },
+
+ # rule: internal_bad_guy_2
+ # This does NOT work, because rule "employee_workplace" hits
+ # with its ip-address-mask of the whole network
+ {
+ # don't accept connection from this ip-address
+ mask => '^10\.95\.81\.5$',
+ accept => 0,
+ }
+ ]
+ }
+
+Start the proxyserver like this:
+
+ rem well-set Oracle_home needed for Oracle
+ set ORACLE_HOME=d:\oracle\ora81
+ dbiproxy --configfile proxy_oracle.cfg
+
+
+=head2 Testing the connection from a remote machine
+
+Call a program "dbish" from your commandline. I take the machine from rule "internal_webserver"
+
+ dbish "dbi:Proxy:hostname=oracle.zdf;port=12400;dsn=dbi:Oracle:e01" informationdesk xxx
+
+There will be a shell-prompt:
+
+ informationdesk@dbi...> alive
+
+ Current statement buffer (enter '/'...):
+ alive
+
+ informationdesk@dbi...> /
+ COUNT(*)
+ '1'
+ [1 rows of 1 fields returned]
+
+
+=head2 Testing the connection with a perl-script
+
+Create a perl-script like this:
+
+ # file: oratest.pl
+ # call me like this: perl oratest.pl user password
+
+ use strict;
+ use DBI;
+
+ my $user = shift || die "Usage: $0 user password";
+ my $pass = shift || die "Usage: $0 user password";
+ my $config = {
+ dsn_at_proxy => "dbi:Oracle:e01",
+ proxy => "hostname=oechsle.zdf;port=12400",
+ };
+ my $dsn = sprintf "dbi:Proxy:%s;dsn=%s",
+ $config->{proxy},
+ $config->{dsn_at_proxy};
+
+ my $dbh = DBI->connect( $dsn, $user, $pass )
+ || die "connect did not work: $DBI::errstr";
+
+ my $sql = "search_city";
+ printf "%s\n%s\n%s\n", "="x40, $sql, "="x40;
+ my $cur = $dbh->prepare($sql);
+ $cur->bind_param(1,'905%');
+ &show_result ($cur);
+
+ my $sql = "search_area";
+ printf "%s\n%s\n%s\n", "="x40, $sql, "="x40;
+ my $cur = $dbh->prepare($sql);
+ $cur->bind_param(1,'Pfarr%');
+ $cur->bind_param(2,'Bronnamberg%');
+ &show_result ($cur);
+
+ my $sql = "statistic_area";
+ printf "%s\n%s\n%s\n", "="x40, $sql, "="x40;
+ my $cur = $dbh->prepare($sql);
+ $cur->bind_param(1,'Pfarr%');
+ &show_result ($cur);
+
+ $dbh->disconnect;
+ exit;
+
+
+ sub show_result {
+ my $cur = shift;
+ unless ($cur->execute()) {
+ print "Could not execute\n";
+ return;
+ }
+
+ my $rownum = 0;
+ while (my @row = $cur->fetchrow_array()) {
+ printf "Row is: %s\n", join(", ",@row);
+ if ($rownum++ > 5) {
+ print "... and so on\n";
+ last;
+ }
+ }
+ $cur->finish;
+ }
+
+The result
+
+ C:\>perl oratest.pl informationdesk xxx
+ ========================================
+ search_city
+ ========================================
+ Row is: 3322, 9050, Chemnitz
+ Row is: 3678, 9051, Chemnitz
+ Row is: 10447, 9051, Chemnitz
+ Row is: 12128, 9051, Chemnitz
+ Row is: 10954, 90513, Zirndorf
+ Row is: 5808, 90513, Zirndorf
+ Row is: 5715, 90513, Zirndorf
+ ... and so on
+ ========================================
+ search_area
+ ========================================
+ Row is: 101, Bronnamberg
+ Row is: 400, Pfarramt Zirndorf
+ Row is: 400, Pfarramt Rosstal
+ Row is: 400, Pfarramt Oberasbach
+ Row is: 401, Pfarramt Zirndorf
+ Row is: 401, Pfarramt Rosstal
+ ========================================
+ statistic_area
+ ========================================
+ DBD::Proxy::st execute failed: Server returned error: Failed to execute method CallMethod: Unknown SQL query: statistic_area at E:/Perl/site/lib/DBI/ProxyServer.pm line 258.
+ Could not execute
+
+
+=head2 How the configuration works
+
+The most important section to control access to your dbi-proxy is "client=>"
+in the file "proxy_oracle.cfg":
+
+Controlling which person at which machine is allowed to access
+
+=over 4
+
+=item * "mask" is a perl regular expression against the plain ip-address of the machine which wishes to connect _or_ the reverse-lookup from a nameserver.
+
+=item * "accept" tells the dbiproxy-server wether ip-adresse like in "mask" are allowed to connect or not (0/1)
+
+=item * "users" is a reference to a list of usernames which must be matched, this is NOT a regular expression.
+
+=back
+
+Controlling which SQL-statements are allowed
+
+You can put every SQL-statement you like in simply ommiting "sql => ...", but the more important thing is to restrict the connection so that only allowed queries are possible.
+
+If you include an sql-section in your config-file like this:
+
+ sql => {
+ alive => 'select count(*) from dual',
+ statistic_area => 'select count(*) from e01admin.e01e203 where geb_bezei like ?',
+ }
+
+The user is allowed to put two queries against the dbi-proxy. The queries are _not_ "select count(*)...", the queries are "alive" and "statistic_area"! These keywords are replaced by the real query. So you can run a query for "alive":
+
+ my $sql = "alive";
+ my $cur = $dbh->prepare($sql);
+ ...
+
+The flexibility is that you can put parameters in the where-part of the query so the query are not static. Simply replace a value in the where-part of the query through a question mark and bind it as a parameter to the query.
+
+ my $sql = "statistic_area";
+ my $cur = $dbh->prepare($sql);
+ $cur->bind_param(1,'905%');
+ # A second parameter would be called like this:
+ # $cur->bind_param(2,'98%');
+
+The result is this query:
+
+ select count(*) from e01admin.e01e203
+ where geb_bezei like '905%'
+
+Don't try to put parameters into the sql-query like this:
+
+ # Does not work like you think.
+ # Only the first word of the query is parsed,
+ # so it's changed to "statistic_area", the rest is omitted.
+ # You _have_ to work with $cur->bind_param.
+ my $sql = "statistic_area 905%";
+ my $cur = $dbh->prepare($sql);
+ ...
+
+
+=head2 Problems
+
+=over 4
+
+=item * I don't know how to restrict users to special databases.
+
+=item * I don't know how to pass query-parameters via dbish
+
+=back
+
+
+=head1 AUTHOR
+
+ Copyright (c) 1997 Jochen Wiedmann
+ Am Eisteich 9
+ 72555 Metzingen
+ Germany
+
+ Email: joe@ispsoft.de
+ Phone: +49 7123 14881
+
+The DBI::ProxyServer module is free software; you can redistribute it
+and/or modify it under the same terms as Perl itself. In particular
+permission is granted to Tim Bunce for distributing this as a part of
+the DBI.
+
+
+=head1 SEE ALSO
+
+L<dbiproxy>, L<DBD::Proxy>, L<DBI>, L<RPC::PlServer>,
+L<RPC::PlClient>, L<Net::Daemon>, L<Net::Daemon::Log>,
+L<Sys::Syslog>, L<Win32::EventLog>, L<syslog>
diff --git a/lib/DBI/PurePerl.pm b/lib/DBI/PurePerl.pm
new file mode 100644
index 0000000..593379d
--- /dev/null
+++ b/lib/DBI/PurePerl.pm
@@ -0,0 +1,1259 @@
+########################################################################
+package # hide from PAUSE
+ DBI;
+# vim: ts=8:sw=4
+########################################################################
+#
+# Copyright (c) 2002,2003 Tim Bunce Ireland.
+#
+# See COPYRIGHT section in DBI.pm for usage and distribution rights.
+#
+########################################################################
+#
+# Please send patches and bug reports to
+#
+# Jeff Zucker <jeff@vpservices.com> with cc to <dbi-dev@perl.org>
+#
+########################################################################
+
+use strict;
+use Carp;
+require Symbol;
+
+require utf8;
+*utf8::is_utf8 = sub { # hack for perl 5.6
+ require bytes;
+ return unless defined $_[0];
+ return !(length($_[0]) == bytes::length($_[0]))
+} unless defined &utf8::is_utf8;
+
+$DBI::PurePerl = $ENV{DBI_PUREPERL} || 1;
+$DBI::PurePerl::VERSION = sprintf("2.%06d", q$Revision: 14285 $ =~ /(\d+)/o);
+
+$DBI::neat_maxlen ||= 400;
+
+$DBI::tfh = Symbol::gensym();
+open $DBI::tfh, ">&STDERR" or warn "Can't dup STDERR: $!";
+select( (select($DBI::tfh), $| = 1)[0] ); # autoflush
+
+# check for weaken support, used by ChildHandles
+my $HAS_WEAKEN = eval {
+ require Scalar::Util;
+ # this will croak() if this Scalar::Util doesn't have a working weaken().
+ Scalar::Util::weaken( my $test = [] );
+ 1;
+};
+
+%DBI::last_method_except = map { $_=>1 } qw(DESTROY _set_fbav set_err);
+
+use constant SQL_ALL_TYPES => 0;
+use constant SQL_ARRAY => 50;
+use constant SQL_ARRAY_LOCATOR => 51;
+use constant SQL_BIGINT => (-5);
+use constant SQL_BINARY => (-2);
+use constant SQL_BIT => (-7);
+use constant SQL_BLOB => 30;
+use constant SQL_BLOB_LOCATOR => 31;
+use constant SQL_BOOLEAN => 16;
+use constant SQL_CHAR => 1;
+use constant SQL_CLOB => 40;
+use constant SQL_CLOB_LOCATOR => 41;
+use constant SQL_DATE => 9;
+use constant SQL_DATETIME => 9;
+use constant SQL_DECIMAL => 3;
+use constant SQL_DOUBLE => 8;
+use constant SQL_FLOAT => 6;
+use constant SQL_GUID => (-11);
+use constant SQL_INTEGER => 4;
+use constant SQL_INTERVAL => 10;
+use constant SQL_INTERVAL_DAY => 103;
+use constant SQL_INTERVAL_DAY_TO_HOUR => 108;
+use constant SQL_INTERVAL_DAY_TO_MINUTE => 109;
+use constant SQL_INTERVAL_DAY_TO_SECOND => 110;
+use constant SQL_INTERVAL_HOUR => 104;
+use constant SQL_INTERVAL_HOUR_TO_MINUTE => 111;
+use constant SQL_INTERVAL_HOUR_TO_SECOND => 112;
+use constant SQL_INTERVAL_MINUTE => 105;
+use constant SQL_INTERVAL_MINUTE_TO_SECOND => 113;
+use constant SQL_INTERVAL_MONTH => 102;
+use constant SQL_INTERVAL_SECOND => 106;
+use constant SQL_INTERVAL_YEAR => 101;
+use constant SQL_INTERVAL_YEAR_TO_MONTH => 107;
+use constant SQL_LONGVARBINARY => (-4);
+use constant SQL_LONGVARCHAR => (-1);
+use constant SQL_MULTISET => 55;
+use constant SQL_MULTISET_LOCATOR => 56;
+use constant SQL_NUMERIC => 2;
+use constant SQL_REAL => 7;
+use constant SQL_REF => 20;
+use constant SQL_ROW => 19;
+use constant SQL_SMALLINT => 5;
+use constant SQL_TIME => 10;
+use constant SQL_TIMESTAMP => 11;
+use constant SQL_TINYINT => (-6);
+use constant SQL_TYPE_DATE => 91;
+use constant SQL_TYPE_TIME => 92;
+use constant SQL_TYPE_TIMESTAMP => 93;
+use constant SQL_TYPE_TIMESTAMP_WITH_TIMEZONE => 95;
+use constant SQL_TYPE_TIME_WITH_TIMEZONE => 94;
+use constant SQL_UDT => 17;
+use constant SQL_UDT_LOCATOR => 18;
+use constant SQL_UNKNOWN_TYPE => 0;
+use constant SQL_VARBINARY => (-3);
+use constant SQL_VARCHAR => 12;
+use constant SQL_WCHAR => (-8);
+use constant SQL_WLONGVARCHAR => (-10);
+use constant SQL_WVARCHAR => (-9);
+
+# for Cursor types
+use constant SQL_CURSOR_FORWARD_ONLY => 0;
+use constant SQL_CURSOR_KEYSET_DRIVEN => 1;
+use constant SQL_CURSOR_DYNAMIC => 2;
+use constant SQL_CURSOR_STATIC => 3;
+use constant SQL_CURSOR_TYPE_DEFAULT => SQL_CURSOR_FORWARD_ONLY;
+
+use constant IMA_HAS_USAGE => 0x0001; #/* check parameter usage */
+use constant IMA_FUNC_REDIRECT => 0x0002; #/* is $h->func(..., "method")*/
+use constant IMA_KEEP_ERR => 0x0004; #/* don't reset err & errstr */
+use constant IMA_KEEP_ERR_SUB => 0x0008; #/* '' if in nested call */
+use constant IMA_NO_TAINT_IN => 0x0010; #/* don't check for tainted args*/
+use constant IMA_NO_TAINT_OUT => 0x0020; #/* don't taint results */
+use constant IMA_COPY_UP_STMT => 0x0040; #/* copy sth Statement to dbh */
+use constant IMA_END_WORK => 0x0080; #/* set on commit & rollback */
+use constant IMA_STUB => 0x0100; #/* donothing eg $dbh->connected */
+use constant IMA_CLEAR_STMT => 0x0200; #/* clear Statement before call */
+use constant IMA_UNRELATED_TO_STMT=> 0x0400; #/* profile as empty Statement */
+use constant IMA_NOT_FOUND_OKAY => 0x0800; #/* not error if not found */
+use constant IMA_EXECUTE => 0x1000; #/* do/execute: DBIcf_Executed */
+use constant IMA_SHOW_ERR_STMT => 0x2000; #/* dbh meth relates to Statement*/
+use constant IMA_HIDE_ERR_PARAMVALUES => 0x4000; #/* ParamValues are not relevant */
+use constant IMA_IS_FACTORY => 0x8000; #/* new h ie connect & prepare */
+use constant IMA_CLEAR_CACHED_KIDS => 0x10000; #/* clear CachedKids before call */
+
+use constant DBIstcf_STRICT => 0x0001;
+use constant DBIstcf_DISCARD_STRING => 0x0002;
+
+my %is_flag_attribute = map {$_ =>1 } qw(
+ Active
+ AutoCommit
+ ChopBlanks
+ CompatMode
+ Executed
+ Taint
+ TaintIn
+ TaintOut
+ InactiveDestroy
+ AutoInactiveDestroy
+ LongTruncOk
+ MultiThread
+ PrintError
+ PrintWarn
+ RaiseError
+ ShowErrorStatement
+ Warn
+);
+my %is_valid_attribute = map {$_ =>1 } (keys %is_flag_attribute, qw(
+ ActiveKids
+ Attribution
+ BegunWork
+ CachedKids
+ Callbacks
+ ChildHandles
+ CursorName
+ Database
+ DebugDispatch
+ Driver
+ Err
+ Errstr
+ ErrCount
+ FetchHashKeyName
+ HandleError
+ HandleSetErr
+ ImplementorClass
+ Kids
+ LongReadLen
+ NAME NAME_uc NAME_lc NAME_uc_hash NAME_lc_hash
+ NULLABLE
+ NUM_OF_FIELDS
+ NUM_OF_PARAMS
+ Name
+ PRECISION
+ ParamValues
+ Profile
+ Provider
+ ReadOnly
+ RootClass
+ RowCacheSize
+ RowsInCache
+ SCALE
+ State
+ Statement
+ TYPE
+ Type
+ TraceLevel
+ Username
+ Version
+));
+
+sub valid_attribute {
+ my $attr = shift;
+ return 1 if $is_valid_attribute{$attr};
+ return 1 if $attr =~ m/^[a-z]/; # starts with lowercase letter
+ return 0
+}
+
+my $initial_setup;
+sub initial_setup {
+ $initial_setup = 1;
+ print $DBI::tfh __FILE__ . " version " . $DBI::PurePerl::VERSION . "\n"
+ if $DBI::dbi_debug & 0xF;
+ untie $DBI::err;
+ untie $DBI::errstr;
+ untie $DBI::state;
+ untie $DBI::rows;
+ #tie $DBI::lasth, 'DBI::var', '!lasth'; # special case: return boolean
+}
+
+sub _install_method {
+ my ( $caller, $method, $from, $param_hash ) = @_;
+ initial_setup() unless $initial_setup;
+
+ my ($class, $method_name) = $method =~ /^[^:]+::(.+)::(.+)$/;
+ my $bitmask = $param_hash->{'O'} || 0;
+ my @pre_call_frag;
+
+ return if $method_name eq 'can';
+
+ push @pre_call_frag, q{
+ # ignore DESTROY for outer handle (DESTROY for inner likely to follow soon)
+ return if $h_inner;
+ # handle AutoInactiveDestroy and InactiveDestroy
+ $h->{InactiveDestroy} = 1
+ if $h->{AutoInactiveDestroy} and $$ != $h->{dbi_pp_pid};
+ $h->{Active} = 0
+ if $h->{InactiveDestroy};
+ # copy err/errstr/state up to driver so $DBI::err etc still work
+ if ($h->{err} and my $drh = $h->{Driver}) {
+ $drh->{$_} = $h->{$_} for ('err','errstr','state');
+ }
+ } if $method_name eq 'DESTROY';
+
+ push @pre_call_frag, q{
+ return $h->{$_[0]} if exists $h->{$_[0]};
+ } if $method_name eq 'FETCH' && !exists $ENV{DBI_TRACE}; # XXX ?
+
+ push @pre_call_frag, "return;"
+ if IMA_STUB & $bitmask;
+
+ push @pre_call_frag, q{
+ $method_name = pop @_;
+ } if IMA_FUNC_REDIRECT & $bitmask;
+
+ push @pre_call_frag, q{
+ my $parent_dbh = $h->{Database};
+ } if (IMA_COPY_UP_STMT|IMA_EXECUTE) & $bitmask;
+
+ push @pre_call_frag, q{
+ warn "No Database set for $h on $method_name!" unless $parent_dbh; # eg proxy problems
+ $parent_dbh->{Statement} = $h->{Statement} if $parent_dbh;
+ } if IMA_COPY_UP_STMT & $bitmask;
+
+ push @pre_call_frag, q{
+ $h->{Executed} = 1;
+ $parent_dbh->{Executed} = 1 if $parent_dbh;
+ } if IMA_EXECUTE & $bitmask;
+
+ push @pre_call_frag, q{
+ %{ $h->{CachedKids} } = () if $h->{CachedKids};
+ } if IMA_CLEAR_CACHED_KIDS & $bitmask;
+
+ if (IMA_KEEP_ERR & $bitmask) {
+ push @pre_call_frag, q{
+ my $keep_error = 1;
+ };
+ }
+ else {
+ my $ke_init = (IMA_KEEP_ERR_SUB & $bitmask)
+ ? q{= $h->{dbi_pp_parent}->{dbi_pp_call_depth} }
+ : "";
+ push @pre_call_frag, qq{
+ my \$keep_error $ke_init;
+ };
+ my $keep_error_code = q{
+ #warn "$method_name cleared err";
+ $h->{err} = $DBI::err = undef;
+ $h->{errstr} = $DBI::errstr = undef;
+ $h->{state} = $DBI::state = '';
+ };
+ $keep_error_code = q{
+ printf $DBI::tfh " !! %s: %s CLEARED by call to }.$method_name.q{ method\n".
+ $h->{err}, $h->{err}
+ if defined $h->{err} && $DBI::dbi_debug & 0xF;
+ }. $keep_error_code
+ if exists $ENV{DBI_TRACE};
+ push @pre_call_frag, ($ke_init)
+ ? qq{ unless (\$keep_error) { $keep_error_code }}
+ : $keep_error_code
+ unless $method_name eq 'set_err';
+ }
+
+ push @pre_call_frag, q{
+ my $ErrCount = $h->{ErrCount};
+ };
+
+ push @pre_call_frag, q{
+ if (($DBI::dbi_debug & 0xF) >= 2) {
+ local $^W;
+ my $args = join " ", map { DBI::neat($_) } ($h, @_);
+ printf $DBI::tfh " > $method_name in $imp ($args) [$@]\n";
+ }
+ } if exists $ENV{DBI_TRACE}; # note use of 'exists'
+
+ push @pre_call_frag, q{
+ $h->{'dbi_pp_last_method'} = $method_name;
+ } unless exists $DBI::last_method_except{$method_name};
+
+ # --- post method call code fragments ---
+ my @post_call_frag;
+
+ push @post_call_frag, q{
+ if (my $trace_level = ($DBI::dbi_debug & 0xF)) {
+ if ($h->{err}) {
+ printf $DBI::tfh " !! ERROR: %s %s\n", $h->{err}, $h->{errstr};
+ }
+ my $ret = join " ", map { DBI::neat($_) } @ret;
+ my $msg = " < $method_name= $ret";
+ $msg = ($trace_level >= 2) ? Carp::shortmess($msg) : "$msg\n";
+ print $DBI::tfh $msg;
+ }
+ } if exists $ENV{DBI_TRACE}; # note use of exists
+
+ push @post_call_frag, q{
+ $h->{Executed} = 0;
+ if ($h->{BegunWork}) {
+ $h->{BegunWork} = 0;
+ $h->{AutoCommit} = 1;
+ }
+ } if IMA_END_WORK & $bitmask;
+
+ push @post_call_frag, q{
+ if ( ref $ret[0] and
+ UNIVERSAL::isa($ret[0], 'DBI::_::common') and
+ defined( (my $h_new = tied(%{$ret[0]})||$ret[0])->{err} )
+ ) {
+ # copy up info/warn to drh so PrintWarn on connect is triggered
+ $h->set_err($h_new->{err}, $h_new->{errstr}, $h_new->{state})
+ }
+ } if IMA_IS_FACTORY & $bitmask;
+
+ push @post_call_frag, q{
+ $keep_error = 0 if $keep_error && $h->{ErrCount} > $ErrCount;
+
+ $DBI::err = $h->{err};
+ $DBI::errstr = $h->{errstr};
+ $DBI::state = $h->{state};
+
+ if ( !$keep_error
+ && defined(my $err = $h->{err})
+ && ($call_depth <= 1 && !$h->{dbi_pp_parent}{dbi_pp_call_depth})
+ ) {
+
+ my($pe,$pw,$re,$he) = @{$h}{qw(PrintError PrintWarn RaiseError HandleError)};
+ my $msg;
+
+ if ($err && ($pe || $re || $he) # error
+ or (!$err && length($err) && $pw) # warning
+ ) {
+ my $last = ($DBI::last_method_except{$method_name})
+ ? ($h->{'dbi_pp_last_method'}||$method_name) : $method_name;
+ my $errstr = $h->{errstr} || $DBI::errstr || $err || '';
+ my $msg = sprintf "%s %s %s: %s", $imp, $last,
+ ($err eq "0") ? "warning" : "failed", $errstr;
+
+ if ($h->{'ShowErrorStatement'} and my $Statement = $h->{Statement}) {
+ $msg .= ' [for Statement "' . $Statement;
+ if (my $ParamValues = $h->FETCH('ParamValues')) {
+ $msg .= '" with ParamValues: ';
+ $msg .= DBI::_concat_hash_sorted($ParamValues, "=", ", ", 1, undef);
+ $msg .= "]";
+ }
+ else {
+ $msg .= '"]';
+ }
+ }
+ if ($err eq "0") { # is 'warning' (not info)
+ carp $msg if $pw;
+ }
+ else {
+ my $do_croak = 1;
+ if (my $subsub = $h->{'HandleError'}) {
+ $do_croak = 0 if &$subsub($msg,$h,$ret[0]);
+ }
+ if ($do_croak) {
+ printf $DBI::tfh " $method_name has failed ($h->{PrintError},$h->{RaiseError})\n"
+ if ($DBI::dbi_debug & 0xF) >= 4;
+ carp $msg if $pe;
+ die $msg if $h->{RaiseError};
+ }
+ }
+ }
+ }
+ };
+
+
+ my $method_code = q[
+ sub {
+ my $h = shift;
+ my $h_inner = tied(%$h);
+ $h = $h_inner if $h_inner;
+
+ my $imp;
+ if ($method_name eq 'DESTROY') {
+ # during global destruction, $h->{...} can trigger "Can't call FETCH on an undef value"
+ # implying that tied() above lied to us, so we need to use eval
+ local $@; # protect $@
+ $imp = eval { $h->{"ImplementorClass"} } or return; # probably global destruction
+ }
+ else {
+ $imp = $h->{"ImplementorClass"} or do {
+ warn "Can't call $method_name method on handle $h after take_imp_data()\n"
+ if not exists $h->{Active};
+ return; # or, more likely, global destruction
+ };
+ }
+
+ ] . join("\n", '', @pre_call_frag, '') . q[
+
+ my $call_depth = $h->{'dbi_pp_call_depth'} + 1;
+ local ($h->{'dbi_pp_call_depth'}) = $call_depth;
+
+ my @ret;
+ my $sub = $imp->can($method_name);
+ if (!$sub and IMA_FUNC_REDIRECT & $bitmask and $sub = $imp->can('func')) {
+ push @_, $method_name;
+ }
+ if ($sub) {
+ (wantarray) ? (@ret = &$sub($h,@_)) : (@ret = scalar &$sub($h,@_));
+ }
+ else {
+ # XXX could try explicit fallback to $imp->can('AUTOLOAD') etc
+ # which would then let Multiplex pass PurePerl tests, but some
+ # hook into install_method may be better.
+ croak "Can't locate DBI object method \"$method_name\" via package \"$imp\""
+ if ] . ((IMA_NOT_FOUND_OKAY & $bitmask) ? 0 : 1) . q[;
+ }
+
+ ] . join("\n", '', @post_call_frag, '') . q[
+
+ return (wantarray) ? @ret : $ret[0];
+ }
+ ];
+ no strict qw(refs);
+ my $code_ref = eval qq{#line 1 "DBI::PurePerl $method"\n$method_code};
+ warn "$@\n$method_code\n" if $@;
+ die "$@\n$method_code\n" if $@;
+ *$method = $code_ref;
+ if (0 && $method =~ /\b(connect|FETCH)\b/) { # debuging tool
+ my $l=0; # show line-numbered code for method
+ warn "*$method code:\n".join("\n", map { ++$l.": $_" } split/\n/,$method_code);
+ }
+}
+
+
+sub _new_handle {
+ my ($class, $parent, $attr, $imp_data, $imp_class) = @_;
+
+ DBI->trace_msg(" New $class (for $imp_class, parent=$parent, id=".($imp_data||'').")\n")
+ if $DBI::dbi_debug >= 3;
+
+ $attr->{ImplementorClass} = $imp_class
+ or Carp::croak("_new_handle($class): 'ImplementorClass' attribute not given");
+
+ # This is how we create a DBI style Object:
+ # %outer gets tied to %$attr (which becomes the 'inner' handle)
+ my (%outer, $i, $h);
+ $i = tie %outer, $class, $attr; # ref to inner hash (for driver)
+ $h = bless \%outer, $class; # ref to outer hash (for application)
+ # The above tie and bless may migrate down into _setup_handle()...
+ # Now add magic so DBI method dispatch works
+ DBI::_setup_handle($h, $imp_class, $parent, $imp_data);
+ return $h unless wantarray;
+ return ($h, $i);
+}
+
+sub _setup_handle {
+ my($h, $imp_class, $parent, $imp_data) = @_;
+ my $h_inner = tied(%$h) || $h;
+ if (($DBI::dbi_debug & 0xF) >= 4) {
+ local $^W;
+ print $DBI::tfh " _setup_handle(@_)\n";
+ }
+ $h_inner->{"imp_data"} = $imp_data;
+ $h_inner->{"ImplementorClass"} = $imp_class;
+ $h_inner->{"Kids"} = $h_inner->{"ActiveKids"} = 0; # XXX not maintained
+ if ($parent) {
+ foreach (qw(
+ RaiseError PrintError PrintWarn HandleError HandleSetErr
+ Warn LongTruncOk ChopBlanks AutoCommit ReadOnly
+ ShowErrorStatement FetchHashKeyName LongReadLen CompatMode
+ )) {
+ $h_inner->{$_} = $parent->{$_}
+ if exists $parent->{$_} && !exists $h_inner->{$_};
+ }
+ if (ref($parent) =~ /::db$/) {
+ $h_inner->{Database} = $parent;
+ $parent->{Statement} = $h_inner->{Statement};
+ $h_inner->{NUM_OF_PARAMS} = 0;
+ }
+ elsif (ref($parent) =~ /::dr$/){
+ $h_inner->{Driver} = $parent;
+ }
+ $h_inner->{dbi_pp_parent} = $parent;
+
+ # add to the parent's ChildHandles
+ if ($HAS_WEAKEN) {
+ my $handles = $parent->{ChildHandles} ||= [];
+ push @$handles, $h;
+ Scalar::Util::weaken($handles->[-1]);
+ # purge destroyed handles occasionally
+ if (@$handles % 120 == 0) {
+ @$handles = grep { defined } @$handles;
+ Scalar::Util::weaken($_) for @$handles; # re-weaken after grep
+ }
+ }
+ }
+ else { # setting up a driver handle
+ $h_inner->{Warn} = 1;
+ $h_inner->{PrintWarn} = $^W;
+ $h_inner->{AutoCommit} = 1;
+ $h_inner->{TraceLevel} = 0;
+ $h_inner->{CompatMode} = (1==0);
+ $h_inner->{FetchHashKeyName} ||= 'NAME';
+ $h_inner->{LongReadLen} ||= 80;
+ $h_inner->{ChildHandles} ||= [] if $HAS_WEAKEN;
+ $h_inner->{Type} ||= 'dr';
+ }
+ $h_inner->{"dbi_pp_call_depth"} = 0;
+ $h_inner->{"dbi_pp_pid"} = $$;
+ $h_inner->{ErrCount} = 0;
+ $h_inner->{Active} = 1;
+}
+
+sub constant {
+ warn "constant(@_) called unexpectedly"; return undef;
+}
+
+sub trace {
+ my ($h, $level, $file) = @_;
+ $level = $h->parse_trace_flags($level)
+ if defined $level and !DBI::looks_like_number($level);
+ my $old_level = $DBI::dbi_debug;
+ _set_trace_file($file) if $level;
+ if (defined $level) {
+ $DBI::dbi_debug = $level;
+ print $DBI::tfh " DBI $DBI::VERSION (PurePerl) "
+ . "dispatch trace level set to $DBI::dbi_debug\n"
+ if $DBI::dbi_debug & 0xF;
+ }
+ _set_trace_file($file) if !$level;
+ return $old_level;
+}
+
+sub _set_trace_file {
+ my ($file) = @_;
+ #
+ # DAA add support for filehandle inputs
+ #
+ # DAA required to avoid closing a prior fh trace()
+ $DBI::tfh = undef unless $DBI::tfh_needs_close;
+
+ if (ref $file eq 'GLOB') {
+ $DBI::tfh = $file;
+ select((select($DBI::tfh), $| = 1)[0]);
+ $DBI::tfh_needs_close = 0;
+ return 1;
+ }
+ if ($file && ref \$file eq 'GLOB') {
+ $DBI::tfh = *{$file}{IO};
+ select((select($DBI::tfh), $| = 1)[0]);
+ $DBI::tfh_needs_close = 0;
+ return 1;
+ }
+ $DBI::tfh_needs_close = 1;
+ if (!$file || $file eq 'STDERR') {
+ open $DBI::tfh, ">&STDERR" or carp "Can't dup STDERR: $!";
+ }
+ elsif ($file eq 'STDOUT') {
+ open $DBI::tfh, ">&STDOUT" or carp "Can't dup STDOUT: $!";
+ }
+ else {
+ open $DBI::tfh, ">>$file" or carp "Can't open $file: $!";
+ }
+ select((select($DBI::tfh), $| = 1)[0]);
+ return 1;
+}
+sub _get_imp_data { shift->{"imp_data"}; }
+sub _svdump { }
+sub dump_handle {
+ my ($h,$msg,$level) = @_;
+ $msg||="dump_handle $h";
+ print $DBI::tfh "$msg:\n";
+ for my $attrib (sort keys %$h) {
+ print $DBI::tfh "\t$attrib => ".DBI::neat($h->{$attrib})."\n";
+ }
+}
+
+sub _handles {
+ my $h = shift;
+ my $h_inner = tied %$h;
+ if ($h_inner) { # this is okay
+ return $h unless wantarray;
+ return ($h, $h_inner);
+ }
+ # XXX this isn't okay... we have an inner handle but
+ # currently have no way to get at its outer handle,
+ # so we just warn and return the inner one for both...
+ Carp::carp("Can't return outer handle from inner handle using DBI::PurePerl");
+ return $h unless wantarray;
+ return ($h,$h);
+}
+
+sub hash {
+ my ($key, $type) = @_;
+ my ($hash);
+ if (!$type) {
+ $hash = 0;
+ # XXX The C version uses the "char" type, which could be either
+ # signed or unsigned. I use signed because so do the two
+ # compilers on my system.
+ for my $char (unpack ("c*", $key)) {
+ $hash = $hash * 33 + $char;
+ }
+ $hash &= 0x7FFFFFFF; # limit to 31 bits
+ $hash |= 0x40000000; # set bit 31
+ return -$hash; # return negative int
+ }
+ elsif ($type == 1) { # Fowler/Noll/Vo hash
+ # see http://www.isthe.com/chongo/tech/comp/fnv/
+ require Math::BigInt; # feel free to reimplement w/o BigInt!
+ (my $version = $Math::BigInt::VERSION || 0) =~ s/_.*//; # eg "1.70_01"
+ if ($version >= 1.56) {
+ $hash = Math::BigInt->new(0x811c9dc5);
+ for my $uchar (unpack ("C*", $key)) {
+ # multiply by the 32 bit FNV magic prime mod 2^64
+ $hash = ($hash * 0x01000193) & 0xffffffff;
+ # xor the bottom with the current octet
+ $hash ^= $uchar;
+ }
+ # cast to int
+ return unpack "i", pack "i", $hash;
+ }
+ croak("DBI::PurePerl doesn't support hash type 1 without Math::BigInt >= 1.56 (available on CPAN)");
+ }
+ else {
+ croak("bad hash type $type");
+ }
+}
+
+sub looks_like_number {
+ my @new = ();
+ for my $thing(@_) {
+ if (!defined $thing or $thing eq '') {
+ push @new, undef;
+ }
+ else {
+ push @new, ($thing =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) ? 1 : 0;
+ }
+ }
+ return (@_ >1) ? @new : $new[0];
+}
+
+sub neat {
+ my $v = shift;
+ return "undef" unless defined $v;
+ my $quote = q{"};
+ if (not utf8::is_utf8($v)) {
+ return $v if (($v & ~ $v) eq "0"); # is SvNIOK
+ $quote = q{'};
+ }
+ my $maxlen = shift || $DBI::neat_maxlen;
+ if ($maxlen && $maxlen < length($v) + 2) {
+ $v = substr($v,0,$maxlen-5);
+ $v .= '...';
+ }
+ $v =~ s/[^[:print:]]/./g;
+ return "$quote$v$quote";
+}
+
+sub sql_type_cast {
+ my (undef, $sql_type, $flags) = @_;
+
+ return -1 unless defined $_[0];
+
+ my $cast_ok = 1;
+
+ my $evalret = eval {
+ use warnings FATAL => qw(numeric);
+ if ($sql_type == SQL_INTEGER) {
+ my $dummy = $_[0] + 0;
+ return 1;
+ }
+ elsif ($sql_type == SQL_DOUBLE) {
+ my $dummy = $_[0] + 0.0;
+ return 1;
+ }
+ elsif ($sql_type == SQL_NUMERIC) {
+ my $dummy = $_[0] + 0.0;
+ return 1;
+ }
+ else {
+ return -2;
+ }
+ } or $^W && warn $@; # XXX warnings::warnif("numeric", $@) ?
+
+ return $evalret if defined($evalret) && ($evalret == -2);
+ $cast_ok = 0 unless $evalret;
+
+ # DBIstcf_DISCARD_STRING not supported for PurePerl currently
+
+ return 2 if $cast_ok;
+ return 0 if $flags & DBIstcf_STRICT;
+ return 1;
+}
+
+sub dbi_time {
+ return time();
+}
+
+sub DBI::st::TIEHASH { bless $_[1] => $_[0] };
+
+sub _concat_hash_sorted {
+ my ( $hash_ref, $kv_separator, $pair_separator, $use_neat, $num_sort ) = @_;
+ # $num_sort: 0=lexical, 1=numeric, undef=try to guess
+
+ return undef unless defined $hash_ref;
+ die "hash is not a hash reference" unless ref $hash_ref eq 'HASH';
+ my $keys = _get_sorted_hash_keys($hash_ref, $num_sort);
+ my $string = '';
+ for my $key (@$keys) {
+ $string .= $pair_separator if length $string > 0;
+ my $value = $hash_ref->{$key};
+ if ($use_neat) {
+ $value = DBI::neat($value, 0);
+ }
+ else {
+ $value = (defined $value) ? "'$value'" : 'undef';
+ }
+ $string .= $key . $kv_separator . $value;
+ }
+ return $string;
+}
+
+sub _get_sorted_hash_keys {
+ my ($hash_ref, $num_sort) = @_;
+ if (not defined $num_sort) {
+ my $sort_guess = 1;
+ $sort_guess = (not looks_like_number($_)) ? 0 : $sort_guess
+ for keys %$hash_ref;
+ $num_sort = $sort_guess;
+ }
+
+ my @keys = keys %$hash_ref;
+ no warnings 'numeric';
+ my @sorted = ($num_sort)
+ ? sort { $a <=> $b or $a cmp $b } @keys
+ : sort @keys;
+ return \@sorted;
+}
+
+
+
+package
+ DBI::var;
+
+sub FETCH {
+ my($key)=shift;
+ return $DBI::err if $$key eq '*err';
+ return $DBI::errstr if $$key eq '&errstr';
+ Carp::confess("FETCH $key not supported when using DBI::PurePerl");
+}
+
+package
+ DBD::_::common;
+
+sub swap_inner_handle {
+ my ($h1, $h2) = @_;
+ # can't make this work till we can get the outer handle from the inner one
+ # probably via a WeakRef
+ return $h1->set_err($DBI::stderr, "swap_inner_handle not currently supported by DBI::PurePerl");
+}
+
+sub trace { # XXX should set per-handle level, not global
+ my ($h, $level, $file) = @_;
+ $level = $h->parse_trace_flags($level)
+ if defined $level and !DBI::looks_like_number($level);
+ my $old_level = $DBI::dbi_debug;
+ DBI::_set_trace_file($file) if defined $file;
+ if (defined $level) {
+ $DBI::dbi_debug = $level;
+ if ($DBI::dbi_debug) {
+ printf $DBI::tfh
+ " %s trace level set to %d in DBI $DBI::VERSION (PurePerl)\n",
+ $h, $DBI::dbi_debug;
+ print $DBI::tfh " Full trace not available because DBI_TRACE is not in environment\n"
+ unless exists $ENV{DBI_TRACE};
+ }
+ }
+ return $old_level;
+}
+*debug = \&trace; *debug = \&trace; # twice to avoid typo warning
+
+sub FETCH {
+ my($h,$key)= @_;
+ my $v = $h->{$key};
+ #warn ((exists $h->{$key}) ? "$key=$v\n" : "$key NONEXISTANT\n");
+ return $v if defined $v;
+ if ($key =~ /^NAME_.c$/) {
+ my $cols = $h->FETCH('NAME');
+ return undef unless $cols;
+ my @lcols = map { lc $_ } @$cols;
+ $h->{NAME_lc} = \@lcols;
+ my @ucols = map { uc $_ } @$cols;
+ $h->{NAME_uc} = \@ucols;
+ return $h->FETCH($key);
+ }
+ if ($key =~ /^NAME.*_hash$/) {
+ my $i=0;
+ for my $c(@{$h->FETCH('NAME')||[]}) {
+ $h->{'NAME_hash'}->{$c} = $i;
+ $h->{'NAME_lc_hash'}->{"\L$c"} = $i;
+ $h->{'NAME_uc_hash'}->{"\U$c"} = $i;
+ $i++;
+ }
+ return $h->{$key};
+ }
+ if (!defined $v && !exists $h->{$key}) {
+ return ($h->FETCH('TaintIn') && $h->FETCH('TaintOut')) if $key eq'Taint';
+ return (1==0) if $is_flag_attribute{$key}; # return perl-style sv_no, not undef
+ return $DBI::dbi_debug if $key eq 'TraceLevel';
+ return [] if $key eq 'ChildHandles' && $HAS_WEAKEN;
+ if ($key eq 'Type') {
+ return "dr" if $h->isa('DBI::dr');
+ return "db" if $h->isa('DBI::db');
+ return "st" if $h->isa('DBI::st');
+ Carp::carp( sprintf "Can't determine Type for %s",$h );
+ }
+ if (!$is_valid_attribute{$key} and $key =~ m/^[A-Z]/) {
+ local $^W; # hide undef warnings
+ Carp::carp( sprintf "Can't get %s->{%s}: unrecognised attribute (@{[ %$h ]})",$h,$key )
+ }
+ }
+ return $v;
+}
+sub STORE {
+ my ($h,$key,$value) = @_;
+ if ($key eq 'AutoCommit') {
+ Carp::croak("DBD driver has not implemented the AutoCommit attribute")
+ unless $value == -900 || $value == -901;
+ $value = ($value == -901);
+ }
+ elsif ($key =~ /^Taint/ ) {
+ Carp::croak(sprintf "Can't set %s->{%s}: Taint mode not supported by DBI::PurePerl",$h,$key)
+ if $value;
+ }
+ elsif ($key eq 'TraceLevel') {
+ $h->trace($value);
+ return 1;
+ }
+ elsif ($key eq 'NUM_OF_FIELDS') {
+ $h->{$key} = $value;
+ if ($value) {
+ my $fbav = DBD::_::st::dbih_setup_fbav($h);
+ @$fbav = (undef) x $value if @$fbav != $value;
+ }
+ return 1;
+ }
+ elsif (!$is_valid_attribute{$key} && $key =~ /^[A-Z]/ && !exists $h->{$key}) {
+ Carp::carp(sprintf "Can't set %s->{%s}: unrecognised attribute or invalid value %s",
+ $h,$key,$value);
+ }
+ $h->{$key} = $is_flag_attribute{$key} ? !!$value : $value;
+ return 1;
+}
+sub err { return shift->{err} }
+sub errstr { return shift->{errstr} }
+sub state { return shift->{state} }
+sub set_err {
+ my ($h, $errnum,$msg,$state, $method, $rv) = @_;
+ $h = tied(%$h) || $h;
+
+ if (my $hss = $h->{HandleSetErr}) {
+ return if $hss->($h, $errnum, $msg, $state, $method);
+ }
+
+ if (!defined $errnum) {
+ $h->{err} = $DBI::err = undef;
+ $h->{errstr} = $DBI::errstr = undef;
+ $h->{state} = $DBI::state = '';
+ return;
+ }
+
+ if ($h->{errstr}) {
+ $h->{errstr} .= sprintf " [err was %s now %s]", $h->{err}, $errnum
+ if $h->{err} && $errnum && $h->{err} ne $errnum;
+ $h->{errstr} .= sprintf " [state was %s now %s]", $h->{state}, $state
+ if $h->{state} and $h->{state} ne "S1000" && $state && $h->{state} ne $state;
+ $h->{errstr} .= "\n$msg" if $h->{errstr} ne $msg;
+ $DBI::errstr = $h->{errstr};
+ }
+ else {
+ $h->{errstr} = $DBI::errstr = $msg;
+ }
+
+ # assign if higher priority: err > "0" > "" > undef
+ my $err_changed;
+ if ($errnum # new error: so assign
+ or !defined $h->{err} # no existing warn/info: so assign
+ # new warn ("0" len 1) > info ("" len 0): so assign
+ or defined $errnum && length($errnum) > length($h->{err})
+ ) {
+ $h->{err} = $DBI::err = $errnum;
+ ++$h->{ErrCount} if $errnum;
+ ++$err_changed;
+ }
+
+ if ($err_changed) {
+ $state ||= "S1000" if $DBI::err;
+ $h->{state} = $DBI::state = ($state eq "00000") ? "" : $state
+ if $state;
+ }
+
+ if (my $p = $h->{Database}) { # just sth->dbh, not dbh->drh (see ::db::DESTROY)
+ $p->{err} = $DBI::err;
+ $p->{errstr} = $DBI::errstr;
+ $p->{state} = $DBI::state;
+ }
+
+ $h->{'dbi_pp_last_method'} = $method;
+ return $rv; # usually undef
+}
+sub trace_msg {
+ my ($h, $msg, $minlevel)=@_;
+ $minlevel = 1 unless defined $minlevel;
+ return unless $minlevel <= ($DBI::dbi_debug & 0xF);
+ print $DBI::tfh $msg;
+ return 1;
+}
+sub private_data {
+ warn "private_data @_";
+}
+sub take_imp_data {
+ my $dbh = shift;
+ # A reasonable default implementation based on the one in DBI.xs.
+ # Typically a pure-perl driver would have their own take_imp_data method
+ # that would delete all but the essential items in the hash before einding with:
+ # return $dbh->SUPER::take_imp_data();
+ # Of course it's useless if the driver doesn't also implement support for
+ # the dbi_imp_data attribute to the connect() method.
+ require Storable;
+ croak("Can't take_imp_data from handle that's not Active")
+ unless $dbh->{Active};
+ for my $sth (@{ $dbh->{ChildHandles} || [] }) {
+ next unless $sth;
+ $sth->finish if $sth->{Active};
+ bless $sth, 'DBI::zombie';
+ }
+ delete $dbh->{$_} for (keys %is_valid_attribute);
+ delete $dbh->{$_} for grep { m/^dbi_/ } keys %$dbh;
+ # warn "@{[ %$dbh ]}";
+ local $Storable::forgive_me = 1; # in case there are some CODE refs
+ my $imp_data = Storable::freeze($dbh);
+ # XXX um, should probably untie here - need to check dispatch behaviour
+ return $imp_data;
+}
+sub rows {
+ return -1; # always returns -1 here, see DBD::_::st::rows below
+}
+sub DESTROY {
+}
+
+package
+ DBD::_::dr;
+
+sub dbixs_revision {
+ return 0;
+}
+
+package
+ DBD::_::db;
+
+sub connected {
+}
+
+
+package
+ DBD::_::st;
+
+sub fetchrow_arrayref {
+ my $h = shift;
+ # if we're here then driver hasn't implemented fetch/fetchrow_arrayref
+ # so we assume they've implemented fetchrow_array and call that instead
+ my @row = $h->fetchrow_array or return;
+ return $h->_set_fbav(\@row);
+}
+# twice to avoid typo warning
+*fetch = \&fetchrow_arrayref; *fetch = \&fetchrow_arrayref;
+
+sub fetchrow_array {
+ my $h = shift;
+ # if we're here then driver hasn't implemented fetchrow_array
+ # so we assume they've implemented fetch/fetchrow_arrayref
+ my $row = $h->fetch or return;
+ return @$row;
+}
+*fetchrow = \&fetchrow_array; *fetchrow = \&fetchrow_array;
+
+sub fetchrow_hashref {
+ my $h = shift;
+ my $row = $h->fetch or return;
+ my $FetchCase = shift;
+ my $FetchHashKeyName = $FetchCase || $h->{'FetchHashKeyName'} || 'NAME';
+ my $FetchHashKeys = $h->FETCH($FetchHashKeyName);
+ my %rowhash;
+ @rowhash{ @$FetchHashKeys } = @$row;
+ return \%rowhash;
+}
+sub dbih_setup_fbav {
+ my $h = shift;
+ return $h->{'_fbav'} || do {
+ $DBI::rows = $h->{'_rows'} = 0;
+ my $fields = $h->{'NUM_OF_FIELDS'}
+ or DBI::croak("NUM_OF_FIELDS not set");
+ my @row = (undef) x $fields;
+ \@row;
+ };
+}
+sub _get_fbav {
+ my $h = shift;
+ my $av = $h->{'_fbav'} ||= dbih_setup_fbav($h);
+ $DBI::rows = ++$h->{'_rows'};
+ return $av;
+}
+sub _set_fbav {
+ my $h = shift;
+ my $fbav = $h->{'_fbav'};
+ if ($fbav) {
+ $DBI::rows = ++$h->{'_rows'};
+ }
+ else {
+ $fbav = $h->_get_fbav;
+ }
+ my $row = shift;
+ if (my $bc = $h->{'_bound_cols'}) {
+ for my $i (0..@$row-1) {
+ my $bound = $bc->[$i];
+ $fbav->[$i] = ($bound) ? ($$bound = $row->[$i]) : $row->[$i];
+ }
+ }
+ else {
+ @$fbav = @$row;
+ }
+ return $fbav;
+}
+sub bind_col {
+ my ($h, $col, $value_ref,$from_bind_columns) = @_;
+ my $fbav = $h->{'_fbav'} ||= dbih_setup_fbav($h); # from _get_fbav()
+ my $num_of_fields = @$fbav;
+ DBI::croak("bind_col: column $col is not a valid column (1..$num_of_fields)")
+ if $col < 1 or $col > $num_of_fields;
+ return 1 if not defined $value_ref; # ie caller is just trying to set TYPE
+ DBI::croak("bind_col($col,$value_ref) needs a reference to a scalar")
+ unless ref $value_ref eq 'SCALAR';
+ $h->{'_bound_cols'}->[$col-1] = $value_ref;
+ return 1;
+}
+sub finish {
+ my $h = shift;
+ $h->{'_fbav'} = undef;
+ $h->{'Active'} = 0;
+ return 1;
+}
+sub rows {
+ my $h = shift;
+ my $rows = $h->{'_rows'};
+ return -1 unless defined $rows;
+ return $rows;
+}
+
+1;
+__END__
+
+=pod
+
+=head1 NAME
+
+DBI::PurePerl -- a DBI emulation using pure perl (no C/XS compilation required)
+
+=head1 SYNOPSIS
+
+ BEGIN { $ENV{DBI_PUREPERL} = 2 }
+ use DBI;
+
+=head1 DESCRIPTION
+
+This is a pure perl emulation of the DBI internals. In almost all
+cases you will be better off using standard DBI since the portions
+of the standard version written in C make it *much* faster.
+
+However, if you are in a situation where it isn't possible to install
+a compiled version of standard DBI, and you're using pure-perl DBD
+drivers, then this module allows you to use most common features
+of DBI without needing any changes in your scripts.
+
+=head1 EXPERIMENTAL STATUS
+
+DBI::PurePerl is new so please treat it as experimental pending
+more extensive testing. So far it has passed all tests with DBD::CSV,
+DBD::AnyData, DBD::XBase, DBD::Sprite, DBD::mysqlPP. Please send
+bug reports to Jeff Zucker at <jeff@vpservices.com> with a cc to
+<dbi-dev@perl.org>.
+
+=head1 USAGE
+
+The usage is the same as for standard DBI with the exception
+that you need to set the environment variable DBI_PUREPERL if
+you want to use the PurePerl version.
+
+ DBI_PUREPERL == 0 (the default) Always use compiled DBI, die
+ if it isn't properly compiled & installed
+
+ DBI_PUREPERL == 1 Use compiled DBI if it is properly compiled
+ & installed, otherwise use PurePerl
+
+ DBI_PUREPERL == 2 Always use PurePerl
+
+You may set the enviornment variable in your shell (e.g. with
+set or setenv or export, etc) or else set it in your script like
+this:
+
+ BEGIN { $ENV{DBI_PUREPERL}=2 }
+
+before you C<use DBI;>.
+
+=head1 INSTALLATION
+
+In most situations simply install DBI (see the DBI pod for details).
+
+In the situation in which you can not install DBI itself, you
+may manually copy DBI.pm and PurePerl.pm into the appropriate
+directories.
+
+For example:
+
+ cp DBI.pm /usr/jdoe/mylibs/.
+ cp PurePerl.pm /usr/jdoe/mylibs/DBI/.
+
+Then add this to the top of scripts:
+
+ BEGIN {
+ $ENV{DBI_PUREPERL} = 1; # or =2
+ unshift @INC, '/usr/jdoe/mylibs';
+ }
+
+(Or should we perhaps patch Makefile.PL so that if DBI_PUREPERL
+is set to 2 prior to make, the normal compile process is skipped
+and the files are installed automatically?)
+
+=head1 DIFFERENCES BETWEEN DBI AND DBI::PurePerl
+
+=head2 Attributes
+
+Boolean attributes still return boolean values but the actual values
+used may be different, i.e., 0 or undef instead of an empty string.
+
+Some handle attributes are either not supported or have very limited
+functionality:
+
+ ActiveKids
+ InactiveDestroy
+ AutoInactiveDestroy
+ Kids
+ Taint
+ TaintIn
+ TaintOut
+
+(and probably others)
+
+=head2 Tracing
+
+Trace functionality is more limited and the code to handle tracing is
+only embedded into DBI:PurePerl if the DBI_TRACE environment variable
+is defined. To enable total tracing you can set the DBI_TRACE
+environment variable as usual. But to enable individual handle
+tracing using the trace() method you also need to set the DBI_TRACE
+environment variable, but set it to 0.
+
+=head2 Parameter Usage Checking
+
+The DBI does some basic parameter count checking on method calls.
+DBI::PurePerl doesn't.
+
+=head2 Speed
+
+DBI::PurePerl is slower. Although, with some drivers in some
+contexts this may not be very significant for you.
+
+By way of example... the test.pl script in the DBI source
+distribution has a simple benchmark that just does:
+
+ my $null_dbh = DBI->connect('dbi:NullP:','','');
+ my $i = 10_000;
+ $null_dbh->prepare('') while $i--;
+
+In other words just prepares a statement, creating and destroying
+a statement handle, over and over again. Using the real DBI this
+runs at ~4550 handles per second whereas DBI::PurePerl manages
+~2800 per second on the same machine (not too bad really).
+
+=head2 May not fully support hash()
+
+If you want to use type 1 hash, i.e., C<hash($string,1)> with
+DBI::PurePerl, you'll need version 1.56 or higher of Math::BigInt
+(available on CPAN).
+
+=head2 Doesn't support preparse()
+
+The DBI->preparse() method isn't supported in DBI::PurePerl.
+
+=head2 Doesn't support DBD::Proxy
+
+There's a subtle problem somewhere I've not been able to identify.
+DBI::ProxyServer seem to work fine with DBI::PurePerl but DBD::Proxy
+does not work 100% (which is sad because that would be far more useful :)
+Try re-enabling t/80proxy.t for DBI::PurePerl to see if the problem
+that remains will affect you're usage.
+
+=head2 Others
+
+ can() - doesn't have any special behaviour
+
+Please let us know if you find any other differences between DBI
+and DBI::PurePerl.
+
+=head1 AUTHORS
+
+Tim Bunce and Jeff Zucker.
+
+Tim provided the direction and basis for the code. The original
+idea for the module and most of the brute force porting from C to
+Perl was by Jeff. Tim then reworked some core parts to boost the
+performance and accuracy of the emulation. Thanks also to Randal
+Schwartz and John Tobey for patches.
+
+=head1 COPYRIGHT
+
+Copyright (c) 2002 Tim Bunce Ireland.
+
+See COPYRIGHT section in DBI.pm for usage and distribution rights.
+
+=cut
diff --git a/lib/DBI/SQL/Nano.pm b/lib/DBI/SQL/Nano.pm
new file mode 100644
index 0000000..dc0711f
--- /dev/null
+++ b/lib/DBI/SQL/Nano.pm
@@ -0,0 +1,1010 @@
+#######################################################################
+#
+# DBI::SQL::Nano - a very tiny SQL engine
+#
+# Copyright (c) 2010 by Jens Rehsack < rehsack AT cpan.org >
+# Copyright (c) 2004 by Jeff Zucker < jzucker AT cpan.org >
+#
+# All rights reserved.
+#
+# You may freely distribute and/or modify this module under the terms
+# of either the GNU General Public License (GPL) or the Artistic License,
+# as specified in the Perl README file.
+#
+# See the pod at the bottom of this file for help information
+#
+#######################################################################
+
+#######################
+package DBI::SQL::Nano;
+#######################
+use strict;
+use warnings;
+use vars qw( $VERSION $versions );
+
+use Carp qw(croak);
+
+require DBI; # for looks_like_number()
+
+BEGIN
+{
+ $VERSION = sprintf( "1.%06d", q$Revision: 14600 $ =~ /(\d+)/o );
+
+ $versions->{nano_version} = $VERSION;
+ if ( $ENV{DBI_SQL_NANO} || !eval { require SQL::Statement; $SQL::Statement::VERSION ge '1.28' } )
+ {
+ @DBI::SQL::Nano::Statement::ISA = qw(DBI::SQL::Nano::Statement_);
+ @DBI::SQL::Nano::Table::ISA = qw(DBI::SQL::Nano::Table_);
+ }
+ else
+ {
+ @DBI::SQL::Nano::Statement::ISA = qw( SQL::Statement );
+ @DBI::SQL::Nano::Table::ISA = qw( SQL::Eval::Table);
+ $versions->{statement_version} = $SQL::Statement::VERSION;
+ }
+}
+
+###################################
+package DBI::SQL::Nano::Statement_;
+###################################
+
+use Carp qw(croak);
+use Errno;
+
+if ( eval { require Clone; } )
+{
+ Clone->import("clone");
+}
+else
+{
+ require Storable; # in CORE since 5.7.3
+ *clone = \&Storable::dclone;
+}
+
+sub new
+{
+ my ( $class, $sql ) = @_;
+ my $self = {};
+ bless $self, $class;
+ return $self->prepare($sql);
+}
+
+#####################################################################
+# PREPARE
+#####################################################################
+sub prepare
+{
+ my ( $self, $sql ) = @_;
+ $sql =~ s/\s+$//;
+ for ($sql)
+ {
+ /^\s*CREATE\s+TABLE\s+(.*?)\s*\((.+)\)\s*$/is
+ && do
+ {
+ $self->{command} = 'CREATE';
+ $self->{table_name} = $1;
+ $self->{column_names} = parse_coldef_list($2) if $2;
+ $self->{column_names} or croak "Can't find columns";
+ };
+ /^\s*DROP\s+TABLE\s+(IF\s+EXISTS\s+)?(.*?)\s*$/is
+ && do
+ {
+ $self->{command} = 'DROP';
+ $self->{table_name} = $2;
+ $self->{ignore_missing_table} = 1 if $1;
+ };
+ /^\s*SELECT\s+(.*?)\s+FROM\s+(\S+)((.*))?/is
+ && do
+ {
+ $self->{command} = 'SELECT';
+ $self->{column_names} = parse_comma_list($1) if $1;
+ $self->{column_names} or croak "Can't find columns";
+ $self->{table_name} = $2;
+ if ( my $clauses = $4 )
+ {
+ if ( $clauses =~ /^(.*)\s+ORDER\s+BY\s+(.*)$/is )
+ {
+ $clauses = $1;
+ $self->{order_clause} = $self->parse_order_clause($2);
+ }
+ $self->{where_clause} = $self->parse_where_clause($clauses) if ($clauses);
+ }
+ };
+ /^\s*INSERT\s+(?:INTO\s+)?(\S+)\s*(\((.*?)\))?\s*VALUES\s*\((.+)\)/is
+ && do
+ {
+ $self->{command} = 'INSERT';
+ $self->{table_name} = $1;
+ $self->{column_names} = parse_comma_list($2) if $2;
+ $self->{values} = $self->parse_values_list($4) if $4;
+ $self->{values} or croak "Can't parse values";
+ };
+ /^\s*DELETE\s+FROM\s+(\S+)((.*))?/is
+ && do
+ {
+ $self->{command} = 'DELETE';
+ $self->{table_name} = $1;
+ $self->{where_clause} = $self->parse_where_clause($3) if $3;
+ };
+ /^\s*UPDATE\s+(\S+)\s+SET\s+(.+)(\s+WHERE\s+.+)/is
+ && do
+ {
+ $self->{command} = 'UPDATE';
+ $self->{table_name} = $1;
+ $self->parse_set_clause($2) if $2;
+ $self->{where_clause} = $self->parse_where_clause($3) if $3;
+ };
+ }
+ croak "Couldn't parse" unless ( $self->{command} and $self->{table_name} );
+ return $self;
+}
+
+sub parse_order_clause
+{
+ my ( $self, $str ) = @_;
+ my @clause = split /\s+/, $str;
+ return { $clause[0] => 'ASC' } if ( @clause == 1 );
+ croak "Bad ORDER BY clause '$str'" if ( @clause > 2 );
+ $clause[1] ||= '';
+ return { $clause[0] => uc $clause[1] }
+ if $clause[1] =~ /^ASC$/i
+ or $clause[1] =~ /^DESC$/i;
+ croak "Bad ORDER BY clause '$clause[1]'";
+}
+
+sub parse_coldef_list
+{ # check column definitions
+ my @col_defs;
+ for ( split ',', shift )
+ {
+ my $col = clean_parse_str($_);
+ if ( $col =~ /^(\S+?)\s+.+/ )
+ { # doesn't check what it is
+ $col = $1; # just checks if it exists
+ }
+ else
+ {
+ croak "No column definition for '$_'";
+ }
+ push @col_defs, $col;
+ }
+ return \@col_defs;
+}
+
+sub parse_comma_list
+{
+ [ map { clean_parse_str($_) } split( ',', shift ) ];
+}
+sub clean_parse_str { local $_ = shift; s/\(//; s/\)//; s/^\s+//; s/\s+$//; $_; }
+
+sub parse_values_list
+{
+ my ( $self, $str ) = @_;
+ [ map { $self->parse_value( clean_parse_str($_) ) } split( ',', $str ) ];
+}
+
+sub parse_set_clause
+{
+ my $self = shift;
+ my @cols = split /,/, shift;
+ my $set_clause;
+ for my $col (@cols)
+ {
+ my ( $col_name, $value ) = $col =~ /^\s*(.+?)\s*=\s*(.+?)\s*$/s;
+ push @{ $self->{column_names} }, $col_name;
+ push @{ $self->{values} }, $self->parse_value($value);
+ }
+ croak "Can't parse set clause" unless ( $self->{column_names} and $self->{values} );
+}
+
+sub parse_value
+{
+ my ( $self, $str ) = @_;
+ return unless ( defined $str );
+ $str =~ s/\s+$//;
+ $str =~ s/^\s+//;
+ if ( $str =~ /^\?$/ )
+ {
+ push @{ $self->{params} }, '?';
+ return {
+ value => '?',
+ type => 'placeholder'
+ };
+ }
+ return {
+ value => undef,
+ type => 'NULL'
+ } if ( $str =~ /^NULL$/i );
+ return {
+ value => $1,
+ type => 'string'
+ } if ( $str =~ /^'(.+)'$/s );
+ return {
+ value => $str,
+ type => 'number'
+ } if ( DBI::looks_like_number($str) );
+ return {
+ value => $str,
+ type => 'column'
+ };
+}
+
+sub parse_where_clause
+{
+ my ( $self, $str ) = @_;
+ $str =~ s/\s+$//;
+ if ( $str =~ /^\s*WHERE\s+(.*)/i )
+ {
+ $str = $1;
+ }
+ else
+ {
+ croak "Couldn't find WHERE clause in '$str'";
+ }
+ my ($neg) = $str =~ s/^\s*(NOT)\s+//is;
+ my $opexp = '=|<>|<=|>=|<|>|LIKE|CLIKE|IS';
+ my ( $val1, $op, $val2 ) = $str =~ /^(.+?)\s*($opexp)\s*(.+)\s*$/iso;
+ croak "Couldn't parse WHERE expression '$str'" unless ( defined $val1 and defined $op and defined $val2 );
+ return {
+ arg1 => $self->parse_value($val1),
+ arg2 => $self->parse_value($val2),
+ op => $op,
+ neg => $neg,
+ };
+}
+
+#####################################################################
+# EXECUTE
+#####################################################################
+sub execute
+{
+ my ( $self, $data, $params ) = @_;
+ my $num_placeholders = $self->params;
+ my $num_params = scalar @$params || 0;
+ croak "Number of params '$num_params' does not match number of placeholders '$num_placeholders'"
+ unless ( $num_placeholders == $num_params );
+ if ( scalar @$params )
+ {
+ for my $i ( 0 .. $#{ $self->{values} } )
+ {
+ if ( $self->{values}->[$i]->{type} eq 'placeholder' )
+ {
+ $self->{values}->[$i]->{value} = shift @$params;
+ }
+ }
+ if ( $self->{where_clause} )
+ {
+ if ( $self->{where_clause}->{arg1}->{type} eq 'placeholder' )
+ {
+ $self->{where_clause}->{arg1}->{value} = shift @$params;
+ }
+ if ( $self->{where_clause}->{arg2}->{type} eq 'placeholder' )
+ {
+ $self->{where_clause}->{arg2}->{value} = shift @$params;
+ }
+ }
+ }
+ my $command = $self->{command};
+ ( $self->{'NUM_OF_ROWS'}, $self->{'NUM_OF_FIELDS'}, $self->{'data'}, ) = $self->$command( $data, $params );
+ $self->{NAME} ||= $self->{column_names};
+ return $self->{'NUM_OF_ROWS'} || '0E0';
+}
+
+my $enoentstr = "Cannot open .*\(" . Errno::ENOENT . "\)";
+my $enoentrx = qr/$enoentstr/;
+
+sub DROP ($$$)
+{
+ my ( $self, $data, $params ) = @_;
+
+ my $table;
+ my @err;
+ eval {
+ local $SIG{__WARN__} = sub { push @err, @_ };
+ ($table) = $self->open_tables( $data, 0, 1 );
+ };
+ if ( $self->{ignore_missing_table} and ( $@ or @err ) and grep { $_ =~ $enoentrx } ( @err, $@ ) )
+ {
+ $@ = '';
+ return ( -1, 0 );
+ }
+
+ croak( $@ || $err[0] ) if ( $@ || @err );
+ return ( -1, 0 ) unless $table;
+
+ $table->drop($data);
+ ( -1, 0 );
+}
+
+sub CREATE ($$$)
+{
+ my ( $self, $data, $params ) = @_;
+ my $table = $self->open_tables( $data, 1, 1 );
+ $table->push_names( $data, $self->{column_names} );
+ ( 0, 0 );
+}
+
+sub INSERT ($$$)
+{
+ my ( $self, $data, $params ) = @_;
+ my $table = $self->open_tables( $data, 0, 1 );
+ $self->verify_columns($table);
+ $table->seek( $data, 0, 2 ) unless ( $table->can('insert_one_row') );
+ my ($array) = [];
+ my ( $val, $col, $i );
+ $self->{column_names} = $table->col_names() unless ( $self->{column_names} );
+ my $cNum = scalar( @{ $self->{column_names} } ) if ( $self->{column_names} );
+ my $param_num = 0;
+
+ if ($cNum)
+ {
+ for ( $i = 0; $i < $cNum; $i++ )
+ {
+ $col = $self->{column_names}->[$i];
+ $array->[ $self->column_nums( $table, $col ) ] = $self->row_values($i);
+ }
+ }
+ else
+ {
+ croak "Bad col names in INSERT";
+ }
+
+ $table->can('insert_new_row') ? $table->insert_new_row( $data, $array ) : $table->push_row( $data, $array );
+
+ return ( 1, 0 );
+}
+
+sub DELETE ($$$)
+{
+ my ( $self, $data, $params ) = @_;
+ my $table = $self->open_tables( $data, 0, 1 );
+ $self->verify_columns($table);
+ my ($affected) = 0;
+ my ( @rows, $array );
+ my $can_dor = $table->can('delete_one_row');
+ while ( $array = $table->fetch_row($data) )
+ {
+ if ( $self->eval_where( $table, $array ) )
+ {
+ ++$affected;
+ if ( $self->{fetched_from_key} )
+ {
+ $array = $self->{fetched_value};
+ $table->delete_one_row( $data, $array );
+ return ( $affected, 0 );
+ }
+ push( @rows, $array ) if ($can_dor);
+ }
+ else
+ {
+ push( @rows, $array ) unless ($can_dor);
+ }
+ }
+ if ($can_dor)
+ {
+ foreach $array (@rows)
+ {
+ $table->delete_one_row( $data, $array );
+ }
+ }
+ else
+ {
+ $table->seek( $data, 0, 0 );
+ foreach $array (@rows)
+ {
+ $table->push_row( $data, $array );
+ }
+ $table->truncate($data);
+ }
+ return ( $affected, 0 );
+}
+
+sub _anycmp($$;$)
+{
+ my ( $a, $b, $case_fold ) = @_;
+
+ if ( !defined($a) || !defined($b) )
+ {
+ return defined($a) - defined($b);
+ }
+ elsif ( DBI::looks_like_number($a) && DBI::looks_like_number($b) )
+ {
+ return $a <=> $b;
+ }
+ else
+ {
+ return $case_fold ? lc($a) cmp lc($b) || $a cmp $b : $a cmp $b;
+ }
+}
+
+sub SELECT ($$$)
+{
+ my ( $self, $data, $params ) = @_;
+ my $table = $self->open_tables( $data, 0, 0 );
+ $self->verify_columns($table);
+ my $tname = $self->{table_name};
+ my ($affected) = 0;
+ my ( @rows, %cols, $array, $val, $col, $i );
+ while ( $array = $table->fetch_row($data) )
+ {
+ if ( $self->eval_where( $table, $array ) )
+ {
+ $array = $self->{fetched_value} if ( $self->{fetched_from_key} );
+ unless ( keys %cols )
+ {
+ my $col_nums = $self->column_nums($table);
+ %cols = reverse %{$col_nums};
+ }
+
+ my $rowhash;
+ for ( sort keys %cols )
+ {
+ $rowhash->{ $cols{$_} } = $array->[$_];
+ }
+ my @newarray;
+ for ( $i = 0; $i < @{ $self->{column_names} }; $i++ )
+ {
+ $col = $self->{column_names}->[$i];
+ push @newarray, $rowhash->{$col};
+ }
+ push( @rows, \@newarray );
+ return ( scalar(@rows), scalar @{ $self->{column_names} }, \@rows )
+ if ( $self->{fetched_from_key} );
+ }
+ }
+ if ( $self->{order_clause} )
+ {
+ my ( $sort_col, $desc ) = each %{ $self->{order_clause} };
+ my @sortCols = ( $self->column_nums( $table, $sort_col, 1 ) );
+ $sortCols[1] = uc $desc eq 'DESC' ? 1 : 0;
+
+ @rows = sort {
+ my ( $result, $colNum, $desc );
+ my $i = 0;
+ do
+ {
+ $colNum = $sortCols[ $i++ ];
+ $desc = $sortCols[ $i++ ];
+ $result = _anycmp( $a->[$colNum], $b->[$colNum] );
+ $result = -$result if ($desc);
+ } while ( !$result && $i < @sortCols );
+ $result;
+ } @rows;
+ }
+ ( scalar(@rows), scalar @{ $self->{column_names} }, \@rows );
+}
+
+sub UPDATE ($$$)
+{
+ my ( $self, $data, $params ) = @_;
+ my $table = $self->open_tables( $data, 0, 1 );
+ $self->verify_columns($table);
+ return undef unless $table;
+ my $affected = 0;
+ my $can_usr = $table->can('update_specific_row');
+ my $can_uor = $table->can('update_one_row');
+ my $can_rwu = $can_usr || $can_uor;
+ my ( @rows, $array, $f_array, $val, $col, $i );
+
+ while ( $array = $table->fetch_row($data) )
+ {
+ if ( $self->eval_where( $table, $array ) )
+ {
+ $array = $self->{fetched_value} if ( $self->{fetched_from_key} and $can_rwu );
+ my $orig_ary = clone($array) if ($can_usr);
+ for ( $i = 0; $i < @{ $self->{column_names} }; $i++ )
+ {
+ $col = $self->{column_names}->[$i];
+ $array->[ $self->column_nums( $table, $col ) ] = $self->row_values($i);
+ }
+ $affected++;
+ if ( $self->{fetched_value} )
+ {
+ if ($can_usr)
+ {
+ $table->update_specific_row( $data, $array, $orig_ary );
+ }
+ elsif ($can_uor)
+ {
+ $table->update_one_row( $data, $array );
+ }
+ return ( $affected, 0 );
+ }
+ push( @rows, $can_usr ? [ $array, $orig_ary ] : $array );
+ }
+ else
+ {
+ push( @rows, $array ) unless ($can_rwu);
+ }
+ }
+ if ($can_rwu)
+ {
+ foreach my $array (@rows)
+ {
+ if ($can_usr)
+ {
+ $table->update_specific_row( $data, @$array );
+ }
+ elsif ($can_uor)
+ {
+ $table->update_one_row( $data, $array );
+ }
+ }
+ }
+ else
+ {
+ $table->seek( $data, 0, 0 );
+ foreach my $array (@rows)
+ {
+ $table->push_row( $data, $array );
+ }
+ $table->truncate($data);
+ }
+
+ return ( $affected, 0 );
+}
+
+sub verify_columns
+{
+ my ( $self, $table ) = @_;
+ my @cols = @{ $self->{column_names} };
+ if ( $self->{where_clause} )
+ {
+ if ( my $col = $self->{where_clause}->{arg1} )
+ {
+ push @cols, $col->{value} if $col->{type} eq 'column';
+ }
+ if ( my $col = $self->{where_clause}->{arg2} )
+ {
+ push @cols, $col->{value} if $col->{type} eq 'column';
+ }
+ }
+ for (@cols)
+ {
+ $self->column_nums( $table, $_ );
+ }
+}
+
+sub column_nums
+{
+ my ( $self, $table, $stmt_col_name, $find_in_stmt ) = @_;
+ my %dbd_nums = %{ $table->col_nums() };
+ my @dbd_cols = @{ $table->col_names() };
+ my %stmt_nums;
+ if ( $stmt_col_name and !$find_in_stmt )
+ {
+ while ( my ( $k, $v ) = each %dbd_nums )
+ {
+ return $v if uc $k eq uc $stmt_col_name;
+ }
+ croak "No such column '$stmt_col_name'";
+ }
+ if ( $stmt_col_name and $find_in_stmt )
+ {
+ for my $i ( 0 .. @{ $self->{column_names} } )
+ {
+ return $i if uc $stmt_col_name eq uc $self->{column_names}->[$i];
+ }
+ croak "No such column '$stmt_col_name'";
+ }
+ for my $i ( 0 .. $#dbd_cols )
+ {
+ for my $stmt_col ( @{ $self->{column_names} } )
+ {
+ $stmt_nums{$stmt_col} = $i if uc $dbd_cols[$i] eq uc $stmt_col;
+ }
+ }
+ return \%stmt_nums;
+}
+
+sub eval_where
+{
+ my ( $self, $table, $rowary ) = @_;
+ my $where = $self->{"where_clause"} || return 1;
+ my $col_nums = $table->col_nums();
+ my %cols = reverse %{$col_nums};
+ my $rowhash;
+ for ( sort keys %cols )
+ {
+ $rowhash->{ uc $cols{$_} } = $rowary->[$_];
+ }
+ return $self->process_predicate( $where, $table, $rowhash );
+}
+
+sub process_predicate
+{
+ my ( $self, $pred, $table, $rowhash ) = @_;
+ my $val1 = $pred->{arg1};
+ if ( $val1->{type} eq 'column' )
+ {
+ $val1 = $rowhash->{ uc $val1->{value} };
+ }
+ else
+ {
+ $val1 = $val1->{value};
+ }
+ my $val2 = $pred->{arg2};
+ if ( $val2->{type} eq 'column' )
+ {
+ $val2 = $rowhash->{ uc $val2->{value} };
+ }
+ else
+ {
+ $val2 = $val2->{value};
+ }
+ my $op = $pred->{op};
+ my $neg = $pred->{neg};
+ if ( $op eq '=' and !$neg and $table->can('fetch_one_row') )
+ {
+ my $key_col = $table->fetch_one_row( 1, 1 );
+ if ( $pred->{arg1}->{value} =~ /^$key_col$/i )
+ {
+ $self->{fetched_from_key} = 1;
+ $self->{fetched_value} = $table->fetch_one_row( 0, $pred->{arg2}->{value} );
+ return 1;
+ }
+ }
+ my $match = $self->is_matched( $val1, $op, $val2 ) || 0;
+ if ($neg) { $match = $match ? 0 : 1; }
+ return $match;
+}
+
+sub is_matched
+{
+ my ( $self, $val1, $op, $val2 ) = @_;
+ if ( $op eq 'IS' )
+ {
+ return 1 if ( !defined $val1 or $val1 eq '' );
+ return 0;
+ }
+ $val1 = '' unless ( defined $val1 );
+ $val2 = '' unless ( defined $val2 );
+ if ( $op =~ /LIKE|CLIKE/i )
+ {
+ $val2 = quotemeta($val2);
+ $val2 =~ s/\\%/.*/g;
+ $val2 =~ s/_/./g;
+ }
+ if ( $op eq 'LIKE' ) { return $val1 =~ /^$val2$/s; }
+ if ( $op eq 'CLIKE' ) { return $val1 =~ /^$val2$/si; }
+ if ( DBI::looks_like_number($val1) && DBI::looks_like_number($val2) )
+ {
+ if ( $op eq '<' ) { return $val1 < $val2; }
+ if ( $op eq '>' ) { return $val1 > $val2; }
+ if ( $op eq '=' ) { return $val1 == $val2; }
+ if ( $op eq '<>' ) { return $val1 != $val2; }
+ if ( $op eq '<=' ) { return $val1 <= $val2; }
+ if ( $op eq '>=' ) { return $val1 >= $val2; }
+ }
+ else
+ {
+ if ( $op eq '<' ) { return $val1 lt $val2; }
+ if ( $op eq '>' ) { return $val1 gt $val2; }
+ if ( $op eq '=' ) { return $val1 eq $val2; }
+ if ( $op eq '<>' ) { return $val1 ne $val2; }
+ if ( $op eq '<=' ) { return $val1 ge $val2; }
+ if ( $op eq '>=' ) { return $val1 le $val2; }
+ }
+}
+
+sub params
+{
+ my ( $self, $val_num ) = @_;
+ if ( !$self->{"params"} ) { return 0; }
+ if ( defined $val_num )
+ {
+ return $self->{"params"}->[$val_num];
+ }
+ if (wantarray)
+ {
+ return @{ $self->{"params"} };
+ }
+ else
+ {
+ return scalar @{ $self->{"params"} };
+ }
+
+}
+
+sub open_tables
+{
+ my ( $self, $data, $createMode, $lockMode ) = @_;
+ my $table_name = $self->{table_name};
+ my $table;
+ eval { $table = $self->open_table( $data, $table_name, $createMode, $lockMode ) };
+ if ($@)
+ {
+ chomp $@;
+ croak $@;
+ }
+ croak "Couldn't open table '$table_name'" unless $table;
+ if ( !$self->{column_names} or $self->{column_names}->[0] eq '*' )
+ {
+ $self->{column_names} = $table->col_names();
+ }
+ return $table;
+}
+
+sub row_values
+{
+ my ( $self, $val_num ) = @_;
+ if ( !$self->{"values"} ) { return 0; }
+ if ( defined $val_num )
+ {
+ return $self->{"values"}->[$val_num]->{value};
+ }
+ if (wantarray)
+ {
+ return map { $_->{"value"} } @{ $self->{"values"} };
+ }
+ else
+ {
+ return scalar @{ $self->{"values"} };
+ }
+}
+
+sub column_names
+{
+ my ($self) = @_;
+ my @col_names;
+ if ( $self->{column_names} and $self->{column_names}->[0] ne '*' )
+ {
+ @col_names = @{ $self->{column_names} };
+ }
+ return @col_names;
+}
+
+###############################
+package DBI::SQL::Nano::Table_;
+###############################
+
+use Carp qw(croak);
+
+sub new ($$)
+{
+ my ( $proto, $attr ) = @_;
+ my ($self) = {%$attr};
+
+ defined( $self->{col_names} ) and "ARRAY" eq ref( $self->{col_names} )
+ or croak("attribute 'col_names' must be defined as an array");
+ exists( $self->{col_nums} ) or $self->{col_nums} = _map_colnums( $self->{col_names} );
+ defined( $self->{col_nums} ) and "HASH" eq ref( $self->{col_nums} )
+ or croak("attribute 'col_nums' must be defined as a hash");
+
+ bless( $self, ( ref($proto) || $proto ) );
+ return $self;
+}
+
+sub _map_colnums
+{
+ my $col_names = $_[0];
+ my %col_nums;
+ for my $i ( 0 .. $#$col_names )
+ {
+ next unless $col_names->[$i];
+ $col_nums{ $col_names->[$i] } = $i;
+ }
+ return \%col_nums;
+}
+
+sub row() { return $_[0]->{row}; }
+sub column($) { return $_[0]->{row}->[ $_[0]->column_num( $_[1] ) ]; }
+sub column_num($) { $_[0]->{col_nums}->{ $_[1] }; }
+sub col_nums() { $_[0]->{col_nums} }
+sub col_names() { $_[0]->{col_names}; }
+
+sub drop ($$) { croak "Abstract method " . ref( $_[0] ) . "::drop called" }
+sub fetch_row ($$$) { croak "Abstract method " . ref( $_[0] ) . "::fetch_row called" }
+sub push_row ($$$) { croak "Abstract method " . ref( $_[0] ) . "::push_row called" }
+sub push_names ($$$) { croak "Abstract method " . ref( $_[0] ) . "::push_names called" }
+sub truncate ($$) { croak "Abstract method " . ref( $_[0] ) . "::truncate called" }
+sub seek ($$$$) { croak "Abstract method " . ref( $_[0] ) . "::seek called" }
+
+1;
+__END__
+
+=pod
+
+=head1 NAME
+
+DBI::SQL::Nano - a very tiny SQL engine
+
+=head1 SYNOPSIS
+
+ BEGIN { $ENV{DBI_SQL_NANO}=1 } # forces use of Nano rather than SQL::Statement
+ use DBI::SQL::Nano;
+ use Data::Dumper;
+ my $stmt = DBI::SQL::Nano::Statement->new(
+ "SELECT bar,baz FROM foo WHERE qux = 1"
+ ) or die "Couldn't parse";
+ print Dumper $stmt;
+
+=head1 DESCRIPTION
+
+C<< DBI::SQL::Nano >> is meant as a I<very> minimal SQL engine for use in
+situations where SQL::Statement is not available. In most situations you are
+better off installing L<SQL::Statement> although DBI::SQL::Nano may be faster
+for some B<very> simple tasks.
+
+DBI::SQL::Nano, like SQL::Statement is primarily intended to provide a SQL
+engine for use with some pure perl DBDs including L<DBD::DBM>, L<DBD::CSV>,
+L<DBD::AnyData>, and L<DBD::Excel>. It is not of much use in and of itself.
+You can dump out the structure of a parsed SQL statement, but that is about
+it.
+
+=head1 USAGE
+
+=head2 Setting the DBI_SQL_NANO flag
+
+By default, when a C<< DBD >> uses C<< DBI::SQL::Nano >>, the module will
+look to see if C<< SQL::Statement >> is installed. If it is, SQL::Statement
+objects are used. If SQL::Statement is not available, DBI::SQL::Nano
+objects are used.
+
+In some cases, you may wish to use DBI::SQL::Nano objects even if
+SQL::Statement is available. To force usage of DBI::SQL::Nano objects
+regardless of the availability of SQL::Statement, set the environment
+variable DBI_SQL_NANO to 1.
+
+You can set the environment variable in your shell prior to running your
+script (with SET or EXPORT or whatever), or else you can set it in your
+script by putting this at the top of the script:
+
+ BEGIN { $ENV{DBI_SQL_NANO} = 1 }
+
+=head2 Supported SQL syntax
+
+ Here's a pseudo-BNF. Square brackets [] indicate optional items;
+ Angle brackets <> indicate items defined elsewhere in the BNF.
+
+ statement ::=
+ DROP TABLE [IF EXISTS] <table_name>
+ | CREATE TABLE <table_name> <col_def_list>
+ | INSERT INTO <table_name> [<insert_col_list>] VALUES <val_list>
+ | DELETE FROM <table_name> [<where_clause>]
+ | UPDATE <table_name> SET <set_clause> <where_clause>
+ | SELECT <select_col_list> FROM <table_name> [<where_clause>]
+ [<order_clause>]
+
+ the optional IF EXISTS clause ::=
+ * similar to MySQL - prevents errors when trying to drop
+ a table that doesn't exist
+
+ identifiers ::=
+ * table and column names should be valid SQL identifiers
+ * especially avoid using spaces and commas in identifiers
+ * note: there is no error checking for invalid names, some
+ will be accepted, others will cause parse failures
+
+ table_name ::=
+ * only one table (no multiple table operations)
+ * see identifier for valid table names
+
+ col_def_list ::=
+ * a parens delimited, comma-separated list of column names
+ * see identifier for valid column names
+ * column types and column constraints may be included but are ignored
+ e.g. these are all the same:
+ (id,phrase)
+ (id INT, phrase VARCHAR(40))
+ (id INT PRIMARY KEY, phrase VARCHAR(40) NOT NULL)
+ * you are *strongly* advised to put in column types even though
+ they are ignored ... it increases portability
+
+ insert_col_list ::=
+ * a parens delimited, comma-separated list of column names
+ * as in standard SQL, this is optional
+
+ select_col_list ::=
+ * a comma-separated list of column names
+ * or an asterisk denoting all columns
+
+ val_list ::=
+ * a parens delimited, comma-separated list of values which can be:
+ * placeholders (an unquoted question mark)
+ * numbers (unquoted numbers)
+ * column names (unquoted strings)
+ * nulls (unquoted word NULL)
+ * strings (delimited with single quote marks);
+ * note: leading and trailing percent mark (%) and underscore (_)
+ can be used as wildcards in quoted strings for use with
+ the LIKE and CLIKE operators
+ * note: escaped single quotation marks within strings are not
+ supported, neither are embedded commas, use placeholders instead
+
+ set_clause ::=
+ * a comma-separated list of column = value pairs
+ * see val_list for acceptable value formats
+
+ where_clause ::=
+ * a single "column/value <op> column/value" predicate, optionally
+ preceded by "NOT"
+ * note: multiple predicates combined with ORs or ANDs are not supported
+ * see val_list for acceptable value formats
+ * op may be one of:
+ < > >= <= = <> LIKE CLIKE IS
+ * CLIKE is a case insensitive LIKE
+
+ order_clause ::= column_name [ASC|DESC]
+ * a single column optional ORDER BY clause is supported
+ * as in standard SQL, if neither ASC (ascending) nor
+ DESC (descending) is specified, ASC becomes the default
+
+=head1 TABLES
+
+DBI::SQL::Nano::Statement operates on exactly one table. This table will be
+opened by inherit from DBI::SQL::Nano::Statement and implements the
+C<< open_table >> method.
+
+ sub open_table ($$$$$)
+ {
+ ...
+ return Your::Table->new( \%attributes );
+ }
+
+DBI::SQL::Nano::Statement_ expects a rudimentary interface is implemented by
+the table object, as well as SQL::Statement expects.
+
+ package Your::Table;
+
+ use vars qw(@ISA);
+ @ISA = qw(DBI::SQL::Nano::Table);
+
+ sub drop ($$) { ... }
+ sub fetch_row ($$$) { ... }
+ sub push_row ($$$) { ... }
+ sub push_names ($$$) { ... }
+ sub truncate ($$) { ... }
+ sub seek ($$$$) { ... }
+
+The base class interfaces are provided by DBI::SQL::Nano::Table_ in case of
+relying on DBI::SQL::Nano or SQL::Eval::Table (see L<SQL::Eval> for details)
+otherwise.
+
+=head1 BUGS AND LIMITATIONS
+
+There are no known bugs in DBI::SQL::Nano::Statement. If you find a one
+and want to report, please see L<DBI> for how to report bugs.
+
+DBI::SQL::Nano::Statement is designed to provide a minimal subset for
+executing SQL statements.
+
+The most important limitation might be the restriction on one table per
+statement. This implies, that no JOINs are supported and there cannot be
+any foreign key relation between tables.
+
+The where clause evaluation of DBI::SQL::Nano::Statement is very slow
+(SQL::Statement uses a precompiled evaluation).
+
+INSERT can handle only one row per statement. To insert multiple rows,
+use placeholders as explained in DBI.
+
+The DBI::SQL::Nano parser is very limited and does not support any
+additional syntax such as brackets, comments, functions, aggregations
+etc.
+
+In contrast to SQL::Statement, temporary tables are not supported.
+
+=head1 ACKNOWLEDGEMENTS
+
+Tim Bunce provided the original idea for this module, helped me out of the
+tangled trap of namespaces, and provided help and advice all along the way.
+Although I wrote it from the ground up, it is based on Jochen Wiedmann's
+original design of SQL::Statement, so much of the credit for the API goes
+to him.
+
+=head1 AUTHOR AND COPYRIGHT
+
+This module is originally written by Jeff Zucker < jzucker AT cpan.org >
+
+This module is currently maintained by Jens Rehsack < jrehsack AT cpan.org >
+
+Copyright (C) 2010 by Jens Rehsack, all rights reserved.
+Copyright (C) 2004 by Jeff Zucker, all rights reserved.
+
+You may freely distribute and/or modify this module under the terms of
+either the GNU General Public License (GPL) or the Artistic License,
+as specified in the Perl README file.
+
+=cut
+
diff --git a/lib/DBI/Util/CacheMemory.pm b/lib/DBI/Util/CacheMemory.pm
new file mode 100644
index 0000000..f111432
--- /dev/null
+++ b/lib/DBI/Util/CacheMemory.pm
@@ -0,0 +1,117 @@
+package DBI::Util::CacheMemory;
+
+# $Id: CacheMemory.pm 10314 2007-11-26 22:25:33Z timbo $
+#
+# Copyright (c) 2007, Tim Bunce, Ireland
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+DBI::Util::CacheMemory - a very fast but very minimal subset of Cache::Memory
+
+=head1 DESCRIPTION
+
+Like Cache::Memory (part of the Cache distribution) but doesn't support any fancy features.
+
+This module aims to be a very fast compatible strict sub-set for simple cases,
+such as basic client-side caching for DBD::Gofer.
+
+Like Cache::Memory, and other caches in the Cache and Cache::Cache
+distributions, the data will remain in the cache until cleared, it expires,
+or the process dies. The cache object simply going out of scope will I<not>
+destroy the data.
+
+=head1 METHODS WITH CHANGES
+
+=head2 new
+
+All options except C<namespace> are ignored.
+
+=head2 set
+
+Doesn't support expiry.
+
+=head2 purge
+
+Same as clear() - deletes everything in the namespace.
+
+=head1 METHODS WITHOUT CHANGES
+
+=over
+
+=item clear
+
+=item count
+
+=item exists
+
+=item remove
+
+=back
+
+=head1 UNSUPPORTED METHODS
+
+If it's not listed above, it's not supported.
+
+=cut
+
+our $VERSION = sprintf("0.%06d", q$Revision: 10314 $ =~ /(\d+)/o);
+
+my %cache;
+
+sub new {
+ my ($class, %options ) = @_;
+ my $namespace = $options{namespace} ||= 'Default';
+ #$options{_cache} = \%cache; # can be handy for debugging/dumping
+ my $self = bless \%options => $class;
+ $cache{ $namespace } ||= {}; # init - ensure it exists
+ return $self;
+}
+
+sub set {
+ my ($self, $key, $value) = @_;
+ $cache{ $self->{namespace} }->{$key} = $value;
+}
+
+sub get {
+ my ($self, $key) = @_;
+ return $cache{ $self->{namespace} }->{$key};
+}
+
+sub exists {
+ my ($self, $key) = @_;
+ return exists $cache{ $self->{namespace} }->{$key};
+}
+
+sub remove {
+ my ($self, $key) = @_;
+ return delete $cache{ $self->{namespace} }->{$key};
+}
+
+sub purge {
+ return shift->clear;
+}
+
+sub clear {
+ $cache{ shift->{namespace} } = {};
+}
+
+sub count {
+ return scalar keys %{ $cache{ shift->{namespace} } };
+}
+
+sub size {
+ my $c = $cache{ shift->{namespace} };
+ my $size = 0;
+ while ( my ($k,$v) = each %$c ) {
+ $size += length($k) + length($v);
+ }
+ return $size;
+}
+
+1;
diff --git a/lib/DBI/Util/_accessor.pm b/lib/DBI/Util/_accessor.pm
new file mode 100644
index 0000000..7836ebe
--- /dev/null
+++ b/lib/DBI/Util/_accessor.pm
@@ -0,0 +1,65 @@
+package DBI::Util::_accessor;
+use strict;
+use Carp;
+our $VERSION = sprintf("0.%06d", q$Revision: 9478 $ =~ /(\d+)/);
+
+# inspired by Class::Accessor::Fast
+
+sub new {
+ my($proto, $fields) = @_;
+ my($class) = ref $proto || $proto;
+ $fields ||= {};
+
+ my @dubious = grep { !m/^_/ && !$proto->can($_) } keys %$fields;
+ carp "$class doesn't have accessors for fields: @dubious" if @dubious;
+
+ # make a (shallow) copy of $fields.
+ bless {%$fields}, $class;
+}
+
+sub mk_accessors {
+ my($self, @fields) = @_;
+ $self->mk_accessors_using('make_accessor', @fields);
+}
+
+sub mk_accessors_using {
+ my($self, $maker, @fields) = @_;
+ my $class = ref $self || $self;
+
+ # So we don't have to do lots of lookups inside the loop.
+ $maker = $self->can($maker) unless ref $maker;
+
+ no strict 'refs';
+ foreach my $field (@fields) {
+ my $accessor = $self->$maker($field);
+ *{$class."\:\:$field"} = $accessor
+ unless defined &{$class."\:\:$field"};
+ }
+ #my $hash_ref = \%{$class."\:\:_accessors_hash};
+ #$hash_ref->{$_}++ for @fields;
+ # XXX also copy down _accessors_hash of base class(es)
+ # so one in this class is complete
+ return;
+}
+
+sub make_accessor {
+ my($class, $field) = @_;
+ return sub {
+ my $self = shift;
+ return $self->{$field} unless @_;
+ croak "Too many arguments to $field" if @_ > 1;
+ return $self->{$field} = shift;
+ };
+}
+
+sub make_accessor_autoviv_hashref {
+ my($class, $field) = @_;
+ return sub {
+ my $self = shift;
+ return $self->{$field} ||= {} unless @_;
+ croak "Too many arguments to $field" if @_ > 1;
+ return $self->{$field} = shift;
+ };
+}
+
+1;
diff --git a/lib/DBI/W32ODBC.pm b/lib/DBI/W32ODBC.pm
new file mode 100644
index 0000000..ac2aea1
--- /dev/null
+++ b/lib/DBI/W32ODBC.pm
@@ -0,0 +1,181 @@
+package
+ DBI; # hide this non-DBI package from simple indexers
+
+# $Id: W32ODBC.pm 8696 2007-01-24 23:12:38Z timbo $
+#
+# Copyright (c) 1997,1999 Tim Bunce
+# With many thanks to Patrick Hollins for polishing.
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+=head1 NAME
+
+DBI::W32ODBC - An experimental DBI emulation layer for Win32::ODBC
+
+=head1 SYNOPSIS
+
+ use DBI::W32ODBC;
+
+ # apart from the line above everything is just the same as with
+ # the real DBI when using a basic driver with few features.
+
+=head1 DESCRIPTION
+
+This is an experimental pure perl DBI emulation layer for Win32::ODBC
+
+If you can improve this code I'd be interested in hearing about it. If
+you are having trouble using it please respect the fact that it's very
+experimental. Ideally fix it yourself and send me the details.
+
+=head2 Some Things Not Yet Implemented
+
+ Most attributes including PrintError & RaiseError.
+ type_info and table_info
+
+Volunteers welcome!
+
+=cut
+
+${'DBI::VERSION'} # hide version from PAUSE indexer
+ = "0.01";
+
+my $Revision = sprintf("12.%06d", q$Revision: 8696 $ =~ /(\d+)/o);
+
+
+sub DBI::W32ODBC::import { } # must trick here since we're called DBI/W32ODBC.pm
+
+
+use Carp;
+
+use Win32::ODBC;
+
+@ISA = qw(Win32::ODBC);
+
+use strict;
+
+$DBI::dbi_debug = $ENV{PERL_DBI_DEBUG} || 0;
+carp "Loaded (W32ODBC) DBI.pm ${'DBI::VERSION'} (debug $DBI::dbi_debug)"
+ if $DBI::dbi_debug;
+
+
+
+sub connect {
+ my ($class, $dbname, $dbuser, $dbpasswd, $module, $attr) = @_;
+ $dbname .= ";UID=$dbuser" if $dbuser;
+ $dbname .= ";PWD=$dbpasswd" if $dbpasswd;
+ my $h = new Win32::ODBC $dbname;
+ warn "Error connecting to $dbname: ".Win32::ODBC::Error()."\n" unless $h;
+ bless $h, $class if $h; # rebless into our class
+ $h;
+}
+
+
+sub quote {
+ my ($h, $string) = @_;
+ return "NULL" if !defined $string;
+ $string =~ s/'/''/g; # standard
+ # This hack seems to be required for Access but probably breaks for
+ # other databases when using \r and \n. It would be better if we could
+ # use ODBC options to detect that we're actually using Access.
+ $string =~ s/\r/' & chr\$(13) & '/g;
+ $string =~ s/\n/' & chr\$(10) & '/g;
+ "'$string'";
+}
+
+sub do {
+ my($h, $statement, $attribs, @params) = @_;
+ Carp::carp "\$h->do() attribs unused" if $attribs;
+ my $new_h = $h->prepare($statement) or return undef; ##
+ pop @{ $h->{'___sths'} }; ## certian death assured
+ $new_h->execute(@params) or return undef; ##
+ my $rows = $new_h->rows; ##
+ $new_h->finish; ## bang bang
+ ($rows == 0) ? "0E0" : $rows;
+}
+
+# ---
+
+sub prepare {
+ my ($h, $sql) = @_;
+ ## opens a new connection with every prepare to allow
+ ## multiple, concurrent queries
+ my $new_h = new Win32::ODBC $h->{DSN}; ##
+ return undef if not $new_h; ## bail if no connection
+ bless $new_h; ## shouldn't be sub-classed...
+ $new_h->{'__prepare'} = $sql; ##
+ $new_h->{NAME} = []; ##
+ $new_h->{NUM_OF_FIELDS} = -1; ##
+ push @{ $h->{'___sths'} } ,$new_h; ## save sth in parent for mass destruction
+ return $new_h; ##
+}
+
+sub execute {
+ my ($h) = @_;
+ my $rc = $h->Sql($h->{'__prepare'});
+ return undef if $rc;
+ my @fields = $h->FieldNames;
+ $h->{NAME} = \@fields;
+ $h->{NUM_OF_FIELDS} = scalar @fields;
+ $h; # return dbh as pseudo sth
+}
+
+
+sub fetchrow_hashref { ## provide DBI compatibility
+ my $h = shift;
+ my $NAME = shift || "NAME";
+ my $row = $h->fetchrow_arrayref or return undef;
+ my %hash;
+ @hash{ @{ $h->{$NAME} } } = @$row;
+ return \%hash;
+}
+
+sub fetchrow {
+ my $h = shift;
+ return unless $h->FetchRow();
+ my $fields_r = $h->{NAME};
+ return $h->Data(@$fields_r);
+}
+sub fetch {
+ my @row = shift->fetchrow;
+ return undef unless @row;
+ return \@row;
+}
+*fetchrow_arrayref = \&fetch; ## provide DBI compatibility
+*fetchrow_array = \&fetchrow; ## provide DBI compatibility
+
+sub rows {
+ shift->RowCount;
+}
+
+sub finish {
+ shift->Close; ## uncommented this line
+}
+
+# ---
+
+sub commit {
+ shift->Transact(ODBC::SQL_COMMIT);
+}
+sub rollback {
+ shift->Transact(ODBC::SQL_ROLLBACK);
+}
+
+sub disconnect {
+ my ($h) = shift; ## this will kill all the statement handles
+ foreach (@{$h->{'___sths'}}) { ## created for a specific connection
+ $_->Close if $_->{DSN}; ##
+ } ##
+ $h->Close; ##
+}
+
+sub err {
+ (shift->Error)[0];
+}
+sub errstr {
+ scalar( shift->Error );
+}
+
+# ---
+
+1;
diff --git a/lib/Win32/DBIODBC.pm b/lib/Win32/DBIODBC.pm
new file mode 100644
index 0000000..a93f69b
--- /dev/null
+++ b/lib/Win32/DBIODBC.pm
@@ -0,0 +1,248 @@
+package # hide this package from CPAN indexer
+ Win32::ODBC;
+
+#use strict;
+
+use DBI;
+
+# once we've been loaded we don't want perl to load the real Win32::ODBC
+$INC{'Win32/ODBC.pm'} = $INC{'Win32/DBIODBC.pm'} || 1;
+
+#my $db = new Win32::ODBC("DSN=$self->{'DSN'};UID=$self->{'UID'};PWD=$self->{'PWD'};");
+
+#EMU --- my $db = new Win32::ODBC("DSN=$DSN;UID=$login;PWD=$password;");
+sub new
+{
+ shift;
+ my $connect_line= shift;
+
+# [R] self-hack to allow empty UID and PWD
+ my $temp_connect_line;
+ $connect_line=~/DSN=\w+/;
+ $temp_connect_line="$&;";
+ if ($connect_line=~/UID=\w?/)
+ {$temp_connect_line.="$&;";}
+ else {$temp_connect_line.="UID=;";};
+ if ($connect_line=~/PWD=\w?/)
+ {$temp_connect_line.="$&;";}
+ else {$temp_connect_line.="PWD=;";};
+ $connect_line=$temp_connect_line;
+# -[R]-
+
+ my $self= {};
+
+
+ $_=$connect_line;
+ /^(DSN=)(.*)(;UID=)(.*)(;PWD=)(.*)(;)$/;
+
+ #---- DBI CONNECTION VARIABLES
+
+ $self->{ODBC_DSN}=$2;
+ $self->{ODBC_UID}=$4;
+ $self->{ODBC_PWD}=$6;
+
+
+ #---- DBI CONNECTION VARIABLES
+ $self->{DBI_DBNAME}=$self->{ODBC_DSN};
+ $self->{DBI_USER}=$self->{ODBC_UID};
+ $self->{DBI_PASSWORD}=$self->{ODBC_PWD};
+ $self->{DBI_DBD}='ODBC';
+
+ #---- DBI CONNECTION
+ $self->{'DBI_DBH'}=DBI->connect($self->{'DBI_DBNAME'},
+ $self->{'DBI_USER'},$self->{'DBI_PASSWORD'},$self->{'DBI_DBD'});
+
+ warn "Error($DBI::err) : $DBI::errstr\n" if ! $self->{'DBI_DBH'};
+
+
+ #---- RETURN
+
+ bless $self;
+}
+
+
+#EMU --- $db->Sql('SELECT * FROM DUAL');
+sub Sql
+{
+ my $self= shift;
+ my $SQL_statment=shift;
+
+ # print " SQL : $SQL_statment \n";
+
+ $self->{'DBI_SQL_STATMENT'}=$SQL_statment;
+
+ my $dbh=$self->{'DBI_DBH'};
+
+ # print " DBH : $dbh \n";
+
+ my $sth=$dbh->prepare("$SQL_statment");
+
+ # print " STH : $sth \n";
+
+ $self->{'DBI_STH'}=$sth;
+
+ if ($sth)
+ {
+ $sth->execute();
+ }
+
+ #--- GET ERROR MESSAGES
+ $self->{DBI_ERR}=$DBI::err;
+ $self->{DBI_ERRSTR}=$DBI::errstr;
+
+ if ($sth)
+ {
+ #--- GET COLUMNS NAMES
+ $self->{'DBI_NAME'} = $sth->{NAME};
+ }
+
+# [R] provide compatibility with Win32::ODBC's way of identifying erraneous SQL statements
+ return ($self->{'DBI_ERR'})?1:undef;
+# -[R]-
+}
+
+
+#EMU --- $db->FetchRow())
+sub FetchRow
+{
+ my $self= shift;
+
+ my $sth=$self->{'DBI_STH'};
+ if ($sth)
+ {
+ my @row=$sth->fetchrow_array;
+ $self->{'DBI_ROW'}=\@row;
+
+ if (scalar(@row)>0)
+ {
+ #-- the row of result is not nul
+ #-- return somthing nothing will be return else
+ return 1;
+ }
+ }
+ return undef;
+}
+
+# [R] provide compatibility with Win32::ODBC's Data() method.
+sub Data
+{
+ my $self=shift;
+ my @array=@{$self->{'DBI_ROW'}};
+ foreach my $element (@array)
+ {
+ # remove padding of spaces by DBI
+ $element=~s/(\s*$)//;
+ };
+ return (wantarray())?@array:join('', @array);
+};
+# -[R]-
+
+#EMU --- %record = $db->DataHash;
+sub DataHash
+{
+ my $self= shift;
+
+ my $p_name=$self->{'DBI_NAME'};
+ my $p_row=$self->{'DBI_ROW'};
+
+ my @name=@$p_name;
+ my @row=@$p_row;
+
+ my %DataHash;
+#print @name; print "\n"; print @row;
+# [R] new code that seems to work consistent with Win32::ODBC
+ while (@name)
+ {
+ my $name=shift(@name);
+ my $value=shift(@row);
+
+ # remove padding of spaces by DBI
+ $name=~s/(\s*$)//;
+ $value=~s/(\s*$)//;
+
+ $DataHash{$name}=$value;
+ };
+# -[R]-
+
+# [R] old code that didn't appear to work
+# foreach my $name (@name)
+# {
+# $name=~s/(^\s*)|(\s*$)//;
+# my @arr=@$name;
+# foreach (@arr)
+# {
+# print "lot $name name col $_ or ROW= 0 $row[0] 1 $row[1] 2 $row[2] \n ";
+# $DataHash{$name}=shift(@row);
+# }
+# }
+# -[R]-
+
+ #--- Return Hash
+ return %DataHash;
+}
+
+
+#EMU --- $db->Error()
+sub Error
+{
+ my $self= shift;
+
+ if ($self->{'DBI_ERR'} ne '')
+ {
+ #--- Return error message
+ $self->{'DBI_ERRSTR'};
+ }
+
+ #-- else good no error message
+
+}
+
+# [R] provide compatibility with Win32::ODBC's Close() method.
+sub Close
+{
+ my $self=shift;
+
+ my $dbh=$self->{'DBI_DBH'};
+ $dbh->disconnect;
+}
+# -[R]-
+
+1;
+
+__END__
+
+# [R] to -[R]- indicate sections edited by me, Roy Lee
+
+=head1 NAME
+
+Win32::DBIODBC - Win32::ODBC emulation layer for the DBI
+
+=head1 SYNOPSIS
+
+ use Win32::DBIODBC; # instead of use Win32::ODBC
+
+=head1 DESCRIPTION
+
+This is a I<very> basic I<very> alpha quality Win32::ODBC emulation
+for the DBI. To use it just replace
+
+ use Win32::ODBC;
+
+in your scripts with
+
+ use Win32::DBIODBC;
+
+or, while experimenting, you can pre-load this module without changing your
+scripts by doing
+
+ perl -MWin32::DBIODBC your_script_name
+
+=head1 TO DO
+
+Error handling is virtually non-existent.
+
+=head1 AUTHOR
+
+Tom Horen <tho@melexis.com>
+
+=cut
diff --git a/t/01basics.t b/t/01basics.t
new file mode 100755
index 0000000..2c11f3c
--- /dev/null
+++ b/t/01basics.t
@@ -0,0 +1,336 @@
+#!perl -w
+
+use strict;
+
+use Test::More tests => 130;
+use File::Spec;
+
+$|=1;
+
+## ----------------------------------------------------------------------------
+## 01basic.t - test of some basic DBI functions
+## ----------------------------------------------------------------------------
+# Mostly this script takes care of testing the items exported by the 3
+# tags below (in this order):
+# - :sql_types
+# - :squl_cursor_types
+# - :util
+# It also then handles some other class methods and functions of DBI, such
+# as the following:
+# - $DBI::dbi_debug & its relation to DBI->trace
+# - DBI->internal
+# and then tests on that return value:
+# - $i->debug
+# - $i->{DebugDispatch}
+# - $i->{Warn}
+# - $i->{Attribution}
+# - $i->{Version}
+# - $i->{private_test1}
+# - $i->{cachedKids}
+# - $i->{Kids}
+# - $i->{ActiveKids}
+# - $i->{Active}
+# - and finally that it will not autovivify
+# - DBI->available_drivers
+# - DBI->installed_versions (only for developers)
+## ----------------------------------------------------------------------------
+
+## load DBI and export some symbols
+BEGIN {
+ use_ok('DBI', qw(
+ :sql_types
+ :sql_cursor_types
+ :utils
+ ));
+}
+
+## ----------------------------------------------------------------------------
+## testing the :sql_types exports
+
+cmp_ok(SQL_GUID , '==', -11, '... testing sql_type');
+cmp_ok(SQL_WLONGVARCHAR , '==', -10, '... testing sql_type');
+cmp_ok(SQL_WVARCHAR , '==', -9, '... testing sql_type');
+cmp_ok(SQL_WCHAR , '==', -8, '... testing sql_type');
+cmp_ok(SQL_BIT , '==', -7, '... testing sql_type');
+cmp_ok(SQL_TINYINT , '==', -6, '... testing sql_type');
+cmp_ok(SQL_BIGINT , '==', -5, '... testing sql_type');
+cmp_ok(SQL_LONGVARBINARY , '==', -4, '... testing sql_type');
+cmp_ok(SQL_VARBINARY , '==', -3, '... testing sql_type');
+cmp_ok(SQL_BINARY , '==', -2, '... testing sql_type');
+cmp_ok(SQL_LONGVARCHAR , '==', -1, '... testing sql_type');
+cmp_ok(SQL_UNKNOWN_TYPE , '==', 0, '... testing sql_type');
+cmp_ok(SQL_ALL_TYPES , '==', 0, '... testing sql_type');
+cmp_ok(SQL_CHAR , '==', 1, '... testing sql_type');
+cmp_ok(SQL_NUMERIC , '==', 2, '... testing sql_type');
+cmp_ok(SQL_DECIMAL , '==', 3, '... testing sql_type');
+cmp_ok(SQL_INTEGER , '==', 4, '... testing sql_type');
+cmp_ok(SQL_SMALLINT , '==', 5, '... testing sql_type');
+cmp_ok(SQL_FLOAT , '==', 6, '... testing sql_type');
+cmp_ok(SQL_REAL , '==', 7, '... testing sql_type');
+cmp_ok(SQL_DOUBLE , '==', 8, '... testing sql_type');
+cmp_ok(SQL_DATETIME , '==', 9, '... testing sql_type');
+cmp_ok(SQL_DATE , '==', 9, '... testing sql_type');
+cmp_ok(SQL_INTERVAL , '==', 10, '... testing sql_type');
+cmp_ok(SQL_TIME , '==', 10, '... testing sql_type');
+cmp_ok(SQL_TIMESTAMP , '==', 11, '... testing sql_type');
+cmp_ok(SQL_VARCHAR , '==', 12, '... testing sql_type');
+cmp_ok(SQL_BOOLEAN , '==', 16, '... testing sql_type');
+cmp_ok(SQL_UDT , '==', 17, '... testing sql_type');
+cmp_ok(SQL_UDT_LOCATOR , '==', 18, '... testing sql_type');
+cmp_ok(SQL_ROW , '==', 19, '... testing sql_type');
+cmp_ok(SQL_REF , '==', 20, '... testing sql_type');
+cmp_ok(SQL_BLOB , '==', 30, '... testing sql_type');
+cmp_ok(SQL_BLOB_LOCATOR , '==', 31, '... testing sql_type');
+cmp_ok(SQL_CLOB , '==', 40, '... testing sql_type');
+cmp_ok(SQL_CLOB_LOCATOR , '==', 41, '... testing sql_type');
+cmp_ok(SQL_ARRAY , '==', 50, '... testing sql_type');
+cmp_ok(SQL_ARRAY_LOCATOR , '==', 51, '... testing sql_type');
+cmp_ok(SQL_MULTISET , '==', 55, '... testing sql_type');
+cmp_ok(SQL_MULTISET_LOCATOR , '==', 56, '... testing sql_type');
+cmp_ok(SQL_TYPE_DATE , '==', 91, '... testing sql_type');
+cmp_ok(SQL_TYPE_TIME , '==', 92, '... testing sql_type');
+cmp_ok(SQL_TYPE_TIMESTAMP , '==', 93, '... testing sql_type');
+cmp_ok(SQL_TYPE_TIME_WITH_TIMEZONE , '==', 94, '... testing sql_type');
+cmp_ok(SQL_TYPE_TIMESTAMP_WITH_TIMEZONE , '==', 95, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_YEAR , '==', 101, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_MONTH , '==', 102, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_DAY , '==', 103, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_HOUR , '==', 104, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_MINUTE , '==', 105, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_SECOND , '==', 106, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_YEAR_TO_MONTH , '==', 107, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_DAY_TO_HOUR , '==', 108, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_DAY_TO_MINUTE , '==', 109, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_DAY_TO_SECOND , '==', 110, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_HOUR_TO_MINUTE , '==', 111, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_HOUR_TO_SECOND , '==', 112, '... testing sql_type');
+cmp_ok(SQL_INTERVAL_MINUTE_TO_SECOND , '==', 113, '... testing sql_type');
+
+## ----------------------------------------------------------------------------
+## testing the :sql_cursor_types exports
+
+cmp_ok(SQL_CURSOR_FORWARD_ONLY, '==', 0, '... testing sql_cursor_types');
+cmp_ok(SQL_CURSOR_KEYSET_DRIVEN, '==', 1, '... testing sql_cursor_types');
+cmp_ok(SQL_CURSOR_DYNAMIC, '==', 2, '... testing sql_cursor_types');
+cmp_ok(SQL_CURSOR_STATIC, '==', 3, '... testing sql_cursor_types');
+cmp_ok(SQL_CURSOR_TYPE_DEFAULT, '==', 0, '... testing sql_cursor_types');
+
+## ----------------------------------------------------------------------------
+## test the :util exports
+
+## testing looks_like_number
+
+my @is_num = looks_like_number(undef, "", "foo", 1, ".", 2, "2");
+
+ok(!defined $is_num[0], '... looks_like_number : undef -> undef');
+ok(!defined $is_num[1], '... looks_like_number : "" -> undef (eg "don\'t know")');
+ok( defined $is_num[2], '... looks_like_number : "foo" -> defined false');
+ok( !$is_num[2], '... looks_like_number : "foo" -> defined false');
+ok( $is_num[3], '... looks_like_number : 1 -> true');
+ok( !$is_num[4], '... looks_like_number : "." -> false');
+ok( $is_num[5], '... looks_like_number : 1 -> true');
+ok( $is_num[6], '... looks_like_number : 1 -> true');
+
+## testing neat
+
+cmp_ok($DBI::neat_maxlen, '==', 1000, "... $DBI::neat_maxlen initial state is 400");
+
+is(neat(1 + 1), "2", '... neat : 1 + 1 -> "2"');
+is(neat("2"), "'2'", '... neat : 2 -> "\'2\'"');
+is(neat(undef), "undef", '... neat : undef -> "undef"');
+
+## testing neat_list
+
+is(neat_list([ 1 + 1, "2", undef, "foobarbaz"], 8, "|"), "2|'2'|undef|'foo...'", '... test array argument w/seperator and maxlen');
+is(neat_list([ 1 + 1, "2", undef, "foobarbaz"]), "2, '2', undef, 'foobarbaz'", '... test array argument w/out seperator or maxlen');
+
+
+## ----------------------------------------------------------------------------
+## testing DBI functions
+
+## test DBI->internal
+
+my $switch = DBI->internal;
+
+isa_ok($switch, 'DBI::dr');
+
+## checking attributes of $switch
+
+# NOTE:
+# check too see if this covers all the attributes or not
+
+# TO DO:
+# these three can be improved
+$switch->debug(0);
+pass('... test debug');
+$switch->{DebugDispatch} = 0; # handled by Switch
+pass('... test DebugDispatch');
+$switch->{Warn} = 1; # handled by DBI core
+pass('... test Warn');
+
+like($switch->{'Attribution'}, qr/DBI.*? by Tim Bunce/, '... this should say Tim Bunce');
+
+# is this being presumptious?
+is($switch->{'Version'}, $DBI::VERSION, '... the version should match DBI version');
+
+cmp_ok(($switch->{private_test1} = 1), '==', 1, '... this should work and return 1');
+cmp_ok($switch->{private_test1}, '==', 1, '... this should equal 1');
+
+is($switch->{CachedKids}, undef, '... CachedKids should be undef initially');
+my $cache = {};
+$switch->{CachedKids} = $cache;
+is($switch->{CachedKids}, $cache, '... CachedKids should be our ref');
+
+cmp_ok($switch->{Kids}, '==', 0, '... this should be zero');
+cmp_ok($switch->{ActiveKids}, '==', 0, '... this should be zero');
+
+ok($switch->{Active}, '... Active flag is true');
+
+# test attribute warnings
+{
+ my $warn = "";
+ local $SIG{__WARN__} = sub { $warn .= "@_" };
+ $switch->{FooBarUnknown} = 1;
+ like($warn, qr/Can't set.*FooBarUnknown/, '... we should get a warning here');
+
+ $warn = "";
+ $_ = $switch->{BarFooUnknown};
+ like($warn, qr/Can't get.*BarFooUnknown/, '... we should get a warning here');
+
+ $warn = "";
+ my $dummy = $switch->{$_} for qw(private_foo dbd_foo dbi_foo); # special cases
+ cmp_ok($warn, 'eq', "", '... we should get no warnings here');
+}
+
+# is this here for a reason? Are we testing anything?
+
+$switch->trace_msg("Test \$h->trace_msg text.\n", 1);
+DBI->trace_msg("Test DBI->trace_msg text.\n", 1);
+
+## testing DBI->available_drivers
+
+my @drivers = DBI->available_drivers();
+cmp_ok(scalar(@drivers), '>', 0, '... we at least have one driver installed');
+
+# NOTE:
+# we lowercase the interpolated @drivers array
+# so that our reg-exp will match on VMS & Win32
+
+like(lc("@drivers"), qr/examplep/, '... we should at least have ExampleP installed');
+
+# call available_drivers in scalar context
+
+my $num_drivers = DBI->available_drivers;
+cmp_ok($num_drivers, '>', 0, '... we should at least have one driver');
+
+## testing DBI::hash
+
+cmp_ok(DBI::hash("foo1" ), '==', -1077531989, '... should be -1077531989');
+cmp_ok(DBI::hash("foo1",0), '==', -1077531989, '... should be -1077531989');
+cmp_ok(DBI::hash("foo2",0), '==', -1077531990, '... should be -1077531990');
+SKIP: {
+ skip("Math::BigInt < 1.56",2)
+ if $DBI::PurePerl && !eval { require Math::BigInt; require_version Math::BigInt 1.56 };
+ skip("Math::BigInt $Math::BigInt::VERSION broken",2)
+ if $DBI::PurePerl && $Math::BigInt::VERSION =~ /^1\.8[45]/;
+ my $bigint_vers = $Math::BigInt::VERSION || "";
+ if (!$DBI::PurePerl) {
+ cmp_ok(DBI::hash("foo1",1), '==', -1263462440);
+ cmp_ok(DBI::hash("foo2",1), '==', -1263462437);
+ }
+ else {
+ # for PurePerl we use Math::BigInt but that's often caused test failures that
+ # aren't DBI's fault. So we just warn (via a skip) if it's not working right.
+ skip("Seems like your Math::BigInt $Math::BigInt::VERSION has a bug",2)
+ unless (DBI::hash("foo1X",1) == -1263462440) && (DBI::hash("foo2",1) == -1263462437);
+ ok(1, "Math::BigInt $Math::BigInt::VERSION worked okay");
+ ok(1);
+ }
+}
+
+is(data_string_desc(""), "UTF8 off, ASCII, 0 characters 0 bytes");
+is(data_string_desc(42), "UTF8 off, ASCII, 2 characters 2 bytes");
+is(data_string_desc("foo"), "UTF8 off, ASCII, 3 characters 3 bytes");
+is(data_string_desc(undef), "UTF8 off, undef");
+is(data_string_desc("bar\x{263a}"), "UTF8 on, non-ASCII, 4 characters 6 bytes");
+is(data_string_desc("\xEA"), "UTF8 off, non-ASCII, 1 characters 1 bytes");
+
+is(data_string_diff( "", ""), "");
+is(data_string_diff( "",undef), "String b is undef, string a has 0 characters");
+is(data_string_diff(undef,undef), "");
+is(data_string_diff("aaa","aaa"), "");
+
+is(data_string_diff("aaa","aba"), "Strings differ at index 1: a[1]=a, b[1]=b");
+is(data_string_diff("aba","aaa"), "Strings differ at index 1: a[1]=b, b[1]=a");
+is(data_string_diff("aa" ,"aaa"), "String a truncated after 2 characters");
+is(data_string_diff("aaa","aa" ), "String b truncated after 2 characters");
+
+is(data_diff( "", ""), "");
+is(data_diff(undef,undef), "");
+is(data_diff("aaa","aaa"), "");
+
+is(data_diff( "",undef),
+ join "","a: UTF8 off, ASCII, 0 characters 0 bytes\n",
+ "b: UTF8 off, undef\n",
+ "String b is undef, string a has 0 characters\n");
+is(data_diff("aaa","aba"),
+ join "","a: UTF8 off, ASCII, 3 characters 3 bytes\n",
+ "b: UTF8 off, ASCII, 3 characters 3 bytes\n",
+ "Strings differ at index 1: a[1]=a, b[1]=b\n");
+is(data_diff(pack("C",0xEA), pack("U",0xEA)),
+ join "", "a: UTF8 off, non-ASCII, 1 characters 1 bytes\n",
+ "b: UTF8 on, non-ASCII, 1 characters 2 bytes\n",
+ "Strings contain the same sequence of characters\n");
+is(data_diff(pack("C",0xEA), pack("U",0xEA), 1), ""); # no logical difference
+
+
+## ----------------------------------------------------------------------------
+# restrict this test to just developers
+
+SKIP: {
+ skip 'developer tests', 4 unless -d ".svn" || -d ".git";
+
+ if ($^O eq "MSWin32" && eval { require Win32API::File }) {
+ Win32API::File::SetErrorMode(Win32API::File::SEM_FAILCRITICALERRORS());
+ }
+
+ print "Test DBI->installed_versions (for @drivers)\n";
+ print "(If one of those drivers, or the configuration for it, is bad\n";
+ print "then these tests can kill or freeze the process here. That's not the DBI's fault.)\n";
+ $SIG{ALRM} = sub {
+ die "Test aborted because a driver (one of: @drivers) hung while loading"
+ ." (almost certainly NOT a DBI problem)";
+ };
+ alarm(20);
+
+ ## ----------------------------------------------------------------------------
+ ## test installed_versions
+
+ # scalar context
+ my $installed_versions = DBI->installed_versions;
+
+ is(ref($installed_versions), 'HASH', '... we got a hash of installed versions');
+ cmp_ok(scalar(keys(%{$installed_versions})), '>=', 1, '... make sure we have at least one');
+
+ # list context
+ my @installed_drivers = DBI->installed_versions;
+
+ cmp_ok(scalar(@installed_drivers), '>=', 1, '... make sure we got at least one');
+ like("@installed_drivers", qr/Sponge/, '... make sure at least one of them is DBD::Sponge');
+}
+
+## testing dbi_debug
+
+cmp_ok($DBI::dbi_debug, '==', 0, "... DBI::dbi_debug's initial state is 0");
+
+SKIP: {
+ my $null = File::Spec->devnull();
+ skip "cannot find : $null", 2 unless ($^O eq "MSWin32" || -e $null);
+
+ DBI->trace(15,$null);
+ cmp_ok($DBI::dbi_debug, '==', 15, "... DBI::dbi_debug is 15");
+ DBI->trace(0, undef);
+ cmp_ok($DBI::dbi_debug, '==', 0, "... DBI::dbi_debug is 0");
+}
+
+1;
diff --git a/t/02dbidrv.t b/t/02dbidrv.t
new file mode 100755
index 0000000..7a80ffe
--- /dev/null
+++ b/t/02dbidrv.t
@@ -0,0 +1,254 @@
+#!perl -w
+# vim:sw=4:ts=8:et
+$|=1;
+
+use strict;
+
+use Test::More tests => 53;
+
+## ----------------------------------------------------------------------------
+## 02dbidrv.t - ...
+## ----------------------------------------------------------------------------
+# This test creates a Test Driver (DBD::Test) and then exercises it.
+# NOTE:
+# There are a number of tests as well that are embedded within the actual
+# driver code as well
+## ----------------------------------------------------------------------------
+
+## load DBI
+
+BEGIN {
+ use_ok('DBI');
+}
+
+## ----------------------------------------------------------------------------
+## create a Test Driver (DBD::Test)
+
+## main Test Driver Package
+{
+ package DBD::Test;
+
+ use strict;
+ use warnings;
+
+ my $drh = undef;
+
+ sub driver {
+ return $drh if $drh;
+
+ Test::More::pass('... DBD::Test->driver called to getnew Driver handle');
+
+ my($class, $attr) = @_;
+ $class = "${class}::dr";
+ ($drh) = DBI::_new_drh($class, {
+ Name => 'Test',
+ Version => '$Revision: 11.11 $',
+ },
+ 77 # 'implementors data'
+ );
+
+ Test::More::ok($drh, "... new Driver handle ($drh) created successfully");
+ Test::More::isa_ok($drh, 'DBI::dr');
+
+ return $drh;
+ }
+}
+
+## Test Driver
+{
+ package DBD::Test::dr;
+
+ use strict;
+ use warnings;
+
+ $DBD::Test::dr::imp_data_size = 0;
+
+ Test::More::cmp_ok($DBD::Test::dr::imp_data_size, '==', 0, '... check DBD::Test::dr::imp_data_size to avoid typo');
+
+ sub DESTROY { undef }
+
+ sub data_sources {
+ my ($h) = @_;
+
+ Test::More::ok($h, '... Driver object passed to data_sources');
+ Test::More::isa_ok($h, 'DBI::dr');
+ Test::More::ok(!tied $h, '... Driver object is not tied');
+
+ return ("dbi:Test:foo", "dbi:Test:bar");
+ }
+}
+
+## Test db package
+{
+ package DBD::Test::db;
+
+ use strict;
+
+ $DBD::Test::db::imp_data_size = 0;
+
+ Test::More::cmp_ok($DBD::Test::db::imp_data_size, '==', 0, '... check DBD::Test::db::imp_data_size to avoid typo');
+
+ sub do {
+ my $h = shift;
+
+ Test::More::ok($h, '... Database object passed to do');
+ Test::More::isa_ok($h, 'DBI::db');
+ Test::More::ok(!tied $h, '... Database object is not tied');
+
+ my $drh_i = $h->{Driver};
+
+ Test::More::ok($drh_i, '... got Driver object from Database object with Driver attribute');
+ Test::More::isa_ok($drh_i, "DBI::dr");
+ Test::More::ok(!tied %{$drh_i}, '... Driver object is not tied');
+
+ my $drh_o = $h->FETCH('Driver');
+
+ Test::More::ok($drh_o, '... got Driver object from Database object by FETCH-ing Driver attribute');
+ Test::More::isa_ok($drh_o, "DBI::dr");
+ SKIP: {
+ Test::More::skip "running DBI::PurePerl", 1 if $DBI::PurePerl;
+ Test::More::ok(tied %{$drh_o}, '... Driver object is not tied');
+ }
+
+ # return this to make our test pass
+ return 1;
+ }
+
+ sub data_sources {
+ my ($dbh, $attr) = @_;
+ my @ds = $dbh->SUPER::data_sources($attr);
+
+ Test::More::is_deeply((
+ \@ds,
+ [ 'dbi:Test:foo', 'dbi:Test:bar' ]
+ ),
+ '... checking fetched datasources from Driver'
+ );
+
+ push @ds, "dbi:Test:baz";
+ return @ds;
+ }
+
+ sub disconnect {
+ shift->STORE(Active => 0);
+ }
+}
+
+## ----------------------------------------------------------------------------
+## test the Driver (DBD::Test)
+
+$INC{'DBD/Test.pm'} = 'dummy'; # required to fool DBI->install_driver()
+
+# Note that install_driver should *not* normally be called directly.
+# This test does so only because it's a test of install_driver!
+
+my $drh = DBI->install_driver('Test');
+
+ok($drh, '... got a Test Driver object back from DBI->install_driver');
+isa_ok($drh, 'DBI::dr');
+
+cmp_ok(DBI::_get_imp_data($drh), '==', 77, '... checking the DBI::_get_imp_data function');
+
+my @ds1 = DBI->data_sources("Test");
+is_deeply((
+ [ @ds1 ],
+ [ 'dbi:Test:foo', 'dbi:Test:bar' ]
+ ), '... got correct datasources from DBI->data_sources("Test")'
+);
+
+SKIP: {
+ skip "Kids attribute not supported under DBI::PurePerl", 1 if $DBI::PurePerl;
+
+ cmp_ok($drh->{Kids}, '==', 0, '... this Driver does not yet have any Kids');
+}
+
+# create scope to test $dbh DESTROY behaviour
+do {
+
+ my $dbh = $drh->connect;
+
+ ok($dbh, '... got a database handle from calling $drh->connect');
+ isa_ok($dbh, 'DBI::db');
+
+ SKIP: {
+ skip "Kids attribute not supported under DBI::PurePerl", 1 if $DBI::PurePerl;
+
+ cmp_ok($drh->{Kids}, '==', 1, '... this Driver does not yet have any Kids');
+ }
+
+ my @ds2 = $dbh->data_sources();
+ is_deeply((
+ [ @ds2 ],
+ [ 'dbi:Test:foo', 'dbi:Test:bar', 'dbi:Test:baz' ]
+ ), '... got correct datasources from $dbh->data_sources()'
+ );
+
+ ok($dbh->do('dummy'), '... this will trigger more driver internal tests above in DBD::Test::db');
+
+ $dbh->disconnect;
+
+ $drh->set_err("41", "foo 41 drh");
+ cmp_ok($drh->err, '==', 41, '... checking Driver handle err set with set_err method');
+ $dbh->set_err("42", "foo 42 dbh");
+ cmp_ok($dbh->err, '==', 42, '... checking Database handle err set with set_err method');
+ cmp_ok($drh->err, '==', 41, '... checking Database handle err set with Driver handle set_err method');
+
+};
+
+SKIP: {
+ skip "Kids attribute not supported under DBI::PurePerl", 1 if $DBI::PurePerl;
+
+ cmp_ok($drh->{Kids}, '==', 0, '... this Driver does not yet have any Kids')
+ or $drh->dump_handle("bad Kids",3);
+}
+
+# copied up to drh from dbh when dbh was DESTROYd
+cmp_ok($drh->err, '==', 42, '... $dbh->DESTROY should set $drh->err to 42');
+
+$drh->set_err("99", "foo");
+cmp_ok($DBI::err, '==', 99, '... checking $DBI::err set with Driver handle set_err method');
+is($DBI::errstr, "foo 42 dbh [err was 42 now 99]\nfoo", '... checking $DBI::errstr');
+
+$drh->default_user("",""); # just to reset err etc
+$drh->set_err(1, "errmsg", "00000");
+is($DBI::state, "", '... checking $DBI::state');
+
+$drh->set_err(1, "test error 1");
+is($DBI::state, 'S1000', '... checking $DBI::state');
+
+$drh->set_err(2, "test error 2", "IM999");
+is($DBI::state, 'IM999', '... checking $DBI::state');
+
+SKIP: {
+ skip "using DBI::PurePerl", 1 if $DBI::PurePerl;
+ eval {
+ $DBI::rows = 1
+ };
+ like($@, qr/Can't modify/, '... trying to assign to $DBI::rows should throw an excpetion'); #'
+}
+
+is($drh->{FetchHashKeyName}, 'NAME', '... FetchHashKeyName is NAME');
+$drh->{FetchHashKeyName} = 'NAME_lc';
+is($drh->{FetchHashKeyName}, 'NAME_lc', '... FetchHashKeyName is now changed to NAME_lc');
+
+ok(!$drh->disconnect_all, '... calling $drh->disconnect_all (not implemented but will fail silently)');
+
+ok defined $drh->dbixs_revision, 'has dbixs_revision';
+ok($drh->dbixs_revision =~ m/^\d+$/, 'has integer dbixs_revision');
+
+SKIP: {
+ skip "using DBI::PurePerl", 5 if $DBI::PurePerl;
+ my $can = $drh->can('FETCH');
+
+ ok($can, '... $drh can FETCH');
+ is(ref($can), "CODE", '... and it returned a proper CODE ref');
+
+ my $name = $can->($drh, "Name");
+
+ ok($name, '... used FETCH returned from can to fetch the Name attribute');
+ is($name, "Test", '... the Name attribute is equal to Test');
+
+ ok(!$drh->can('disconnect_all'), '... ');
+}
+
+1;
diff --git a/t/03handle.t b/t/03handle.t
new file mode 100644
index 0000000..7440ad0
--- /dev/null
+++ b/t/03handle.t
@@ -0,0 +1,410 @@
+#!perl -w
+$|=1;
+
+use strict;
+
+use Test::More tests => 137;
+
+## ----------------------------------------------------------------------------
+## 03handle.t - tests handles
+## ----------------------------------------------------------------------------
+# This set of tests exercises the different handles; Driver, Database and
+# Statement in various ways, in particular in their interactions with one
+# another
+## ----------------------------------------------------------------------------
+
+BEGIN {
+ use_ok( 'DBI' );
+}
+
+# installed drivers should start empty
+my %drivers = DBI->installed_drivers();
+is(scalar keys %drivers, 0);
+
+## ----------------------------------------------------------------------------
+# get the Driver handle
+
+my $driver = "ExampleP";
+
+my $drh = DBI->install_driver($driver);
+isa_ok( $drh, 'DBI::dr' );
+
+SKIP: {
+ skip "Kids attribute not supported under DBI::PurePerl", 1 if $DBI::PurePerl;
+
+ cmp_ok($drh->{Kids}, '==', 0, '... this Driver does not yet have any Kids');
+}
+
+# now the driver should be registered
+%drivers = DBI->installed_drivers();
+is(scalar keys %drivers, 1);
+ok(exists $drivers{ExampleP});
+ok($drivers{ExampleP}->isa('DBI::dr'));
+
+my $using_dbd_gofer = ($ENV{DBI_AUTOPROXY}||'') =~ /^dbi:Gofer.*transport=/i;
+
+## ----------------------------------------------------------------------------
+# do database handle tests inside do BLOCK to capture scope
+
+do {
+ my $dbh = DBI->connect("dbi:$driver:", '', '');
+ isa_ok($dbh, 'DBI::db');
+
+ my $drh = $dbh->{Driver}; # (re)get drh here so tests can work using_dbd_gofer
+
+ SKIP: {
+ skip "Kids and ActiveKids attributes not supported under DBI::PurePerl", 2 if $DBI::PurePerl;
+
+ cmp_ok($drh->{Kids}, '==', 1, '... our Driver has one Kid');
+ cmp_ok($drh->{ActiveKids}, '==', 1, '... our Driver has one ActiveKid');
+ }
+
+ my $sql = "select name from ?";
+
+ my $sth1 = $dbh->prepare_cached($sql);
+ isa_ok($sth1, 'DBI::st');
+ ok($sth1->execute("."), '... execute ran successfully');
+
+ my $ck = $dbh->{CachedKids};
+ is(ref($ck), "HASH", '... we got the CachedKids hash');
+
+ cmp_ok(scalar(keys(%{$ck})), '==', 1, '... there is one CachedKid');
+ ok(eq_set(
+ [ values %{$ck} ],
+ [ $sth1 ]
+ ),
+ '... our statment handle should be in the CachedKids');
+
+ ok($sth1->{Active}, '... our first statment is Active');
+
+ {
+ my $warn = 0; # use this to check that we are warned
+ local $SIG{__WARN__} = sub { ++$warn if $_[0] =~ /still active/i };
+
+ my $sth2 = $dbh->prepare_cached($sql);
+ isa_ok($sth2, 'DBI::st');
+
+ is($sth1, $sth2, '... prepare_cached returned the same statement handle');
+ cmp_ok($warn,'==', 1, '... we got warned about our first statement handle being still active');
+
+ ok(!$sth1->{Active}, '... our first statment is no longer Active since we re-prepared it');
+
+ my $sth3 = $dbh->prepare_cached($sql, { foo => 1 });
+ isa_ok($sth3, 'DBI::st');
+
+ isnt($sth1, $sth3, '... prepare_cached returned a different statement handle now');
+ cmp_ok(scalar(keys(%{$ck})), '==', 2, '... there are two CachedKids');
+ ok(eq_set(
+ [ values %{$ck} ],
+ [ $sth1, $sth3 ]
+ ),
+ '... both statment handles should be in the CachedKids');
+
+ ok($sth1->execute("."), '... executing first statement handle again');
+ ok($sth1->{Active}, '... first statement handle is now active again');
+
+ my $sth4 = $dbh->prepare_cached($sql, undef, 3);
+ isa_ok($sth4, 'DBI::st');
+
+ isnt($sth1, $sth4, '... our fourth statement handle is not the same as our first');
+ ok($sth1->{Active}, '... first statement handle is still active');
+
+ cmp_ok(scalar(keys(%{$ck})), '==', 2, '... there are two CachedKids');
+ ok(eq_set(
+ [ values %{$ck} ],
+ [ $sth2, $sth4 ]
+ ),
+ '... second and fourth statment handles should be in the CachedKids');
+
+ $sth1->finish;
+ ok(!$sth1->{Active}, '... first statement handle is no longer active');
+
+ ok($sth4->execute("."), '... fourth statement handle executed properly');
+ ok($sth4->{Active}, '... fourth statement handle is Active');
+
+ my $sth5 = $dbh->prepare_cached($sql, undef, 1);
+ isa_ok($sth5, 'DBI::st');
+
+ cmp_ok($warn, '==', 1, '... we still only got one warning');
+
+ is($sth4, $sth5, '... fourth statement handle and fifth one match');
+ ok(!$sth4->{Active}, '... fourth statement handle is not Active');
+ ok(!$sth5->{Active}, '... fifth statement handle is not Active (shouldnt be its the same as fifth)');
+
+ cmp_ok(scalar(keys(%{$ck})), '==', 2, '... there are two CachedKids');
+ ok(eq_set(
+ [ values %{$ck} ],
+ [ $sth2, $sth5 ]
+ ),
+ '... second and fourth/fifth statment handles should be in the CachedKids');
+ }
+
+ SKIP: {
+ skip "swap_inner_handle() not supported under DBI::PurePerl", 23 if $DBI::PurePerl;
+
+ my $sth6 = $dbh->prepare($sql);
+ $sth6->execute(".");
+ my $sth1_driver_name = $sth1->{Database}{Driver}{Name};
+
+ ok( $sth6->{Active}, '... sixth statement handle is active');
+ ok(!$sth1->{Active}, '... first statement handle is not active');
+
+ ok($sth1->swap_inner_handle($sth6), '... first statement handle becomes the sixth');
+ ok(!$sth6->{Active}, '... sixth statement handle is now not active');
+ ok( $sth1->{Active}, '... first statement handle is now active again');
+
+ ok($sth1->swap_inner_handle($sth6), '... first statement handle becomes the sixth');
+ ok( $sth6->{Active}, '... sixth statement handle is active');
+ ok(!$sth1->{Active}, '... first statement handle is not active');
+
+ ok($sth1->swap_inner_handle($sth6), '... first statement handle becomes the sixth');
+ ok(!$sth6->{Active}, '... sixth statement handle is now not active');
+ ok( $sth1->{Active}, '... first statement handle is now active again');
+
+ $sth1->{PrintError} = 0;
+ ok(!$sth1->swap_inner_handle($dbh), '... can not swap a sth with a dbh');
+ cmp_ok( $sth1->errstr, 'eq', "Can't swap_inner_handle between sth and dbh");
+
+ ok($sth1->swap_inner_handle($sth6), '... first statement handle becomes the sixth');
+ ok( $sth6->{Active}, '... sixth statement handle is active');
+ ok(!$sth1->{Active}, '... first statement handle is not active');
+
+ $sth6->finish;
+
+ ok(my $dbh_nullp = DBI->connect("dbi:NullP:", undef, undef, { go_bypass => 1 }));
+ ok(my $sth7 = $dbh_nullp->prepare(""));
+
+ $sth1->{PrintError} = 0;
+ ok(!$sth1->swap_inner_handle($sth7), "... can't swap_inner_handle with handle from different parent");
+ cmp_ok( $sth1->errstr, 'eq', "Can't swap_inner_handle with handle from different parent");
+
+ cmp_ok( $sth1->{Database}{Driver}{Name}, 'eq', $sth1_driver_name );
+ ok( $sth1->swap_inner_handle($sth7,1), "... can swap to different parent if forced");
+ cmp_ok( $sth1->{Database}{Driver}{Name}, 'eq', "NullP" );
+
+ $dbh_nullp->disconnect;
+ }
+
+ ok( $dbh->ping, 'ping should be true before disconnect');
+ $dbh->disconnect;
+ $dbh->{PrintError} = 0; # silence 'not connected' warning
+ ok( !$dbh->ping, 'ping should be false after disconnect');
+
+ SKIP: {
+ skip "Kids and ActiveKids attributes not supported under DBI::PurePerl", 2 if $DBI::PurePerl;
+
+ cmp_ok($drh->{Kids}, '==', 1, '... our Driver has one Kid after disconnect');
+ cmp_ok($drh->{ActiveKids}, '==', 0, '... our Driver has no ActiveKids after disconnect');
+ }
+
+};
+
+if ($using_dbd_gofer) {
+ $drh->{CachedKids} = {};
+}
+
+# make sure our driver has no more kids after this test
+# NOTE:
+# this also assures us that the next test has an empty slate as well
+SKIP: {
+ skip "Kids attribute not supported under DBI::PurePerl", 1 if $DBI::PurePerl;
+
+ cmp_ok($drh->{Kids}, '==', 0, "... our $drh->{Name} driver should have 0 Kids after dbh was destoryed");
+}
+
+## ----------------------------------------------------------------------------
+# handle reference leak tests
+
+# NOTE:
+# this test checks for reference leaks by testing the Kids attribute
+# which is not supported by DBI::PurePerl, so we just do not run this
+# for DBI::PurePerl all together. Even though some of the tests would
+# pass, it does not make sense becuase in the end, what is actually
+# being tested for will give a false positive
+
+sub work {
+ my (%args) = @_;
+ my $dbh = DBI->connect("dbi:$driver:", '', '');
+ isa_ok( $dbh, 'DBI::db' );
+
+ cmp_ok($drh->{Kids}, '==', 1, '... the Driver should have 1 Kid(s) now');
+
+ if ( $args{Driver} ) {
+ isa_ok( $dbh->{Driver}, 'DBI::dr' );
+ } else {
+ pass( "not testing Driver here" );
+ }
+
+ my $sth = $dbh->prepare_cached("select name from ?");
+ isa_ok( $sth, 'DBI::st' );
+
+ if ( $args{Database} ) {
+ isa_ok( $sth->{Database}, 'DBI::db' );
+ } else {
+ pass( "not testing Database here" );
+ }
+
+ $dbh->disconnect;
+ # both handles should be freed here
+}
+
+SKIP: {
+ skip "Kids attribute not supported under DBI::PurePerl", 25 if $DBI::PurePerl;
+ skip "drh Kids not testable under DBD::Gofer", 25 if $using_dbd_gofer;
+
+ foreach my $args (
+ {},
+ { Driver => 1 },
+ { Database => 1 },
+ { Driver => 1, Database => 1 },
+ ) {
+ work( %{$args} );
+ cmp_ok($drh->{Kids}, '==', 0, '... the Driver should have no Kids');
+ }
+
+ # make sure we have no kids when we end this
+ cmp_ok($drh->{Kids}, '==', 0, '... the Driver should have no Kids at the end of this test');
+}
+
+## ----------------------------------------------------------------------------
+# handle take_imp_data test
+
+SKIP: {
+ skip "take_imp_data test not supported under DBD::Gofer", 19 if $using_dbd_gofer;
+
+ my $dbh = DBI->connect("dbi:$driver:", '', '');
+ isa_ok($dbh, "DBI::db");
+ my $drh = $dbh->{Driver}; # (re)get drh here so tests can work using_dbd_gofer
+
+ cmp_ok($drh->{Kids}, '==', 1, '... our Driver should have 1 Kid(s) here')
+ unless $DBI::PurePerl && pass();
+
+ $dbh->prepare("select name from ?"); # destroyed at once
+ my $sth2 = $dbh->prepare("select name from ?"); # inactive
+ my $sth3 = $dbh->prepare("select name from ?"); # active:
+ $sth3->execute(".");
+ is $sth3->{Active}, 1;
+ is $dbh->{ActiveKids}, 1
+ unless $DBI::PurePerl && pass();
+
+ my $ChildHandles = $dbh->{ChildHandles};
+
+ skip "take_imp_data test needs weakrefs", 15 if not $ChildHandles;
+
+ ok $ChildHandles, 'we need weakrefs for take_imp_data to work safely with child handles';
+ is @$ChildHandles, 3, 'should have 3 entries (implementation detail)';
+ is grep({ defined } @$ChildHandles), 2, 'should have 2 defined handles';
+
+ my $imp_data = $dbh->take_imp_data;
+ ok($imp_data, '... we got some imp_data to test');
+ # generally length($imp_data) = 112 for 32bit, 116 for 64 bit
+ # (as of DBI 1.37) but it can differ on some platforms
+ # depending on structure packing by the compiler
+ # so we just test that it's something reasonable:
+ cmp_ok(length($imp_data), '>=', 80, '... test that our imp_data is greater than or equal to 80, this is reasonable');
+
+ cmp_ok($drh->{Kids}, '==', 0, '... our Driver should have 0 Kid(s) after calling take_imp_data');
+
+ is ref $sth3, 'DBI::zombie', 'sth should be reblessed';
+ eval { $sth3->finish };
+ like $@, qr/Can't locate object method/;
+
+ {
+ my @warn;
+ local $SIG{__WARN__} = sub { push @warn, $_[0] if $_[0] =~ /after take_imp_data/; print "warn: @_\n"; };
+
+ my $drh = $dbh->{Driver};
+ ok(!defined $drh, '... our Driver should be undefined');
+
+ my $trace_level = $dbh->{TraceLevel};
+ ok(!defined $trace_level, '... our TraceLevel should be undefined');
+
+ ok(!defined $dbh->disconnect, '... disconnect should return undef');
+
+ ok(!defined $dbh->quote(42), '... quote should return undefined');
+
+ cmp_ok(scalar @warn, '==', 4, '... we should have gotten 4 warnings');
+ }
+
+ my $dbh2 = DBI->connect("dbi:$driver:", '', '', { dbi_imp_data => $imp_data });
+ isa_ok($dbh2, "DBI::db");
+ # need a way to test dbi_imp_data has been used
+
+ cmp_ok($drh->{Kids}, '==', 1, '... our Driver should have 1 Kid(s) again')
+ unless $DBI::PurePerl && pass();
+
+}
+
+# we need this SKIP block on its own since we are testing the
+# destruction of objects within the scope of the above SKIP
+# block
+SKIP: {
+ skip "Kids attribute not supported under DBI::PurePerl", 1 if $DBI::PurePerl;
+
+ cmp_ok($drh->{Kids}, '==', 0, '... our Driver has no Kids after this test');
+}
+
+## ----------------------------------------------------------------------------
+# NullP statement handle attributes without execute
+
+my $driver2 = "NullP";
+
+my $drh2 = DBI->install_driver($driver);
+isa_ok( $drh2, 'DBI::dr' );
+
+SKIP: {
+ skip "Kids attribute not supported under DBI::PurePerl", 1 if $DBI::PurePerl;
+
+ cmp_ok($drh2->{Kids}, '==', 0, '... our Driver (2) has no Kids before this test');
+}
+
+do {
+ my $dbh = DBI->connect("dbi:$driver2:", '', '');
+ isa_ok($dbh, "DBI::db");
+
+ my $sth = $dbh->prepare("foo bar");
+ isa_ok($sth, "DBI::st");
+
+ cmp_ok($sth->{NUM_OF_PARAMS}, '==', 0, '... NUM_OF_PARAMS is 0');
+ is($sth->{NUM_OF_FIELDS}, undef, '... NUM_OF_FIELDS should be undef');
+ is($sth->{Statement}, "foo bar", '... Statement is "foo bar"');
+
+ ok(!defined $sth->{NAME}, '... NAME is undefined');
+ ok(!defined $sth->{TYPE}, '... TYPE is undefined');
+ ok(!defined $sth->{SCALE}, '... SCALE is undefined');
+ ok(!defined $sth->{PRECISION}, '... PRECISION is undefined');
+ ok(!defined $sth->{NULLABLE}, '... NULLABLE is undefined');
+ ok(!defined $sth->{RowsInCache}, '... RowsInCache is undefined');
+ ok(!defined $sth->{ParamValues}, '... ParamValues is undefined');
+ # derived NAME attributes
+ ok(!defined $sth->{NAME_uc}, '... NAME_uc is undefined');
+ ok(!defined $sth->{NAME_lc}, '... NAME_lc is undefined');
+ ok(!defined $sth->{NAME_hash}, '... NAME_hash is undefined');
+ ok(!defined $sth->{NAME_uc_hash}, '... NAME_uc_hash is undefined');
+ ok(!defined $sth->{NAME_lc_hash}, '... NAME_lc_hash is undefined');
+
+ my $dbh_ref = ref($dbh);
+ my $sth_ref = ref($sth);
+
+ ok($dbh_ref->can("prepare"), '... $dbh can call "prepare"');
+ ok(!$dbh_ref->can("nonesuch"), '... $dbh cannot call "nonesuch"');
+ ok($sth_ref->can("execute"), '... $sth can call "execute"');
+
+ # what is this test for??
+
+ # I don't know why this warning has the "(perhaps ...)" suffix, it shouldn't:
+ # Can't locate object method "nonesuch" via package "DBI::db" (perhaps you forgot to load "DBI::db"?)
+ eval { ref($dbh)->nonesuch; };
+
+ $dbh->disconnect;
+};
+
+SKIP: {
+ skip "Kids attribute not supported under DBI::PurePerl", 1 if $DBI::PurePerl;
+
+ cmp_ok($drh2->{Kids}, '==', 0, '... our Driver (2) has no Kids after this test');
+}
+
+## ----------------------------------------------------------------------------
+
+1;
diff --git a/t/04mods.t b/t/04mods.t
new file mode 100644
index 0000000..97638d0
--- /dev/null
+++ b/t/04mods.t
@@ -0,0 +1,59 @@
+#!perl -w
+$|=1;
+
+use strict;
+
+use Test::More tests => 12;
+
+## ----------------------------------------------------------------------------
+## 04mods.t - ...
+## ----------------------------------------------------------------------------
+# Note:
+# the modules tested here are all marked as new and not guaranteed, so this if
+# they change, these will fail.
+## ----------------------------------------------------------------------------
+
+BEGIN {
+ use_ok( 'DBI' );
+
+ # load these first, since the other two load them
+ # and we want to catch the error first
+ use_ok( 'DBI::Const::GetInfo::ANSI' );
+ use_ok( 'DBI::Const::GetInfo::ODBC' );
+
+ use_ok( 'DBI::Const::GetInfoType', qw(%GetInfoType) );
+ use_ok( 'DBI::Const::GetInfoReturn', qw(%GetInfoReturnTypes %GetInfoReturnValues) );
+}
+
+## test GetInfoType
+
+cmp_ok(scalar(keys(%GetInfoType)), '>', 1, '... we have at least one key in the GetInfoType hash');
+
+is_deeply(
+ \%GetInfoType,
+ { %DBI::Const::GetInfo::ANSI::InfoTypes, %DBI::Const::GetInfo::ODBC::InfoTypes },
+ '... the GetInfoType hash is constructed from the ANSI and ODBC hashes'
+ );
+
+## test GetInfoReturnTypes
+
+cmp_ok(scalar(keys(%GetInfoReturnTypes)), '>', 1, '... we have at least one key in the GetInfoReturnType hash');
+
+is_deeply(
+ \%GetInfoReturnTypes,
+ { %DBI::Const::GetInfo::ANSI::ReturnTypes, %DBI::Const::GetInfo::ODBC::ReturnTypes },
+ '... the GetInfoReturnType hash is constructed from the ANSI and ODBC hashes'
+ );
+
+## test GetInfoReturnValues
+
+cmp_ok(scalar(keys(%GetInfoReturnValues)), '>', 1, '... we have at least one key in the GetInfoReturnValues hash');
+
+# ... testing GetInfoReturnValues any further would be difficult
+
+## test the two methods found in DBI::Const::GetInfoReturn
+
+can_ok('DBI::Const::GetInfoReturn', 'Format');
+can_ok('DBI::Const::GetInfoReturn', 'Explain');
+
+1;
diff --git a/t/05concathash.t b/t/05concathash.t
new file mode 100644
index 0000000..554fc34
--- /dev/null
+++ b/t/05concathash.t
@@ -0,0 +1,190 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl CatHash.t'
+
+#########################
+
+# change 'tests => 1' to 'tests => last_test_to_print';
+
+use strict;
+use Benchmark qw(:all);
+use Scalar::Util qw(looks_like_number);
+no warnings 'uninitialized';
+
+use Test::More tests => 41;
+
+BEGIN { use_ok('DBI') };
+
+# null and undefs -- segfaults?;
+is (DBI::_concat_hash_sorted(undef, "=", ":", 0, undef), undef);
+is (DBI::_concat_hash_sorted({ }, "=", ":", 0, undef), "");
+eval { DBI::_concat_hash_sorted([], "=", ":", 0, undef) };
+like ($@ || "", qr/is not a hash reference/);
+is (DBI::_concat_hash_sorted({ }, undef, ":", 0, undef), "");
+is (DBI::_concat_hash_sorted({ }, "=", undef, 0, undef), "");
+is (DBI::_concat_hash_sorted({ }, "=", ":", undef, undef),"");
+
+# simple cases
+is (DBI::_concat_hash_sorted({ 1=>"a", 2=>"b" }, "=", ", ", undef, undef), "1='a', 2='b'");
+# nul byte in key sep and pair sep
+# (nul byte in hash not supported)
+is DBI::_concat_hash_sorted({ 1=>"a", 2=>"b" }, "=\000=", ":\000:", undef, undef),
+ "1=\000='a':\000:2=\000='b'", 'should work with nul bytes in kv_sep and pair_sep';
+is DBI::_concat_hash_sorted({ 1=>"a\000a", 2=>"b" }, "=", ":", 1, undef),
+ "1='a.a':2='b'", 'should work with nul bytes in hash value (neat)';
+is DBI::_concat_hash_sorted({ 1=>"a\000a", 2=>"b" }, "=", ":", 0, undef),
+ "1='a\000a':2='b'", 'should work with nul bytes in hash value (not neat)';
+
+# Simple stress tests
+# limit stress when performing automated testing
+# eg http://www.nntp.perl.org/group/perl.cpan.testers/2009/06/msg4374116.html
+my $stress = $ENV{AUTOMATED_TESTING} ? 1_000 : 10_000;
+ok(DBI::_concat_hash_sorted({bob=>'two', fred=>'one' }, "="x$stress, ":", 1, undef));
+ok(DBI::_concat_hash_sorted({bob=>'two', fred=>'one' }, "=", ":"x$stress, 1, undef));
+ok(DBI::_concat_hash_sorted({map {$_=>undef} (1..1000)}, "="x$stress, ":", 1, undef));
+ok(DBI::_concat_hash_sorted({map {$_=>undef} (1..1000)}, "=", ":"x$stress, 1, undef), 'test');
+ok(DBI::_concat_hash_sorted({map {$_=>undef} (1..100)}, "="x$stress, ":"x$stress, 1, undef), 'test');
+
+my $simple_hash = {
+ bob=>"there",
+ jack=>12,
+ fred=>"there",
+ norman=>"there",
+ # sam =>undef
+};
+
+my $simple_numeric = {
+ 1=>"there",
+ 2=>"there",
+ 16 => 'yo',
+ 07 => "buddy",
+ 49 => undef,
+};
+
+my $simple_mixed = {
+ bob=>"there",
+ jack=>12,
+ fred=>"there",
+ sam =>undef,
+ 1=>"there",
+ 32=>"there",
+ 16 => 'yo',
+ 07 => "buddy",
+ 49 => undef,
+};
+
+my $simple_float = {
+ 1.12 =>"there",
+ 3.1415926 =>"there",
+ 32=>"there",
+ 1.6 => 'yo',
+ 0.78 => "buddy",
+ 49 => undef,
+};
+
+#eval {
+# DBI::_concat_hash_sorted($simple_hash, "=",,":",1,12);
+#};
+ok(1," Unknown sort order");
+#like ($@, qr/Unknown sort order/, "Unknown sort order");
+
+
+
+## Loopify and Add Neat
+
+
+my %neats = (
+ "Neat"=>0,
+ "Not Neat"=> 1
+);
+my %sort_types = (
+ guess=>undef,
+ numeric => 1,
+ lexical=> 0
+);
+my %hashes = (
+ Numeric=>$simple_numeric,
+ "Simple Hash" => $simple_hash,
+ "Mixed Hash" => $simple_mixed,
+ "Float Hash" => $simple_float
+);
+
+for my $sort_type (keys %sort_types){
+ for my $neat (keys %neats) {
+ for my $hash (keys %hashes) {
+ test_concat_hash($hash, $neat, $sort_type);
+ }
+ }
+}
+
+sub test_concat_hash {
+ my ($hash, $neat, $sort_type) = @_;
+ my @args = ($hashes{$hash}, "=", ":",$neats{$neat}, $sort_types{$sort_type});
+ is (
+ DBI::_concat_hash_sorted(@args),
+ _concat_hash_sorted(@args),
+ "$hash - $neat $sort_type"
+ );
+}
+
+if (0) {
+ eval {
+ cmpthese(200_000, {
+ Perl => sub {_concat_hash_sorted($simple_hash, "=", ":",0,undef); },
+ C=> sub {DBI::_concat_hash_sorted($simple_hash, "=", ":",0,1);}
+ });
+
+ print "\n";
+ cmpthese(200_000, {
+ NotNeat => sub {DBI::_concat_hash_sorted(
+ $simple_hash, "=", ":",1,undef);
+ },
+ Neat => sub {DBI::_concat_hash_sorted(
+ $simple_hash, "=", ":",0,undef);
+ }
+ });
+ };
+}
+#CatHash::_concat_hash_values({ }, ":-",,"::",1,1);
+
+
+sub _concat_hash_sorted {
+ my ( $hash_ref, $kv_separator, $pair_separator, $use_neat, $num_sort ) = @_;
+ # $num_sort: 0=lexical, 1=numeric, undef=try to guess
+
+ return undef unless defined $hash_ref;
+ die "hash is not a hash reference" unless ref $hash_ref eq 'HASH';
+ my $keys = _get_sorted_hash_keys($hash_ref, $num_sort);
+ my $string = '';
+ for my $key (@$keys) {
+ $string .= $pair_separator if length $string > 0;
+ my $value = $hash_ref->{$key};
+ if ($use_neat) {
+ $value = DBI::neat($value, 0);
+ }
+ else {
+ $value = (defined $value) ? "'$value'" : 'undef';
+ }
+ $string .= $key . $kv_separator . $value;
+ }
+ return $string;
+}
+
+sub _get_sorted_hash_keys {
+ my ($hash_ref, $sort_type) = @_;
+ if (not defined $sort_type) {
+ my $sort_guess = 1;
+ $sort_guess = (not looks_like_number($_)) ? 0 : $sort_guess
+ for keys %$hash_ref;
+ $sort_type = $sort_guess;
+ }
+
+ my @keys = keys %$hash_ref;
+ no warnings 'numeric';
+ my @sorted = ($sort_type)
+ ? sort { $a <=> $b or $a cmp $b } @keys
+ : sort @keys;
+ #warn "$sort_type = @sorted\n";
+ return \@sorted;
+}
+
+1;
diff --git a/t/06attrs.t b/t/06attrs.t
new file mode 100644
index 0000000..89ba7c1
--- /dev/null
+++ b/t/06attrs.t
@@ -0,0 +1,311 @@
+#!perl -w
+
+use strict;
+
+use Test::More tests => 148;
+
+## ----------------------------------------------------------------------------
+## 06attrs.t - ...
+## ----------------------------------------------------------------------------
+# This test checks the parameters and the values associated with them for
+# the three different handles (Driver, Database, Statement)
+## ----------------------------------------------------------------------------
+
+BEGIN {
+ use_ok( 'DBI' )
+}
+
+$|=1;
+
+my $using_autoproxy = ($ENV{DBI_AUTOPROXY});
+my $dsn = 'dbi:ExampleP:dummy';
+
+# Connect to the example driver.
+my $dbh = DBI->connect($dsn, '', '', {
+ PrintError => 0, RaiseError => 1,
+});
+
+isa_ok( $dbh, 'DBI::db' );
+
+# Clean up when we're done.
+END { $dbh->disconnect if $dbh };
+
+## ----------------------------------------------------------------------------
+# Check the database handle attributes.
+
+# bit flag attr
+ok( $dbh->{Warn}, '... checking Warn attribute for dbh');
+ok( $dbh->{Active}, '... checking Active attribute for dbh');
+ok( $dbh->{AutoCommit}, '... checking AutoCommit attribute for dbh');
+ok(!$dbh->{CompatMode}, '... checking CompatMode attribute for dbh');
+ok(!$dbh->{InactiveDestroy}, '... checking InactiveDestory attribute for dbh');
+ok(!$dbh->{AutoInactiveDestroy}, '... checking AutoInactiveDestory attribute for dbh');
+ok(!$dbh->{PrintError}, '... checking PrintError attribute for dbh');
+ok( $dbh->{PrintWarn}, '... checking PrintWarn attribute for dbh'); # true because of perl -w above
+ok( $dbh->{RaiseError}, '... checking RaiseError attribute for dbh');
+ok(!$dbh->{ShowErrorStatement}, '... checking ShowErrorStatement attribute for dbh');
+ok(!$dbh->{ChopBlanks}, '... checking ChopBlanks attribute for dbh');
+ok(!$dbh->{LongTruncOk}, '... checking LongTrunkOk attribute for dbh');
+ok(!$dbh->{TaintIn}, '... checking TaintIn attribute for dbh');
+ok(!$dbh->{TaintOut}, '... checking TaintOut attribute for dbh');
+ok(!$dbh->{Taint}, '... checking Taint attribute for dbh');
+ok(!$dbh->{Executed}, '... checking Executed attribute for dbh');
+
+# other attr
+cmp_ok($dbh->{ErrCount}, '==', 0, '... checking ErrCount attribute for dbh');
+
+SKIP: {
+ skip "Kids and ActiveKids attribute not supported under DBI::PurePerl", 2 if $DBI::PurePerl;
+
+ cmp_ok($dbh->{Kids}, '==', 0, '... checking Kids attribute for dbh');;
+ cmp_ok($dbh->{ActiveKids}, '==', 0, '... checking ActiveKids attribute for dbh');;
+}
+
+is($dbh->{CachedKids}, undef, '... checking CachedKids attribute for dbh');
+ok(!defined $dbh->{HandleError}, '... checking HandleError attribute for dbh');
+ok(!defined $dbh->{Profile}, '... checking Profile attribute for dbh');
+ok(!defined $dbh->{Statement}, '... checking Statement attribute for dbh');
+ok(!defined $dbh->{RowCacheSize}, '... checking RowCacheSize attribute for dbh');
+ok(!defined $dbh->{ReadOnly}, '... checking ReadOnly attribute for dbh');
+
+is($dbh->{FetchHashKeyName}, 'NAME', '... checking FetchHashKeyName attribute for dbh');
+is($dbh->{Name}, 'dummy', '... checking Name attribute for dbh') # fails for Multiplex
+ unless $using_autoproxy && ok(1);
+
+cmp_ok($dbh->{TraceLevel}, '==', $DBI::dbi_debug & 0xF, '... checking TraceLevel attribute for dbh');
+cmp_ok($dbh->{LongReadLen}, '==', 80, '... checking LongReadLen attribute for dbh');
+
+is_deeply [ $dbh->FETCH_many(qw(HandleError FetchHashKeyName LongReadLen ErrCount)) ],
+ [ undef, qw(NAME 80 0) ], 'should be able to FETCH_many';
+
+is $dbh->{examplep_private_dbh_attrib}, 42, 'should see driver-private dbh attribute value';
+
+# Raise an error.
+eval {
+ $dbh->do('select foo from foo')
+};
+like($@, qr/^DBD::\w+::db do failed: Unknown field names: foo/ , '... catching exception');
+
+ok(defined $dbh->err, '... $dbh->err is undefined');
+like($dbh->errstr, qr/^Unknown field names: foo\b/, '... checking $dbh->errstr');
+
+is($dbh->state, 'S1000', '... checking $dbh->state');
+
+ok($dbh->{Executed}, '... checking Executed attribute for dbh'); # even though it failed
+$dbh->{Executed} = 0; # reset(able)
+cmp_ok($dbh->{Executed}, '==', 0, '... checking Executed attribute for dbh (after reset)');
+
+cmp_ok($dbh->{ErrCount}, '==', 1, '... checking ErrCount attribute for dbh (after error was generated)');
+
+## ----------------------------------------------------------------------------
+# Test the driver handle attributes.
+
+my $drh = $dbh->{Driver};
+isa_ok( $drh, 'DBI::dr' );
+
+ok($dbh->err, '... checking $dbh->err');
+
+cmp_ok($drh->{ErrCount}, '==', 0, '... checking ErrCount attribute for drh');
+
+ok( $drh->{Warn}, '... checking Warn attribute for drh');
+ok( $drh->{Active}, '... checking Active attribute for drh');
+ok( $drh->{AutoCommit}, '... checking AutoCommit attribute for drh');
+ok(!$drh->{CompatMode}, '... checking CompatMode attribute for drh');
+ok(!$drh->{InactiveDestroy}, '... checking InactiveDestory attribute for drh');
+ok(!$drh->{AutoInactiveDestroy}, '... checking AutoInactiveDestory attribute for drh');
+ok(!$drh->{PrintError}, '... checking PrintError attribute for drh');
+ok( $drh->{PrintWarn}, '... checking PrintWarn attribute for drh'); # true because of perl -w above
+ok(!$drh->{RaiseError}, '... checking RaiseError attribute for drh');
+ok(!$drh->{ShowErrorStatement}, '... checking ShowErrorStatement attribute for drh');
+ok(!$drh->{ChopBlanks}, '... checking ChopBlanks attribute for drh');
+ok(!$drh->{LongTruncOk}, '... checking LongTrunkOk attribute for drh');
+ok(!$drh->{TaintIn}, '... checking TaintIn attribute for drh');
+ok(!$drh->{TaintOut}, '... checking TaintOut attribute for drh');
+ok(!$drh->{Taint}, '... checking Taint attribute for drh');
+
+SKIP: {
+ skip "Executed attribute not supported under DBI::PurePerl", 1 if $DBI::PurePerl;
+
+ ok($drh->{Executed}, '... checking Executed attribute for drh') # due to the do() above
+}
+
+SKIP: {
+ skip "Kids and ActiveKids attribute not supported under DBI::PurePerl", 2 if ($DBI::PurePerl or $dbh->{mx_handle_list});
+ cmp_ok($drh->{Kids}, '==', 1, '... checking Kids attribute for drh');
+ cmp_ok($drh->{ActiveKids}, '==', 1, '... checking ActiveKids attribute for drh');
+}
+
+is($drh->{CachedKids}, undef, '... checking CachedKids attribute for drh');
+ok(!defined $drh->{HandleError}, '... checking HandleError attribute for drh');
+ok(!defined $drh->{Profile}, '... checking Profile attribute for drh');
+ok(!defined $drh->{ReadOnly}, '... checking ReadOnly attribute for drh');
+
+cmp_ok($drh->{TraceLevel}, '==', $DBI::dbi_debug & 0xF, '... checking TraceLevel attribute for drh');
+cmp_ok($drh->{LongReadLen}, '==', 80, '... checking LongReadLen attribute for drh');
+
+is($drh->{FetchHashKeyName}, 'NAME', '... checking FetchHashKeyName attribute for drh');
+is($drh->{Name}, 'ExampleP', '... checking Name attribute for drh')
+ unless $using_autoproxy && ok(1);
+
+## ----------------------------------------------------------------------------
+# Test the statement handle attributes.
+
+# Create a statement handle.
+my $sth = $dbh->prepare("select ctime, name from ?");
+isa_ok($sth, "DBI::st");
+
+ok(!$sth->{Executed}, '... checking Executed attribute for sth');
+ok(!$dbh->{Executed}, '... checking Executed attribute for dbh');
+cmp_ok($sth->{ErrCount}, '==', 0, '... checking ErrCount attribute for sth');
+
+# Trigger an exception.
+eval {
+ $sth->execute("foo")
+};
+# we don't check actual opendir error msg because of locale differences
+like($@, qr/^DBD::\w+::st execute failed: .*opendir\(foo\): /msi, '... checking exception');
+
+# Test all of the statement handle attributes.
+like($sth->errstr, qr/opendir\(foo\): /, '... checking $sth->errstr');
+is($sth->state, 'S1000', '... checking $sth->state');
+ok($sth->{Executed}, '... checking Executed attribute for sth'); # even though it failed
+ok($dbh->{Executed}, '... checking Exceuted attribute for dbh'); # due to $sth->prepare, even though it failed
+
+cmp_ok($sth->{ErrCount}, '==', 1, '... checking ErrCount attribute for sth');
+eval {
+ $sth->{ErrCount} = 42
+};
+like($@, qr/STORE failed:/, '... checking exception');
+
+cmp_ok($sth->{ErrCount}, '==', 42 , '... checking ErrCount attribute for sth (after assignment)');
+
+$sth->{ErrCount} = 0;
+cmp_ok($sth->{ErrCount}, '==', 0, '... checking ErrCount attribute for sth (after reset)');
+
+# booleans
+ok( $sth->{Warn}, '... checking Warn attribute for sth');
+ok(!$sth->{Active}, '... checking Active attribute for sth');
+ok(!$sth->{CompatMode}, '... checking CompatMode attribute for sth');
+ok(!$sth->{InactiveDestroy}, '... checking InactiveDestroy attribute for sth');
+ok(!$sth->{AutoInactiveDestroy}, '... checking AutoInactiveDestroy attribute for sth');
+ok(!$sth->{PrintError}, '... checking PrintError attribute for sth');
+ok( $sth->{PrintWarn}, '... checking PrintWarn attribute for sth');
+ok( $sth->{RaiseError}, '... checking RaiseError attribute for sth');
+ok(!$sth->{ShowErrorStatement}, '... checking ShowErrorStatement attribute for sth');
+ok(!$sth->{ChopBlanks}, '... checking ChopBlanks attribute for sth');
+ok(!$sth->{LongTruncOk}, '... checking LongTrunkOk attribute for sth');
+ok(!$sth->{TaintIn}, '... checking TaintIn attribute for sth');
+ok(!$sth->{TaintOut}, '... checking TaintOut attribute for sth');
+ok(!$sth->{Taint}, '... checking Taint attribute for sth');
+
+# common attr
+SKIP: {
+ skip "Kids and ActiveKids attribute not supported under DBI::PurePerl", 2 if $DBI::PurePerl;
+ cmp_ok($sth->{Kids}, '==', 0, '... checking Kids attribute for sth');
+ cmp_ok($sth->{ActiveKids}, '==', 0, '... checking ActiveKids attribute for sth');
+}
+
+ok(!defined $sth->{CachedKids}, '... checking CachedKids attribute for sth');
+ok(!defined $sth->{HandleError}, '... checking HandleError attribute for sth');
+ok(!defined $sth->{Profile}, '... checking Profile attribute for sth');
+ok(!defined $sth->{ReadOnly}, '... checking ReadOnly attribute for sth');
+
+cmp_ok($sth->{TraceLevel}, '==', $DBI::dbi_debug & 0xF, '... checking TraceLevel attribute for sth');
+cmp_ok($sth->{LongReadLen}, '==', 80, '... checking LongReadLen attribute for sth');
+
+is($sth->{FetchHashKeyName}, 'NAME', '... checking FetchHashKeyName attribute for sth');
+
+# sth specific attr
+ok(!defined $sth->{CursorName}, '... checking CursorName attribute for sth');
+
+cmp_ok($sth->{NUM_OF_FIELDS}, '==', 2, '... checking NUM_OF_FIELDS attribute for sth');
+cmp_ok($sth->{NUM_OF_PARAMS}, '==', 1, '... checking NUM_OF_PARAMS attribute for sth');
+
+my $name = $sth->{NAME};
+is(ref($name), 'ARRAY', '... checking type of NAME attribute for sth');
+cmp_ok(scalar(@{$name}), '==', 2, '... checking number of elements returned');
+is_deeply($name, ['ctime', 'name' ], '... checking values returned');
+
+my $name_lc = $sth->{NAME_lc};
+is(ref($name_lc), 'ARRAY', '... checking type of NAME_lc attribute for sth');
+cmp_ok(scalar(@{$name_lc}), '==', 2, '... checking number of elements returned');
+is_deeply($name_lc, ['ctime', 'name' ], '... checking values returned');
+
+my $name_uc = $sth->{NAME_uc};
+is(ref($name_uc), 'ARRAY', '... checking type of NAME_uc attribute for sth');
+cmp_ok(scalar(@{$name_uc}), '==', 2, '... checking number of elements returned');
+is_deeply($name_uc, ['CTIME', 'NAME' ], '... checking values returned');
+
+my $nhash = $sth->{NAME_hash};
+is(ref($nhash), 'HASH', '... checking type of NAME_hash attribute for sth');
+cmp_ok(scalar(keys(%{$nhash})), '==', 2, '... checking number of keys returned');
+cmp_ok($nhash->{ctime}, '==', 0, '... checking values returned');
+cmp_ok($nhash->{name}, '==', 1, '... checking values returned');
+
+my $nhash_lc = $sth->{NAME_lc_hash};
+is(ref($nhash_lc), 'HASH', '... checking type of NAME_lc_hash attribute for sth');
+cmp_ok(scalar(keys(%{$nhash_lc})), '==', 2, '... checking number of keys returned');
+cmp_ok($nhash_lc->{ctime}, '==', 0, '... checking values returned');
+cmp_ok($nhash_lc->{name}, '==', 1, '... checking values returned');
+
+my $nhash_uc = $sth->{NAME_uc_hash};
+is(ref($nhash_uc), 'HASH', '... checking type of NAME_uc_hash attribute for sth');
+cmp_ok(scalar(keys(%{$nhash_uc})), '==', 2, '... checking number of keys returned');
+cmp_ok($nhash_uc->{CTIME}, '==', 0, '... checking values returned');
+cmp_ok($nhash_uc->{NAME}, '==', 1, '... checking values returned');
+
+my $type = $sth->{TYPE};
+is(ref($type), 'ARRAY', '... checking type of TYPE attribute for sth');
+cmp_ok(scalar(@{$type}), '==', 2, '... checking number of elements returned');
+is_deeply($type, [ 4, 12 ], '... checking values returned');
+
+my $null = $sth->{NULLABLE};
+is(ref($null), 'ARRAY', '... checking type of NULLABLE attribute for sth');
+cmp_ok(scalar(@{$null}), '==', 2, '... checking number of elements returned');
+is_deeply($null, [ 0, 0 ], '... checking values returned');
+
+# Should these work? They don't.
+my $prec = $sth->{PRECISION};
+is(ref($prec), 'ARRAY', '... checking type of PRECISION attribute for sth');
+cmp_ok(scalar(@{$prec}), '==', 2, '... checking number of elements returned');
+is_deeply($prec, [ 10, 1024 ], '... checking values returned');
+
+my $scale = $sth->{SCALE};
+is(ref($scale), 'ARRAY', '... checking type of SCALE attribute for sth');
+cmp_ok(scalar(@{$scale}), '==', 2, '... checking number of elements returned');
+is_deeply($scale, [ 0, 0 ], '... checking values returned');
+
+my $params = $sth->{ParamValues};
+is(ref($params), 'HASH', '... checking type of ParamValues attribute for sth');
+is($params->{1}, 'foo', '... checking values returned');
+
+is($sth->{Statement}, "select ctime, name from ?", '... checking Statement attribute for sth');
+ok(!defined $sth->{RowsInCache}, '... checking type of RowsInCache attribute for sth');
+
+is $sth->{examplep_private_sth_attrib}, 24, 'should see driver-private sth attribute value';
+
+# $h->{TraceLevel} tests are in t/09trace.t
+
+note "Checking inheritance\n";
+
+SKIP: {
+ skip "drh->dbh->sth inheritance test skipped with DBI_AUTOPROXY", 2 if $ENV{DBI_AUTOPROXY};
+
+sub check_inherited {
+ my ($drh, $attr, $value, $skip_sth) = @_;
+ local $drh->{$attr} = $value;
+ local $drh->{PrintError} = 1;
+ my $dbh = $drh->connect("dummy");
+ is $dbh->{$attr}, $drh->{$attr}, "dbh $attr value should be inherited from drh";
+ unless ($skip_sth) {
+ my $sth = $dbh->prepare("select name from .");
+ is $sth->{$attr}, $dbh->{$attr}, "sth $attr value should be inherited from dbh";
+ }
+}
+
+check_inherited($drh, "ReadOnly", 1, 0);
+
+}
+
+1;
+# end
diff --git a/t/07kids.t b/t/07kids.t
new file mode 100644
index 0000000..8364ad2
--- /dev/null
+++ b/t/07kids.t
@@ -0,0 +1,102 @@
+#!perl -w
+$|=1;
+
+use strict;
+
+use Test::More;
+
+use DBI 1.50; # also tests Exporter::require_version
+
+BEGIN {
+ plan skip_all => '$h->{Kids} attribute not supported for DBI::PurePerl'
+ if $DBI::PurePerl && $DBI::PurePerl; # doubled to avoid typo warning
+ plan tests => 20;
+}
+
+## ----------------------------------------------------------------------------
+## 07kids.t
+## ----------------------------------------------------------------------------
+# This test check the Kids and the ActiveKids attributes and how they act
+# in various situations.
+#
+# Check the database handle's kids:
+# - upon creation of handle
+# - upon creation of statement handle
+# - after execute of statement handle
+# - after finish of statement handle
+# - after destruction of statement handle
+# Check the driver handle's kids:
+# - after creation of database handle
+# - after disconnection of database handle
+# - after destruction of database handle
+## ----------------------------------------------------------------------------
+
+
+# Connect to the example driver and create a database handle
+my $dbh = DBI->connect('dbi:ExampleP:dummy', '', '',
+ {
+ PrintError => 1,
+ RaiseError => 0
+ });
+
+# check our database handle to make sure its good
+isa_ok($dbh, 'DBI::db');
+
+# check that it has no Kids or ActiveKids yet
+cmp_ok($dbh->{Kids}, '==', 0, '... database handle has 0 Kid(s) at start');
+cmp_ok($dbh->{ActiveKids}, '==', 0, '... database handle has 0 ActiveKid(s) at start');
+
+# create a scope for our $sth to live and die in
+do {
+
+ # create a statement handle
+ my $sth = $dbh->prepare('select uid from ./');
+
+ # verify that it is a correct statement handle
+ isa_ok($sth, "DBI::st");
+
+ # check our Kids and ActiveKids after prepare
+ cmp_ok($dbh->{Kids}, '==', 1, '... database handle has 1 Kid(s) after $dbh->prepare');
+ cmp_ok($dbh->{ActiveKids}, '==', 0, '... database handle has 0 ActiveKid(s) after $dbh->prepare');
+
+ $sth->execute();
+
+ # check our Kids and ActiveKids after execute
+ cmp_ok($dbh->{Kids}, '==', 1, '... database handle has 1 Kid(s) after $sth->execute');
+ cmp_ok($dbh->{ActiveKids}, '==', 1, '... database handle has 1 ActiveKid(s) after $sth->execute');
+
+ $sth->finish();
+
+ # check our Kids and Activekids after finish
+ cmp_ok($dbh->{Kids}, '==', 1, '... database handle has 1 Kid(s) after $sth->finish');
+ cmp_ok($dbh->{ActiveKids}, '==', 0, '... database handle has 0 ActiveKid(s) after $sth->finish');
+
+};
+
+# now check it after the statement handle has been destroyed
+cmp_ok($dbh->{Kids}, '==', 0, '... database handle has 0 Kid(s) after $sth is destroyed');
+cmp_ok($dbh->{ActiveKids}, '==', 0, '... database handle has 0 ActiveKid(s) after $sth is destroyed');
+
+# get the database handles driver Driver
+my $drh = $dbh->{Driver};
+
+# check that is it a correct driver handle
+isa_ok($drh, "DBI::dr");
+
+# check the driver's Kids and ActiveKids
+cmp_ok( $drh->{Kids}, '==', 1, '... driver handle has 1 Kid(s)');
+cmp_ok( $drh->{ActiveKids}, '==', 1, '... driver handle has 1 ActiveKid(s)');
+
+$dbh->disconnect;
+
+# check the driver's Kids and ActiveKids after $dbh->disconnect
+cmp_ok( $drh->{Kids}, '==', 1, '... driver handle has 1 Kid(s) after $dbh->disconnect');
+cmp_ok( $drh->{ActiveKids}, '==', 0, '... driver handle has 0 ActiveKid(s) after $dbh->disconnect');
+
+undef $dbh;
+ok(!defined($dbh), '... lets be sure that $dbh is not undefined');
+
+# check the driver's Kids and ActiveKids after undef $dbh
+cmp_ok( $drh->{Kids}, '==', 0, '... driver handle has 0 Kid(s) after undef $dbh');
+cmp_ok( $drh->{ActiveKids}, '==', 0, '... driver handle has 0 ActiveKid(s) after undef $dbh');
+
diff --git a/t/08keeperr.t b/t/08keeperr.t
new file mode 100644
index 0000000..617a81d
--- /dev/null
+++ b/t/08keeperr.t
@@ -0,0 +1,291 @@
+#!perl -w
+
+use strict;
+
+use Test::More tests => 79;
+
+## ----------------------------------------------------------------------------
+## 08keeperr.t
+## ----------------------------------------------------------------------------
+#
+## ----------------------------------------------------------------------------
+
+BEGIN {
+ use_ok('DBI');
+}
+
+$|=1;
+$^W=1;
+
+## ----------------------------------------------------------------------------
+# subclass DBI
+
+# DBI subclass
+package My::DBI;
+use base 'DBI';
+
+# Database handle subclass
+package My::DBI::db;
+use base 'DBI::db';
+
+# Statement handle subclass
+package My::DBI::st;
+use base 'DBI::st';
+
+sub execute {
+ my $sth = shift;
+ # we localize an attribute here to check that the correpoding STORE
+ # at scope exit doesn't clear any recorded error
+ local $sth->{Warn} = 0;
+ my $rv = $sth->SUPER::execute(@_);
+ return $rv;
+}
+
+
+## ----------------------------------------------------------------------------
+# subclass the subclass of DBI
+
+package Test;
+
+use strict;
+use base 'My::DBI';
+
+use DBI;
+
+my @con_info = ('dbi:ExampleP:.', undef, undef, { PrintError => 0, RaiseError => 1 });
+
+sub test_select {
+ my $dbh = shift;
+ eval { $dbh->selectrow_arrayref('select * from foo') };
+ $dbh->disconnect;
+ return $@;
+}
+
+my $err1 = test_select( My::DBI->connect(@con_info) );
+Test::More::like($err1, qr/^DBD::(ExampleP|Multiplex|Gofer)::db selectrow_arrayref failed: opendir/, '... checking error');
+
+my $err2 = test_select( DBI->connect(@con_info) );
+Test::More::like($err2, qr/^DBD::(ExampleP|Multiplex|Gofer)::db selectrow_arrayref failed: opendir/, '... checking error');
+
+package main;
+
+# test ping does not destroy the errstr
+sub ping_keeps_err {
+ my $dbh = DBI->connect('DBI:ExampleP:', undef, undef, { PrintError => 0 });
+
+ $dbh->set_err(42, "ERROR 42");
+ is $dbh->err, 42;
+ is $dbh->errstr, "ERROR 42";
+ ok $dbh->ping, "ping returns true";
+ is $dbh->err, 42, "err unchanged after ping";
+ is $dbh->errstr, "ERROR 42", "errstr unchanged after ping";
+
+ $dbh->disconnect;
+
+ $dbh->set_err(42, "ERROR 42");
+ is $dbh->err, 42, "err unchanged after ping";
+ is $dbh->errstr, "ERROR 42", "errstr unchanged after ping";
+ ok !$dbh->ping, "ping returns false";
+ # it's reasonable for ping() to set err/errstr if it fails
+ # so here we just test that there is an error
+ ok $dbh->err, "err true after failed ping";
+ ok $dbh->errstr, "errstr true after failed ping";
+}
+
+## ----------------------------------------------------------------------------
+print "Test HandleSetErr\n";
+
+my $dbh = DBI->connect(@con_info);
+isa_ok($dbh, "DBI::db");
+
+$dbh->{RaiseError} = 1;
+$dbh->{PrintError} = 1;
+$dbh->{PrintWarn} = 1;
+
+# warning handler
+my %warn = ( failed => 0, warning => 0 );
+my @handlewarn = (0,0,0);
+$SIG{__WARN__} = sub {
+ my $msg = shift;
+ if ($msg =~ /^DBD::\w+::\S+\s+(\S+)\s+(\w+)/) {
+ ++$warn{$2};
+ $msg =~ s/\n/\\n/g;
+ print "warn: '$msg'\n";
+ return;
+ }
+ warn $msg;
+};
+
+# HandleSetErr handler
+$dbh->{HandleSetErr} = sub {
+ my ($h, $err, $errstr, $state) = @_;
+ return 0
+ unless defined $err;
+ ++$handlewarn[ $err ? 2 : length($err) ]; # count [info, warn, err] calls
+ return 1
+ if $state && $state eq "return"; # for tests
+ ($_[1], $_[2], $_[3]) = (99, "errstr99", "OV123")
+ if $state && $state eq "override"; # for tests
+ return 0
+ if $err; # be transparent for errors
+ local $^W;
+ print "HandleSetErr called: h=$h, err=$err, errstr=$errstr, state=$state\n";
+ return 0;
+};
+
+# start our tests
+
+ok(!defined $DBI::err, '... $DBI::err is not defined');
+
+# ----
+
+$dbh->set_err("", "(got info)");
+
+ok(defined $DBI::err, '... $DBI::err is defined'); # true
+is($DBI::err, "", '... $DBI::err is an empty string');
+is($DBI::errstr, "(got info)", '... $DBI::errstr is as we expected');
+is($dbh->errstr, "(got info)", '... $dbh->errstr matches $DBI::errstr');
+cmp_ok($warn{failed}, '==', 0, '... $warn{failed} is 0');
+cmp_ok($warn{warning}, '==', 0, '... $warn{warning} is 0');
+is_deeply(\@handlewarn, [ 1, 0, 0 ], '... the @handlewarn array is (1, 0, 0)');
+
+# ----
+
+$dbh->set_err(0, "(got warn)", "AA001"); # triggers PrintWarn
+
+ok(defined $DBI::err, '... $DBI::err is defined');
+is($DBI::err, "0", '... $DBI::err is "0"');
+is($DBI::errstr, "(got info)\n(got warn)",
+ '... $DBI::errstr is as we expected');
+is($dbh->errstr, "(got info)\n(got warn)",
+ '... $dbh->errstr matches $DBI::errstr');
+is($DBI::state, "AA001", '... $DBI::state is AA001');
+cmp_ok($warn{warning}, '==', 1, '... $warn{warning} is 1');
+is_deeply(\@handlewarn, [ 1, 1, 0 ], '... the @handlewarn array is (1, 1, 0)');
+
+
+# ----
+
+$dbh->set_err("", "(got more info)"); # triggers PrintWarn
+
+ok(defined $DBI::err, '... $DBI::err is defined');
+is($DBI::err, "0", '... $DBI::err is "0"'); # not "", ie it's still a warn
+is($dbh->err, "0", '... $dbh->err is "0"');
+is($DBI::state, "AA001", '... $DBI::state is AA001');
+is($DBI::errstr, "(got info)\n(got warn)\n(got more info)",
+ '... $DBI::errstr is as we expected');
+is($dbh->errstr, "(got info)\n(got warn)\n(got more info)",
+ '... $dbh->errstr matches $DBI::errstr');
+cmp_ok($warn{warning}, '==', 2, '... $warn{warning} is 2');
+is_deeply(\@handlewarn, [ 2, 1, 0 ], '... the @handlewarn array is (2, 1, 0)');
+
+
+# ----
+
+$dbh->{RaiseError} = 0;
+$dbh->{PrintError} = 1;
+
+# ----
+
+$dbh->set_err("42", "(got error)", "AA002");
+
+ok(defined $DBI::err, '... $DBI::err is defined');
+cmp_ok($DBI::err, '==', 42, '... $DBI::err is 42');
+cmp_ok($warn{warning}, '==', 2, '... $warn{warning} is 2');
+is($dbh->errstr, "(got info)\n(got warn)\n(got more info) [state was AA001 now AA002]\n(got error)",
+ '... $dbh->errstr is as we expected');
+is($DBI::state, "AA002", '... $DBI::state is AA002');
+is_deeply(\@handlewarn, [ 2, 1, 1 ], '... the @handlewarn array is (2, 1, 1)');
+
+# ----
+
+$dbh->set_err("", "(got info)");
+
+ok(defined $DBI::err, '... $DBI::err is defined');
+cmp_ok($DBI::err, '==', 42, '... $DBI::err is 42');
+cmp_ok($warn{warning}, '==', 2, '... $warn{warning} is 2');
+is($dbh->errstr, "(got info)\n(got warn)\n(got more info) [state was AA001 now AA002]\n(got error)\n(got info)",
+ '... $dbh->errstr is as we expected');
+is_deeply(\@handlewarn, [ 3, 1, 1 ], '... the @handlewarn array is (3, 1, 1)');
+
+# ----
+
+$dbh->set_err("0", "(got warn)"); # no PrintWarn because it's already an err
+
+ok(defined $DBI::err, '... $DBI::err is defined');
+cmp_ok($DBI::err, '==', 42, '... $DBI::err is 42');
+cmp_ok($warn{warning}, '==', 2, '... $warn{warning} is 2');
+is($dbh->errstr, "(got info)\n(got warn)\n(got more info) [state was AA001 now AA002]\n(got error)\n(got info)\n(got warn)",
+ '... $dbh->errstr is as we expected');
+is_deeply(\@handlewarn, [ 3, 2, 1 ], '... the @handlewarn array is (3, 2, 1)');
+
+# ----
+
+$dbh->set_err("4200", "(got new error)", "AA003");
+
+ok(defined $DBI::err, '... $DBI::err is defined');
+cmp_ok($DBI::err, '==', 4200, '... $DBI::err is 4200');
+cmp_ok($warn{warning}, '==', 2, '... $warn{warning} is 2');
+is($dbh->errstr, "(got info)\n(got warn)\n(got more info) [state was AA001 now AA002]\n(got error)\n(got info)\n(got warn) [err was 42 now 4200] [state was AA002 now AA003]\n(got new error)",
+ '... $dbh->errstr is as we expected');
+is_deeply(\@handlewarn, [ 3, 2, 2 ], '... the @handlewarn array is (3, 2, 2)');
+
+# ----
+
+$dbh->set_err(undef, "foo", "bar"); # clear error
+
+ok(!defined $dbh->errstr, '... $dbh->errstr is defined');
+ok(!defined $dbh->err, '... $dbh->err is defined');
+is($dbh->state, "", '... $dbh->state is an empty string');
+
+# ----
+
+%warn = ( failed => 0, warning => 0 );
+@handlewarn = (0,0,0);
+
+# ----
+
+my @ret;
+@ret = $dbh->set_err(1, "foo"); # PrintError
+
+cmp_ok(scalar(@ret), '==', 1, '... only returned one value');
+ok(!defined $ret[0], '... the first value is undefined');
+ok(!defined $dbh->set_err(2, "bar"), '... $dbh->set_err returned undefiend'); # PrintError
+ok(!defined $dbh->set_err(3, "baz"), '... $dbh->set_err returned undefiend'); # PrintError
+ok(!defined $dbh->set_err(0, "warn"), '... $dbh->set_err returned undefiend'); # PrintError
+is($dbh->errstr, "foo [err was 1 now 2]\nbar [err was 2 now 3]\nbaz\nwarn",
+ '... $dbh->errstr is as we expected');
+is($warn{failed}, 4, '... $warn{failed} is 4');
+is_deeply(\@handlewarn, [ 0, 1, 3 ], '... the @handlewarn array is (0, 1, 3)');
+
+# ----
+
+$dbh->set_err(undef, undef, undef); # clear error
+
+@ret = $dbh->set_err(1, "foo", "AA123", "method");
+cmp_ok(scalar @ret, '==', 1, '... only returned one value');
+ok(!defined $ret[0], '... the first value is undefined');
+
+@ret = $dbh->set_err(1, "foo", "AA123", "method", "42");
+cmp_ok(scalar @ret, '==', 1, '... only returned one value');
+is($ret[0], "42", '... the first value is "42"');
+
+@ret = $dbh->set_err(1, "foo", "return");
+cmp_ok(scalar @ret, '==', 0, '... returned no values');
+
+# ----
+
+$dbh->set_err(undef, undef, undef); # clear error
+
+@ret = $dbh->set_err("", "info", "override");
+cmp_ok(scalar @ret, '==', 1, '... only returned one value');
+ok(!defined $ret[0], '... the first value is undefined');
+cmp_ok($dbh->err, '==', 99, '... $dbh->err is 99');
+is($dbh->errstr, "errstr99", '... $dbh->errstr is as we expected');
+is($dbh->state, "OV123", '... $dbh->state is as we expected');
+$dbh->disconnect;
+
+ping_keeps_err();
+
+1;
+# end
diff --git a/t/09trace.t b/t/09trace.t
new file mode 100644
index 0000000..021bc5c
--- /dev/null
+++ b/t/09trace.t
@@ -0,0 +1,137 @@
+#!perl -w
+# vim:sw=4:ts=8
+
+use strict;
+
+use Test::More tests => 99;
+
+## ----------------------------------------------------------------------------
+## 09trace.t
+## ----------------------------------------------------------------------------
+#
+## ----------------------------------------------------------------------------
+
+BEGIN {
+ $ENV{DBI_TRACE} = 0; # for PurePerl - ensure DBI_TRACE is in the env
+ use_ok( 'DBI' );
+}
+
+$|=1;
+
+
+my $trace_file = "dbitrace$$.log";
+
+1 while unlink $trace_file;
+warn "Can't unlink existing $trace_file: $!" if -e $trace_file;
+
+my $orig_trace_level = DBI->trace;
+DBI->trace(3, $trace_file); # enable trace before first driver load
+
+my $dbh = DBI->connect('dbi:ExampleP(AutoCommit=>1):', undef, undef);
+die "Unable to connect to ExampleP driver: $DBI::errstr" unless $dbh;
+
+isa_ok($dbh, 'DBI::db');
+
+$dbh->dump_handle("dump_handle test, write to log file", 2);
+
+DBI->trace(0, undef); # turn off and restore to STDERR
+
+SKIP: {
+ skip "cygwin has buffer flushing bug", 1 if ($^O =~ /cygwin/i);
+ ok( -s $trace_file, "trace file size = " . -s $trace_file);
+}
+
+DBI->trace($orig_trace_level); # no way to restore previous outfile XXX
+
+
+# Clean up when we're done.
+END { $dbh->disconnect if $dbh;
+ 1 while unlink $trace_file; };
+
+## ----------------------------------------------------------------------------
+# Check the database handle attributes.
+
+cmp_ok($dbh->{TraceLevel}, '==', $DBI::dbi_debug & 0xF, '... checking TraceLevel attribute');
+
+1 while unlink $trace_file;
+
+$dbh->trace(0, $trace_file);
+ok( -f $trace_file, '... trace file successfully created');
+
+my @names = qw(
+ SQL
+ CON
+ ENC
+ DBD
+ TXN
+ foo bar baz boo bop
+);
+my %flag;
+my $all_flags = 0;
+
+foreach my $name (@names) {
+ print "parse_trace_flag $name\n";
+ ok( my $flag1 = $dbh->parse_trace_flag($name) );
+ ok( my $flag2 = $dbh->parse_trace_flags($name) );
+ is( $flag1, $flag2 );
+
+ $dbh->{TraceLevel} = $flag1;
+ is( $dbh->{TraceLevel}, $flag1 );
+
+ $dbh->{TraceLevel} = 0;
+ is( $dbh->{TraceLevel}, 0 );
+
+ $dbh->trace($flag1);
+ is $dbh->trace, $flag1;
+ is $dbh->{TraceLevel}, $flag1;
+
+ $dbh->{TraceLevel} = $name; # set by name
+ $dbh->{TraceLevel} = undef; # check no change on undef
+ is( $dbh->{TraceLevel}, $flag1 );
+
+ $flag{$name} = $flag1;
+ $all_flags |= $flag1
+ if defined $flag1; # reduce noise if there's a bug
+}
+
+print "parse_trace_flag @names\n";
+ok(eq_set([ keys %flag ], [ @names ]), '...');
+$dbh->{TraceLevel} = 0;
+$dbh->{TraceLevel} = join "|", @names;
+is($dbh->{TraceLevel}, $all_flags, '...');
+
+{
+ print "inherit\n";
+ my $sth = $dbh->prepare("select ctime, name from foo");
+ isa_ok( $sth, 'DBI::st' );
+ is( $sth->{TraceLevel}, $all_flags );
+}
+
+$dbh->{TraceLevel} = 0;
+ok !$dbh->{TraceLevel};
+$dbh->{TraceLevel} = 'ALL';
+ok $dbh->{TraceLevel};
+
+{
+ print "test unknown parse_trace_flag\n";
+ my $warn = 0;
+ local $SIG{__WARN__} = sub {
+ if ($_[0] =~ /unknown/i) { ++$warn; print "caught warn: ",@_ }else{ warn @_ }
+ };
+ is $dbh->parse_trace_flag("nonesuch"), undef;
+ is $warn, 0;
+ is $dbh->parse_trace_flags("nonesuch"), 0;
+ is $warn, 1;
+ is $dbh->parse_trace_flags("nonesuch|SQL|nonesuch2"), $dbh->parse_trace_flag("SQL");
+ is $warn, 2;
+}
+
+$dbh->dump_handle("dump_handle test, write to log file", 2);
+
+$dbh->trace(0);
+ok !$dbh->{TraceLevel};
+$dbh->trace(undef, "STDERR"); # close $trace_file
+ok( -s $trace_file );
+
+1;
+# end
diff --git a/t/10examp.t b/t/10examp.t
new file mode 100644
index 0000000..b7f063a
--- /dev/null
+++ b/t/10examp.t
@@ -0,0 +1,579 @@
+#!perl -w
+
+use lib qw(blib/arch blib/lib); # needed since -T ignores PERL5LIB
+use DBI qw(:sql_types);
+use Config;
+use Cwd;
+use strict;
+use Data::Dumper;
+
+$^W = 1;
+$| = 1;
+
+require File::Basename;
+require File::Spec;
+require VMS::Filespec if $^O eq 'VMS';
+
+use Test::More tests => 229;
+
+do {
+ # provide some protection against growth in size of '.' during the test
+ # which was probable cause of this failure
+ # http://www.nntp.perl.org/group/perl.cpan.testers/2009/09/msg5297317.html
+ my $tmpfile = "deleteme_$$";
+ open my $fh, ">$tmpfile";
+ close $fh;
+ unlink $tmpfile;
+};
+
+# "globals"
+my ($r, $dbh);
+
+ok !eval {
+ $dbh = DBI->connect("dbi:NoneSuch:foobar", 1, 1, { RaiseError => 1, AutoCommit => 1 });
+}, 'connect should fail';
+like($@, qr/install_driver\(NoneSuch\) failed/, '... we should have an exception here');
+ok(!$dbh, '... $dbh2 should not be defined');
+
+$dbh = DBI->connect('dbi:ExampleP:', '', '');
+
+sub check_connect_cached {
+ # connect_cached
+ # ------------------------------------------
+ # This test checks that connect_cached works
+ # and how it then relates to the CachedKids
+ # attribute for the driver.
+
+ ok my $dbh_cached_1 = DBI->connect_cached('dbi:ExampleP:', '', '', { TraceLevel=>0, Executed => 0 });
+
+ ok my $dbh_cached_2 = DBI->connect_cached('dbi:ExampleP:', '', '', { TraceLevel=>0, Executed => 0 });
+
+ is($dbh_cached_1, $dbh_cached_2, '... these 2 handles are cached, so they are the same');
+
+ ok my $dbh_cached_3 = DBI->connect_cached('dbi:ExampleP:', '', '', { examplep_foo => 1 });
+
+ isnt($dbh_cached_3, $dbh_cached_2, '... this handle was created with different parameters, so it is not the same');
+
+ # check that cached_connect applies attributes to handles returned from the cache
+ # (The specific case of Executed is relevant to DBD::Gofer retry-on-error logic)
+ ok $dbh_cached_1->do("select * from ."); # set Executed flag
+ ok $dbh_cached_1->{Executed}, 'Executed should be true';
+ ok my $dbh_cached_4 = DBI->connect_cached('dbi:ExampleP:', '', '', { TraceLevel=>0, Executed => 0 });
+ is $dbh_cached_4, $dbh_cached_1, 'should return same handle';
+ ok !$dbh_cached_4->{Executed}, 'Executed should be false because reset by connect attributes';
+
+ my $drh = $dbh->{Driver};
+ isa_ok($drh, "DBI::dr");
+
+ my @cached_kids = values %{$drh->{CachedKids}};
+ ok(eq_set(\@cached_kids, [ $dbh_cached_1, $dbh_cached_3 ]), '... these are our cached kids');
+
+ $drh->{CachedKids} = {};
+ cmp_ok(scalar(keys %{$drh->{CachedKids}}), '==', 0, '... we have emptied out cache');
+}
+
+check_connect_cached();
+
+$dbh->{AutoCommit} = 1;
+$dbh->{PrintError} = 0;
+
+ok($dbh->{AutoCommit} == 1);
+cmp_ok($dbh->{PrintError}, '==', 0, '... PrintError should be 0');
+
+is($dbh->{FetchHashKeyName}, 'NAME', '... FetchHashKey is NAME');
+
+# test access to driver-private attributes
+like($dbh->{example_driver_path}, qr/DBD\/ExampleP\.pm$/, '... checking the example driver_path');
+
+print "others\n";
+eval { $dbh->commit('dummy') };
+ok($@ =~ m/DBI commit: invalid number of arguments:/, $@)
+ unless $DBI::PurePerl && ok(1);
+
+ok($dbh->ping, "ping should return true");
+
+# --- errors
+my $cursor_e = $dbh->prepare("select unknown_field_name from ?");
+is($cursor_e, undef, "prepare should fail");
+ok($dbh->err, "sth->err should be true");
+ok($DBI::err, "DBI::err should be true");
+cmp_ok($DBI::err, 'eq', $dbh->err , "\$DBI::err should match \$dbh->err");
+like($DBI::errstr, qr/Unknown field names: unknown_field_name/, "\$DBI::errstr should contain error string");
+cmp_ok($DBI::errstr, 'eq', $dbh->errstr, "\$DBI::errstr should match \$dbh->errstr");
+
+# --- func
+ok($dbh->errstr eq $dbh->func('errstr'));
+
+my $std_sql = "select mode,size,name from ?";
+my $csr_a = $dbh->prepare($std_sql);
+ok(ref $csr_a);
+ok($csr_a->{NUM_OF_FIELDS} == 3);
+
+SKIP: {
+ skip "inner/outer handles not fully supported for DBI::PurePerl", 3 if $DBI::PurePerl;
+ ok(tied %{ $csr_a->{Database} }); # ie is 'outer' handle
+ ok($csr_a->{Database} eq $dbh, "$csr_a->{Database} ne $dbh")
+ unless $dbh->{mx_handle_list} && ok(1); # skip for Multiplex tests
+ ok(tied %{ $csr_a->{Database}->{Driver} }); # ie is 'outer' handle
+}
+
+my $driver_name = $csr_a->{Database}->{Driver}->{Name};
+ok($driver_name eq 'ExampleP')
+ unless $ENV{DBI_AUTOPROXY} && ok(1);
+
+# --- FetchHashKeyName
+$dbh->{FetchHashKeyName} = 'NAME_uc';
+my $csr_b = $dbh->prepare($std_sql);
+$csr_b->execute('.');
+ok(ref $csr_b);
+
+ok($csr_a != $csr_b);
+
+ok("@{$csr_b->{NAME_lc}}" eq "mode size name"); # before NAME
+ok("@{$csr_b->{NAME_uc}}" eq "MODE SIZE NAME");
+ok("@{$csr_b->{NAME}}" eq "mode size name");
+ok("@{$csr_b->{ $csr_b->{FetchHashKeyName} }}" eq "MODE SIZE NAME");
+
+ok("@{[sort keys %{$csr_b->{NAME_lc_hash}}]}" eq "mode name size");
+ok("@{[sort values %{$csr_b->{NAME_lc_hash}}]}" eq "0 1 2");
+ok("@{[sort keys %{$csr_b->{NAME_uc_hash}}]}" eq "MODE NAME SIZE");
+ok("@{[sort values %{$csr_b->{NAME_uc_hash}}]}" eq "0 1 2");
+
+do "t/lib.pl";
+
+# get a dir always readable on all platforms
+#my $dir = getcwd() || cwd();
+#$dir = VMS::Filespec::unixify($dir) if $^O eq 'VMS';
+# untaint $dir
+#$dir =~ m/(.*)/; $dir = $1 || die;
+my $dir = test_dir ();
+
+# ---
+
+my($col0, $col1, $col2, $col3, $rows);
+my(@row_a, @row_b);
+
+ok($csr_a->bind_columns(undef, \($col0, $col1, $col2)) );
+ok($csr_a->execute( $dir ), $DBI::errstr);
+
+@row_a = $csr_a->fetchrow_array;
+ok(@row_a);
+
+# check bind_columns
+is($row_a[0], $col0);
+is($row_a[1], $col1);
+is($row_a[2], $col2);
+
+ok( ! $csr_a->bind_columns(undef, \($col0, $col1)) );
+like $csr_a->errstr, '/bind_columns called with 2 values but 3 are needed/', 'errstr should contain error message';
+ok( ! $csr_a->bind_columns(undef, \($col0, $col1, $col2, $col3)) );
+like $csr_a->errstr, '/bind_columns called with 4 values but 3 are needed/', 'errstr should contain error message';
+
+ok( $csr_a->bind_col(2, undef, { foo => 42 }) );
+ok ! eval { $csr_a->bind_col(0, undef) };
+like $@, '/bind_col: column 0 is not a valid column \(1..3\)/', 'errstr should contain error message';
+ok ! eval { $csr_a->bind_col(4, undef) };
+like $@, '/bind_col: column 4 is not a valid column \(1..3\)/', 'errstr should contain error message';
+
+ok($csr_b->bind_param(1, $dir));
+ok($csr_b->execute());
+@row_b = @{ $csr_b->fetchrow_arrayref };
+ok(@row_b);
+
+ok("@row_a" eq "@row_b");
+@row_b = $csr_b->fetchrow_array;
+ok("@row_a" ne "@row_b");
+
+ok($csr_a->finish);
+ok($csr_b->finish);
+
+$csr_a = undef; # force destruction of this cursor now
+ok(1);
+
+print "fetchrow_hashref('NAME_uc')\n";
+ok($csr_b->execute());
+my $row_b = $csr_b->fetchrow_hashref('NAME_uc');
+ok($row_b);
+ok($row_b->{MODE} == $row_a[0]);
+ok($row_b->{SIZE} == $row_a[1]);
+ok($row_b->{NAME} eq $row_a[2]);
+
+print "fetchrow_hashref('ParamValues')\n";
+ok($csr_b->execute());
+ok(!defined eval { $csr_b->fetchrow_hashref('ParamValues') } ); # PurePerl croaks
+
+print "FetchHashKeyName\n";
+ok($csr_b->execute());
+$row_b = $csr_b->fetchrow_hashref();
+ok($row_b);
+ok(keys(%$row_b) == 3);
+ok($row_b->{MODE} == $row_a[0]);
+ok($row_b->{SIZE} == $row_a[1]);
+ok($row_b->{NAME} eq $row_a[2]);
+
+print "fetchall_arrayref\n";
+ok($csr_b->execute());
+$r = $csr_b->fetchall_arrayref;
+ok($r);
+ok(@$r);
+ok($r->[0]->[0] == $row_a[0]);
+ok($r->[0]->[1] == $row_a[1]);
+ok($r->[0]->[2] eq $row_a[2]);
+
+print "fetchall_arrayref array slice\n";
+ok($csr_b->execute());
+$r = $csr_b->fetchall_arrayref([2,1]);
+ok($r && @$r);
+ok($r->[0]->[1] == $row_a[1]);
+ok($r->[0]->[0] eq $row_a[2]);
+
+print "fetchall_arrayref hash slice\n";
+ok($csr_b->execute());
+$r = $csr_b->fetchall_arrayref({ SizE=>1, nAMe=>1});
+ok($r && @$r);
+ok($r->[0]->{SizE} == $row_a[1]);
+ok($r->[0]->{nAMe} eq $row_a[2]);
+
+ok ! $csr_b->fetchall_arrayref({ NoneSuch=>1 });
+like $DBI::errstr, qr/Invalid column name/;
+
+print "fetchall_arrayref renaming hash slice\n";
+ok($csr_b->execute());
+$r = $csr_b->fetchall_arrayref(\{ 1 => "Koko", 2 => "Nimi"});
+ok($r && @$r);
+ok($r->[0]->{Koko} == $row_a[1]);
+ok($r->[0]->{Nimi} eq $row_a[2]);
+
+ok ! eval { $csr_b->fetchall_arrayref(\{ 9999 => "Koko" }) };
+like $@, qr/\Qis not a valid column/;
+
+print "fetchall_arrayref empty renaming hash slice\n";
+ok($csr_b->execute());
+$r = $csr_b->fetchall_arrayref(\{});
+ok($r && @$r);
+ok(keys %{$r->[0]} == 0);
+
+ok($csr_b->execute());
+ok(!$csr_b->fetchall_arrayref(\[]));
+like $DBI::errstr, qr/\Qfetchall_arrayref(REF) invalid/;
+
+print "fetchall_arrayref hash\n";
+ok($csr_b->execute());
+$r = $csr_b->fetchall_arrayref({});
+ok($r);
+ok(keys %{$r->[0]} == 3);
+ok("@{$r->[0]}{qw(MODE SIZE NAME)}" eq "@row_a", "'@{$r->[0]}{qw(MODE SIZE NAME)}' ne '@row_a'");
+
+print "rows()\n"; # assumes previous fetch fetched all rows
+$rows = $csr_b->rows;
+ok($rows > 0, "row count $rows");
+ok($rows == @$r, "$rows vs ".@$r);
+ok($rows == $DBI::rows, "$rows vs $DBI::rows");
+
+print "fetchall_arrayref array slice and max rows\n";
+ok($csr_b->execute());
+$r = $csr_b->fetchall_arrayref([0], 1);
+ok($r);
+is_deeply($r, [[$row_a[0]]]);
+
+$r = $csr_b->fetchall_arrayref([], 1);
+is @$r, 1, 'should fetch one row';
+
+$r = $csr_b->fetchall_arrayref([], 99999);
+ok @$r, 'should fetch all the remaining rows';
+
+$r = $csr_b->fetchall_arrayref([], 99999);
+is $r, undef, 'should return undef as there are no more rows';
+
+# ---
+
+print "selectrow_array\n";
+@row_b = $dbh->selectrow_array($std_sql, undef, $dir);
+ok(@row_b == 3);
+ok("@row_b" eq "@row_a");
+
+print "selectrow_hashref\n";
+$r = $dbh->selectrow_hashref($std_sql, undef, $dir);
+ok(keys %$r == 3);
+ok($r->{MODE} eq $row_a[0]);
+ok($r->{SIZE} eq $row_a[1]);
+ok($r->{NAME} eq $row_a[2]);
+
+print "selectall_arrayref\n";
+$r = $dbh->selectall_arrayref($std_sql, undef, $dir);
+ok($r);
+ok(@{$r->[0]} == 3);
+ok("@{$r->[0]}" eq "@row_a");
+ok(@$r == $rows);
+
+print "selectall_arrayref Slice array slice\n";
+$r = $dbh->selectall_arrayref($std_sql, { Slice => [ 2, 0 ] }, $dir);
+ok($r);
+ok(@{$r->[0]} == 2);
+ok("@{$r->[0]}" eq "$row_a[2] $row_a[0]", qq{"@{$r->[0]}" eq "$row_a[2] $row_a[0]"});
+ok(@$r == $rows);
+
+print "selectall_arrayref Columns array slice\n";
+$r = $dbh->selectall_arrayref($std_sql, { Columns => [ 3, 1 ] }, $dir);
+ok($r);
+ok(@{$r->[0]} == 2);
+ok("@{$r->[0]}" eq "$row_a[2] $row_a[0]", qq{"@{$r->[0]}" eq "$row_a[2] $row_a[0]"});
+ok(@$r == $rows);
+
+print "selectall_arrayref hash slice\n";
+$r = $dbh->selectall_arrayref($std_sql, { Columns => { MoDe=>1, NamE=>1 } }, $dir);
+ok($r);
+ok(keys %{$r->[0]} == 2);
+ok(exists $r->[0]{MoDe});
+ok(exists $r->[0]{NamE});
+ok($r->[0]{MoDe} eq $row_a[0]);
+ok($r->[0]{NamE} eq $row_a[2]);
+ok(@$r == $rows);
+
+print "selectall_hashref\n";
+$r = $dbh->selectall_hashref($std_sql, 'NAME', undef, $dir);
+ok($r, "selectall_hashref result");
+is(ref $r, 'HASH', "selectall_hashref HASH: ".ref $r);
+is(scalar keys %$r, $rows);
+is($r->{ $row_a[2] }{SIZE}, $row_a[1], qq{$r->{ $row_a[2] }{SIZE} eq $row_a[1]});
+
+print "selectall_hashref by column number\n";
+$r = $dbh->selectall_hashref($std_sql, 3, undef, $dir);
+ok($r);
+ok($r->{ $row_a[2] }{SIZE} eq $row_a[1], qq{$r->{ $row_a[2] }{SIZE} eq $row_a[1]});
+
+print "selectcol_arrayref\n";
+$r = $dbh->selectcol_arrayref($std_sql, undef, $dir);
+ok($r);
+ok(@$r == $rows);
+ok($r->[0] eq $row_b[0]);
+
+print "selectcol_arrayref column slice\n";
+$r = $dbh->selectcol_arrayref($std_sql, { Columns => [3,2] }, $dir);
+ok($r);
+# warn Dumper([\@row_b, $r]);
+ok(@$r == $rows * 2);
+ok($r->[0] eq $row_b[2]);
+ok($r->[1] eq $row_b[1]);
+
+# ---
+
+print "others...\n";
+my $csr_c;
+$csr_c = $dbh->prepare("select unknown_field_name1 from ?");
+ok(!defined $csr_c);
+ok($DBI::errstr =~ m/Unknown field names: unknown_field_name1/);
+
+print "RaiseError & PrintError & ShowErrorStatement\n";
+$dbh->{RaiseError} = 1;
+ok($dbh->{RaiseError});
+$dbh->{ShowErrorStatement} = 1;
+ok($dbh->{ShowErrorStatement});
+
+my $error_sql = "select unknown_field_name2 from ?";
+
+ok(! eval { $csr_c = $dbh->prepare($error_sql); 1; });
+#print "$@\n";
+like $@, qr/\Q$error_sql/; # ShowErrorStatement
+like $@, qr/Unknown field names: unknown_field_name2/;
+
+# check attributes are inherited
+my $se_sth1 = $dbh->prepare("select mode from ?");
+ok($se_sth1->{RaiseError});
+ok($se_sth1->{ShowErrorStatement});
+
+# check ShowErrorStatement ParamValues are included and sorted
+$se_sth1->bind_param($_, "val$_") for (1..11);
+ok( !eval { $se_sth1->execute } );
+like $@, qr/\[for Statement "select mode from \?" with ParamValues: 1='val1', 2='val2', 3='val3', 4='val4', 5='val5', 6='val6', 7='val7', 8='val8', 9='val9', 10='val10', 11='val11'\]/;
+
+# this test relies on the fact that ShowErrorStatement is set above
+TODO: {
+ local $TODO = "rt66127 not fixed yet";
+ eval {
+ local $se_sth1->{PrintError} = 0;
+ $se_sth1->execute(1,2);
+ };
+ unlike($@, qr/ParamValues:/, 'error string does not contain ParamValues');
+ is($se_sth1->{ParamValues}, undef, 'ParamValues is empty')
+ or diag(Dumper($se_sth1->{ParamValues}));
+};
+# check that $dbh->{Statement} tracks last _executed_ sth
+$se_sth1 = $dbh->prepare("select mode from ?");
+ok($se_sth1->{Statement} eq "select mode from ?");
+ok($dbh->{Statement} eq "select mode from ?") or print "got: $dbh->{Statement}\n";
+my $se_sth2 = $dbh->prepare("select name from ?");
+ok($se_sth2->{Statement} eq "select name from ?");
+ok($dbh->{Statement} eq "select name from ?");
+$se_sth1->execute('.');
+ok($dbh->{Statement} eq "select mode from ?");
+
+# show error param values
+ok(! eval { $se_sth1->execute('first','second') }); # too many params
+ok($@ =~ /\b1='first'/, $@);
+ok($@ =~ /\b2='second'/, $@);
+
+$se_sth1->finish;
+$se_sth2->finish;
+
+$dbh->{RaiseError} = 0;
+ok(!$dbh->{RaiseError});
+$dbh->{ShowErrorStatement} = 0;
+ok(!$dbh->{ShowErrorStatement});
+
+{
+ my @warn;
+ local($SIG{__WARN__}) = sub { push @warn, @_ };
+ $dbh->{PrintError} = 1;
+ ok($dbh->{PrintError});
+ ok(! $dbh->selectall_arrayref("select unknown_field_name3 from ?"));
+ ok("@warn" =~ m/Unknown field names: unknown_field_name3/);
+ $dbh->{PrintError} = 0;
+ ok(!$dbh->{PrintError});
+}
+
+
+print "HandleError\n";
+my $HandleErrorReturn;
+my $HandleError = sub {
+ my $msg = sprintf "HandleError: %s [h=%s, rv=%s, #=%d]",
+ $_[0],$_[1],(defined($_[2])?$_[2]:'undef'),scalar(@_);
+ die $msg if $HandleErrorReturn < 0;
+ print "$msg\n";
+ $_[2] = 42 if $HandleErrorReturn == 2;
+ return $HandleErrorReturn;
+};
+
+$dbh->{HandleError} = $HandleError;
+ok($dbh->{HandleError});
+ok($dbh->{HandleError} == $HandleError);
+
+$dbh->{RaiseError} = 1;
+$dbh->{PrintError} = 0;
+$error_sql = "select unknown_field_name2 from ?";
+
+print "HandleError -> die\n";
+$HandleErrorReturn = -1;
+ok(! eval { $csr_c = $dbh->prepare($error_sql); 1; });
+ok($@ =~ m/^HandleError:/, $@);
+
+print "HandleError -> 0 -> RaiseError\n";
+$HandleErrorReturn = 0;
+ok(! eval { $csr_c = $dbh->prepare($error_sql); 1; });
+ok($@ =~ m/^DBD::(ExampleP|Multiplex|Gofer)::db prepare failed:/, $@);
+
+print "HandleError -> 1 -> return (original)undef\n";
+$HandleErrorReturn = 1;
+$r = eval { $csr_c = $dbh->prepare($error_sql); };
+ok(!$@, $@);
+ok(!defined($r), $r);
+
+print "HandleError -> 2 -> return (modified)42\n";
+$HandleErrorReturn = 2;
+$r = eval { $csr_c = $dbh->prepare($error_sql); };
+ok(!$@, $@);
+ok($r==42) unless $dbh->{mx_handle_list} && ok(1); # skip for Multiplex
+
+$dbh->{HandleError} = undef;
+ok(!$dbh->{HandleError});
+
+{
+ # dump_results;
+ my $sth = $dbh->prepare($std_sql);
+
+ isa_ok($sth, "DBI::st");
+
+ if (length(File::Spec->updir)) {
+ ok($sth->execute(File::Spec->updir));
+ } else {
+ ok($sth->execute('../'));
+ }
+
+ my $dump_file = 'dumpcsr.tst';
+ SKIP: {
+ skip "# dump_results test skipped: unable to open $dump_file: $!\n", 4
+ unless open(DUMP_RESULTS, ">$dump_file");
+ ok($sth->dump_results("10", "\n", ",\t", \*DUMP_RESULTS));
+ close(DUMP_RESULTS) or warn "close $dump_file: $!";
+ ok(-s $dump_file > 0);
+ is( unlink( $dump_file ), 1, "Remove $dump_file" );
+ ok( !-e $dump_file, "Actually gone" );
+ }
+
+}
+
+note "table_info\n";
+# First generate a list of all subdirectories
+$dir = File::Basename::dirname( $INC{"DBI.pm"} );
+my $dh;
+ok(opendir($dh, $dir));
+my(%dirs, %unexpected, %missing);
+while (defined(my $file = readdir($dh))) {
+ $dirs{$file} = 1 if -d File::Spec->catdir($dir,$file);
+}
+note( "Local $dir subdirs: @{[ keys %dirs ]}" );
+closedir($dh);
+my $sth = $dbh->table_info($dir, undef, "%", "TABLE");
+ok($sth);
+%unexpected = %dirs;
+%missing = ();
+while (my $ref = $sth->fetchrow_hashref()) {
+ if (exists($unexpected{$ref->{'TABLE_NAME'}})) {
+ delete $unexpected{$ref->{'TABLE_NAME'}};
+ } else {
+ $missing{$ref->{'TABLE_NAME'}} = 1;
+ }
+}
+ok(keys %unexpected == 0)
+ or diag "Unexpected directories: ", join(",", keys %unexpected), "\n";
+ok(keys %missing == 0)
+ or diag "Missing directories: ", join(",", keys %missing), "\n";
+
+note "tables\n";
+my @tables_expected = (
+ q{"schema"."table"},
+ q{"sch-ema"."table"},
+ q{"schema"."ta-ble"},
+ q{"sch ema"."table"},
+ q{"schema"."ta ble"},
+);
+my @tables = $dbh->tables(undef, undef, "%", "VIEW");
+ok(@tables == @tables_expected, "Table count mismatch".@tables_expected." vs ".@tables);
+ok($tables[$_] eq $tables_expected[$_], "$tables[$_] ne $tables_expected[$_]")
+ foreach (0..$#tables_expected);
+
+for (my $i = 0; $i < 300; $i += 100) {
+ note "Testing the fake directories ($i).\n";
+ ok($csr_a = $dbh->prepare("SELECT name, mode FROM long_list_$i"));
+ ok($csr_a->execute(), $DBI::errstr);
+ my $ary = $csr_a->fetchall_arrayref;
+ ok(@$ary == $i, @$ary." rows instead of $i");
+ if ($i) {
+ my @n1 = map { $_->[0] } @$ary;
+ my @n2 = reverse map { "file$_" } 1..$i;
+ ok("@n1" eq "@n2", "'@n1' ne '@n2'");
+ }
+ else {
+ ok(1);
+ }
+}
+
+
+SKIP: {
+ skip "test not tested with Multiplex", 1
+ if $dbh->{mx_handle_list};
+ note "Testing \$dbh->func().\n";
+ my %tables;
+ %tables = map { $_ =~ /lib/ ? ($_, 1) : () } $dbh->tables();
+ my @func_tables = $dbh->func('lib', 'examplep_tables');
+ foreach my $t (@func_tables) {
+ defined(delete $tables{$t}) or print "Unexpected table: $t\n";
+ }
+ is(keys(%tables), 0);
+}
+
+$dbh->disconnect;
+ok(!$dbh->{Active});
+ok(!$dbh->ping, "ping should return false after disconnect");
+
+1;
diff --git a/t/11fetch.t b/t/11fetch.t
new file mode 100644
index 0000000..5f2fedc
--- /dev/null
+++ b/t/11fetch.t
@@ -0,0 +1,124 @@
+#!perl -w
+# vim:ts=8:sw=4
+$|=1;
+
+use strict;
+
+use Test::More;
+use DBI;
+use Storable qw(dclone);
+use Data::Dumper;
+
+$Data::Dumper::Indent = 1;
+$Data::Dumper::Sortkeys = 1;
+$Data::Dumper::Quotekeys = 0;
+
+plan tests => 24;
+
+my $dbh = DBI->connect("dbi:Sponge:foo","","", {
+ PrintError => 0,
+ RaiseError => 1,
+});
+
+my $source_rows = [ # data for DBD::Sponge to return via fetch
+ [ 41, "AAA", 9 ],
+ [ 41, "BBB", 9 ],
+ [ 42, "BBB", undef ],
+ [ 43, "ccc", 7 ],
+ [ 44, "DDD", 6 ],
+];
+
+sub go {
+ my $source = shift || $source_rows;
+ my $sth = $dbh->prepare("foo", {
+ rows => dclone($source),
+ NAME => [ qw(C1 C2 C3) ],
+ });
+ ok($sth->execute(), $DBI::errstr);
+ return $sth;
+}
+
+my($sth, $col0, $col1, $col2, $rows);
+
+# --- fetchrow_arrayref
+# --- fetchrow_array
+# etc etc
+
+# --- fetchall_hashref
+my @fetchall_hashref_results = ( # single keys
+ C1 => {
+ 41 => { C1 => 41, C2 => 'BBB', C3 => 9 },
+ 42 => { C1 => 42, C2 => 'BBB', C3 => undef },
+ 43 => { C1 => 43, C2 => 'ccc', C3 => 7 },
+ 44 => { C1 => 44, C2 => 'DDD', C3 => 6 }
+ },
+ C2 => {
+ AAA => { C1 => 41, C2 => 'AAA', C3 => 9 },
+ BBB => { C1 => 42, C2 => 'BBB', C3 => undef },
+ DDD => { C1 => 44, C2 => 'DDD', C3 => 6 },
+ ccc => { C1 => 43, C2 => 'ccc', C3 => 7 }
+ },
+ [ 'C2' ] => { # single key within arrayref
+ AAA => { C1 => 41, C2 => 'AAA', C3 => 9 },
+ BBB => { C1 => 42, C2 => 'BBB', C3 => undef },
+ DDD => { C1 => 44, C2 => 'DDD', C3 => 6 },
+ ccc => { C1 => 43, C2 => 'ccc', C3 => 7 }
+ },
+);
+push @fetchall_hashref_results, ( # multiple keys
+ [ 'C1', 'C2' ] => {
+ '41' => {
+ AAA => { C1 => '41', C2 => 'AAA', C3 => 9 },
+ BBB => { C1 => '41', C2 => 'BBB', C3 => 9 }
+ },
+ '42' => {
+ BBB => { C1 => '42', C2 => 'BBB', C3 => undef }
+ },
+ '43' => {
+ ccc => { C1 => '43', C2 => 'ccc', C3 => 7 }
+ },
+ '44' => {
+ DDD => { C1 => '44', C2 => 'DDD', C3 => 6 }
+ }
+ },
+);
+
+my %dump;
+
+while (my $keyfield = shift @fetchall_hashref_results) {
+ my $expected = shift @fetchall_hashref_results;
+ my $k = (ref $keyfield) ? "[@$keyfield]" : $keyfield;
+ print "# fetchall_hashref($k)\n";
+ ok($sth = go());
+ my $result = $sth->fetchall_hashref($keyfield);
+ ok($result);
+ is_deeply($result, $expected);
+ # $dump{$k} = dclone $result; # just for adding tests
+}
+
+warn Dumper \%dump if %dump;
+
+# test assignment to NUM_OF_FIELDS automatically alters the row buffer
+$sth = go();
+my $row = $sth->fetchrow_arrayref;
+is scalar @$row, 3;
+is $sth->{NUM_OF_FIELDS}, 3;
+is scalar @{ $sth->_get_fbav }, 3;
+$sth->{NUM_OF_FIELDS} = 4;
+is $sth->{NUM_OF_FIELDS}, 4;
+is scalar @{ $sth->_get_fbav }, 4;
+$sth->{NUM_OF_FIELDS} = 2;
+is $sth->{NUM_OF_FIELDS}, 2;
+is scalar @{ $sth->_get_fbav }, 2;
+
+$sth->finish;
+
+
+if (0) {
+ my @perf = map { [ int($_/100), $_, $_ ] } 0..10000;
+ require Benchmark;
+ Benchmark::timethis(10, sub { go(\@perf)->fetchall_hashref([ 'C1','C2','C3' ]) });
+}
+
+
+1; # end
diff --git a/t/12quote.t b/t/12quote.t
new file mode 100644
index 0000000..c7dc948
--- /dev/null
+++ b/t/12quote.t
@@ -0,0 +1,48 @@
+#!perl -w
+
+use lib qw(blib/arch blib/lib); # needed since -T ignores PERL5LIB
+use strict;
+
+use Test::More tests => 10;
+
+use DBI qw(:sql_types);
+use Config;
+use Cwd;
+
+$^W = 1;
+$| = 1;
+
+my $dbh = DBI->connect('dbi:ExampleP:', '', '');
+
+sub check_quote {
+ # checking quote
+ is($dbh->quote("quote's"), "'quote''s'", '... quoting strings with embedded single quotes');
+ is($dbh->quote("42", SQL_VARCHAR), "'42'", '... quoting number as SQL_VARCHAR');
+ is($dbh->quote("42", SQL_INTEGER), "42", '... quoting number as SQL_INTEGER');
+ is($dbh->quote(undef), "NULL", '... quoting undef as NULL');
+}
+
+check_quote();
+
+sub check_quote_identifier {
+
+ is($dbh->quote_identifier('foo'), '"foo"', '... properly quotes foo as "foo"');
+ is($dbh->quote_identifier('f"o'), '"f""o"', '... properly quotes f"o as "f""o"');
+ is($dbh->quote_identifier('foo','bar'), '"foo"."bar"', '... properly quotes foo, bar as "foo"."bar"');
+ is($dbh->quote_identifier(undef,undef,'bar'), '"bar"', '... properly quotes undef, undef, bar as "bar"');
+
+ is($dbh->quote_identifier('foo',undef,'bar'), '"foo"."bar"', '... properly quotes foo, undef, bar as "foo"."bar"');
+
+ SKIP: {
+ skip "Can't test alternate quote_identifier logic with DBI_AUTOPROXY", 1
+ if $ENV{DBI_AUTOPROXY};
+ my $qi = $dbh->{dbi_quote_identifier_cache} || die "test out of date with dbi internals?";
+ $qi->[1] = '@'; # SQL_CATALOG_NAME_SEPARATOR
+ $qi->[2] = 2; # SQL_CATALOG_LOCATION
+ is($dbh->quote_identifier('foo',undef,'bar'), '"bar"@"foo"', '... now quotes it as "bar"@"foo" after flushing cache');
+ }
+}
+
+check_quote_identifier();
+
+1;
diff --git a/t/13taint.t b/t/13taint.t
new file mode 100644
index 0000000..4fd1076
--- /dev/null
+++ b/t/13taint.t
@@ -0,0 +1,133 @@
+#!perl -wT
+
+use lib qw(blib/arch blib/lib); # needed since -T ignores PERL5LIB
+use DBI qw(:sql_types);
+use Config;
+use Cwd;
+use strict;
+
+
+$^W = 1;
+$| = 1;
+
+require VMS::Filespec if $^O eq 'VMS';
+
+use Test::More;
+
+# Check Taint attribute works. This requires this test to be run
+# manually with the -T flag: "perl -T -Mblib t/examp.t"
+sub is_tainted {
+ my $foo;
+ return ! eval { ($foo=join('',@_)), kill 0; 1; };
+}
+sub mk_tainted {
+ my $string = shift;
+ return substr($string.$^X, 0, length($string));
+}
+
+plan skip_all => "Taint attributes not supported with DBI::PurePerl" if $DBI::PurePerl;
+plan skip_all => "Taint attribute tests require taint mode (perl -T)" unless is_tainted($^X);
+plan skip_all => "Taint attribute tests not functional with DBI_AUTOPROXY" if $ENV{DBI_AUTOPROXY};
+
+plan tests => 36;
+
+# get a dir always readable on all platforms
+my $dir = getcwd() || cwd();
+$dir = VMS::Filespec::unixify($dir) if $^O eq 'VMS';
+$dir =~ m/(.*)/; $dir = $1 || die; # untaint $dir
+
+my ($r, $dbh);
+
+$dbh = DBI->connect('dbi:ExampleP:', '', '', { PrintError=>0, RaiseError=>1, Taint => 1 });
+
+my $std_sql = "select mode,size,name from ?";
+my $csr_a = $dbh->prepare($std_sql);
+ok(ref $csr_a);
+
+ok($dbh->{'Taint'});
+ok($dbh->{'TaintIn'} == 1);
+ok($dbh->{'TaintOut'} == 1);
+
+$dbh->{'TaintOut'} = 0;
+ok($dbh->{'Taint'} == 0);
+ok($dbh->{'TaintIn'} == 1);
+ok($dbh->{'TaintOut'} == 0);
+
+$dbh->{'Taint'} = 0;
+ok($dbh->{'Taint'} == 0);
+ok($dbh->{'TaintIn'} == 0);
+ok($dbh->{'TaintOut'} == 0);
+
+$dbh->{'TaintIn'} = 1;
+ok($dbh->{'Taint'} == 0);
+ok($dbh->{'TaintIn'} == 1);
+ok($dbh->{'TaintOut'} == 0);
+
+$dbh->{'TaintOut'} = 1;
+ok($dbh->{'Taint'} == 1);
+ok($dbh->{'TaintIn'} == 1);
+ok($dbh->{'TaintOut'} == 1);
+
+$dbh->{'Taint'} = 0;
+my $st;
+eval { $st = $dbh->prepare($std_sql); };
+ok(ref $st);
+
+ok($st->{'Taint'} == 0);
+
+ok($st->execute( $dir ), 'should execute ok');
+
+my @row = $st->fetchrow_array;
+ok(@row);
+
+ok(!is_tainted($row[0]));
+ok(!is_tainted($row[1]));
+ok(!is_tainted($row[2]));
+
+print "TaintIn\n";
+$st->{'TaintIn'} = 1;
+
+@row = $st->fetchrow_array;
+ok(@row);
+
+ok(!is_tainted($row[0]));
+ok(!is_tainted($row[1]));
+ok(!is_tainted($row[2]));
+
+print "TaintOut\n";
+$st->{'TaintOut'} = 1;
+
+@row = $st->fetchrow_array;
+ok(@row);
+
+ok(is_tainted($row[0]));
+ok(is_tainted($row[1]));
+ok(is_tainted($row[2]));
+
+$st->finish;
+
+my $tainted_sql = mk_tainted($std_sql);
+my $tainted_dot = mk_tainted('.');
+
+$dbh->{'Taint'} = $csr_a->{'Taint'} = 1;
+eval { $dbh->prepare($tainted_sql); 1; };
+ok($@ =~ /Insecure dependency/, $@);
+eval { $csr_a->execute($tainted_dot); 1; };
+ok($@ =~ /Insecure dependency/, $@);
+undef $@;
+
+$dbh->{'TaintIn'} = $csr_a->{'TaintIn'} = 0;
+
+eval { $dbh->prepare($tainted_sql); 1; };
+ok(!$@, $@);
+eval { $csr_a->execute($tainted_dot); 1; };
+ok(!$@, $@);
+
+$csr_a->{Taint} = 0;
+ok($csr_a->{Taint} == 0);
+
+$csr_a->finish;
+
+$dbh->disconnect;
+
+1;
diff --git a/t/14utf8.t b/t/14utf8.t
new file mode 100644
index 0000000..c141e38
--- /dev/null
+++ b/t/14utf8.t
@@ -0,0 +1,76 @@
+#!perl -w
+# vim:ts=8:sw=4
+$|=1;
+
+use Test::More;
+use DBI;
+
+plan skip_all => "Requires perl 5.8"
+ unless $] >= 5.008;
+
+eval {
+ require Storable;
+ import Storable qw(dclone);
+ require Encode;
+ import Encode qw(_utf8_on _utf8_off is_utf8);
+};
+
+plan skip_all => "Unable to load required module ($@)"
+ unless defined &_utf8_on;
+
+plan tests => 16;
+
+$dbh = DBI->connect("dbi:Sponge:foo","","", {
+ PrintError => 0,
+ RaiseError => 1,
+});
+
+my $source_rows = [ # data for DBD::Sponge to return via fetch
+ [ 41, "AAA", 9 ],
+ [ 42, "BB", undef ],
+ [ 43, undef, 7 ],
+ [ 44, "DDD", 6 ],
+];
+
+my($sth, $col0, $col1, $col2, $rows);
+
+# set utf8 on one of the columns so we can check it carries through into the
+# keys of fetchrow_hashref
+my @col_names = qw(Col1 Col2 Col3);
+_utf8_on($col_names[1]);
+ok is_utf8($col_names[1]);
+ok !is_utf8($col_names[0]);
+
+$sth = $dbh->prepare("foo", {
+ rows => dclone($source_rows),
+ NAME => \@col_names,
+});
+
+ok($sth->bind_columns(\($col0, $col1, $col2)) );
+ok($sth->execute(), $DBI::errstr);
+
+ok $sth->fetch;
+cmp_ok $col1, 'eq', "AAA";
+ok !is_utf8($col1);
+
+# force utf8 flag on
+_utf8_on($col1);
+ok is_utf8($col1);
+
+ok $sth->fetch;
+cmp_ok $col1, 'eq', "BB";
+# XXX sadly this test doesn't detect the problem when using DBD::Sponge
+# because DBD::Sponge uses $sth->_set_fbav (correctly) and that uses
+# sv_setsv which doesn't have the utf8 persistence that sv_setpv does.
+ok !is_utf8($col1); # utf8 flag should have been reset
+
+ok $sth->fetch;
+ok !defined $col1; # null
+ok !is_utf8($col1); # utf8 flag should have been reset
+
+ok my $hash = $sth->fetchrow_hashref;
+ok 1 == grep { is_utf8($_) } keys %$hash;
+
+$sth->finish;
+
+# end
diff --git a/t/15array.t b/t/15array.t
new file mode 100644
index 0000000..2b91001
--- /dev/null
+++ b/t/15array.t
@@ -0,0 +1,254 @@
+#!perl -w
+$|=1;
+
+use strict;
+
+use Test::More tests => 55;
+
+## ----------------------------------------------------------------------------
+## 15array.t
+## ----------------------------------------------------------------------------
+#
+## ----------------------------------------------------------------------------
+
+BEGIN {
+ use_ok('DBI');
+}
+
+# create a database handle
+my $dbh = DBI->connect("dbi:Sponge:dummy", '', '', {
+ RaiseError => 1,
+ ShowErrorStatement => 1,
+ AutoCommit => 1
+});
+
+# check that our db handle is good
+isa_ok($dbh, "DBI::db");
+
+my $rv;
+my $rows = [];
+my $tuple_status = [];
+my $dumped;
+
+my $sth = $dbh->prepare("insert", {
+ rows => $rows, # where to 'insert' (push) the rows
+ NUM_OF_PARAMS => 4,
+ execute_hook => sub { # DBD::Sponge hook to make certain data trigger an error for that row
+ local $^W;
+ return $_[0]->set_err(1,"errmsg") if grep { $_ and $_ eq "B" } @_;
+ return 1;
+ }
+ });
+
+isa_ok($sth, "DBI::st");
+
+cmp_ok(scalar @{$rows}, '==', 0, '... we should have 0 rows');
+
+# -----------------------------------------------
+
+ok(! eval {
+ local $sth->{PrintError} = 0;
+ $sth->execute_array(
+ {
+ ArrayTupleStatus => $tuple_status
+ },
+ [ 1, 2, 3 ], # array of integers
+ 42, # scalar 42 treated as array of 42's
+ undef, # scalar undef treated as array of undef's
+ [ qw(A B C) ], # array of strings
+ ) },
+ '... execute_array should return false'
+);
+ok $@, 'execute_array failure with RaiseError should have died';
+like $sth->errstr, '/executing 3 generated 1 errors/';
+
+cmp_ok(scalar @{$rows}, '==', 2, '... we should have 2 rows');
+cmp_ok(scalar @{$tuple_status}, '==', 3, '... we should have 3 tuple_status');
+
+ok(eq_array(
+ $rows,
+ [ [1, 42, undef, 'A'], [3, 42, undef, 'C'] ]
+ ),
+ '... our rows are as expected');
+
+ok(eq_array(
+ $tuple_status,
+ [1, [1, 'errmsg', 'S1000'], 1]
+ ),
+ '... our tuple_status is as expected');
+
+# -----------------------------------------------
+# --- change one param and re-execute
+
+@$rows = ();
+ok( $sth->bind_param_array(4, [ qw(a b c) ]), '... bind_param_array should return true');
+ok( $sth->execute_array({ ArrayTupleStatus => $tuple_status }), '... execute_array should return true');
+
+cmp_ok(scalar @{$rows}, '==', 3, '... we should have 3 rows');
+cmp_ok(scalar @{$tuple_status}, '==', 3, '... we should have 3 tuple_status');
+
+ok(eq_array(
+ $rows,
+ [ [1, 42, undef, 'a'], [2, 42, undef, 'b'], [3, 42, undef, 'c'] ]
+ ),
+ '... our rows are as expected');
+
+ok(eq_array(
+ $tuple_status,
+ [1, 1, 1]
+ ),
+ '... our tuple_status is as expected');
+
+# -----------------------------------------------
+# --- call execute_array in array context to get executed AND affected
+@$rows = ();
+my ($executed, $affected) = $sth->execute_array({ ArrayTupleStatus => $tuple_status });
+ok($executed, '... execute_array should return true');
+cmp_ok($executed, '==', 3, '... we should have executed 3 rows');
+cmp_ok($affected, '==', 3, '... we should have affected 3 rows');
+
+# -----------------------------------------------
+# --- with no values for bind params, should execute zero times
+
+@$rows = ();
+$rv = $sth->execute_array( { ArrayTupleStatus => $tuple_status }, [], [], [], []);
+ok($rv, '... execute_array should return true');
+ok(!($rv+0), '... execute_array should return 0 (but true)');
+
+cmp_ok(scalar @{$rows}, '==', 0, '... we should have 0 rows');
+cmp_ok(scalar @{$tuple_status}, '==', 0,'... we should have 0 tuple_status');
+
+# -----------------------------------------------
+# --- with only scalar values for bind params, should execute just once
+
+@$rows = ();
+$rv = $sth->execute_array( { ArrayTupleStatus => $tuple_status }, 5, 6, 7, 8);
+cmp_ok($rv, '==', 1, '... execute_array should return 1');
+
+cmp_ok(scalar @{$rows}, '==', 1, '... we should have 1 rows');
+ok(eq_array( $rows, [ [5,6,7,8] ]), '... our rows are as expected');
+cmp_ok(scalar @{$tuple_status}, '==', 1,'... we should have 1 tuple_status');
+ok(eq_array( $tuple_status, [1]), '... our tuple_status is as expected');
+
+# -----------------------------------------------
+# --- with mix of scalar values and arrays only arrays control tuples
+
+@$rows = ();
+$rv = $sth->execute_array( { ArrayTupleStatus => $tuple_status }, 5, [], 7, 8);
+cmp_ok($rv, '==', 0, '... execute_array should return 0');
+
+cmp_ok(scalar @{$rows}, '==', 0, '... we should have 0 rows');
+cmp_ok(scalar @{$tuple_status}, '==', 0,'... we should have 0 tuple_status');
+
+# -----------------------------------------------
+# --- catch 'undefined value' bug with zero bind values
+
+@$rows = ();
+my $sth_other = $dbh->prepare("insert", {
+ rows => $rows, # where to 'insert' (push) the rows
+ NUM_OF_PARAMS => 1,
+});
+
+isa_ok($sth_other, "DBI::st");
+
+$rv = $sth_other->execute_array( {}, [] );
+ok($rv, '... execute_array should return true');
+ok(!($rv+0), '... execute_array should return 0 (but true)');
+# no ArrayTupleStatus
+
+cmp_ok(scalar @{$rows}, '==', 0, '... we should have 0 rows');
+
+# -----------------------------------------------
+# --- ArrayTupleFetch code-ref tests ---
+
+my $index = 0;
+
+my $fetchrow = sub { # generate 5 rows of two integer values
+ return if $index >= 2;
+ $index +=1;
+ # There doesn't seem any reliable way to force $index to be
+ # treated as a string (and so dumped as such). We just have to
+ # make the test case allow either 1 or '1'.
+ return [ $index, 'a','b','c' ];
+};
+
+@$rows = ();
+ok( $sth->execute_array({
+ ArrayTupleFetch => $fetchrow,
+ ArrayTupleStatus => $tuple_status
+ }), '... execute_array should return true');
+
+cmp_ok(scalar @{$rows}, '==', 2, '... we should have 2 rows');
+cmp_ok(scalar @{$tuple_status}, '==', 2, '... we should have 2 tuple_status');
+
+ok(eq_array(
+ $rows,
+ [ [1, 'a', 'b', 'c'], [2, 'a', 'b', 'c'] ]
+ ),
+ '... rows should match'
+);
+
+ok(eq_array(
+ $tuple_status,
+ [1, 1]
+ ),
+ '... tuple_status should match'
+);
+
+# -----------------------------------------------
+# --- ArrayTupleFetch sth tests ---
+
+my $fetch_sth = $dbh->prepare("foo", {
+ rows => [ map { [ $_,'x','y','z' ] } 7..9 ],
+ NUM_OF_FIELDS => 4
+ });
+
+isa_ok($fetch_sth, "DBI::st");
+
+$fetch_sth->execute();
+
+@$rows = ();
+
+ok( $sth->execute_array({
+ ArrayTupleFetch => $fetch_sth,
+ ArrayTupleStatus => $tuple_status,
+ }), '... execute_array should return true');
+
+cmp_ok(scalar @{$rows}, '==', 3, '... we should have 3 rows');
+cmp_ok(scalar @{$tuple_status}, '==', 3, '... we should have 3 tuple_status');
+
+ok(eq_array(
+ $rows,
+ [ [7, 'x', 'y', 'z'], [8, 'x', 'y', 'z'], [9, 'x', 'y', 'z'] ]
+ ),
+ '... rows should match'
+);
+
+ok(eq_array(
+ $tuple_status,
+ [1, 1, 1]
+ ),
+ '... tuple status should match'
+);
+
+# -----------------------------------------------
+# --- error detection tests ---
+
+$sth->{RaiseError} = 0;
+$sth->{PrintError} = 0;
+
+ok(!defined $sth->execute_array( { ArrayTupleStatus => $tuple_status }, [1],[2]), '... execute_array should return undef');
+is($sth->errstr, '2 bind values supplied but 4 expected', '... errstr is as expected');
+
+ok(!defined $sth->execute_array( { ArrayTupleStatus => { } }, [ 1, 2, 3 ]), '... execute_array should return undef');
+is( $sth->errstr, 'ArrayTupleStatus attribute must be an arrayref', '... errstr is as expected');
+
+ok(!defined $sth->execute_array( { ArrayTupleStatus => $tuple_status }, 1,{},3,4), '... execute_array should return undef');
+is( $sth->errstr, 'Value for parameter 2 must be a scalar or an arrayref, not a HASH', '... errstr is as expected');
+
+ok(!defined $sth->bind_param_array(":foo", [ qw(a b c) ]), '... bind_param_array should return undef');
+is( $sth->errstr, "Can't use named placeholder ':foo' for non-driver supported bind_param_array", '... errstr is as expected');
+
+$dbh->disconnect;
+
+1;
diff --git a/t/16destroy.t b/t/16destroy.t
new file mode 100644
index 0000000..a2945c4
--- /dev/null
+++ b/t/16destroy.t
@@ -0,0 +1,147 @@
+#!perl -w
+
+use strict;
+
+use Test::More tests => 20;
+
+BEGIN{ use_ok( 'DBI' ) }
+
+my $expect_active;
+
+## main Test Driver Package
+{
+ package DBD::Test;
+
+ use strict;
+ use warnings;
+
+ my $drh = undef;
+
+ sub driver {
+ return $drh if $drh;
+ my ($class, $attr) = @_;
+ $class = "${class}::dr";
+ ($drh) = DBI::_new_drh($class, {
+ Name => 'Test',
+ Version => '1.0',
+ }, 77 );
+ return $drh;
+ }
+
+ sub CLONE { undef $drh }
+}
+
+## Test Driver
+{
+ package DBD::Test::dr;
+
+ use warnings;
+ use Test::More;
+
+ sub connect { # normally overridden, but a handy default
+ my($drh, $dbname, $user, $auth, $attrs)= @_;
+ my ($outer, $dbh) = DBI::_new_dbh($drh);
+ $dbh->STORE(Active => 1);
+ $dbh->STORE(AutoCommit => 1);
+ $dbh->STORE( $_ => $attrs->{$_}) for keys %$attrs;
+ return $outer;
+ }
+
+ $DBD::Test::dr::imp_data_size = 0;
+ cmp_ok($DBD::Test::dr::imp_data_size, '==', 0, '... check DBD::Test::dr::imp_data_size to avoid typo');
+}
+
+## Test db package
+{
+ package DBD::Test::db;
+
+ use strict;
+ use warnings;
+ use Test::More;
+
+ $DBD::Test::db::imp_data_size = 0;
+ cmp_ok($DBD::Test::db::imp_data_size, '==', 0, '... check DBD::Test::db::imp_data_size to avoid typo');
+
+ sub STORE {
+ my ($dbh, $attrib, $value) = @_;
+ # would normally validate and only store known attributes
+ # else pass up to DBI to handle
+ if ($attrib eq 'AutoCommit') {
+ # convert AutoCommit values to magic ones to let DBI
+ # know that the driver has 'handled' the AutoCommit attribute
+ $value = ($value) ? -901 : -900;
+ }
+ return $dbh->{$attrib} = $value if $attrib =~ /^examplep_/;
+ return $dbh->SUPER::STORE($attrib, $value);
+ }
+
+ sub DESTROY {
+ if ($expect_active < 0) { # inside child
+ my $self = shift;
+ exit $self->FETCH('Active') || 0 unless $^O eq 'MSWin32';
+
+ # On Win32, the forked child is actually a thread. So don't exit,
+ # and report failure directly.
+ fail 'Child should be inactive on DESTROY' if $self->FETCH('Active');
+ } else {
+ return $expect_active
+ ? ok( shift->FETCH('Active'), 'Should be active in DESTROY')
+ : ok( !shift->FETCH('Active'), 'Should not be active in DESTROY');
+ }
+ }
+}
+
+my $dsn = 'dbi:ExampleP:dummy';
+
+$INC{'DBD/Test.pm'} = 'dummy'; # required to fool DBI->install_driver()
+ok my $drh = DBI->install_driver('Test'), 'Install test driver';
+
+NOSETTING: {
+ # Try defaults.
+ ok my $dbh = $drh->connect, 'Connect to test driver';
+ ok $dbh->{Active}, 'Should start active';
+ $expect_active = 1;
+}
+
+IAD: {
+ # Try InactiveDestroy.
+ ok my $dbh = $drh->connect($dsn, '', '', { InactiveDestroy => 1 }),
+ 'Create with ActiveDestroy';
+ ok $dbh->{InactiveDestroy}, 'InactiveDestroy should be set';
+ ok $dbh->{Active}, 'Should start active';
+ $expect_active = 0;
+}
+
+AIAD: {
+ # Try AutoInactiveDestroy.
+ ok my $dbh = $drh->connect($dsn, '', '', { AutoInactiveDestroy => 1 }),
+ 'Create with AutoInactiveDestroy';
+ ok $dbh->{AutoInactiveDestroy}, 'InactiveDestroy should be set';
+ ok $dbh->{Active}, 'Should start active';
+ $expect_active = 1;
+}
+
+FORK: {
+ # Try AutoInactiveDestroy and fork.
+ ok my $dbh = $drh->connect($dsn, '', '', { AutoInactiveDestroy => 1 }),
+ 'Create with AutoInactiveDestroy again';
+ ok $dbh->{AutoInactiveDestroy}, 'InactiveDestroy should be set';
+ ok $dbh->{Active}, 'Should start active';
+
+ my $pid = eval { fork() };
+ if (not defined $pid) {
+ chomp $@;
+ my $msg = "AutoInactiveDestroy destroy test skipped";
+ diag "$msg because $@\n";
+ pass $msg; # in lieu of the child status test
+ }
+ elsif ($pid) {
+ # parent.
+ $expect_active = 1;
+ wait;
+ ok $? == 0, 'Child should be inactive on DESTROY';
+ } else {
+ # child.
+ $expect_active = -1;
+ }
+}
diff --git a/t/19fhtrace.t b/t/19fhtrace.t
new file mode 100644
index 0000000..d310db4
--- /dev/null
+++ b/t/19fhtrace.t
@@ -0,0 +1,306 @@
+#!perl -w
+# vim:sw=4:ts=8
+
+use strict;
+
+use Test::More tests => 27;
+
+## ----------------------------------------------------------------------------
+## 09trace.t
+## ----------------------------------------------------------------------------
+#
+## ----------------------------------------------------------------------------
+
+BEGIN {
+ use_ok( 'DBI' );
+}
+
+$|=1;
+
+our $fancylogfn = "fancylog$$.log";
+our $trace_file = "dbitrace$$.log";
+
+# Clean up when we're done.
+END { 1 while unlink $fancylogfn;
+ 1 while unlink $trace_file; };
+
+package PerlIO::via::TraceDBI;
+
+our $logline;
+
+sub OPEN {
+ return 1;
+}
+
+sub PUSHED
+{
+ my ($class,$mode,$fh) = @_;
+ # When writing we buffer the data
+ my $buf = '';
+ return bless \$buf,$class;
+}
+
+sub FILL
+{
+ my ($obj,$fh) = @_;
+ return $logline;
+}
+
+sub READLINE
+{
+ my ($obj,$fh) = @_;
+ return $logline;
+}
+
+sub WRITE
+{
+ my ($obj,$buf,$fh) = @_;
+# print "\n*** WRITING $buf\n";
+ $logline = $buf;
+ return length($buf);
+}
+
+sub FLUSH
+{
+ my ($obj,$fh) = @_;
+ return 0;
+}
+
+sub CLOSE {
+# print "\n*** CLOSING!!!\n";
+ $logline = "**** CERRADO! ***";
+ return -1;
+}
+
+1;
+
+package PerlIO::via::MyFancyLogLayer;
+
+sub OPEN {
+ my ($obj, $path, $mode, $fh) = @_;
+ $$obj = $path;
+ return 1;
+}
+
+sub PUSHED
+{
+ my ($class,$mode,$fh) = @_;
+ # When writing we buffer the data
+ my $logger;
+ return bless \$logger,$class;
+}
+
+sub WRITE
+{
+ my ($obj,$buf,$fh) = @_;
+ $$obj->log($buf);
+ return length($buf);
+}
+
+sub FLUSH
+{
+ my ($obj,$fh) = @_;
+ return 0;
+}
+
+sub CLOSE {
+ my $self = shift;
+ $$self->close();
+ return 0;
+}
+
+1;
+
+package MyFancyLogger;
+
+use Symbol qw(gensym);
+
+sub new
+{
+ my $self = {};
+ my $fh = gensym();
+ open $fh, '>', $fancylogfn;
+ $self->{_fh} = $fh;
+ $self->{_buf} = '';
+ return bless $self, shift;
+}
+
+sub log
+{
+ my $self = shift;
+ my $fh = $self->{_fh};
+ $self->{_buf} .= shift;
+ print $fh "At ", scalar localtime(), ':', $self->{_buf}, "\n" and
+ $self->{_buf} = ''
+ if $self->{_buf}=~tr/\n//;
+}
+
+sub close {
+ my $self = shift;
+ return unless exists $self->{_fh};
+ my $fh = $self->{_fh};
+ print $fh "At ", scalar localtime(), ':', $self->{_buf}, "\n" and
+ $self->{_buf} = ''
+ if $self->{_buf};
+ close $fh;
+ delete $self->{_fh};
+}
+
+1;
+
+package main;
+
+## ----------------------------------------------------------------------------
+# Connect to the example driver.
+
+my $dbh = DBI->connect('dbi:ExampleP:dummy', '', '',
+ { PrintError => 0,
+ RaiseError => 1,
+ PrintWarn => 1,
+ });
+isa_ok( $dbh, 'DBI::db' );
+
+# Clean up when we're done.
+END { $dbh->disconnect if $dbh };
+
+## ----------------------------------------------------------------------------
+# Check the database handle attributes.
+
+cmp_ok($dbh->{TraceLevel}, '==', $DBI::dbi_debug & 0xF, '... checking TraceLevel attribute');
+
+1 while unlink $trace_file;
+
+my $tracefd;
+## ----------------------------------------------------------------------------
+# First use regular filehandle
+open $tracefd, '>>', $trace_file;
+
+my $oldfd = select($tracefd);
+$| = 1;
+select $oldfd;
+
+ok(-f $trace_file, '... regular fh: trace file successfully created');
+
+$dbh->trace(2, $tracefd);
+ok( 1, '... regular fh: filehandle successfully set');
+
+#
+# read current size of file
+#
+my $filesz = (stat $tracefd)[7];
+$dbh->trace_msg("First logline\n", 1);
+#
+# read new file size and verify its different
+#
+my $newfsz = (stat $tracefd)[7];
+SKIP: {
+ skip 'on VMS autoflush using select does not work', 1 if $^O eq 'VMS';
+ ok(($filesz != $newfsz), '... regular fh: trace_msg');
+}
+
+$dbh->trace(undef, "STDOUT"); # close $trace_file
+ok(-f $trace_file, '... regular fh: file successfully changed');
+
+$filesz = (stat $tracefd)[7];
+$dbh->trace_msg("Next logline\n");
+#
+# read new file size and verify its same
+#
+$newfsz = (stat $tracefd)[7];
+ok(($filesz == $newfsz), '... regular fh: trace_msg after changing trace output');
+
+#1 while unlink $trace_file;
+
+$dbh->trace(0); # disable trace
+
+{ # Open trace to glob. started failing in perl-5.10
+ my $tf = "foo.log";
+ 1 while unlink $tf;
+ 1 while unlink "*main::FOO";
+ 1 while unlink "*main::STDERR";
+ is (-f $tf, undef, "Tracefile removed");
+ ok (open (FOO, ">", $tf), "Tracefile FOO opened");
+ ok (-f $tf, "Tracefile created");
+ DBI->trace (1, *FOO);
+ is (-f "*main::FOO", undef, "Regression test");
+ DBI->trace_msg ("foo\n", 1);
+ DBI->trace (0, *STDERR);
+ close FOO;
+ open my $fh, "<", $tf;
+ is ((<$fh>)[-1], "foo\n", "Traced message");
+ close $fh;
+ is (-f "*main::STDERR", undef, "Regression test");
+ 1 while unlink $tf;
+ }
+
+SKIP: {
+ eval { require 5.008; };
+ skip "Layered I/O not available in Perl $^V", 13
+ if $@;
+## ----------------------------------------------------------------------------
+# Then use layered filehandle
+#
+open TRACEFD, '+>:via(TraceDBI)', 'layeredtrace.out';
+print TRACEFD "*** Test our layer\n";
+my $result = <TRACEFD>;
+is $result, "*** Test our layer\n", "... layered fh: file is layered: $result\n";
+
+$dbh->trace(1, \*TRACEFD);
+ok( 1, '... layered fh: filehandle successfully set');
+
+$dbh->trace_msg("Layered logline\n", 1);
+
+$result = <TRACEFD>;
+is $result, "Layered logline\n", "... layered fh: trace_msg: $result\n";
+
+$dbh->trace(1, "STDOUT"); # close $trace_file
+$result = <TRACEFD>;
+is $result, "Layered logline\n", "... layered fh: close doesn't close: $result\n";
+
+$dbh->trace_msg("Next logline\n", 1);
+$result = <TRACEFD>;
+is $result, "Layered logline\n", "... layered fh: trace_msg after change trace output: $result\n";
+
+## ----------------------------------------------------------------------------
+# Then use scalar filehandle
+#
+my $tracestr;
+open TRACEFD, '+>:scalar', \$tracestr;
+print TRACEFD "*** Test our layer\n";
+ok 1, "... scalar trace: file is layered: $tracestr\n";
+
+$dbh->trace(1, \*TRACEFD);
+ok 1, '... scalar trace: filehandle successfully set';
+
+$dbh->trace_msg("Layered logline\n", 1);
+ok 1, "... scalar trace: $tracestr\n";
+
+$dbh->trace(1, "STDOUT"); # close $trace_file
+ok 1, "... scalar trace: close doesn't close: $tracestr\n";
+
+$dbh->trace_msg("Next logline\n", 1);
+ok 1, "... scalar trace: after change trace output: $tracestr\n";
+
+## ----------------------------------------------------------------------------
+# Then use fancy logger
+#
+open my $fh, '>:via(MyFancyLogLayer)', MyFancyLogger->new();
+
+$dbh->trace('SQL', $fh);
+
+$dbh->trace_msg("Layered logline\n", 1);
+ok 1, "... logger: trace_msg\n";
+
+$dbh->trace(1, "STDOUT"); # close $trace_file
+ok 1, "... logger: close doesn't close\n";
+
+$dbh->trace_msg("Next logline\n", 1);
+ok 1, "... logger: trace_msg after change trace output\n";
+
+close $fh;
+
+}
+
+1;
+
+# end
diff --git a/t/20meta.t b/t/20meta.t
new file mode 100644
index 0000000..a8d609e
--- /dev/null
+++ b/t/20meta.t
@@ -0,0 +1,32 @@
+#!perl -w
+
+use strict;
+use Test::More tests => 8;
+
+$|=1;
+$^W=1;
+
+BEGIN { use_ok( 'DBI', ':sql_types' ) }
+BEGIN { use_ok( 'DBI::DBD::Metadata' ) } # just to check for syntax errors etc
+
+my $dbh = DBI->connect("dbi:ExampleP:.","","", { FetchHashKeyName => 'NAME_lc' })
+ or die "Unable to connect to ExampleP driver: $DBI::errstr";
+
+isa_ok($dbh, 'DBI::db');
+#$dbh->trace(3);
+
+#use Data::Dumper;
+#print Dumper($dbh->type_info_all);
+#print Dumper($dbh->type_info);
+#print Dumper($dbh->type_info(DBI::SQL_INTEGER));
+
+my @ti = $dbh->type_info;
+ok(@ti>0);
+
+is($dbh->type_info(SQL_INTEGER)->{DATA_TYPE}, SQL_INTEGER);
+is($dbh->type_info(SQL_INTEGER)->{TYPE_NAME}, 'INTEGER');
+
+is($dbh->type_info(SQL_VARCHAR)->{DATA_TYPE}, SQL_VARCHAR);
+is($dbh->type_info(SQL_VARCHAR)->{TYPE_NAME}, 'VARCHAR');
+
+1;
diff --git a/t/30subclass.t b/t/30subclass.t
new file mode 100644
index 0000000..3217a9e
--- /dev/null
+++ b/t/30subclass.t
@@ -0,0 +1,182 @@
+#!perl -w
+
+use strict;
+
+$|=1;
+$^W=1;
+
+my $calls = 0;
+my %my_methods;
+
+
+# =================================================
+# Example code for sub classing the DBI.
+#
+# Note that the extra ::db and ::st classes must be set up
+# as sub classes of the corresponding DBI classes.
+#
+# This whole mechanism is new and experimental - it may change!
+
+package MyDBI;
+@MyDBI::ISA = qw(DBI);
+
+# the MyDBI::dr::connect method is NOT called!
+# you can either override MyDBI::connect()
+# or use MyDBI::db::connected()
+
+package MyDBI::db;
+@MyDBI::db::ISA = qw(DBI::db);
+
+sub prepare {
+ my($dbh, @args) = @_;
+ ++$my_methods{prepare};
+ ++$calls;
+ my $sth = $dbh->SUPER::prepare(@args);
+ return $sth;
+}
+
+
+package MyDBI::st;
+@MyDBI::st::ISA = qw(DBI::st);
+
+sub fetch {
+ my($sth, @args) = @_;
+ ++$my_methods{fetch};
+ ++$calls;
+ # this is just to trigger (re)STORE on exit to test that the STORE
+ # doesn't clear any erro condition
+ local $sth->{Taint} = 0;
+ my $row = $sth->SUPER::fetch(@args);
+ if ($row) {
+ # modify fetched data as an example
+ $row->[1] = lc($row->[1]);
+
+ # also demonstrate calling set_err()
+ return $sth->set_err(1,"Don't be so negative",undef,"fetch")
+ if $row->[0] < 0;
+ # ... and providing alternate results
+ # (although typically would trap and hide and error from SUPER::fetch)
+ return $sth->set_err(2,"Don't exagerate",undef, undef, [ 42,"zz",0 ])
+ if $row->[0] > 42;
+ }
+ return $row;
+}
+
+
+# =================================================
+package main;
+
+use Test::More tests => 43;
+
+BEGIN {
+ use_ok( 'DBI' );
+}
+
+my $tmp;
+
+#DBI->trace(2);
+my $dbh = MyDBI->connect("dbi:Sponge:foo","","", {
+ PrintError => 0,
+ RaiseError => 1,
+ CompatMode => 1, # just for clone test
+});
+isa_ok($dbh, 'MyDBI::db');
+is($dbh->{CompatMode}, 1);
+undef $dbh;
+
+$dbh = DBI->connect("dbi:Sponge:foo","","", {
+ PrintError => 0,
+ RaiseError => 1,
+ RootClass => "MyDBI",
+ CompatMode => 1, # just for clone test
+ dbi_foo => 1, # just to help debugging clone etc
+});
+isa_ok( $dbh, 'MyDBI::db');
+is($dbh->{CompatMode}, 1);
+
+#$dbh->trace(5);
+my $sth = $dbh->prepare("foo",
+ # data for DBD::Sponge to return via fetch
+ { rows => [
+ [ 40, "AAA", 9 ],
+ [ 41, "BB", 8 ],
+ [ -1, "C", 7 ],
+ [ 49, "DD", 6 ]
+ ],
+ }
+);
+
+is($calls, 1);
+isa_ok($sth, 'MyDBI::st');
+
+my $row = $sth->fetch;
+is($calls, 2);
+is($row->[1], "aaa");
+
+$row = $sth->fetch;
+is($calls, 3);
+is($row->[1], "bb");
+
+is($DBI::err, undef);
+$row = eval { $sth->fetch };
+my $eval_err = $@;
+is(!defined $row, 1);
+is(substr($eval_err,0,50), "DBD::Sponge::st fetch failed: Don't be so negative");
+
+#$sth->trace(5);
+#$sth->{PrintError} = 1;
+$sth->{RaiseError} = 0;
+$row = eval { $sth->fetch };
+isa_ok($row, 'ARRAY');
+is($row->[0], 42);
+is($DBI::err, 2);
+like($DBI::errstr, qr/Don't exagerate/);
+is($@ =~ /Don't be so negative/, $@);
+
+
+my $dbh2 = $dbh->clone;
+isa_ok( $dbh2, 'MyDBI::db', "Clone A" );
+is($dbh2 != $dbh, 1);
+is($dbh2->{CompatMode}, 1);
+
+my $dbh3 = $dbh->clone({});
+isa_ok( $dbh3, 'MyDBI::db', 'Clone B' );
+is($dbh3 != $dbh, 1);
+is($dbh3 != $dbh2, 1);
+isa_ok( $dbh3, 'MyDBI::db');
+is($dbh3->{CompatMode}, 1);
+
+my $dbh2c = $dbh2->clone;
+isa_ok( $dbh2c, 'MyDBI::db', "Clone of clone A" );
+is($dbh2c != $dbh2, 1);
+is($dbh2c->{CompatMode}, 1);
+
+my $dbh3c = $dbh3->clone({ CompatMode => 0 });
+isa_ok( $dbh3c, 'MyDBI::db', 'Clone of clone B' );
+is((grep { $dbh3c == $_ } $dbh, $dbh2, $dbh3), 0);
+isa_ok( $dbh3c, 'MyDBI::db');
+ok(!$dbh3c->{CompatMode});
+
+$tmp = $dbh->sponge_test_installed_method('foo','bar');
+isa_ok( $tmp, "ARRAY", "installed method" );
+is_deeply( $tmp, [qw( foo bar )] );
+$tmp = eval { $dbh->sponge_test_installed_method() };
+is(!$tmp, 1);
+is($dbh->err, 42);
+is($dbh->errstr, "not enough parameters");
+
+
+$dbh = eval { DBI->connect("dbi:Sponge:foo","","", {
+ RootClass => 'nonesuch1', PrintError => 0, RaiseError => 0, });
+};
+ok( !defined($dbh), "Failed connect #1" );
+is(substr($@,0,25), "Can't locate nonesuch1.pm");
+
+$dbh = eval { nonesuch2->connect("dbi:Sponge:foo","","", {
+ PrintError => 0, RaiseError => 0, });
+};
+ok( !defined($dbh), "Failed connect #2" );
+is(substr($@,0,36), q{Can't locate object method "connect"});
+
+print "@{[ %my_methods ]}\n";
+1;
diff --git a/t/31methcache.t b/t/31methcache.t
new file mode 100644
index 0000000..2ffd0a5
--- /dev/null
+++ b/t/31methcache.t
@@ -0,0 +1,153 @@
+#!perl -w
+#
+# check that the inner-method lookup cache works
+# (or rather, check that it doesn't cache things when it shouldn't)
+
+BEGIN { eval "use threads;" } # Must be first
+my $use_threads_err = $@;
+use Config qw(%Config);
+# With this test code and threads, 5.8.1 has issues with freeing freed
+# scalars, while 5.8.9 doesn't; I don't know about in-between - DAPM
+my $has_threads = $Config{useithreads};
+die $use_threads_err if $has_threads && $use_threads_err;
+
+
+use strict;
+
+$|=1;
+$^W=1;
+
+
+
+use Test::More tests => 49;
+
+BEGIN {
+ use_ok( 'DBI' );
+}
+
+sub new_handle {
+ my $dbh = DBI->connect("dbi:Sponge:foo","","", {
+ PrintError => 0,
+ RaiseError => 1,
+ });
+
+ my $sth = $dbh->prepare("foo",
+ # data for DBD::Sponge to return via fetch
+ { rows =>
+ [
+ [ "row0" ],
+ [ "row1" ],
+ [ "row2" ],
+ [ "row3" ],
+ [ "row4" ],
+ [ "row5" ],
+ [ "row6" ],
+ ],
+ }
+ );
+
+ return ($dbh, $sth);
+}
+
+
+sub Foo::local1 { [ "local1" ] };
+sub Foo::local2 { [ "local2" ] };
+
+
+my $fetch_hook;
+{
+ package Bar;
+ @Bar::ISA = qw(DBD::_::st);
+ sub fetch { &$fetch_hook };
+}
+
+sub run_tests {
+ my ($desc, $dbh, $sth) = @_;
+ my $row = $sth->fetch;
+ is($row->[0], "row0", "$desc row0");
+
+ {
+ # replace CV slot
+ no warnings 'redefine';
+ local *DBD::Sponge::st::fetch = sub { [ "local0" ] };
+ $row = $sth->fetch;
+ is($row->[0], "local0", "$desc local0");
+ }
+ $row = $sth->fetch;
+ is($row->[0], "row1", "$desc row1");
+
+ {
+ # replace GP
+ local *DBD::Sponge::st::fetch = *Foo::local1;
+ $row = $sth->fetch;
+ is($row->[0], "local1", "$desc local1");
+ }
+ $row = $sth->fetch;
+ is($row->[0], "row2", "$desc row2");
+
+ {
+ # replace GV
+ local $DBD::Sponge::st::{fetch} = *Foo::local2;
+ $row = $sth->fetch;
+ is($row->[0], "local2", "$desc local2");
+ }
+ $row = $sth->fetch;
+ is($row->[0], "row3", "$desc row3");
+
+ {
+ # @ISA = NoSuchPackage
+ local $DBD::Sponge::st::{fetch};
+ local @DBD::Sponge::st::ISA = qw(NoSuchPackage);
+ eval { local $SIG{__WARN__} = sub {}; $row = $sth->fetch };
+ like($@, qr/Can't locate DBI object method/, "$desc locate DBI object");
+ }
+ $row = $sth->fetch;
+ is($row->[0], "row4", "$desc row4");
+
+ {
+ # @ISA = Bar
+ $fetch_hook = \&DBD::Sponge::st::fetch;
+ local $DBD::Sponge::st::{fetch};
+ local @DBD::Sponge::st::ISA = qw(Bar);
+ $row = $sth->fetch;
+ is($row->[0], "row5", "$desc row5");
+ $fetch_hook = sub { [ "local3" ] };
+ $row = $sth->fetch;
+ is($row->[0], "local3", "$desc local3");
+ }
+ $row = $sth->fetch;
+ is($row->[0], "row6", "$desc row6");
+}
+
+run_tests("plain", new_handle());
+
+
+SKIP: {
+ skip "no threads / perl < 5.8.9", 12 unless $has_threads;
+ # only enable this when handles are allowed to be shared across threads
+ #{
+ # my @h = new_handle();
+ # threads->new(sub { run_tests("threads", @h) })->join;
+ #}
+ threads->new(sub { run_tests("threads-h", new_handle()) })->join;
+};
+
+# using weaken attaches magic to the CV; see whether this interferes
+# with the cache magic
+
+use Scalar::Util qw(weaken);
+my $fetch_ref = \&DBI::st::fetch;
+weaken $fetch_ref;
+run_tests("magic", new_handle());
+
+SKIP: {
+ skip "no threads / perl < 5.8.9", 12 unless $has_threads;
+ # only enable this when handles are allowed to be shared across threads
+ #{
+ # my @h = new_handle();
+ # threads->new(sub { run_tests("threads", @h) })->join;
+ #}
+ threads->new(sub { run_tests("magic threads-h", new_handle()) })->join;
+};
+
+1;
diff --git a/t/35thrclone.t b/t/35thrclone.t
new file mode 100644
index 0000000..b2678e9
--- /dev/null
+++ b/t/35thrclone.t
@@ -0,0 +1,81 @@
+#!perl -w
+$|=1;
+
+# --- Test DBI support for threads created after the DBI was loaded
+
+BEGIN { eval "use threads;" } # Must be first
+my $use_threads_err = $@;
+
+use strict;
+use Config qw(%Config);
+use Test::More;
+
+BEGIN {
+ if (!$Config{useithreads} || $] < 5.008001) {
+ plan skip_all => "this $^O perl $] not supported for DBI iThreads";
+ }
+ die $use_threads_err if $use_threads_err; # need threads
+}
+
+my $threads = 4;
+plan tests => 4 + 4 * $threads;
+
+{
+ package threads_sub;
+ use base qw(threads);
+}
+
+use_ok('DBI');
+
+$DBI::PurePerl = $DBI::PurePerl; # just to silence used only once warning
+$DBI::neat_maxlen = 12345;
+cmp_ok($DBI::neat_maxlen, '==', 12345, '... assignment of neat_maxlen was successful');
+
+my @connect_args = ("dbi:ExampleP:", '', '');
+
+my $dbh_parent = DBI->connect_cached(@connect_args);
+isa_ok( $dbh_parent, 'DBI::db' );
+
+# this our function for the threads to run
+
+sub testing {
+ cmp_ok($DBI::neat_maxlen, '==', 12345, '... DBI::neat_maxlen still holding its value');
+
+ my $dbh = DBI->connect_cached(@connect_args);
+ isa_ok( $dbh, 'DBI::db' );
+ isnt($dbh, $dbh_parent, '... new $dbh is not the same instance as $dbh_parent');
+
+ SKIP: {
+ # skip seems broken with threads (5.8.3)
+ # skip "Kids attribute not supported under DBI::PurePerl", 1 if $DBI::PurePerl;
+
+ cmp_ok($dbh->{Driver}->{Kids}, '==', 1, '... the Driver has one Kid')
+ unless $DBI::PurePerl && ok(1);
+ }
+
+ # RT #77137: a thread created from a thread was crashing the
+ # interpreter
+
+ threads->new(sub {})->join();
+}
+
+# load up the threads
+
+my @thr;
+push @thr, threads_sub->create( \&testing )
+ or die "thread->create failed ($!)"
+ foreach (1..$threads);
+
+# join all the threads
+
+foreach my $thread (@thr) {
+ $thread->join;
+
+ # provide a little insurance against thread scheduling issues (hopefully)
+ # http://www.nntp.perl.org/group/perl.cpan.testers/2009/06/msg4369660.html
+ eval { select undef, undef, undef, 0.2 };
+}
+
+pass('... all tests have passed');
+
+1;
diff --git a/t/40profile.t b/t/40profile.t
new file mode 100644
index 0000000..5cb0023
--- /dev/null
+++ b/t/40profile.t
@@ -0,0 +1,485 @@
+#!perl -w
+$|=1;
+
+#
+# test script for DBI::Profile
+#
+
+use strict;
+
+use Config;
+use DBI::Profile;
+use DBI qw(dbi_time);
+use Data::Dumper;
+use File::Spec;
+use Storable qw(dclone);
+
+use Test::More;
+
+BEGIN {
+ plan skip_all => "profiling not supported for DBI::PurePerl"
+ if $DBI::PurePerl;
+
+ # tie methods (STORE/FETCH etc) get called different number of times
+ plan skip_all => "test results assume perl >= 5.8.2"
+ if $] <= 5.008001;
+
+ # clock instability on xen systems is a reasonably common cause of failure
+ # http://www.nntp.perl.org/group/perl.cpan.testers/2009/05/msg3828158.html
+ # so we'll skip automated testing on those systems
+ plan skip_all => "skipping profile tests on xen (due to clock instability)"
+ if $Config{osvers} =~ /xen/ # eg 2.6.18-4-xen-amd64
+ and $ENV{AUTOMATED_TESTING};
+
+ plan tests => 60;
+}
+
+$Data::Dumper::Indent = 1;
+$Data::Dumper::Terse = 1;
+
+# log file to store profile results
+my $LOG_FILE = "profile$$.log";
+my $orig_dbi_debug = $DBI::dbi_debug;
+DBI->trace($DBI::dbi_debug, $LOG_FILE);
+END {
+ return if $orig_dbi_debug;
+ 1 while unlink $LOG_FILE;
+}
+
+
+print "Test enabling the profile\n";
+
+# make sure profiling starts disabled
+my $dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+ok($dbh, 'connect');
+ok(!$dbh->{Profile} && !$ENV{DBI_PROFILE}, 'Profile and DBI_PROFILE not set');
+
+
+# can turn it on after the fact using a path number
+$dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+$dbh->{Profile} = "4";
+is_deeply sanitize_tree($dbh->{Profile}), bless {
+ 'Path' => [ '!MethodName' ],
+} => 'DBI::Profile';
+
+# using a package name
+$dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+$dbh->{Profile} = "/DBI::Profile";
+is_deeply sanitize_tree($dbh->{Profile}), bless {
+ 'Path' => [ ],
+} => 'DBI::Profile';
+
+# using a combined path and name
+$dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+$dbh->{Profile} = "20/DBI::Profile";
+is_deeply sanitize_tree($dbh->{Profile}), bless {
+ 'Path' => [ '!MethodName', '!Caller2' ],
+} => 'DBI::Profile';
+
+my $t_file = __FILE__;
+$dbh->do("set foo=1"); my $line = __LINE__;
+my $expected_caller = "40profile.t line $line";
+$expected_caller .= " via ${1}40profile.t line 4"
+ if $0 =~ /(zv\w+_)/;
+print Dumper($dbh->{Profile});
+is_deeply sanitize_tree($dbh->{Profile}), bless {
+ 'Path' => [ '!MethodName', '!Caller2' ],
+ 'Data' => { 'do' => {
+ $expected_caller => [ 1, 0, 0, 0, 0, 0, 0 ]
+ } }
+} => 'DBI::Profile'
+ or warn Dumper $dbh->{Profile};
+
+
+# can turn it on at connect
+$dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1, Profile=>6 });
+is_deeply $dbh->{Profile}{Path}, [ '!Statement', '!MethodName' ];
+cmp_ok(keys %{ $dbh->{Profile}{Data} }, '==', 1, 'on at connect, 1 key');
+cmp_ok(keys %{ $dbh->{Profile}{Data}{""} }, '>=', 1, 'on at connect, 1 key'); # at least STORE
+ok(ref $dbh->{Profile}{Data}{""}{STORE}, 'STORE is ref');
+
+print "dbi_profile\n";
+# Try to avoid rounding problem on double precision systems
+# $got->[5] = '1150962858.01596498'
+# $expected->[5] = '1150962858.015965'
+# by treating as a string (because is_deeply stringifies)
+my $t1 = DBI::dbi_time() . "";
+my $dummy_statement = "Hi mom";
+my $dummy_methname = "my_method_name";
+my $leaf = dbi_profile($dbh, $dummy_statement, $dummy_methname, $t1, $t1 + 1);
+print Dumper($dbh->{Profile});
+cmp_ok(keys %{ $dbh->{Profile}{Data} }, '==', 2, 'avoid rounding, 1 key');
+cmp_ok(keys %{ $dbh->{Profile}{Data}{$dummy_statement} }, '==', 1,
+ 'avoid rounding, 1 dummy statement');
+is(ref($dbh->{Profile}{Data}{$dummy_statement}{$dummy_methname}), 'ARRAY',
+ 'dummy method name is array');
+
+ok $leaf, "should return ref to leaf node";
+is ref $leaf, 'ARRAY', "should return ref to leaf node";
+
+my $mine = $dbh->{Profile}{Data}{$dummy_statement}{$dummy_methname};
+
+is $leaf, $mine, "should return ref to correct leaf node";
+
+print "@$mine\n";
+is_deeply $mine, [ 1, 1, 1, 1, 1, $t1, $t1 ];
+
+my $t2 = DBI::dbi_time() . "";
+dbi_profile($dbh, $dummy_statement, $dummy_methname, $t2, $t2 + 2);
+print "@$mine\n";
+is_deeply $mine, [ 2, 3, 1, 1, 2, $t1, $t2 ];
+
+
+print "Test collected profile data\n";
+
+$dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1, Profile=>2 });
+# do a (hopefully) measurable amount of work
+my $sql = "select mode,size,name from ?";
+my $sth = $dbh->prepare($sql);
+for my $loop (1..50) { # enough work for low-res timers or v.fast cpus
+ $sth->execute(".");
+ while ( my $hash = $sth->fetchrow_hashref ) {}
+}
+$dbh->do("set foo=1");
+
+print Dumper($dbh->{Profile});
+
+# check that the proper key was set in Data
+my $data = $dbh->{Profile}{Data}{$sql};
+ok($data, 'profile data');
+is(ref $data, 'ARRAY', 'ARRAY ref');
+ok(@$data == 7, '7 elements');
+ok((grep { defined($_) } @$data) == 7, 'all 7 defined');
+ok((grep { DBI::looks_like_number($_) } @$data) == 7, 'all 7 numeric');
+my ($count, $total, $first, $shortest, $longest, $time1, $time2) = @$data;
+ok($count > 3, 'count is 3');
+ok($total > $first, ' total > first');
+ok($total > $longest, 'total > longest') or
+ warn "total $total > longest $longest: failed\n";
+ok($longest > 0, 'longest > 0') or
+ warn "longest $longest > 0: failed\n"; # XXX theoretically not reliable
+ok($longest > $shortest, 'longest > shortest');
+ok($time1 >= $^T, 'time1 later than start time');
+ok($time2 >= $^T, 'time2 later than start time');
+ok($time1 <= $time2, 'time1 <= time2');
+my $next = int(dbi_time()) + 1;
+ok($next > $time1, 'next > time1') or
+ warn "next $next > first $time1: failed\n";
+ok($next > $time2, 'next > time2') or
+ warn "next $next > last $time2: failed\n";
+if ($shortest < 0) {
+ my $sys = "$Config{archname} $Config{osvers}"; # ie sparc-linux 2.4.20-2.3sparcsmp
+ warn <<EOT;
+Time went backwards at some point during the test on this $sys system!
+Perhaps you have time sync software (like NTP) that adjusted the clock
+by more than $shortest seconds during the test.
+Also some multiprocessor systems, and some virtualization systems can exhibit
+this kind of clock behaviour. Please retry.
+EOT
+ # don't treat small negative values as failure
+ $shortest = 0 if $shortest > -0.008;
+}
+
+
+my $tmp = sanitize_tree($dbh->{Profile});
+$tmp->{Data}{$sql}[0] = -1; # make test insensitive to local file count
+is_deeply $tmp, (bless {
+ 'Path' => [ '!Statement' ],
+ 'Data' => {
+ '' => [ 6, 0, 0, 0, 0, 0, 0 ],
+ $sql => [ -1, 0, 0, 0, 0, 0, 0 ],
+ 'set foo=1' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ }
+} => 'DBI::Profile'), 'profile';
+
+print "Test profile format\n";
+my $output = $dbh->{Profile}->format();
+print "Profile Output\n$output";
+
+# check that output was produced in the expected format
+ok(length $output, 'non zero length');
+ok($output =~ /^DBI::Profile:/, 'DBI::Profile');
+ok($output =~ /\((\d+) calls\)/, 'some calls');
+ok($1 >= $count, 'calls >= count');
+
+# -----------------------------------------------------------------------------------
+
+# try statement and method name and reference-to-scalar path
+my $by_reference = 'foo';
+$dbh = DBI->connect("dbi:ExampleP:", 'usrnam', '', {
+ RaiseError => 1,
+ Profile => { Path => [ '{Username}', '!Statement', \$by_reference, '!MethodName' ] }
+});
+$sql = "select name from .";
+$sth = $dbh->prepare($sql);
+$sth->execute();
+$sth->fetchrow_hashref;
+$by_reference = 'bar';
+$sth->finish;
+undef $sth; # DESTROY
+
+$tmp = sanitize_tree($dbh->{Profile});
+ok $tmp->{Data}{usrnam}{""}{foo}{STORE}, 'username stored';
+$tmp->{Data}{usrnam}{""}{foo} = {};
+# make test insentitive to number of local files
+#warn Dumper($tmp);
+is_deeply $tmp, bless {
+ 'Path' => [ '{Username}', '!Statement', \$by_reference, '!MethodName' ],
+ 'Data' => {
+ '' => { # because Profile was enabled by DBI just before Username was set
+ '' => {
+ 'foo' => {
+ 'STORE' => [ 3, 0, 0, 0, 0, 0, 0 ],
+ }
+ }
+ },
+ 'usrnam' => {
+ '' => {
+ 'foo' => { },
+ },
+ 'select name from .' => {
+ 'foo' => {
+ 'execute' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ 'fetchrow_hashref' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ 'prepare' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ },
+ 'bar' => {
+ 'DESTROY' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ 'finish' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ },
+ },
+ },
+ },
+} => 'DBI::Profile';
+
+$tmp = [ $dbh->{Profile}->as_node_path_list() ];
+is @$tmp, 8, 'should have 8 nodes';
+sanitize_profile_data_nodes($_->[0]) for @$tmp;
+#warn Dumper($dbh->{Profile}->{Data});
+is_deeply $tmp, [
+ [ [ 3, 0, 0, 0, 0, 0, 0 ], '', '', 'foo', 'STORE' ],
+ [ [ 2, 0, 0, 0, 0, 0, 0 ], 'usrnam', '', 'foo', 'STORE' ],
+ [ [ 1, 0, 0, 0, 0, 0, 0 ], 'usrnam', '', 'foo', 'connected' ],
+ [ [ 1, 0, 0, 0, 0, 0, 0 ], 'usrnam', 'select name from .', 'bar', 'DESTROY' ],
+ [ [ 1, 0, 0, 0, 0, 0, 0 ], 'usrnam', 'select name from .', 'bar', 'finish' ],
+ [ [ 1, 0, 0, 0, 0, 0, 0 ], 'usrnam', 'select name from .', 'foo', 'execute' ],
+ [ [ 1, 0, 0, 0, 0, 0, 0 ], 'usrnam', 'select name from .', 'foo', 'fetchrow_hashref' ],
+ [ [ 1, 0, 0, 0, 0, 0, 0 ], 'usrnam', 'select name from .', 'foo', 'prepare' ]
+];
+
+
+print "testing '!File', '!Caller' and their variants in Path\n";
+
+$dbh->{Profile}->{Path} = [ '!File', '!File2', '!Caller', '!Caller2' ];
+$dbh->{Profile}->{Data} = undef;
+
+my $file = (File::Spec->splitpath(__FILE__))[2]; # '40profile.t'
+my ($line1, $line2);
+sub a_sub {
+ $sth = $dbh->prepare("select name from ."); $line2 = __LINE__;
+}
+a_sub(); $line1 = __LINE__;
+
+$tmp = sanitize_profile_data_nodes($dbh->{Profile}{Data});
+#warn Dumper($tmp);
+is_deeply $tmp, {
+ "$file" => {
+ "$file via $file" => {
+ "$file line $line2" => {
+ "$file line $line2 via $file line $line1" => [ 1, 0, 0, 0, 0, 0, 0 ]
+ }
+ }
+ }
+};
+
+
+print "testing '!Time' and variants in Path\n";
+
+undef $sth;
+my $factor = 1_000_000;
+$dbh->{Profile}->{Path} = [ '!Time', "!Time~$factor", '!MethodName' ];
+$dbh->{Profile}->{Data} = undef;
+
+# give up a timeslice in the hope that the following few lines
+# run in well under a second even of slow/overloaded systems
+$t1 = int(dbi_time())+1; 1 while int(dbi_time()-0.01) < $t1; # spin till just after second starts
+$t2 = int($t1/$factor)*$factor;
+
+$sth = $dbh->prepare("select name from .");
+$tmp = sanitize_profile_data_nodes($dbh->{Profile}{Data});
+
+# if actual "!Time" recorded is 'close enough' then we'll pass
+# the test - it's not worth failing just because a system is slow
+$t1 = (keys %$tmp)[0] if (abs($t1 - (keys %$tmp)[0]) <= 5);
+
+is_deeply $tmp, {
+ $t1 => { $t2 => { prepare => [ 1, 0, 0, 0, 0, 0, 0 ] }}
+}, "!Time and !Time~$factor should work"
+ or warn Dumper([$t1, $t2, $tmp]);
+
+
+print "testing &norm_std_n3 in Path\n";
+
+$dbh->{Profile} = '&norm_std_n3'; # assign as string to get magic
+is_deeply $dbh->{Profile}{Path}, [
+ \&DBI::ProfileSubs::norm_std_n3
+];
+$dbh->{Profile}->{Data} = undef;
+$sql = qq{insert into foo20060726 (a,b) values (42,"foo")};
+dbi_profile( { foo => $dbh, bar => undef }, $sql, 'mymethod', 100000000, 100000002);
+$tmp = $dbh->{Profile}{Data};
+#warn Dumper($tmp);
+is_deeply $tmp, {
+ 'insert into foo<N> (a,b) values (<N>,"<S>")' => [ 1, '2', '2', '2', '2', '100000000', '100000000' ]
+}, '&norm_std_n3 should normalize statement';
+
+
+# -----------------------------------------------------------------------------------
+
+print "testing code ref in Path\n";
+
+sub run_test1 {
+ my ($profile) = @_;
+ $dbh = DBI->connect("dbi:ExampleP:", 'usrnam', '', {
+ RaiseError => 1,
+ Profile => $profile,
+ });
+ $sql = "select name from .";
+ $sth = $dbh->prepare($sql);
+ $sth->execute();
+ $sth->fetchrow_hashref;
+ $sth->finish;
+ undef $sth; # DESTROY
+ my $data = sanitize_profile_data_nodes($dbh->{Profile}{Data}, 1);
+ return ($data, $dbh) if wantarray;
+ return $data;
+}
+
+$tmp = run_test1( { Path => [ 'foo', sub { 'bar' }, 'baz' ] });
+is_deeply $tmp, { 'foo' => { 'bar' => { 'baz' => [ 11, 0,0,0,0,0,0 ] } } };
+
+$tmp = run_test1( { Path => [ 'foo', sub { 'ping','pong' } ] });
+is_deeply $tmp, { 'foo' => { 'ping' => { 'pong' => [ 11, 0,0,0,0,0,0 ] } } };
+
+$tmp = run_test1( { Path => [ 'foo', sub { \undef } ] });
+is_deeply $tmp, { 'foo' => undef }, 'should be vetoed';
+
+# check what code ref sees in $_
+$tmp = run_test1( { Path => [ sub { $_ } ] });
+is_deeply $tmp, {
+ '' => [ 6, 0, 0, 0, 0, 0, 0 ],
+ 'select name from .' => [ 5, 0, 0, 0, 0, 0, 0 ]
+}, '$_ should contain statement';
+
+# check what code ref sees in @_
+$tmp = run_test1( { Path => [ sub { my ($h,$method) = @_; return \undef if $method =~ /^[A-Z]+$/; return (ref $h, $method) } ] });
+is_deeply $tmp, {
+ 'DBI::db' => {
+ 'connected' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ 'prepare' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ },
+ 'DBI::st' => {
+ 'fetchrow_hashref' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ 'execute' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ 'finish' => [ 1, 0, 0, 0, 0, 0, 0 ],
+ },
+}, 'should have @_ as keys';
+
+# check we can filter by method
+$tmp = run_test1( { Path => [ sub { return \undef unless $_[1] =~ /^fetch/; return $_[1] } ] });
+#warn Dumper($tmp);
+is_deeply $tmp, {
+ 'fetchrow_hashref' => [ 1, 0, 0, 0, 0, 0, 0 ],
+}, 'should be able to filter by method';
+
+DBI->trace(0, "STDOUT"); # close current log to flush it
+ok(-s $LOG_FILE, 'output should go to log file');
+
+# -----------------------------------------------------------------------------------
+
+print "testing as_text\n";
+
+# check %N$ indices
+$dbh->{Profile}->{Data} = { P1 => { P2 => [ 100, 400, 42, 43, 44, 45, 46, 47 ] } };
+my $as_text = $dbh->{Profile}->as_text({
+ path => [ 'top' ],
+ separator => ':',
+ format => '%1$s %2$d [ %10$d %11$d %12$d %13$d %14$d %15$d %16$d %17$d ]',
+});
+is($as_text, "top:P1:P2 4 [ 100 400 42 43 44 45 46 47 ]", 'as_text');
+
+# test sortsub
+$dbh->{Profile}->{Data} = {
+ A => { Z => [ 101, 1, 2, 3, 4, 5, 6, 7 ] },
+ B => { Y => [ 102, 1, 2, 3, 4, 5, 6, 7 ] },
+};
+$as_text = $dbh->{Profile}->as_text({
+ separator => ':',
+ format => '%1$s %10$d ',
+ sortsub => sub { my $ary=shift; @$ary = sort { $a->[2] cmp $b->[2] } @$ary }
+});
+is($as_text, "B:Y 102 A:Z 101 ", 'as_text sortsub');
+
+# general test, including defaults
+($tmp, $dbh) = run_test1( { Path => [ 'foo', '!MethodName', 'baz' ] });
+$as_text = $dbh->{Profile}->as_text();
+$as_text =~ s/\.00+/.0/g;
+#warn "[$as_text]";
+is $as_text, q{foo > DESTROY > baz: 0.0s / 1 = 0.0s avg (first 0.0s, min 0.0s, max 0.0s)
+foo > STORE > baz: 0.0s / 5 = 0.0s avg (first 0.0s, min 0.0s, max 0.0s)
+foo > connected > baz: 0.0s / 1 = 0.0s avg (first 0.0s, min 0.0s, max 0.0s)
+foo > execute > baz: 0.0s / 1 = 0.0s avg (first 0.0s, min 0.0s, max 0.0s)
+foo > fetchrow_hashref > baz: 0.0s / 1 = 0.0s avg (first 0.0s, min 0.0s, max 0.0s)
+foo > finish > baz: 0.0s / 1 = 0.0s avg (first 0.0s, min 0.0s, max 0.0s)
+foo > prepare > baz: 0.0s / 1 = 0.0s avg (first 0.0s, min 0.0s, max 0.0s)
+}, 'as_text general';
+
+# -----------------------------------------------------------------------------------
+
+print "dbi_profile_merge_nodes\n";
+my $total_time = dbi_profile_merge_nodes(
+ my $totals=[],
+ [ 10, 0.51, 0.11, 0.01, 0.22, 1023110000, 1023110010 ],
+ [ 15, 0.42, 0.12, 0.02, 0.23, 1023110005, 1023110009 ],
+);
+$_ = sprintf "%.2f", $_ for @$totals; # avoid precision issues
+is("@$totals", "25.00 0.93 0.11 0.01 0.23 1023110000.00 1023110010.00",
+ 'merged nodes');
+is($total_time, 0.93, 'merged time');
+
+$total_time = dbi_profile_merge_nodes(
+ $totals=[], {
+ foo => [ 10, 1.51, 0.11, 0.01, 0.22, 1023110000, 1023110010 ],
+ bar => [ 17, 1.42, 0.12, 0.02, 0.23, 1023110005, 1023110009 ],
+ }
+);
+$_ = sprintf "%.2f", $_ for @$totals; # avoid precision issues
+is("@$totals", "27.00 2.93 0.11 0.01 0.23 1023110000.00 1023110010.00",
+ 'merged time foo/bar');
+is($total_time, 2.93, 'merged nodes foo/bar time');
+
+exit 0;
+
+
+sub sanitize_tree {
+ my $data = shift;
+ my $skip_clone = shift;
+ return $data unless ref $data;
+ $data = dclone($data) unless $skip_clone;
+ sanitize_profile_data_nodes($data->{Data}) if $data->{Data};
+ return $data;
+}
+
+sub sanitize_profile_data_nodes {
+ my $node = shift;
+ if (ref $node eq 'HASH') {
+ sanitize_profile_data_nodes($_) for values %$node;
+ }
+ elsif (ref $node eq 'ARRAY') {
+ if (@$node == 7 and DBI::looks_like_number($node->[0])) {
+ # sanitize the profile data node to simplify tests
+ $_ = 0 for @{$node}[1..@$node-1]; # not 0
+ }
+ }
+ return $node;
+}
diff --git a/t/41prof_dump.t b/t/41prof_dump.t
new file mode 100644
index 0000000..c921893
--- /dev/null
+++ b/t/41prof_dump.t
@@ -0,0 +1,105 @@
+#!perl -wl
+# Using -l to ensure ProfileDumper is isolated from changes to $/ and $\ and such
+
+$|=1;
+
+use strict;
+
+#
+# test script for DBI::ProfileDumper
+#
+
+use DBI;
+use Config;
+use Test::More;
+
+BEGIN {
+ plan skip_all => 'profiling not supported for DBI::PurePerl'
+ if $DBI::PurePerl;
+
+ # clock instability on xen systems is a reasonably common cause of failure
+ # http://www.nntp.perl.org/group/perl.cpan.testers/2009/05/msg3828158.html
+ # so we'll skip automated testing on those systems
+ plan skip_all => "skipping profile tests on xen (due to clock instability)"
+ if $Config{osvers} =~ /xen/ # eg 2.6.18-4-xen-amd64
+ and $ENV{AUTOMATED_TESTING};
+
+ plan tests => 15;
+}
+
+BEGIN {
+ use_ok( 'DBI' );
+ use_ok( 'DBI::ProfileDumper' );
+}
+
+my $prof_file = "dbi$$.prof";
+my $prof_backup = $prof_file . ".prev";
+END { 1 while unlink $prof_file;
+ 1 while unlink $prof_backup; }
+
+my $dbh = DBI->connect("dbi:ExampleP:", '', '',
+ { RaiseError=>1, Profile=>"2/DBI::ProfileDumper/File:$prof_file" });
+isa_ok( $dbh, 'DBI::db' );
+isa_ok( $dbh->{Profile}, "DBI::ProfileDumper" );
+isa_ok( $dbh->{Profile}{Data}, 'HASH' );
+isa_ok( $dbh->{Profile}{Path}, 'ARRAY' );
+
+# do a little work
+my $sql = "select mode,size,name from ?";
+my $sth = $dbh->prepare($sql);
+isa_ok( $sth, 'DBI::st' );
+$sth->execute(".");
+
+# check that flush_to_disk doesn't change Path if Path is undef (it
+# did before 1.49)
+{
+ local $dbh->{Profile}->{Path} = undef;
+ $sth->{Profile}->flush_to_disk();
+ is($dbh->{Profile}->{Path}, undef);
+}
+
+$sth->{Profile}->flush_to_disk();
+while ( my $hash = $sth->fetchrow_hashref ) {}
+
+# force output
+undef $sth;
+$dbh->disconnect;
+undef $dbh;
+
+# wrote the profile to disk?
+ok( -s $prof_file, 'Profile is on disk and nonzero size' );
+
+# XXX We're breaking encapsulation here
+open(PROF, $prof_file) or die $!;
+my @prof = <PROF>;
+close PROF;
+
+print @prof;
+
+# has a header?
+like( $prof[0], '/^DBI::ProfileDumper\s+([\d.]+)/', 'Found a version number' );
+
+# version matches VERSION? (DBI::ProfileDumper uses $self->VERSION so
+# it's a stringified version object that looks like N.N.N)
+$prof[0] =~ /^DBI::ProfileDumper\s+([\d.]+)/;
+is( $1, DBI::ProfileDumper->VERSION, "Version numbers match in $prof[0]" );
+
+like( $prof[1], qr{^Path\s+=\s+\[\s+\]}, 'Found the Path');
+ok( $prof[2] =~ m{^Program\s+=\s+(\S+)}, 'Found the Program');
+
+# check that expected key is there
+like(join('', @prof), qr/\+\s+1\s+\Q$sql\E/m);
+
+# unlink($prof_file); # now done by 'make clean'
+
+# should be able to load DBI::ProfileDumper::Apache outside apache
+# this also naturally checks for syntax errors etc.
+SKIP: {
+ skip "developer-only test", 1
+ unless (-d ".svn" || -d ".git") && -f "MANIFEST.SKIP";
+ skip "Apache module not installed", 1
+ unless eval { require Apache };
+ require_ok('DBI::ProfileDumper::Apache')
+}
+
+1;
diff --git a/t/42prof_data.t b/t/42prof_data.t
new file mode 100644
index 0000000..f9ce4a3
--- /dev/null
+++ b/t/42prof_data.t
@@ -0,0 +1,150 @@
+#!perl -w
+$|=1;
+
+use strict;
+
+use DBI;
+use Config;
+use Test::More;
+use Data::Dumper;
+
+BEGIN {
+ plan skip_all => 'profiling not supported for DBI::PurePerl'
+ if $DBI::PurePerl;
+
+ # clock instability on xen systems is a reasonably common cause of failure
+ # http://www.nntp.perl.org/group/perl.cpan.testers/2009/05/msg3828158.html
+ # so we'll skip automated testing on those systems
+ plan skip_all => "skipping profile tests on xen (due to clock instability)"
+ if $Config{osvers} =~ /xen/ # eg 2.6.18-4-xen-amd64
+ and $ENV{AUTOMATED_TESTING};
+
+ plan tests => 31;
+}
+
+BEGIN {
+ use_ok( 'DBI::ProfileDumper' );
+ use_ok( 'DBI::ProfileData' );
+}
+
+my $sql = "select mode,size,name from ?";
+
+my $prof_file = "dbi$$.prof";
+my $prof_backup = $prof_file . ".prev";
+END { 1 while unlink $prof_file;
+ 1 while unlink $prof_backup; }
+
+my $dbh = DBI->connect("dbi:ExampleP:", '', '',
+ { RaiseError=>1, Profile=>"6/DBI::ProfileDumper/File:$prof_file" });
+isa_ok( $dbh, 'DBI::db', 'Created connection' );
+
+# do a little work, but enough to ensure we don't get 0's on systems with low res timers
+foreach (1..6) {
+ $dbh->do("set dummy=$_");
+ my $sth = $dbh->prepare($sql);
+ for my $loop (1..50) {
+ $sth->execute(".");
+ $sth->fetchrow_hashref;
+ $sth->finish;
+ }
+ $sth->{Profile}->flush_to_disk();
+}
+$dbh->disconnect;
+undef $dbh;
+
+
+# wrote the profile to disk?
+ok(-s $prof_file, "Profile written to disk, non-zero size" );
+
+# load up
+my $prof = DBI::ProfileData->new(
+ File => $prof_file,
+ Filter => sub {
+ my ($path_ref, $data_ref) = @_;
+ $path_ref->[0] =~ s/set dummy=\d/set dummy=N/;
+ },
+);
+isa_ok( $prof, 'DBI::ProfileData' );
+cmp_ok( $prof->count, '>=', 3, 'At least 3 profile data items' );
+
+# try a few sorts
+my $nodes = $prof->nodes;
+$prof->sort(field => "longest");
+my $longest = $nodes->[0][4];
+ok($longest);
+$prof->sort(field => "longest", reverse => 1);
+cmp_ok( $nodes->[0][4], '<', $longest );
+
+$prof->sort(field => "count");
+my $most = $nodes->[0];
+ok($most);
+$prof->sort(field => "count", reverse => 1);
+cmp_ok( $nodes->[0][0], '<', $most->[0] );
+
+# remove the top count and make sure it's gone
+my $clone = $prof->clone();
+isa_ok( $clone, 'DBI::ProfileData' );
+$clone->sort(field => "count");
+ok($clone->exclude(key1 => $most->[7]));
+
+# compare keys of the new first element and the old one to make sure
+# exclude works
+ok($clone->nodes()->[0][7] ne $most->[7] &&
+ $clone->nodes()->[0][8] ne $most->[8]);
+
+# there can only be one
+$clone = $prof->clone();
+isa_ok( $clone, 'DBI::ProfileData' );
+ok($clone->match(key1 => $clone->nodes->[0][7]));
+ok($clone->match(key2 => $clone->nodes->[0][8]));
+ok($clone->count == 1);
+
+# take a look through Data
+my $Data = $prof->Data;
+print "SQL: $_\n" for keys %$Data;
+ok(exists($Data->{$sql}), "Data for '$sql' should exist")
+ or print Dumper($Data);
+ok(exists($Data->{$sql}{execute}), "Data for '$sql'->{execute} should exist");
+
+# did the Filter convert set dummy=1 (etc) into set dummy=N?
+ok(exists($Data->{"set dummy=N"}));
+
+# test escaping of \n and \r in keys
+$dbh = DBI->connect("dbi:ExampleP:", '', '',
+ { RaiseError=>1, Profile=>"6/DBI::ProfileDumper/File:$prof_file" });
+isa_ok( $dbh, 'DBI::db', 'Created connection' );
+
+my $sql2 = 'select size from . where name = "LITERAL: \r\n"';
+my $sql3 = "select size from . where name = \"EXPANDED: \r\n\"";
+
+# do a little work
+foreach (1,2,3) {
+ my $sth2 = $dbh->prepare($sql2);
+ isa_ok( $sth2, 'DBI::st' );
+ $sth2->execute();
+ $sth2->fetchrow_hashref;
+ $sth2->finish;
+ my $sth3 = $dbh->prepare($sql3);
+ isa_ok( $sth3, 'DBI::st' );
+ $sth3->execute();
+ $sth3->fetchrow_hashref;
+ $sth3->finish;
+}
+$dbh->disconnect;
+undef $dbh;
+
+# load dbi.prof
+$prof = DBI::ProfileData->new( File => $prof_file, DeleteFiles => 1 );
+isa_ok( $prof, 'DBI::ProfileData' );
+
+ok(not(-e $prof_file), "file should be deleted when DeleteFiles set" );
+
+
+# make sure the keys didn't get garbled
+$Data = $prof->Data;
+ok(exists $Data->{$sql2}, "Data for '$sql2' should exist")
+ or print Dumper($Data);
+ok(exists $Data->{$sql3}, "Data for '$sql3' should exist")
+ or print Dumper($Data);
+
+1;
diff --git a/t/43prof_env.t b/t/43prof_env.t
new file mode 100644
index 0000000..6726cf7
--- /dev/null
+++ b/t/43prof_env.t
@@ -0,0 +1,52 @@
+#!perl -w
+$|=1;
+
+use strict;
+
+#
+# test script for using DBI_PROFILE env var to enable DBI::Profile
+# and testing non-ref assignments to $h->{Profile}
+#
+
+BEGIN { $ENV{DBI_PROFILE} = 6 } # prior to use DBI
+
+use DBI;
+use DBI::Profile;
+use Config;
+use Data::Dumper;
+
+BEGIN {
+ if ($DBI::PurePerl) {
+ print "1..0 # Skipped: profiling not supported for DBI::PurePerl\n";
+ exit 0;
+ }
+}
+
+use Test::More tests => 11;
+
+DBI->trace(0, "STDOUT");
+
+my $dbh1 = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+is(ref $dbh1->{Profile}, "DBI::Profile");
+is(ref $dbh1->{Profile}{Data}, 'HASH');
+is(ref $dbh1->{Profile}{Path}, 'ARRAY');
+
+my $dbh2 = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+is(ref $dbh2->{Profile}, "DBI::Profile");
+is(ref $dbh2->{Profile}{Data}, 'HASH');
+is(ref $dbh2->{Profile}{Path}, 'ARRAY');
+
+is $dbh1->{Profile}, $dbh2->{Profile}, '$h->{Profile} should be shared';
+
+$dbh1->do("set dummy=1");
+$dbh1->do("set dummy=2");
+
+my $profile = $dbh1->{Profile};
+
+my $p_data = $profile->{Data};
+is keys %$p_data, 3; # '', $sql1, $sql2
+ok $p_data->{''};
+ok $p_data->{"set dummy=1"};
+ok $p_data->{"set dummy=2"};
+
+__END__
diff --git a/t/48dbi_dbd_sqlengine.t b/t/48dbi_dbd_sqlengine.t
new file mode 100644
index 0000000..c916d51
--- /dev/null
+++ b/t/48dbi_dbd_sqlengine.t
@@ -0,0 +1,81 @@
+#!perl -w
+$|=1;
+
+use strict;
+
+use Cwd;
+use File::Path;
+use File::Spec;
+use Test::More;
+
+my $using_dbd_gofer = ($ENV{DBI_AUTOPROXY}||"") =~ /^dbi:Gofer.*transport=/i;
+
+my $tbl;
+BEGIN { $tbl = "db_". $$ . "_" };
+#END { $tbl and unlink glob "${tbl}*" }
+
+use_ok ("DBI");
+use_ok ("DBI::DBD::SqlEngine");
+use_ok ("DBD::File");
+
+my $sql_statement = DBI::DBD::SqlEngine::Statement->isa('SQL::Statement');
+my $dbh = DBI->connect( "DBI:File:", undef, undef, { PrintError => 0, RaiseError => 0, } ); # Can't use DBI::DBD::SqlEngine direct
+
+for my $sql ( split "\n", <<"" )
+ CREATE TABLE foo (id INT, foo TEXT)
+ CREATE TABLE bar (id INT, baz TEXT)
+ INSERT INTO foo VALUES (1, "Hello world")
+ INSERT INTO bar VALUES (1, "Bugfixes welcome")
+ INSERT bar VALUES (2, "Bug reports, too")
+ SELECT foo FROM foo where ID=1
+ UPDATE bar SET id=5 WHERE baz="Bugfixes welcome"
+ DELETE FROM foo
+ DELETE FROM bar WHERE baz="Bugfixes welcome"
+
+{
+ my $sth;
+ $sql =~ s/^\s+//;
+ eval { $sth = $dbh->prepare( $sql ); };
+ ok( $sth, "prepare '$sql'" );
+}
+
+for my $line ( split "\n", <<"" )
+ Junk -- Junk
+ CREATE foo (id INT, foo TEXT) -- missing table
+ INSERT INTO bar (1, "Bugfixes welcome") -- missing "VALUES"
+ UPDATE bar id=5 WHERE baz="Bugfixes welcome" -- missing "SET"
+ DELETE * FROM foo -- waste between "DELETE" and "FROM"
+
+{
+ my $sth;
+ $line =~ s/^\s+//;
+ my ($sql, $test) = ( $line =~ m/^([^-]+)\s+--\s+(.*)$/ );
+ eval { $sth = $dbh->prepare( $sql ); };
+ ok( !$sth, "$test: prepare '$sql'" );
+}
+
+SKIP: {
+ # some SQL::Statement / SQL::Parser related tests
+ skip( "Not running with SQL::Statement", 3 ) unless ($sql_statement);
+ for my $line ( split "\n", <<"" )
+ Junk -- Junk
+ CREATE TABLE bar (id INT, baz CHARACTER VARYING(255)) -- invalid column type
+
+ {
+ my $sth;
+ $line =~ s/^\s+//;
+ my ($sql, $test) = ( $line =~ m/^([^-]+)\s+--\s+(.*)$/ );
+ eval { $sth = $dbh->prepare( $sql ); };
+ ok( !$sth, "$test: prepare '$sql'" );
+ }
+
+ my $dbh2 = DBI->connect( "DBI:File:", undef, undef, { sql_dialect => "ANSI" } );
+ my $sth;
+ eval { $sth = $dbh2->prepare( "CREATE TABLE foo (id INTEGER PRIMARY KEY, phrase CHARACTER VARYING(40) UNIQUE)" ); };
+ ok( $sth, "prepared statement using ANSI dialect" );
+ skip( "Gofer proxy prevents fetching embedded SQL::Parser object", 1 );
+ my $sql_parser = $dbh2->FETCH("sql_parser_object");
+ cmp_ok( $sql_parser->dialect(), "eq", "ANSI", "SQL::Parser has 'ANSI' as dialect" );
+}
+
+done_testing ();
diff --git a/t/49dbd_file.t b/t/49dbd_file.t
new file mode 100644
index 0000000..0c64328
--- /dev/null
+++ b/t/49dbd_file.t
@@ -0,0 +1,174 @@
+#!perl -w
+$|=1;
+
+use strict;
+
+use Cwd;
+use File::Path;
+use File::Spec;
+use Test::More;
+
+my $using_dbd_gofer = ($ENV{DBI_AUTOPROXY}||"") =~ /^dbi:Gofer.*transport=/i;
+
+my $tbl;
+BEGIN { $tbl = "db_". $$ . "_" };
+#END { $tbl and unlink glob "${tbl}*" }
+
+use_ok ("DBI");
+use_ok ("DBD::File");
+
+do "t/lib.pl";
+
+my $dir = test_dir ();
+
+my $rowidx = 0;
+my @rows = ( [ "Hello World" ], [ "Hello DBI Developers" ], );
+
+my $dbh;
+
+# Check if we can connect at all
+ok ($dbh = DBI->connect ("dbi:File:"), "Connect clean");
+is (ref $dbh, "DBI::db", "Can connect to DBD::File driver");
+
+my $f_versions = $dbh->func ("f_versions");
+note $f_versions;
+ok ($f_versions, "f_versions");
+
+# Check if all the basic DBI attributes are accepted
+ok ($dbh = DBI->connect ("dbi:File:", undef, undef, {
+ RaiseError => 1,
+ PrintError => 1,
+ AutoCommit => 1,
+ ChopBlanks => 1,
+ ShowErrorStatement => 1,
+ FetchHashKeyName => "NAME_lc",
+ }), "Connect with DBI attributes");
+
+# Check if all the f_ attributes are accepted, in two ways
+ok ($dbh = DBI->connect ("dbi:File:f_ext=.txt;f_dir=.;f_encoding=cp1252;f_schema=test"), "Connect with driver attributes in DSN");
+
+my $encoding = "iso-8859-1";
+
+# now use dir to prove file existence
+ok ($dbh = DBI->connect ("dbi:File:", undef, undef, {
+ f_ext => ".txt",
+ f_dir => $dir,
+ f_schema => undef,
+ f_encoding => $encoding,
+ f_lock => 0,
+
+ RaiseError => 0,
+ PrintError => 0,
+ }), "Connect with driver attributes in hash");
+
+my $sth;
+ok ($sth = $dbh->prepare ("select * from t_sbdgf_53442Gz"), "Prepare select from non-existing file");
+
+{ my @msg;
+ eval {
+ local $SIG{__DIE__} = sub { push @msg, @_ };
+ $sth->execute;
+ };
+ like ("@msg", qr{Cannot open .*t_sbdgf_}, "Cannot open non-existing file");
+ eval {
+ note $dbh->f_get_meta ("t_sbdgf_53442Gz", "f_fqfn");
+ };
+ }
+
+SKIP: {
+ my $fh;
+ my $tbl2 = $tbl . "2";
+
+ my $tbl2_file1 = File::Spec->catfile ($dir, "$tbl2.txt");
+ open $fh, ">", $tbl2_file1 or skip;
+ print $fh "You cannot read this anyway ...";
+ close $fh;
+
+ my $tbl2_file2 = File::Spec->catfile ($dir, "$tbl2");
+ open $fh, ">", $tbl2_file2 or skip;
+ print $fh "Neither that";
+ close $fh;
+
+ ok ($dbh->do ("drop table if exists $tbl2"), "drop manually created table $tbl2 (first file)");
+ ok (! -f $tbl2_file1, "$tbl2_file1 removed");
+ ok ( -f $tbl2_file2, "$tbl2_file2 exists");
+ ok ($dbh->do ("drop table if exists $tbl2"), "drop manually created table $tbl2 (second file)");
+ ok (! -f $tbl2_file2, "$tbl2_file2 removed");
+ }
+
+my @tfhl;
+
+# Now test some basic SQL statements
+my $tbl_file = File::Spec->catfile (Cwd::abs_path( $dir ), "$tbl.txt");
+ok ($dbh->do ("create table $tbl (txt varchar (20))"), "Create table $tbl") or diag $dbh->errstr;
+ok (-f $tbl_file, "Test table exists");
+
+is ($dbh->f_get_meta ($tbl, "f_fqfn"), $tbl_file, "get single table meta data");
+is_deeply ($dbh->f_get_meta ([$tbl, "t_sbdgf_53442Gz"], [qw(f_dir f_ext)]),
+ {
+ $tbl => {
+ f_dir => $dir,
+ f_ext => ".txt",
+ },
+ t_sbdgf_53442Gz => {
+ f_dir => $dir,
+ f_ext => ".txt",
+ },
+ },
+ "get multiple meta data");
+
+# Expected: ("unix", "perlio", "encoding(iso-8859-1)")
+# use Data::Peek; DDumper [ @tfh ];
+my @layer = grep { $_ eq "encoding($encoding)" } @tfhl;
+is (scalar @layer, 1, "encoding shows in layer");
+
+SKIP: {
+ $using_dbd_gofer and skip "modifying meta data doesn't work with Gofer-AutoProxy", 4;
+ ok ($dbh->f_set_meta ($tbl, "f_dir", $dir), "set single meta datum");
+ is ($tbl_file, $dbh->f_get_meta ($tbl, "f_fqfn"), "verify set single meta datum");
+ ok ($dbh->f_set_meta ($tbl, { f_dir => $dir }), "set multiple meta data");
+ is ($tbl_file, $dbh->f_get_meta ($tbl, "f_fqfn"), "verify set multiple meta attributes");
+ }
+
+ok ($sth = $dbh->prepare ("select * from $tbl"), "Prepare select * from $tbl");
+$rowidx = 0;
+SKIP: {
+ $using_dbd_gofer and skip "method intrusion didn't work with proxying", 1;
+ ok ($sth->execute, "execute on $tbl");
+ $dbh->errstr and diag;
+ }
+
+my $uctbl = uc($tbl);
+ok ($sth = $dbh->prepare ("select * from $uctbl"), "Prepare select * from $uctbl");
+$rowidx = 0;
+SKIP: {
+ $using_dbd_gofer and skip "method intrusion didn't work with proxying", 1;
+ ok ($sth->execute, "execute on $uctbl");
+ $dbh->errstr and diag;
+ }
+
+ok ($dbh->do ("drop table $tbl"), "table drop");
+is (-s "$tbl.txt", undef, "Test table removed");
+
+done_testing ();
+
+sub DBD::File::Table::fetch_row ($$)
+{
+ my ($self, $data) = @_;
+ my $meta = $self->{meta};
+ if ($rowidx >= scalar @rows) {
+ $self->{row} = undef;
+ }
+ else {
+ $self->{row} = $rows[$rowidx++];
+ }
+ return $self->{row};
+ } # fetch_row
+
+sub DBD::File::Table::push_names ($$$)
+{
+ my ($self, $data, $row_aryref) = @_;
+ my $meta = $self->{meta};
+ @tfhl = PerlIO::get_layers ($meta->{fh});
+ @{$meta->{col_names}} = @{$row_aryref};
+ } # push_names
diff --git a/t/50dbm_simple.t b/t/50dbm_simple.t
new file mode 100755
index 0000000..e176161
--- /dev/null
+++ b/t/50dbm_simple.t
@@ -0,0 +1,264 @@
+#!perl -w
+$|=1;
+
+use strict;
+use warnings;
+
+require DBD::DBM;
+
+use File::Path;
+use File::Spec;
+use Test::More;
+use Cwd;
+use Config qw(%Config);
+use Storable qw(dclone);
+
+my $using_dbd_gofer = ($ENV{DBI_AUTOPROXY}||'') =~ /^dbi:Gofer.*transport=/i;
+
+use DBI;
+use vars qw( @mldbm_types @dbm_types );
+
+BEGIN {
+
+ # 0=SQL::Statement if avail, 1=DBI::SQL::Nano
+ # next line forces use of Nano rather than default behaviour
+ # $ENV{DBI_SQL_NANO}=1;
+ # This is done in zv*n*_50dbm_simple.t
+
+ push @mldbm_types, '';
+ if (eval { require 'MLDBM.pm'; }) {
+ push @mldbm_types, qw(Data::Dumper Storable); # both in CORE
+ push @mldbm_types, 'FreezeThaw' if eval { require 'FreezeThaw.pm' };
+ push @mldbm_types, 'YAML' if eval { require MLDBM::Serializer::YAML; };
+ push @mldbm_types, 'JSON' if eval { require MLDBM::Serializer::JSON; };
+ }
+
+ # Potential DBM modules in preference order (SDBM_File first)
+ # skip NDBM and ODBM as they don't support EXISTS
+ my @dbms = qw(SDBM_File GDBM_File DB_File BerkeleyDB NDBM_File ODBM_File);
+ my @use_dbms = @ARGV;
+ if( !@use_dbms && $ENV{DBD_DBM_TEST_BACKENDS} ) {
+ @use_dbms = split ' ', $ENV{DBD_DBM_TEST_BACKENDS};
+ }
+
+ if (lc "@use_dbms" eq "all") {
+ # test with as many of the major DBM types as are available
+ @dbm_types = grep { eval { local $^W; require "$_.pm" } } @dbms;
+ }
+ elsif (@use_dbms) {
+ @dbm_types = @use_dbms;
+ }
+ else {
+ # we only test SDBM_File by default to avoid tripping up
+ # on any broken DBM's that may be installed in odd places.
+ # It's only DBD::DBM we're trying to test here.
+ # (However, if SDBM_File is not available, then use another.)
+ for my $dbm (@dbms) {
+ if (eval { local $^W; require "$dbm.pm" }) {
+ @dbm_types = ($dbm);
+ last;
+ }
+ }
+ }
+
+ if( eval { require List::MoreUtils; } )
+ {
+ List::MoreUtils->import("part");
+ }
+ else
+ {
+ # XXX from PP part of List::MoreUtils
+ eval <<'EOP';
+sub part(&@) {
+ my ($code, @list) = @_;
+ my @parts;
+ push @{ $parts[$code->($_)] }, $_ for @list;
+ return @parts;
+}
+EOP
+ }
+}
+
+my $dbi_sql_nano = not DBD::DBM::Statement->isa('SQL::Statement');
+
+do "t/lib.pl";
+
+my $dir = test_dir ();
+
+my %tests_statement_results = (
+ 2 => [
+ "DROP TABLE IF EXISTS fruit", -1,
+ "CREATE TABLE fruit (dKey INT, dVal VARCHAR(10))", '0E0',
+ "INSERT INTO fruit VALUES (1,'oranges' )", 1,
+ "INSERT INTO fruit VALUES (2,'to_change' )", 1,
+ "INSERT INTO fruit VALUES (3, NULL )", 1,
+ "INSERT INTO fruit VALUES (4,'to delete' )", 1,
+ "INSERT INTO fruit VALUES (?,?); #5,via placeholders", 1,
+ "INSERT INTO fruit VALUES (6,'to delete' )", 1,
+ "INSERT INTO fruit VALUES (7,'to_delete' )", 1,
+ "DELETE FROM fruit WHERE dVal='to delete'", 2,
+ "UPDATE fruit SET dVal='apples' WHERE dKey=2", 1,
+ "DELETE FROM fruit WHERE dKey=7", 1,
+ "SELECT * FROM fruit ORDER BY dKey DESC", [
+ [ 5, 'via placeholders' ],
+ [ 3, '' ],
+ [ 2, 'apples' ],
+ [ 1, 'oranges' ],
+ ],
+ "DELETE FROM fruit", 4,
+ $dbi_sql_nano ? () : ( "SELECT COUNT(*) FROM fruit", [ [ 0 ] ] ),
+ "DROP TABLE fruit", -1,
+ ],
+ 3 => [
+ "DROP TABLE IF EXISTS multi_fruit", -1,
+ "CREATE TABLE multi_fruit (dKey INT, dVal VARCHAR(10), qux INT)", '0E0',
+ "INSERT INTO multi_fruit VALUES (1,'oranges' , 11 )", 1,
+ "INSERT INTO multi_fruit VALUES (2,'to_change', 0 )", 1,
+ "INSERT INTO multi_fruit VALUES (3, NULL , 13 )", 1,
+ "INSERT INTO multi_fruit VALUES (4,'to_delete', 14 )", 1,
+ "INSERT INTO multi_fruit VALUES (?,?,?); #5,via placeholders,15", 1,
+ "INSERT INTO multi_fruit VALUES (6,'to_delete', 16 )", 1,
+ "INSERT INTO multi_fruit VALUES (7,'to delete', 17 )", 1,
+ "INSERT INTO multi_fruit VALUES (8,'to remove', 18 )", 1,
+ "UPDATE multi_fruit SET dVal='apples', qux='12' WHERE dKey=2", 1,
+ "DELETE FROM multi_fruit WHERE dVal='to_delete'", 2,
+ "DELETE FROM multi_fruit WHERE qux=17", 1,
+ "DELETE FROM multi_fruit WHERE dKey=8", 1,
+ "SELECT * FROM multi_fruit ORDER BY dKey DESC", [
+ [ 5, 'via placeholders', 15 ],
+ [ 3, undef, 13 ],
+ [ 2, 'apples', 12 ],
+ [ 1, 'oranges', 11 ],
+ ],
+ "DELETE FROM multi_fruit", 4,
+ $dbi_sql_nano ? () : ( "SELECT COUNT(*) FROM multi_fruit", [ [ 0 ] ] ),
+ "DROP TABLE multi_fruit", -1,
+ ],
+);
+
+print "Using DBM modules: @dbm_types\n";
+print "Using MLDBM serializers: @mldbm_types\n" if @mldbm_types;
+
+my %test_statements;
+my %expected_results;
+
+for my $columns ( 2 .. 3 )
+{
+ my $i = 0;
+ my @tests = part { $i++ % 2 } @{ $tests_statement_results{$columns} };
+ @{ $test_statements{$columns} } = @{$tests[0]};
+ @{ $expected_results{$columns} } = @{$tests[1]};
+}
+
+unless (@dbm_types) {
+ plan skip_all => "No DBM modules available";
+}
+
+for my $mldbm ( @mldbm_types ) {
+ my $columns = ($mldbm) ? 3 : 2;
+ for my $dbm_type ( @dbm_types ) {
+ print "\n--- Using $dbm_type ($mldbm) ---\n";
+ eval { do_test( $dbm_type, $mldbm, $columns) }
+ or warn $@;
+ }
+}
+
+done_testing();
+
+sub do_test {
+ my ($dtype, $mldbm, $columns) = @_;
+
+ #diag ("Starting test: " . $starting_test_no);
+
+ # The DBI can't test locking here, sadly, because of the risk it'll hang
+ # on systems with broken NFS locking daemons.
+ # (This test script doesn't test that locking actually works anyway.)
+
+ # use f_lockfile in next release - use it here as test case only
+ my $dsn ="dbi:DBM(RaiseError=0,PrintError=1):dbm_type=$dtype;dbm_mldbm=$mldbm;dbm_lockfile=.lck";
+
+ if ($using_dbd_gofer) {
+ $dsn .= ";f_dir=$dir";
+ }
+
+ my $dbh = DBI->connect( $dsn );
+
+ my $dbm_versions;
+ if ($DBI::VERSION >= 1.37 # needed for install_method
+ && !$ENV{DBI_AUTOPROXY} # can't transparently proxy driver-private methods
+ ) {
+ $dbm_versions = $dbh->dbm_versions;
+ }
+ else {
+ $dbm_versions = $dbh->func('dbm_versions');
+ }
+ note $dbm_versions;
+ ok($dbm_versions, 'dbm_versions');
+ isa_ok($dbh, 'DBI::db');
+
+ # test if it correctly accepts valid $dbh attributes
+ SKIP: {
+ skip "Can't set attributes after connect using DBD::Gofer", 2
+ if $using_dbd_gofer;
+ eval {$dbh->{f_dir}=$dir};
+ ok(!$@);
+ eval {$dbh->{dbm_mldbm}=$mldbm};
+ ok(!$@);
+ }
+
+ # test if it correctly rejects invalid $dbh attributes
+ #
+ eval {
+ local $SIG{__WARN__} = sub { } if $using_dbd_gofer;
+ local $dbh->{RaiseError} = 1;
+ local $dbh->{PrintError} = 0;
+ $dbh->{dbm_bad_name}=1;
+ };
+ ok($@);
+
+ my @queries = @{$test_statements{$columns}};
+ my @results = @{$expected_results{$columns}};
+
+ SKIP:
+ for my $idx ( 0 .. $#queries ) {
+ my $sql = $queries[$idx];
+ $sql =~ s/\S*fruit/${dtype}_fruit/; # include dbm type in table name
+ $sql =~ s/;$//;
+ #diag($sql);
+
+ # XXX FIX INSERT with NULL VALUE WHEN COLUMN NOT NULLABLE
+ $dtype eq 'BerkeleyDB' and !$mldbm and 0 == index($sql, 'INSERT') and $sql =~ s/NULL/''/;
+
+ $sql =~ s/\s*;\s*(?:#(.*))//;
+ my $comment = $1;
+
+ my $sth = $dbh->prepare($sql);
+ ok($sth, "prepare $sql") or diag($dbh->errstr || 'unknown error');
+
+ my @bind;
+ if($sth->{NUM_OF_PARAMS})
+ {
+ @bind = split /,/, $comment;
+ }
+ # if execute errors we will handle it, not PrintError:
+ $sth->{PrintError} = 0;
+ my $n = $sth->execute(@bind);
+ ok($n, 'execute') or diag($sth->errstr || 'unknown error');
+ next if (!defined($n));
+
+ is( $n, $results[$idx], $sql ) unless( 'ARRAY' eq ref $results[$idx] );
+ TODO: {
+ local $TODO = "AUTOPROXY drivers might throw away sth->rows()" if($ENV{DBI_AUTOPROXY});
+ is( $n, $sth->rows, '$sth->execute(' . $sql . ') == $sth->rows' ) if( $sql =~ m/^(?:UPDATE|DELETE)/ );
+ }
+ next unless $sql =~ /SELECT/;
+ my $results='';
+ my $allrows = $sth->fetchall_arrayref();
+ my $expected_rows = $results[$idx];
+ is( $sth->rows, scalar( @{$expected_rows} ), $sql );
+ is_deeply( $allrows, $expected_rows, 'SELECT results' );
+ }
+ $dbh->disconnect;
+ return 1;
+}
+1;
diff --git a/t/51dbm_file.t b/t/51dbm_file.t
new file mode 100644
index 0000000..4b97288
--- /dev/null
+++ b/t/51dbm_file.t
@@ -0,0 +1,130 @@
+#!perl -w
+$| = 1;
+
+use strict;
+use warnings;
+
+use File::Copy ();
+use File::Path;
+use File::Spec ();
+use Test::More;
+
+my $using_dbd_gofer = ( $ENV{DBI_AUTOPROXY} || '' ) =~ /^dbi:Gofer.*transport=/i;
+
+use DBI;
+
+do "t/lib.pl";
+
+my $dir = test_dir();
+
+my $dbh = DBI->connect( 'dbi:DBM:', undef, undef, {
+ f_dir => $dir,
+ sql_identifier_case => 1, # SQL_IC_UPPER
+ }
+);
+
+ok( $dbh->do(q/drop table if exists FRED/), 'drop table' );
+
+my $dirfext = $^O eq 'VMS' ? '.sdbm_dir' : '.dir';
+
+$dbh->do(q/create table fred (a integer, b integer)/);
+ok( -f File::Spec->catfile( $dir, "FRED$dirfext" ), "FRED$dirfext exists" );
+
+rmtree $dir;
+mkpath $dir;
+
+if ($using_dbd_gofer)
+{
+ # can't modify attributes when connect through a Gofer instance
+ $dbh->disconnect();
+ $dbh = DBI->connect( 'dbi:DBM:', undef, undef, {
+ f_dir => $dir,
+ sql_identifier_case => 2, # SQL_IC_LOWER
+ }
+ );
+}
+else
+{
+ $dbh->dbm_clear_meta('fred'); # otherwise the col_names are still known!
+ $dbh->{sql_identifier_case} = 2; # SQL_IC_LOWER
+}
+
+$dbh->do(q/create table FRED (a integer, b integer)/);
+ok( -f File::Spec->catfile( $dir, "fred$dirfext" ), "fred$dirfext exists" );
+
+my $tblfext;
+unless( $using_dbd_gofer )
+{
+ $tblfext = $dbh->{dbm_tables}->{fred}->{f_ext} || '';
+ $tblfext =~ s{/r$}{};
+ ok( -f File::Spec->catfile( $dir, "fred$tblfext" ), "fred$tblfext exists" );
+}
+
+ok( $dbh->do(q/insert into fRED (a,b) values(1,2)/), 'insert into mixed case table' );
+
+# but change fRED to FRED and it works.
+
+ok( $dbh->do(q/insert into FRED (a,b) values(2,1)/), 'insert into uppercase table' );
+
+unless ($using_dbd_gofer)
+{
+ my $fn_tbl2 = $dbh->{dbm_tables}->{fred}->{f_fqfn};
+ $fn_tbl2 =~ s/fred(\.[^.]*)?$/freddy$1/;
+ my @dbfiles = grep { -f $_ } (
+ $dbh->{dbm_tables}->{fred}->{f_fqfn},
+ $dbh->{dbm_tables}->{fred}->{f_fqln},
+ $dbh->{dbm_tables}->{fred}->{f_fqbn} . ".dir"
+ );
+ foreach my $fn (@dbfiles)
+ {
+ my $tgt_fn = $fn;
+ $tgt_fn =~ s/fred(\.[^.]*)?$/freddy$1/;
+ File::Copy::copy( $fn, $tgt_fn );
+ }
+ $dbh->{dbm_tables}->{krueger}->{file} = $fn_tbl2;
+
+ my $r = $dbh->selectall_arrayref(q/select * from Krueger/);
+ ok( @$r == 2, 'rows found via cloned mixed case table' );
+
+ ok( $dbh->do(q/drop table if exists KRUeGEr/), 'drop table' );
+}
+
+my $r = $dbh->selectall_arrayref(q/select * from Fred/);
+ok( @$r == 2, 'rows found via mixed case table' );
+
+SKIP:
+{
+ DBD::DBM::Statement->isa("SQL::Statement") or skip("quoted identifiers aren't supported by DBI::SQL::Nano",1);
+ my $abs_tbl = File::Spec->catfile( $dir, 'fred' );
+ # work around SQL::Statement bug
+ DBD::DBM::Statement->isa("SQL::Statement") and SQL::Statement->VERSION() lt "1.32" and $abs_tbl =~ s|\\|/|g;
+ $r = $dbh->selectall_arrayref( sprintf( q|select * from "%s"|, $abs_tbl ) );
+ ok( @$r == 2, 'rows found via select via fully qualified path' );
+}
+
+if( $using_dbd_gofer )
+{
+ ok( $dbh->do(q/drop table if exists FRED/), 'drop table' );
+ ok( !-f File::Spec->catfile( $dir, "fred$dirfext" ), "fred$dirfext removed" );
+}
+else
+{
+ my $tbl_info = { file => "fred$tblfext" };
+
+ ok( $dbh->disconnect(), "disconnect" );
+ $dbh = DBI->connect( 'dbi:DBM:', undef, undef, {
+ f_dir => $dir,
+ sql_identifier_case => 2, # SQL_IC_LOWER
+ dbm_tables => { fred => $tbl_info },
+ }
+ );
+
+ $r = $dbh->selectall_arrayref(q/select * from Fred/);
+ ok( @$r == 2, 'rows found after reconnect using "dbm_tables"' );
+
+ ok( $dbh->do(q/drop table if exists FRED/), 'drop table' );
+ ok( !-f File::Spec->catfile( $dir, "fred$dirfext" ), "fred$dirfext removed" );
+ ok( !-f File::Spec->catfile( $dir, "fred$tblfext" ), "fred$tblfext removed" );
+}
+
+done_testing();
diff --git a/t/52dbm_complex.t b/t/52dbm_complex.t
new file mode 100644
index 0000000..31dc6e3
--- /dev/null
+++ b/t/52dbm_complex.t
@@ -0,0 +1,359 @@
+#!perl -w
+$| = 1;
+
+use strict;
+use warnings;
+
+require DBD::DBM;
+
+use File::Path;
+use File::Spec;
+use Test::More;
+use Cwd;
+use Config qw(%Config);
+use Storable qw(dclone);
+
+my $using_dbd_gofer = ( $ENV{DBI_AUTOPROXY} || '' ) =~ /^dbi:Gofer.*transport=/i;
+
+use DBI;
+use vars qw( @mldbm_types @dbm_types );
+
+BEGIN
+{
+
+ # 0=SQL::Statement if avail, 1=DBI::SQL::Nano
+ # next line forces use of Nano rather than default behaviour
+ # $ENV{DBI_SQL_NANO}=1;
+ # This is done in zv*n*_50dbm_simple.t
+
+ if ( eval { require 'MLDBM.pm'; } )
+ {
+ push @mldbm_types, qw(Data::Dumper Storable); # both in CORE
+ push @mldbm_types, 'FreezeThaw' if eval { require 'FreezeThaw.pm' };
+ push @mldbm_types, 'YAML' if eval { require MLDBM::Serializer::YAML; };
+ push @mldbm_types, 'JSON' if eval { require MLDBM::Serializer::JSON; };
+ }
+
+ # Potential DBM modules in preference order (SDBM_File first)
+ # skip NDBM and ODBM as they don't support EXISTS
+ my @dbms = qw(SDBM_File GDBM_File DB_File BerkeleyDB NDBM_File ODBM_File);
+ my @use_dbms = @ARGV;
+ if ( !@use_dbms && $ENV{DBD_DBM_TEST_BACKENDS} )
+ {
+ @use_dbms = split ' ', $ENV{DBD_DBM_TEST_BACKENDS};
+ }
+
+ if ( lc "@use_dbms" eq "all" )
+ {
+ # test with as many of the major DBM types as are available
+ @dbm_types = grep {
+ eval { local $^W; require "$_.pm" }
+ } @dbms;
+ }
+ elsif (@use_dbms)
+ {
+ @dbm_types = @use_dbms;
+ }
+ else
+ {
+ # we only test SDBM_File by default to avoid tripping up
+ # on any broken DBM's that may be installed in odd places.
+ # It's only DBD::DBM we're trying to test here.
+ # (However, if SDBM_File is not available, then use another.)
+ for my $dbm (@dbms)
+ {
+ if ( eval { local $^W; require "$dbm.pm" } )
+ {
+ @dbm_types = ($dbm);
+ last;
+ }
+ }
+ }
+
+ if ( eval { require List::MoreUtils; } )
+ {
+ List::MoreUtils->import("part");
+ }
+ else
+ {
+ # XXX from PP part of List::MoreUtils
+ eval <<'EOP';
+sub part(&@) {
+ my ($code, @list) = @_;
+ my @parts;
+ push @{ $parts[$code->($_)] }, $_ for @list;
+ return @parts;
+}
+EOP
+ }
+}
+
+my $haveSS = DBD::DBM::Statement->isa('SQL::Statement');
+
+plan skip_all => "DBI::SQL::Nano is being used" unless ( $haveSS );
+plan skip_all => "Not running with MLDBM" unless ( @mldbm_types );
+
+do "t/lib.pl";
+
+my $dir = test_dir ();
+
+my $dbh = DBI->connect( 'dbi:DBM:', undef, undef, { f_dir => $dir, } );
+
+my $suffix;
+my $tbl_meta;
+
+sub break_at_warn
+{
+ note "break here";
+}
+$SIG{__WARN__} = \&break_at_warn;
+$SIG{__DIE__} = \&break_at_warn;
+
+sub load_tables
+{
+ my ( $dbmtype, $dbmmldbm ) = @_;
+ my $last_suffix;
+
+ if ($using_dbd_gofer)
+ {
+ $dbh->disconnect();
+ $dbh = DBI->connect( "dbi:DBM:", undef, undef, { f_dir => $dir, dbm_type => $dbmtype, dbm_mldbm => $dbmmldbm } );
+ }
+ else
+ {
+ $last_suffix = $suffix;
+ $dbh->{dbm_type} = $dbmtype;
+ $dbh->{dbm_mldbm} = $dbmmldbm;
+ }
+
+ (my $serializer = $dbmmldbm ) =~ s/::/_/g;
+ $suffix = join( "_", $$, $dbmtype, $serializer );
+
+ if ($last_suffix)
+ {
+ for my $table (qw(APPL_%s PREC_%s NODE_%s LANDSCAPE_%s CONTACT_%s NM_LANDSCAPE_%s APPL_CONTACT_%s))
+ {
+ my $readsql = sprintf "SELECT * FROM $table", $last_suffix;
+ my $impsql = sprintf "CREATE TABLE $table AS IMPORT (?)", $suffix;
+ my ($readsth);
+ ok( $readsth = $dbh->prepare($readsql), "prepare: $readsql" );
+ ok( $readsth->execute(), "execute: $readsql" );
+ ok( $dbh->do( $impsql, {}, $readsth ), $impsql ) or warn $dbh->errstr();
+ }
+ }
+ else
+ {
+ for my $sql ( split( "\n", join( '', <<'EOD' ) ) )
+CREATE TABLE APPL_%s (id INT, applname CHAR, appluniq CHAR, version CHAR, appl_type CHAR)
+CREATE TABLE PREC_%s (id INT, appl_id INT, node_id INT, precedence INT)
+CREATE TABLE NODE_%s (id INT, nodename CHAR, os CHAR, version CHAR)
+CREATE TABLE LANDSCAPE_%s (id INT, landscapename CHAR)
+CREATE TABLE CONTACT_%s (id INT, surname CHAR, familyname CHAR, phone CHAR, userid CHAR, mailaddr CHAR)
+CREATE TABLE NM_LANDSCAPE_%s (id INT, ls_id INT, obj_id INT, obj_type INT)
+CREATE TABLE APPL_CONTACT_%s (id INT, contact_id INT, appl_id INT, contact_type CHAR)
+
+INSERT INTO APPL_%s VALUES ( 1, 'ZQF', 'ZFQLIN', '10.2.0.4', 'Oracle DB')
+INSERT INTO APPL_%s VALUES ( 2, 'YRA', 'YRA-UX', '10.2.0.2', 'Oracle DB')
+INSERT INTO APPL_%s VALUES ( 3, 'PRN1', 'PRN1-4.B2', '1.1.22', 'CUPS' )
+INSERT INTO APPL_%s VALUES ( 4, 'PRN2', 'PRN2-4.B2', '1.1.22', 'CUPS' )
+INSERT INTO APPL_%s VALUES ( 5, 'PRN1', 'PRN1-4.B1', '1.1.22', 'CUPS' )
+INSERT INTO APPL_%s VALUES ( 7, 'PRN2', 'PRN2-4.B1', '1.1.22', 'CUPS' )
+INSERT INTO APPL_%s VALUES ( 8, 'sql-stmt', 'SQL::Statement', '1.21', 'Project Web-Site')
+INSERT INTO APPL_%s VALUES ( 9, 'cpan.org', 'http://www.cpan.org/', '1.0', 'Web-Site')
+INSERT INTO APPL_%s VALUES (10, 'httpd', 'cpan-apache', '2.2.13', 'Web-Server')
+INSERT INTO APPL_%s VALUES (11, 'cpan-mods', 'cpan-mods', '8.4.1', 'PostgreSQL DB')
+INSERT INTO APPL_%s VALUES (12, 'cpan-authors', 'cpan-authors', '8.4.1', 'PostgreSQL DB')
+
+INSERT INTO NODE_%s VALUES ( 1, 'ernie', 'RHEL', '5.2')
+INSERT INTO NODE_%s VALUES ( 2, 'bert', 'RHEL', '5.2')
+INSERT INTO NODE_%s VALUES ( 3, 'statler', 'FreeBSD', '7.2')
+INSERT INTO NODE_%s VALUES ( 4, 'waldorf', 'FreeBSD', '7.2')
+INSERT INTO NODE_%s VALUES ( 5, 'piggy', 'NetBSD', '5.0.2')
+INSERT INTO NODE_%s VALUES ( 6, 'kermit', 'NetBSD', '5.0.2')
+INSERT INTO NODE_%s VALUES ( 7, 'samson', 'NetBSD', '5.0.2')
+INSERT INTO NODE_%s VALUES ( 8, 'tiffy', 'NetBSD', '5.0.2')
+INSERT INTO NODE_%s VALUES ( 9, 'rowlf', 'Debian Lenny', '5.0')
+INSERT INTO NODE_%s VALUES (10, 'fozzy', 'Debian Lenny', '5.0')
+
+INSERT INTO PREC_%s VALUES ( 1, 1, 1, 1)
+INSERT INTO PREC_%s VALUES ( 2, 1, 2, 2)
+INSERT INTO PREC_%s VALUES ( 3, 2, 2, 1)
+INSERT INTO PREC_%s VALUES ( 4, 2, 1, 2)
+INSERT INTO PREC_%s VALUES ( 5, 3, 5, 1)
+INSERT INTO PREC_%s VALUES ( 6, 3, 7, 2)
+INSERT INTO PREC_%s VALUES ( 7, 4, 6, 1)
+INSERT INTO PREC_%s VALUES ( 8, 4, 8, 2)
+INSERT INTO PREC_%s VALUES ( 9, 5, 7, 1)
+INSERT INTO PREC_%s VALUES (10, 5, 5, 2)
+INSERT INTO PREC_%s VALUES (11, 6, 8, 1)
+INSERT INTO PREC_%s VALUES (12, 7, 6, 2)
+INSERT INTO PREC_%s VALUES (13, 10, 9, 1)
+INSERT INTO PREC_%s VALUES (14, 10, 10, 1)
+INSERT INTO PREC_%s VALUES (15, 8, 9, 1)
+INSERT INTO PREC_%s VALUES (16, 8, 10, 1)
+INSERT INTO PREC_%s VALUES (17, 9, 9, 1)
+INSERT INTO PREC_%s VALUES (18, 9, 10, 1)
+INSERT INTO PREC_%s VALUES (19, 11, 3, 1)
+INSERT INTO PREC_%s VALUES (20, 11, 4, 2)
+INSERT INTO PREC_%s VALUES (21, 12, 4, 1)
+INSERT INTO PREC_%s VALUES (22, 12, 3, 2)
+
+INSERT INTO LANDSCAPE_%s VALUES (1, 'Logistic')
+INSERT INTO LANDSCAPE_%s VALUES (2, 'Infrastructure')
+INSERT INTO LANDSCAPE_%s VALUES (3, 'CPAN')
+
+INSERT INTO CONTACT_%s VALUES ( 1, 'Hans Peter', 'Mueller', '12345', 'HPMUE', 'hp-mueller@here.com')
+INSERT INTO CONTACT_%s VALUES ( 2, 'Knut', 'Inge', '54321', 'KINGE', 'k-inge@here.com')
+INSERT INTO CONTACT_%s VALUES ( 3, 'Lola', 'Nguyen', '+1-123-45678-90', 'LNYUG', 'lola.ngyuen@customer.com')
+INSERT INTO CONTACT_%s VALUES ( 4, 'Helge', 'Brunft', '+41-123-45678-09', 'HBRUN', 'helge.brunft@external-dc.at')
+
+-- TYPE: 1: APPL 2: NODE 3: CONTACT
+INSERT INTO NM_LANDSCAPE_%s VALUES ( 1, 1, 1, 2)
+INSERT INTO NM_LANDSCAPE_%s VALUES ( 2, 1, 2, 2)
+INSERT INTO NM_LANDSCAPE_%s VALUES ( 3, 3, 3, 2)
+INSERT INTO NM_LANDSCAPE_%s VALUES ( 4, 3, 4, 2)
+INSERT INTO NM_LANDSCAPE_%s VALUES ( 5, 2, 5, 2)
+INSERT INTO NM_LANDSCAPE_%s VALUES ( 6, 2, 6, 2)
+INSERT INTO NM_LANDSCAPE_%s VALUES ( 7, 2, 7, 2)
+INSERT INTO NM_LANDSCAPE_%s VALUES ( 8, 2, 8, 2)
+INSERT INTO NM_LANDSCAPE_%s VALUES ( 9, 3, 9, 2)
+INSERT INTO NM_LANDSCAPE_%s VALUES (10, 3,10, 2)
+INSERT INTO NM_LANDSCAPE_%s VALUES (11, 1, 1, 1)
+INSERT INTO NM_LANDSCAPE_%s VALUES (12, 2, 2, 1)
+INSERT INTO NM_LANDSCAPE_%s VALUES (13, 2, 2, 3)
+INSERT INTO NM_LANDSCAPE_%s VALUES (14, 3, 1, 3)
+
+INSERT INTO APPL_CONTACT_%s VALUES (1, 3, 1, 'OWNER')
+INSERT INTO APPL_CONTACT_%s VALUES (2, 3, 2, 'OWNER')
+INSERT INTO APPL_CONTACT_%s VALUES (3, 4, 3, 'ADMIN')
+INSERT INTO APPL_CONTACT_%s VALUES (4, 4, 4, 'ADMIN')
+INSERT INTO APPL_CONTACT_%s VALUES (5, 4, 5, 'ADMIN')
+INSERT INTO APPL_CONTACT_%s VALUES (6, 4, 6, 'ADMIN')
+EOD
+ {
+ chomp $sql;
+ $sql =~ s/^\s+//;
+ $sql =~ s/--.*$//;
+ $sql =~ s/\s+$//;
+ next if ( '' eq $sql );
+ $sql = sprintf $sql, $suffix;
+ ok( $dbh->do($sql), $sql );
+ }
+ }
+
+ for my $table (qw(APPL_%s PREC_%s NODE_%s LANDSCAPE_%s CONTACT_%s NM_LANDSCAPE_%s APPL_CONTACT_%s))
+ {
+ my $tbl_name = lc sprintf($table, $suffix);
+ $tbl_meta->{$tbl_name} = { dbm_type => $dbmtype, dbm_mldbm => $dbmmldbm };
+ }
+
+ unless ($using_dbd_gofer)
+ {
+ my $tbl_known_meta = $dbh->dbm_get_meta( "+", [ qw(dbm_type dbm_mldbm) ] );
+ is_deeply( $tbl_known_meta, $tbl_meta, "Know meta" );
+ }
+}
+
+sub do_tests
+{
+ my ( $dbmtype, $serializer ) = @_;
+
+ note "Running do_tests for $dbmtype + $serializer";
+
+ load_tables( $dbmtype, $serializer );
+
+ my %joins;
+ my $sql;
+
+ $sql = join( " ",
+ q{SELECT applname, appluniq, version, nodename },
+ sprintf( q{FROM APPL_%s, PREC_%s, NODE_%s }, ($suffix) x 3 ),
+ sprintf( q{WHERE appl_type LIKE '%%DB' AND APPL_%s.id=PREC_%s.appl_id AND}, ($suffix) x 2 ),
+ sprintf( q{PREC_%s.node_id=NODE_%s.id}, ($suffix) x 2 ),
+ );
+
+ $joins{$sql} = [
+ 'ZQF~ZFQLIN~10.2.0.4~ernie', 'ZQF~ZFQLIN~10.2.0.4~bert',
+ 'YRA~YRA-UX~10.2.0.2~bert', 'YRA~YRA-UX~10.2.0.2~ernie',
+ 'cpan-mods~cpan-mods~8.4.1~statler', 'cpan-mods~cpan-mods~8.4.1~waldorf',
+ 'cpan-authors~cpan-authors~8.4.1~waldorf', 'cpan-authors~cpan-authors~8.4.1~statler',
+ ];
+
+ $sql = join( " ",
+ q{SELECT applname, appluniq, version, landscapename, nodename},
+ sprintf( q{FROM APPL_%s, PREC_%s, NODE_%s, LANDSCAPE_%s, NM_LANDSCAPE_%s}, ($suffix) x 5 ),
+ sprintf( q{WHERE appl_type LIKE '%%DB' AND APPL_%s.id=PREC_%s.appl_id AND}, ($suffix) x 2 ),
+ sprintf( q{PREC_%s.node_id=NODE_%s.id AND NM_LANDSCAPE_%s.obj_id=APPL_%s.id AND}, ($suffix) x 4 ),
+ sprintf( q{NM_LANDSCAPE_%s.obj_type=1 AND NM_LANDSCAPE_%s.ls_id=LANDSCAPE_%s.id}, ($suffix) x 3 ),
+ );
+ $joins{$sql} = [
+ 'ZQF~ZFQLIN~10.2.0.4~Logistic~ernie', 'ZQF~ZFQLIN~10.2.0.4~Logistic~bert',
+ 'YRA~YRA-UX~10.2.0.2~Infrastructure~bert', 'YRA~YRA-UX~10.2.0.2~Infrastructure~ernie',
+ ];
+ $sql = join( " ",
+ q{SELECT applname, appluniq, version, surname, familyname, phone, nodename},
+ sprintf( q{FROM APPL_%s, PREC_%s, NODE_%s, CONTACT_%s, APPL_CONTACT_%s}, ($suffix) x 5 ),
+ sprintf( q{WHERE appl_type='CUPS' AND APPL_%s.id=PREC_%s.appl_id AND}, ($suffix) x 2 ),
+ sprintf( q{PREC_%s.node_id=NODE_%s.id AND APPL_CONTACT_%s.appl_id=APPL_%s.id AND}, ($suffix) x 4 ),
+ sprintf( q{APPL_CONTACT_%s.contact_id=CONTACT_%s.id AND PREC_%s.PRECEDENCE=1}, ($suffix) x 3 ),
+ );
+ $joins{$sql} = [
+ 'PRN1~PRN1-4.B2~1.1.22~Helge~Brunft~+41-123-45678-09~piggy',
+ 'PRN2~PRN2-4.B2~1.1.22~Helge~Brunft~+41-123-45678-09~kermit',
+ 'PRN1~PRN1-4.B1~1.1.22~Helge~Brunft~+41-123-45678-09~samson',
+ ];
+ $sql = join( " ",
+ q{SELECT DISTINCT applname, appluniq, version, surname, familyname, phone, nodename},
+ sprintf( q{FROM APPL_%s, PREC_%s, NODE_%s, CONTACT_%s, APPL_CONTACT_%s}, ($suffix) x 5 ),
+ sprintf( q{WHERE appl_type='CUPS' AND APPL_%s.id=PREC_%s.appl_id AND}, ($suffix) x 2 ),
+ sprintf( q{PREC_%s.node_id=NODE_%s.id AND APPL_CONTACT_%s.appl_id=APPL_%s.id}, ($suffix) x 4 ),
+ sprintf( q{AND APPL_CONTACT_%s.contact_id=CONTACT_%s.id}, ($suffix) x 2 ),
+ );
+ $joins{$sql} = [
+ 'PRN1~PRN1-4.B1~1.1.22~Helge~Brunft~+41-123-45678-09~piggy',
+ 'PRN1~PRN1-4.B2~1.1.22~Helge~Brunft~+41-123-45678-09~piggy',
+ 'PRN1~PRN1-4.B1~1.1.22~Helge~Brunft~+41-123-45678-09~samson',
+ 'PRN1~PRN1-4.B2~1.1.22~Helge~Brunft~+41-123-45678-09~samson',
+ 'PRN2~PRN2-4.B2~1.1.22~Helge~Brunft~+41-123-45678-09~kermit',
+ 'PRN2~PRN2-4.B2~1.1.22~Helge~Brunft~+41-123-45678-09~tiffy',
+ ];
+ $sql = join( " ",
+ q{SELECT CONCAT('[% NOW %]') AS "timestamp", applname, appluniq, version, nodename},
+ sprintf( q{FROM APPL_%s, PREC_%s, NODE_%s}, ($suffix) x 3 ),
+ sprintf( q{WHERE appl_type LIKE '%%DB' AND APPL_%s.id=PREC_%s.appl_id AND}, ($suffix) x 2 ),
+ sprintf( q{PREC_%s.node_id=NODE_%s.id}, ($suffix) x 2 ),
+ );
+ $joins{$sql} = [
+ '[% NOW %]~ZQF~ZFQLIN~10.2.0.4~ernie',
+ '[% NOW %]~ZQF~ZFQLIN~10.2.0.4~bert',
+ '[% NOW %]~YRA~YRA-UX~10.2.0.2~bert',
+ '[% NOW %]~YRA~YRA-UX~10.2.0.2~ernie',
+ '[% NOW %]~cpan-mods~cpan-mods~8.4.1~statler',
+ '[% NOW %]~cpan-mods~cpan-mods~8.4.1~waldorf',
+ '[% NOW %]~cpan-authors~cpan-authors~8.4.1~waldorf',
+ '[% NOW %]~cpan-authors~cpan-authors~8.4.1~statler',
+ ];
+
+ while ( my ( $sql, $result ) = each(%joins) )
+ {
+ my $sth = $dbh->prepare($sql);
+ eval { $sth->execute() };
+ warn $@ if $@;
+ my @res;
+ while ( my $row = $sth->fetchrow_arrayref() )
+ {
+ push( @res, join( '~', @{$row} ) );
+ }
+ is( join( '^', sort @res ), join( '^', sort @{$result} ), $sql );
+ }
+}
+
+foreach my $dbmtype (@dbm_types)
+{
+ foreach my $serializer (@mldbm_types)
+ {
+ do_tests( $dbmtype, $serializer );
+ }
+}
+
+done_testing();
diff --git a/t/60preparse.t b/t/60preparse.t
new file mode 100755
index 0000000..6432feb
--- /dev/null
+++ b/t/60preparse.t
@@ -0,0 +1,148 @@
+#!perl -w
+
+use DBI qw(:preparse_flags);
+
+$|=1;
+
+use Test::More;
+
+BEGIN {
+ if ($DBI::PurePerl) {
+ plan skip_all => 'preparse not supported for DBI::PurePerl';
+ }
+ else {
+ plan tests => 39;
+ }
+}
+
+my $dbh = DBI->connect("dbi:ExampleP:", "", "", {
+ PrintError => 0,
+});
+isa_ok( $dbh, 'DBI::db' );
+
+sub pp {
+ my $dbh = shift;
+ my $rv = $dbh->preparse(@_);
+ return $rv;
+}
+
+# --------------------------------------------------------------------- #
+# DBIpp_cm_cs /* C style */
+# DBIpp_cm_hs /* # */
+# DBIpp_cm_dd /* -- */
+# DBIpp_cm_br /* {} */
+# DBIpp_cm_dw /* '-- ' dash dash whitespace */
+# DBIpp_cm_XX /* any of the above */
+
+# DBIpp_ph_qm /* ? */
+# DBIpp_ph_cn /* :1 */
+# DBIpp_ph_cs /* :name */
+# DBIpp_ph_sp /* %s (as return only, not accept) */
+# DBIpp_ph_XX /* any of the above */
+
+# DBIpp_st_qq /* '' char escape */
+# DBIpp_st_bs /* \ char escape */
+# DBIpp_st_XX /* any of the above */
+
+# ===================================================================== #
+# pp (h input return accept expected) #
+# ===================================================================== #
+
+## Comments:
+
+is( pp($dbh, "a#b\nc", DBIpp_cm_cs, DBIpp_cm_hs), "a/*b*/\nc" );
+is( pp($dbh, "a#b\nc", DBIpp_cm_dw, DBIpp_cm_hs), "a-- b\nc" );
+is( pp($dbh, "a/*b*/c", DBIpp_cm_hs, DBIpp_cm_cs), "a#b\nc" );
+is( pp($dbh, "a{b}c", DBIpp_cm_cs, DBIpp_cm_br), "a/*b*/c" );
+is( pp($dbh, "a--b\nc", DBIpp_cm_br, DBIpp_cm_dd), "a{b}\nc" );
+
+is( pp($dbh, "a-- b\n/*c*/d", DBIpp_cm_br, DBIpp_cm_cs|DBIpp_cm_dw), "a{ b}\n{c}d" );
+is( pp($dbh, "a/*b*/c#d\ne--f\nh-- i\nj{k}", 0, DBIpp_cm_XX), "a c\ne\nh\nj " );
+
+## Placeholders:
+
+is( pp($dbh, "a = :1", DBIpp_ph_qm, DBIpp_ph_cn), "a = ?" );
+is( pp($dbh, "a = :1", DBIpp_ph_sp, DBIpp_ph_cn), "a = %s" );
+is( pp($dbh, "a = ?" , DBIpp_ph_cn, DBIpp_ph_qm), "a = :p1" );
+is( pp($dbh, "a = ?" , DBIpp_ph_sp, DBIpp_ph_qm), "a = %s" );
+
+is( pp($dbh, "a = :name", DBIpp_ph_qm, DBIpp_ph_cs), "a = ?" );
+is( pp($dbh, "a = :name", DBIpp_ph_sp, DBIpp_ph_cs), "a = %s" );
+
+is( pp($dbh, "a = ? b = ? c = ?", DBIpp_ph_cn, DBIpp_ph_XX), "a = :p1 b = :p2 c = :p3" );
+
+## Placeholders inside comments (should be ignored where comments style is accepted):
+
+is( pp( $dbh,
+ "a = ? /*b = :1*/ c = ?",
+ DBIpp_cm_dw|DBIpp_ph_cn,
+ DBIpp_cm_cs|DBIpp_ph_qm),
+ "a = :p1 -- b = :1\n c = :p2" );
+
+## Placeholders inside single and double quotes (should be ignored):
+
+is( pp( $dbh,
+ "a = ? 'b = :1' c = ?",
+ DBIpp_ph_cn,
+ DBIpp_ph_XX),
+ "a = :p1 'b = :1' c = :p2" );
+
+is( pp( $dbh,
+ 'a = ? "b = :1" c = ?',
+ DBIpp_ph_cn,
+ DBIpp_ph_XX),
+ 'a = :p1 "b = :1" c = :p2' );
+
+## Comments inside single and double quotes (should be ignored):
+
+is( pp( $dbh,
+ "a = ? '{b = :1}' c = ?",
+ DBIpp_cm_cs|DBIpp_ph_cn,
+ DBIpp_cm_XX|DBIpp_ph_qm),
+ "a = :p1 '{b = :1}' c = :p2" );
+
+is( pp( $dbh,
+ 'a = ? "/*b = :1*/" c = ?',
+ DBIpp_cm_dw|DBIpp_ph_cn,
+ DBIpp_cm_XX|DBIpp_ph_qm),
+ 'a = :p1 "/*b = :1*/" c = :p2' );
+
+## Single and double quoted strings starting inside comments (should be ignored):
+
+is( pp( $dbh,
+ 'a = ? /*"b = :1 */ c = ?',
+ DBIpp_cm_br|DBIpp_ph_cn,
+ DBIpp_cm_XX|DBIpp_ph_qm),
+ 'a = :p1 {"b = :1 } c = :p2' );
+
+## Check error conditions are trapped:
+
+is( pp($dbh, "a = :value and b = :1", DBIpp_ph_qm, DBIpp_ph_cs|DBIpp_ph_cn), undef );
+ok( $DBI::err );
+is( $DBI::errstr, "preparse found mixed placeholder styles (:1 / :name)" );
+
+is( pp($dbh, "a = :1 and b = :3", DBIpp_ph_qm, DBIpp_ph_cn), undef );
+ok( $DBI::err );
+is( $DBI::errstr, "preparse found placeholder :3 out of sequence, expected :2" );
+
+is( pp($dbh, "foo ' comment", 0, 0), "foo ' comment" );
+ok( $DBI::err );
+is( $DBI::errstr, "preparse found unterminated single-quoted string" );
+
+is( pp($dbh, 'foo " comment', 0, 0), 'foo " comment' );
+ok( $DBI::err );
+is( $DBI::errstr, "preparse found unterminated double-quoted string" );
+
+is( pp($dbh, 'foo /* comment', DBIpp_cm_XX, DBIpp_cm_XX), 'foo /* comment' );
+ok( $DBI::err );
+is( $DBI::errstr, "preparse found unterminated bracketed C-style comment" );
+
+is( pp($dbh, 'foo { comment', DBIpp_cm_XX, DBIpp_cm_XX), 'foo { comment' );
+ok( $DBI::err );
+is( $DBI::errstr, "preparse found unterminated bracketed {...} comment" );
+
+# --------------------------------------------------------------------- #
+
+$dbh->disconnect;
+
+1;
diff --git a/t/65transact.t b/t/65transact.t
new file mode 100644
index 0000000..f3d672b
--- /dev/null
+++ b/t/65transact.t
@@ -0,0 +1,35 @@
+#!perl -w
+$|=1;
+
+use strict;
+
+use DBI;
+
+use Test::More;
+
+plan skip_all => 'Transactions not supported by DBD::Gofer'
+ if $ENV{DBI_AUTOPROXY} && $ENV{DBI_AUTOPROXY} =~ /^dbi:Gofer/i;
+
+plan tests => 10;
+
+my $dbh = DBI->connect('dbi:ExampleP(AutoCommit=>1):', undef, undef)
+ or die "Unable to connect to ExampleP driver: $DBI::errstr";
+
+print "begin_work...\n";
+ok($dbh->{AutoCommit});
+ok(!$dbh->{BegunWork});
+
+ok($dbh->begin_work);
+ok(!$dbh->{AutoCommit});
+ok($dbh->{BegunWork});
+
+$dbh->commit;
+ok($dbh->{AutoCommit});
+ok(!$dbh->{BegunWork});
+
+ok($dbh->begin_work({}));
+$dbh->rollback;
+ok($dbh->{AutoCommit});
+ok(!$dbh->{BegunWork});
+
+1;
diff --git a/t/70callbacks.t b/t/70callbacks.t
new file mode 100644
index 0000000..4acb9c3
--- /dev/null
+++ b/t/70callbacks.t
@@ -0,0 +1,207 @@
+#!perl -w
+# vim:ts=8:sw=4
+
+use strict;
+
+use Test::More;
+use DBI;
+
+BEGIN {
+ plan skip_all => '$h->{Callbacks} attribute not supported for DBI::PurePerl'
+ if $DBI::PurePerl && $DBI::PurePerl; # doubled to avoid typo warning
+ plan tests => 63;
+}
+
+$| = 1;
+my $dsn = "dbi:ExampleP:";
+my %called;
+
+ok my $dbh = DBI->connect($dsn, '', ''), "Create dbh";
+
+is $dbh->{Callbacks}, undef, "Callbacks initially undef";
+ok $dbh->{Callbacks} = my $cb = { };
+is ref $dbh->{Callbacks}, 'HASH', "Callbacks can be set to a hash ref";
+is $dbh->{Callbacks}, $cb, "Callbacks set to same hash ref";
+
+$dbh->{Callbacks} = undef;
+is $dbh->{Callbacks}, undef, "Callbacks set to undef again";
+
+ok $dbh->{Callbacks} = {
+ ping => sub {
+ is $_, 'ping', '$_ holds method name';
+ is @_, 1, '@_ holds 1 values';
+ is ref $_[0], 'DBI::db', 'first is $dbh';
+ $called{$_}++;
+ return;
+ },
+ quote_identifier => sub {
+ is @_, 4, '@_ holds 4 values';
+ my $dbh = shift;
+ is ref $dbh, 'DBI::db', 'first is $dbh';
+ is $_[0], 'foo';
+ is $_[1], 'bar';
+ is $_[2], undef;
+ $_[2] = { baz => 1 };
+ $called{$_}++;
+ return (1,2,3); # return something - which is not allowed
+ },
+ disconnect => sub { # test die from within a callback
+ die "You can't disconnect that easily!\n";
+ },
+ "*" => sub {
+ $called{$_}++;
+ return;
+ }
+};
+is keys %{ $dbh->{Callbacks} }, 4;
+
+is ref $dbh->{Callbacks}->{ping}, 'CODE';
+
+$_ = 42;
+ok $dbh->ping;
+is $called{ping}, 1;
+is $_, 42, '$_ not altered by callback';
+
+ok $dbh->ping;
+is $called{ping}, 2;
+
+ok $dbh->type_info_all;
+is $called{type_info_all}, 1, 'fallback callback';
+
+my $attr;
+eval { $dbh->quote_identifier('foo','bar', $attr) };
+is $called{quote_identifier}, 1;
+ok $@, 'quote_identifier callback caused fatal error';
+is ref $attr, 'HASH', 'param modified by callback - not recommended!';
+
+ok !eval { $dbh->disconnect };
+ok $@, "You can't disconnect that easily!\n";
+
+$dbh->{Callbacks} = undef;
+ok $dbh->ping;
+is $called{ping}, 2; # no change
+
+
+# --- test skipping dispatch and fallback callbacks
+
+$dbh->{Callbacks} = {
+ ping => sub {
+ undef $_; # tell dispatch to not call the method
+ return "42 bells";
+ },
+ data_sources => sub {
+ my ($h, $values_to_return) = @_;
+ undef $_; # tell dispatch to not call the method
+ my @ret = 11..10+($values_to_return||0);
+ return @ret;
+ },
+ commit => sub { # test using set_err within a callback
+ my $h = shift;
+ undef $_; # tell dispatch to not call the method
+ return $h->set_err(42, "faked commit failure");
+ },
+};
+
+# these tests are slightly convoluted because messing with the stack is bad for
+# your mental health
+my $rv = $dbh->ping;
+is $rv, "42 bells";
+my @rv = $dbh->ping;
+is scalar @rv, 1, 'should return a single value in list context';
+is "@rv", "42 bells";
+# test returning lists with different number of args to test
+# the stack handling in the dispatch code
+is join(":", $dbh->data_sources()), "";
+is join(":", $dbh->data_sources(0)), "";
+is join(":", $dbh->data_sources(1)), "11";
+is join(":", $dbh->data_sources(2)), "11:12";
+
+{
+local $dbh->{RaiseError} = 1;
+local $dbh->{PrintError} = 0;
+is eval { $dbh->commit }, undef, 'intercepted commit should return undef';
+like $@, '/DBD::\w+::db commit failed: faked commit failure/';
+is $DBI::err, 42;
+is $DBI::errstr, "faked commit failure";
+}
+
+# --- test connect_cached.*
+
+=for comment XXX
+
+The big problem here is that conceptually the Callbacks attribute
+is applied to the $dbh _during_ the $drh->connect() call, so you can't
+set a callback on "connect" on the $dbh because connect isn't called
+on the dbh, but on the $drh.
+
+So a "connect" callback would have to be defined on the $drh, but that's
+cumbersome for the user and then it would apply to all future connects
+using that driver.
+
+The best thing to do is probably to special-case "connect", "connect_cached"
+and (the already special-case) "connect_cached.reused".
+
+=cut
+
+my @args = (
+ $dsn, '', '', {
+ Callbacks => {
+ "connect_cached.new" => sub { $called{new}++; return; },
+ "connect_cached.reused" => sub { $called{cached}++; return; },
+ }
+ }
+);
+
+%called = ();
+
+ok $dbh = DBI->connect(@args), "Create handle with callbacks";
+is keys %called, 0, 'no callback for plain connect';
+
+ok $dbh = DBI->connect_cached(@args), "Create handle with callbacks";
+is $called{new}, 1, "connect_cached.new called";
+is $called{cached}, undef, "connect_cached.reused not yet called";
+
+ok $dbh = DBI->connect_cached(@args), "Create handle with callbacks";
+is $called{cached}, 1, "connect_cached.reused called";
+is $called{new}, 1, "connect_cached.new not called again";
+
+
+# --- test ChildCallbacks.
+%called = ();
+$args[-1] = {
+ Callbacks => my $dbh_callbacks = {
+ ping => sub { $called{ping}++; return; },
+ ChildCallbacks => my $sth_callbacks = {
+ execute => sub { $called{execute}++; return; },
+ fetch => sub { $called{fetch}++; return; },
+ }
+ }
+};
+
+ok $dbh = DBI->connect(@args), "Create handle with ChildCallbacks";
+ok $dbh->ping, 'Ping';
+is $called{ping}, 1, 'Ping callback should have been called';
+ok my $sth = $dbh->prepare('SELECT name from t'), 'Prepare a statement handle (child)';
+ok $sth->{Callbacks}, 'child should have Callbacks';
+is $sth->{Callbacks}, $sth_callbacks, "child Callbacks should be ChildCallbacks of parent"
+ or diag "(dbh Callbacks is $dbh_callbacks)";
+ok $sth->execute, 'Execute';
+is $called{execute}, 1, 'Execute callback should have been called';
+ok $sth->fetch, 'Fetch';
+is $called{fetch}, 1, 'Fetch callback should have been called';
+
+__END__
+
+A generic 'transparent' callback looks like this:
+(this assumes only scalar context will be used)
+
+ sub {
+ my $h = shift;
+ return if our $avoid_deep_recursion->{"$h $_"}++;
+ my $this = $h->$_(@_);
+ undef $_; # tell DBI not to call original method
+ return $this; # tell DBI to return this instead
+ };
+
+XXX should add a test for this
+XXX even better would be to run chunks of the test suite with that as a '*' callback. In theory everything should pass (except this test file, naturally)..
diff --git a/t/72childhandles.t b/t/72childhandles.t
new file mode 100644
index 0000000..48fbe37
--- /dev/null
+++ b/t/72childhandles.t
@@ -0,0 +1,149 @@
+#!perl -w
+$|=1;
+
+use strict;
+
+#
+# test script for the ChildHandles attribute
+#
+
+use DBI;
+
+use Test::More;
+
+my $HAS_WEAKEN = eval {
+ require Scalar::Util;
+ # this will croak() if this Scalar::Util doesn't have a working weaken().
+ Scalar::Util::weaken( my $test = [] ); # same test as in DBI.pm
+ 1;
+};
+if (!$HAS_WEAKEN) {
+ chomp $@;
+ print "1..0 # Skipped: Scalar::Util::weaken not available ($@)\n";
+ exit 0;
+}
+
+plan tests => 16;
+
+my $using_dbd_gofer = ($ENV{DBI_AUTOPROXY}||'') =~ /^dbi:Gofer.*transport=/i;
+
+my $drh;
+
+{
+ # make 10 connections
+ my @dbh;
+ for (1 .. 10) {
+ my $dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+ push @dbh, $dbh;
+ }
+
+ # get the driver handle
+ $drh = $dbh[0]->{Driver};
+ ok $drh;
+
+ # get the kids, should be the same list of connections
+ my $db_handles = $drh->{ChildHandles};
+ is ref $db_handles, 'ARRAY';
+ is scalar @$db_handles, scalar @dbh;
+
+ # make sure all the handles are there
+ my $found = 0;
+ foreach my $h (@dbh) {
+ ++$found if grep { $h == $_ } @$db_handles;
+ }
+ is $found, scalar @dbh;
+}
+
+# now all the out-of-scope DB handles should be gone
+{
+ my $handles = $drh->{ChildHandles};
+ my @db_handles = grep { defined } @$handles;
+ is scalar @db_handles, 0, "All handles should be undef now";
+}
+
+my $dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+
+my $empty = $dbh->{ChildHandles};
+is_deeply $empty, [], "ChildHandles should be an array-ref if wekref is available";
+
+# test child handles for statement handles
+{
+ my @sth;
+ my $sth_count = 20;
+ for (1 .. $sth_count) {
+ my $sth = $dbh->prepare('SELECT name FROM t');
+ push @sth, $sth;
+ }
+ my $handles = $dbh->{ChildHandles};
+ is scalar @$handles, scalar @sth;
+
+ # test a recursive walk like the one in the docs
+ my @lines;
+ sub show_child_handles {
+ my ($h, $level) = @_;
+ $level ||= 0;
+ push(@lines,
+ sprintf "%sh %s %s\n", $h->{Type}, "\t" x $level, $h);
+ show_child_handles($_, $level + 1)
+ for (grep { defined } @{$h->{ChildHandles}});
+ }
+ my $drh = $dbh->{Driver};
+ show_child_handles($drh, 0);
+ print @lines[0..4];
+
+ is scalar @lines, $sth_count + 2;
+ like $lines[0], qr/^drh/;
+ like $lines[1], qr/^dbh/;
+ like $lines[2], qr/^sth/;
+}
+
+my $handles = $dbh->{ChildHandles};
+my @live = grep { defined $_ } @$handles;
+is scalar @live, 0, "handles should be gone now";
+
+# test visit_child_handles
+{
+ my $info;
+ my $visitor = sub {
+ my ($h, $info) = @_;
+ my $type = $h->{Type};
+ ++$info->{ $type }{ ($type eq 'st') ? $h->{Statement} : $h->{Name} };
+ return $info;
+ };
+ DBI->visit_handles($visitor, $info = {});
+ is_deeply $info, {
+ 'dr' => {
+ 'ExampleP' => 1,
+ ($using_dbd_gofer) ? (Gofer => 1) : ()
+ },
+ 'db' => { '' => 1 },
+ };
+
+ my $sth1 = $dbh->prepare('SELECT name FROM t');
+ my $sth2 = $dbh->prepare('SELECT name FROM t');
+ DBI->visit_handles($visitor, $info = {});
+ is_deeply $info, {
+ 'dr' => {
+ 'ExampleP' => 1,
+ ($using_dbd_gofer) ? (Gofer => 1) : ()
+ },
+ 'db' => { '' => 1 },
+ 'st' => { 'SELECT name FROM t' => 2 }
+ };
+
+}
+
+# test that the childhandle array does not grow uncontrollably
+SKIP: {
+ skip "slow tests avoided when using DBD::Gofer", 2 if $using_dbd_gofer;
+
+ for (1 .. 1000) {
+ my $sth = $dbh->prepare('SELECT name FROM t');
+ }
+ my $handles = $dbh->{ChildHandles};
+ cmp_ok scalar @$handles, '<', 1000;
+ my @live = grep { defined } @$handles;
+ is scalar @live, 0;
+}
+
+1;
diff --git a/t/80proxy.t b/t/80proxy.t
new file mode 100644
index 0000000..ab529b6
--- /dev/null
+++ b/t/80proxy.t
@@ -0,0 +1,473 @@
+#!perl -w # -*- perl -*-
+# vim:sw=4:ts=8
+
+require 5.004;
+use strict;
+
+
+use DBI;
+use Config;
+require VMS::Filespec if $^O eq 'VMS';
+require Cwd;
+
+my $haveFileSpec = eval { require File::Spec };
+my $failed_tests = 0;
+
+$| = 1;
+$^W = 1;
+
+# $\ = "\n"; # XXX Triggers bug, check this later (JW, 1998-12-28)
+
+# Can we load the modules? If not, exit the test immediately:
+# Reason is most probable a missing prerequisite.
+#
+# Is syslog available (required for the server)?
+
+eval {
+ local $SIG{__WARN__} = sub { $@ = shift };
+ require Storable;
+ require DBD::Proxy;
+ require DBI::ProxyServer;
+ require RPC::PlServer;
+ require Net::Daemon::Test;
+};
+if ($@) {
+ if ($@ =~ /^Can't locate (\S+)/) {
+ print "1..0 # Skipped: modules required for proxy are probably not installed (e.g., $1)\n";
+ exit 0;
+ }
+ die $@;
+}
+
+if ($DBI::PurePerl) {
+ # XXX temporary I hope
+ print "1..0 # Skipped: DBD::Proxy currently has a problem under DBI::PurePerl\n";
+ exit 0;
+}
+
+{
+ my $numTest = 0;
+ sub _old_Test($;$) {
+ my $result = shift; my $str = shift || '';
+ printf("%sok %d%s\n", ($result ? "" : "not "), ++$numTest, $str);
+ $result;
+ }
+ sub Test ($;$) {
+ my($ok, $msg) = @_;
+ $msg = ($msg) ? " ($msg)" : "";
+ my $line = (caller)[2];
+ ++$numTest;
+ ($ok) ? print "ok $numTest at line $line\n" : print "not ok $numTest\n";
+ warn "# failed test $numTest at line ".(caller)[2]."$msg\n" unless $ok;
+ ++$failed_tests unless $ok;
+ return $ok;
+ }
+}
+
+
+# Create an empty config file to make sure that settings aren't
+# overloaded by /etc/dbiproxy.conf
+my $config_file = "dbiproxytst.conf";
+unlink $config_file;
+(open(FILE, ">$config_file") and
+ (print FILE "{}\n") and
+ close(FILE))
+ or die "Failed to create config file $config_file: $!";
+
+my $debug = ($ENV{DBI_TRACE}||=0) ? 1 : 0;
+my $dbitracelog = "dbiproxy.dbilog";
+
+my ($handle, $port, @child_args);
+
+my $numTests = 136;
+
+if (@ARGV) {
+ $port = $ARGV[0];
+}
+else {
+
+ unlink $dbitracelog;
+ unlink "dbiproxy.log";
+ unlink "dbiproxy.truss";
+
+ # Uncommentand adjust this to isolate pure-perl client from server settings:
+ # local $ENV{DBI_PUREPERL} = 0;
+
+ # If desperate uncomment this and add '-d' after $^X below:
+ # local $ENV{PERLDB_OPTS} = "AutoTrace NonStop=1 LineInfo=dbiproxy.dbg";
+
+ # pass our @INC to children (e.g., so -Mblib passes through)
+ $ENV{PERL5LIB} = join($Config{path_sep}, @INC);
+
+ # server DBI trace level always at least 1
+ my $dbitracelevel = DBI->trace(0) || 1;
+ @child_args = (
+ #'truss', '-o', 'dbiproxy.truss',
+ $^X, 'dbiproxy', '--test', # --test must be first command line arg
+ "--dbitrace=$dbitracelevel=$dbitracelog", # must be second arg
+ '--configfile', $config_file,
+ ($dbitracelevel >= 2 ? ('--debug') : ()),
+ '--mode=single',
+ '--logfile=STDERR',
+ '--timeout=90'
+ );
+ warn " starting test dbiproxy process: @child_args\n" if DBI->trace(0);
+ ($handle, $port) = Net::Daemon::Test->Child($numTests, @child_args);
+}
+
+my $dsn = "DBI:Proxy:hostname=127.0.0.1;port=$port;debug=$debug;dsn=DBI:ExampleP:";
+
+print "Making a first connection and closing it immediately.\n";
+Test(eval { DBI->connect($dsn, '', '', { 'PrintError' => 1 }) })
+ or print "Connect error: " . $DBI::errstr . "\n";
+
+print "Making a second connection.\n";
+my $dbh;
+Test($dbh = eval { DBI->connect($dsn, '', '', { 'PrintError' => 0 }) })
+ or print "Connect error: " . $DBI::errstr . "\n";
+
+print "example_driver_path=$dbh->{example_driver_path}\n";
+Test($dbh->{example_driver_path});
+
+print "Setting AutoCommit\n";
+$@ = "old-error"; # should be preserved across DBI calls
+Test($dbh->{AutoCommit} = 1);
+Test($dbh->{AutoCommit});
+Test($@ eq "old-error", "\$@ now '$@'");
+#$dbh->trace(2);
+
+eval {
+ local $dbh->{ AutoCommit } = 1; # This breaks die!
+ die "BANG!!!\n";
+};
+Test($@ eq "BANG!!!\n", "\$@ value lost");
+
+
+print "begin_work...\n";
+Test($dbh->{AutoCommit});
+Test(!$dbh->{BegunWork});
+
+Test($dbh->begin_work);
+Test(!$dbh->{AutoCommit});
+Test($dbh->{BegunWork});
+
+$dbh->commit;
+Test(!$dbh->{BegunWork});
+Test($dbh->{AutoCommit});
+
+Test($dbh->begin_work({}));
+$dbh->rollback;
+Test($dbh->{AutoCommit});
+Test(!$dbh->{BegunWork});
+
+
+print "Doing a ping.\n";
+$_ = $dbh->ping;
+Test($_);
+Test($_ eq '2'); # ping was DBD::ExampleP's ping
+
+print "Ensure CompatMode enabled.\n";
+Test($dbh->{CompatMode});
+
+print "Trying local quote.\n";
+$dbh->{'proxy_quote'} = 'local';
+Test($dbh->quote("quote's") eq "'quote''s'");
+Test($dbh->quote(undef) eq "NULL");
+
+print "Trying remote quote.\n";
+$dbh->{'proxy_quote'} = 'remote';
+Test($dbh->quote("quote's") eq "'quote''s'");
+Test($dbh->quote(undef) eq "NULL");
+
+# XXX the $optional param is undocumented and may be removed soon
+Test($dbh->quote_identifier('foo') eq '"foo"', $dbh->quote_identifier('foo'));
+Test($dbh->quote_identifier('f"o') eq '"f""o"', $dbh->quote_identifier('f"o'));
+Test($dbh->quote_identifier('foo','bar') eq '"foo"."bar"');
+Test($dbh->quote_identifier('foo',undef,'bar') eq '"foo"."bar"');
+Test($dbh->quote_identifier(undef,undef,'bar') eq '"bar"');
+
+print "Trying commit with invalid number of parameters.\n";
+eval { $dbh->commit('dummy') };
+Test($@ =~ m/^DBI commit: invalid number of arguments:/)
+ unless $DBI::PurePerl && Test(1);
+
+print "Trying select with unknown field name.\n";
+my $cursor_e = $dbh->prepare("select unknown_field_name from ?");
+Test(defined $cursor_e);
+Test(!$cursor_e->execute('a'));
+Test($DBI::err);
+Test($DBI::err == $dbh->err);
+Test($DBI::errstr =~ m/unknown_field_name/, $DBI::errstr);
+
+Test($DBI::errstr eq $dbh->errstr);
+Test($dbh->errstr eq $dbh->func('errstr'));
+
+my $dir = Cwd::cwd(); # a dir always readable on all platforms
+$dir = VMS::Filespec::unixify($dir) if $^O eq 'VMS';
+
+print "Trying a real select.\n";
+my $csr_a = $dbh->prepare("select mode,name from ?");
+Test(ref $csr_a);
+Test($csr_a->execute($dir))
+ or print "Execute failed: ", $csr_a->errstr(), "\n";
+
+print "Repeating the select with second handle.\n";
+my $csr_b = $dbh->prepare("select mode,name from ?");
+Test(ref $csr_b);
+Test($csr_b->execute($dir));
+Test($csr_a != $csr_b);
+Test($csr_a->{NUM_OF_FIELDS} == 2);
+if ($DBI::PurePerl) {
+ $csr_a->trace(2);
+ use Data::Dumper;
+ warn Dumper($csr_a->{Database});
+}
+Test($csr_a->{Database}->{Driver}->{Name} eq 'Proxy', "Name=$csr_a->{Database}->{Driver}->{Name}");
+$csr_a->trace(0), die if $DBI::PurePerl;
+
+my($col0, $col1);
+my(@row_a, @row_b);
+
+#$csr_a->trace(2);
+print "Trying bind_columns.\n";
+Test($csr_a->bind_columns(undef, \($col0, $col1)) );
+Test($csr_a->execute($dir));
+@row_a = $csr_a->fetchrow_array;
+Test(@row_a);
+Test($row_a[0] eq $col0);
+Test($row_a[1] eq $col1);
+
+print "Trying bind_param.\n";
+Test($csr_b->bind_param(1, $dir));
+Test($csr_b->execute());
+@row_b = @{ $csr_b->fetchrow_arrayref };
+Test(@row_b);
+
+Test("@row_a" eq "@row_b");
+@row_b = $csr_b->fetchrow_array;
+Test("@row_a" ne "@row_b")
+ or printf("Expected something different from '%s', got '%s'\n", "@row_a",
+ "@row_b");
+
+print "Trying fetchrow_hashref.\n";
+Test($csr_b->execute());
+my $row_b = $csr_b->fetchrow_hashref;
+Test($row_b);
+print "row_a: @{[ @row_a ]}\n";
+print "row_b: @{[ %$row_b ]}\n";
+Test($row_b->{mode} == $row_a[0]);
+Test($row_b->{name} eq $row_a[1]);
+
+print "Trying fetchrow_hashref with FetchHashKeyName.\n";
+do {
+#local $dbh->{TraceLevel} = 9;
+local $dbh->{FetchHashKeyName} = 'NAME_uc';
+Test($dbh->{FetchHashKeyName} eq 'NAME_uc');
+my $csr_c = $dbh->prepare("select mode,name from ?");
+Test($csr_c->execute($dir), $DBI::errstr);
+$row_b = $csr_c->fetchrow_hashref;
+Test($row_b);
+print "row_b: @{[ %$row_b ]}\n";
+Test($row_b->{MODE} eq $row_a[0]);
+};
+
+print "Trying finish.\n";
+Test($csr_a->finish);
+#Test($csr_b->finish);
+Test(1);
+
+print "Forcing destructor.\n";
+$csr_a = undef; # force destruction of this cursor now
+Test(1);
+
+print "Trying fetchall_arrayref.\n";
+Test($csr_b->execute());
+my $r = $csr_b->fetchall_arrayref;
+Test($r);
+Test(@$r);
+Test($r->[0]->[0] == $row_a[0]);
+Test($r->[0]->[1] eq $row_a[1]);
+
+Test($csr_b->finish);
+
+
+print "Retrying unknown field name.\n";
+my $csr_c;
+$csr_c = $dbh->prepare("select unknown_field_name1 from ?");
+Test($csr_c);
+Test(!$csr_c->execute($dir));
+Test($DBI::errstr =~ m/Unknown field names: unknown_field_name1/)
+ or printf("Wrong error string: %s", $DBI::errstr);
+
+print "Trying RaiseError.\n";
+$dbh->{RaiseError} = 1;
+Test($dbh->{RaiseError});
+Test($csr_c = $dbh->prepare("select unknown_field_name2 from ?"));
+Test(!eval { $csr_c->execute(); 1 });
+#print "$@\n";
+Test($@ =~ m/Unknown field names: unknown_field_name2/);
+$dbh->{RaiseError} = 0;
+Test(!$dbh->{RaiseError});
+
+print "Trying warnings.\n";
+{
+ my @warn;
+ local($SIG{__WARN__}) = sub { push @warn, @_ };
+ $dbh->{PrintError} = 1;
+ Test($dbh->{PrintError});
+ Test(($csr_c = $dbh->prepare("select unknown_field_name3 from ?")));
+ Test(!$csr_c->execute());
+ Test("@warn" =~ m/Unknown field names: unknown_field_name3/);
+ $dbh->{PrintError} = 0;
+ Test(!$dbh->{PrintError});
+}
+$csr_c->finish();
+
+
+print "Trying type_info_all.\n";
+my $array = $dbh->type_info_all();
+Test($array and ref($array) eq 'ARRAY')
+ or printf("Expected ARRAY, got %s, error %s\n", DBI::neat($array),
+ $dbh->errstr());
+Test($array->[0] and ref($array->[0]) eq 'HASH');
+my $ok = 1;
+for (my $i = 1; $i < @{$array}; $i++) {
+ print "$array->[$i]\n";
+ $ok = 0 unless ($array->[$i] and ref($array->[$i]) eq 'ARRAY');
+ print "$ok\n";
+}
+Test($ok);
+
+# Test the table_info method
+# First generate a list of all subdirectories
+$dir = $haveFileSpec ? File::Spec->curdir() : ".";
+Test(opendir(DIR, $dir));
+my(%dirs, %unexpected, %missing);
+while (defined(my $file = readdir(DIR))) {
+ $dirs{$file} = 1 if -d $file;
+}
+closedir(DIR);
+my $sth = $dbh->table_info(undef, undef, undef, undef);
+Test($sth) or warn "table_info failed: ", $dbh->errstr(), "\n";
+%missing = %dirs;
+%unexpected = ();
+while (my $ref = $sth->fetchrow_hashref()) {
+ print "table_info: Found table $ref->{'TABLE_NAME'}\n";
+ if (exists($missing{$ref->{'TABLE_NAME'}})) {
+ delete $missing{$ref->{'TABLE_NAME'}};
+ } else {
+ $unexpected{$ref->{'TABLE_NAME'}} = 1;
+ }
+}
+Test(!$sth->errstr())
+ or print "Fetching table_info rows failed: ", $sth->errstr(), "\n";
+Test(keys %unexpected == 0)
+ or print "Unexpected directories: ", join(",", keys %unexpected), "\n";
+Test(keys %missing == 0)
+ or print "Missing directories: ", join(",", keys %missing), "\n";
+
+# Test the tables method
+%missing = %dirs;
+%unexpected = ();
+print "Expecting directories ", join(",", keys %dirs), "\n";
+foreach my $table ($dbh->tables()) {
+ print "tables: Found table $table\n";
+ if (exists($missing{$table})) {
+ delete $missing{$table};
+ } else {
+ $unexpected{$table} = 1;
+ }
+}
+Test(!$sth->errstr())
+ or print "Fetching table_info rows failed: ", $sth->errstr(), "\n";
+Test(keys %unexpected == 0)
+ or print "Unexpected directories: ", join(",", keys %unexpected), "\n";
+Test(keys %missing == 0)
+ or print "Missing directories: ", join(",", keys %missing), "\n";
+
+
+# Test large recordsets
+for (my $i = 0; $i <= 300; $i += 100) {
+ print "Testing the fake directories ($i).\n";
+ Test($csr_a = $dbh->prepare("SELECT name, mode FROM long_list_$i"));
+ Test($csr_a->execute(), $DBI::errstr);
+ my $ary = $csr_a->fetchall_arrayref;
+ Test(!$DBI::errstr, $DBI::errstr);
+ Test(@$ary == $i, "expected $i got ".@$ary);
+ if ($i) {
+ my @n1 = map { $_->[0] } @$ary;
+ my @n2 = reverse map { "file$_" } 1..$i;
+ Test("@n1" eq "@n2");
+ }
+ else {
+ Test(1);
+ }
+}
+
+
+# Test the RowCacheSize attribute
+Test($csr_a = $dbh->prepare("SELECT * FROM ?"));
+Test($dbh->{'RowCacheSize'} == 20);
+Test($csr_a->{'RowCacheSize'} == 20);
+Test($csr_a->execute('long_list_50'));
+Test($csr_a->fetchrow_arrayref());
+Test($csr_a->{'proxy_data'} and @{$csr_a->{'proxy_data'}} == 19);
+Test($csr_a->finish());
+
+Test($dbh->{'RowCacheSize'} = 30);
+Test($dbh->{'RowCacheSize'} == 30);
+Test($csr_a->{'RowCacheSize'} == 30);
+Test($csr_a->execute('long_list_50'));
+Test($csr_a->fetchrow_arrayref());
+Test($csr_a->{'proxy_data'} and @{$csr_a->{'proxy_data'}} == 29)
+ or print("Expected 29 records in cache, got " . @{$csr_a->{'proxy_data'}} .
+ "\n");
+Test($csr_a->finish());
+
+
+Test($csr_a->{'RowCacheSize'} = 10);
+Test($dbh->{'RowCacheSize'} == 30);
+Test($csr_a->{'RowCacheSize'} == 10);
+Test($csr_a->execute('long_list_50'));
+Test($csr_a->fetchrow_arrayref());
+Test($csr_a->{'proxy_data'} and @{$csr_a->{'proxy_data'}} == 9)
+ or print("Expected 9 records in cache, got " . @{$csr_a->{'proxy_data'}} .
+ "\n");
+Test($csr_a->finish());
+
+$dbh->disconnect;
+
+# Test $dbh->func()
+# print "Testing \$dbh->func().\n";
+# my %tables = map { $_ =~ /lib/ ? ($_, 1) : () } $dbh->tables();
+# $ok = 1;
+# foreach my $t ($dbh->func('lib', 'examplep_tables')) {
+# defined(delete $tables{$t}) or print "Unexpected table: $t\n";
+# }
+# Test(%tables == 0);
+
+if ($failed_tests) {
+ warn "Proxy: @child_args\n";
+ for my $class (qw(Net::Daemon RPC::PlServer Storable)) {
+ (my $pm = $class) =~ s/::/\//g; $pm .= ".pm";
+ my $version = eval { $class->VERSION } || '?';
+ warn sprintf "Using %-13s %-6s %s\n", $class, $version, $INC{$pm};
+ }
+ warn join(", ", map { "$_=$ENV{$_}" } grep { /^LC_|LANG/ } keys %ENV)."\n";
+ warn "More info can be found in $dbitracelog\n";
+ #system("cat $dbitracelog");
+}
+
+
+END {
+ local $?;
+ $handle->Terminate() if $handle;
+ undef $handle;
+ unlink $config_file if $config_file;
+ if (!$failed_tests) {
+ unlink 'dbiproxy.log';
+ unlink $dbitracelog if $dbitracelog;
+ }
+};
+
+1;
diff --git a/t/85gofer.t b/t/85gofer.t
new file mode 100644
index 0000000..8208195
--- /dev/null
+++ b/t/85gofer.t
@@ -0,0 +1,264 @@
+#!perl -w # -*- perl -*-
+# vim:sw=4:ts=8
+$|=1;
+
+use strict;
+use warnings;
+
+use Cwd;
+use Config;
+use Data::Dumper;
+use Test::More 0.84;
+use Getopt::Long;
+
+use DBI qw(dbi_time);
+
+if (my $ap = $ENV{DBI_AUTOPROXY}) { # limit the insanity
+ plan skip_all => "transport+policy tests skipped with non-gofer DBI_AUTOPROXY"
+ if $ap !~ /^dbi:Gofer/i;
+ plan skip_all => "transport+policy tests skipped with non-pedantic policy in DBI_AUTOPROXY"
+ if $ap !~ /policy=pedantic\b/i;
+}
+
+do "t/lib.pl";
+
+# 0=SQL::Statement if avail, 1=DBI::SQL::Nano
+# next line forces use of Nano rather than default behaviour
+# $ENV{DBI_SQL_NANO}=1;
+# This is done in zvn_50dbm.t
+
+GetOptions(
+ 'c|count=i' => \(my $opt_count = (-t STDOUT ? 100 : 0)),
+ 'dbm=s' => \my $opt_dbm,
+ 'v|verbose!' => \my $opt_verbose,
+ 't|transport=s' => \my $opt_transport,
+ 'p|policy=s' => \my $opt_policy,
+) or exit 1;
+
+
+# so users can try others from the command line
+if (!$opt_dbm) {
+ # pick first available, starting with SDBM_File
+ for (qw( SDBM_File GDBM_File DB_File BerkeleyDB )) {
+ if (eval { local $^W; require "$_.pm" }) {
+ $opt_dbm = ($_);
+ last;
+ }
+ }
+ plan skip_all => 'No DBM modules available' if !$opt_dbm;
+}
+
+my @remote_dsns = DBI->data_sources( "dbi:DBM:", {
+ dbm_type => $opt_dbm,
+ f_lockfile => 0,
+ f_dir => test_dir() } );
+my $remote_dsn = $remote_dsns[0];
+( my $remote_driver_dsn = $remote_dsn ) =~ s/dbi:dbm://i;
+# Long timeout for slow/overloaded systems (incl virtual machines with low priority)
+my $timeout = 240;
+
+if ($ENV{DBI_AUTOPROXY}) {
+ # this means we have DBD::Gofer => DBD::Gofer => DBD::DBM!
+ # rather than disable it we let it run because we're twisted
+ # and because it helps find more bugs (though debugging can be painful)
+ warn "\n$0 is running with DBI_AUTOPROXY enabled ($ENV{DBI_AUTOPROXY})\n"
+ unless $0 =~ /\bzv/; # don't warn for t/zvg_85gofer.t
+}
+
+# ensure subprocess (for pipeone and stream transport) will use the same modules as us, ie ./blib
+local $ENV{PERL5LIB} = join $Config{path_sep}, @INC;
+
+my %durations;
+my $getcwd = getcwd();
+my $username = eval { getpwuid($>) } || ''; # fails on windows
+my $can_ssh = ($username && $username eq 'timbo' && -d '.svn'
+ && system("sh -c 'echo > /dev/tcp/localhost/22' 2>/dev/null")==0
+);
+my $perl = "$^X -Mblib=$getcwd/blib"; # ensure sameperl and our blib (note two spaces)
+
+my %trials = (
+ null => {},
+ pipeone => { perl=>$perl, timeout=>$timeout },
+ stream => { perl=>$perl, timeout=>$timeout },
+ stream_ssh => ($can_ssh)
+ ? { perl=>$perl, timeout=>$timeout, url => "ssh:$username\@localhost" }
+ : undef,
+ #http => { url => "http://localhost:8001/gofer" },
+);
+
+# too dependant on local config to make a standard test
+delete $trials{http} unless $username eq 'timbo' && -d '.svn';
+
+my @transports = ($opt_transport) ? ($opt_transport) : (sort keys %trials);
+note("Transports: @transports");
+my @policies = ($opt_policy) ? ($opt_policy) : qw(pedantic classic rush);
+note("Policies: @policies");
+note("Count: $opt_count");
+
+for my $trial (@transports) {
+ (my $transport = $trial) =~ s/_.*//;
+ my $trans_attr = $trials{$trial}
+ or next;
+
+ # XXX temporary restrictions, hopefully
+ if ( ($^O eq 'MSWin32') || ($^O eq 'VMS') ) {
+ # stream needs Fcntl macro F_GETFL for non-blocking
+ # and pipe seems to hang on some windows systems
+ next if $transport eq 'stream' or $transport eq 'pipeone';
+ }
+
+ for my $policy_name (@policies) {
+
+ eval { run_tests($transport, $trans_attr, $policy_name) };
+ ($@) ? fail("$trial: $@") : pass();
+
+ }
+}
+
+# to get baseline for comparisons if doing performance testing
+run_tests('no', {}, 'pedantic') if $opt_count;
+
+while ( my ($activity, $stats_hash) = each %durations ) {
+ note("");
+ $stats_hash->{'~baseline~'} = delete $stats_hash->{"no+pedantic"};
+ for my $perf_tag (reverse sort keys %$stats_hash) {
+ my $dur = $stats_hash->{$perf_tag} || 0.0000001;
+ note sprintf " %6s %-16s: %.6fsec (%5d/sec)",
+ $activity, $perf_tag, $dur/$opt_count, $opt_count/$dur;
+ my $baseline_dur = $stats_hash->{'~baseline~'};
+ note sprintf " %+5.1fms", (($dur-$baseline_dur)/$opt_count)*1000
+ unless $perf_tag eq '~baseline~';
+ note "";
+ }
+}
+
+
+sub run_tests {
+ my ($transport, $trans_attr, $policy_name) = @_;
+
+ my $policy = get_policy($policy_name);
+ my $skip_gofer_checks = ($transport eq 'no');
+
+
+ my $test_run_tag = "Testing $transport transport with $policy_name policy";
+ note "=============";
+ note "$test_run_tag";
+
+ my $driver_dsn = "transport=$transport;policy=$policy_name";
+ $driver_dsn .= join ";", '', map { "$_=$trans_attr->{$_}" } keys %$trans_attr
+ if %$trans_attr;
+
+ my $dsn = "dbi:Gofer:$driver_dsn;dsn=$remote_dsn";
+ $dsn = $remote_dsn if $transport eq 'no';
+ note " $dsn";
+
+ my $dbh = DBI->connect($dsn, undef, undef, { RaiseError => 1, PrintError => 0, ShowErrorStatement => 1 } );
+ die "$test_run_tag aborted: $DBI::errstr\n" unless $dbh; # no point continuing
+ ok $dbh, sprintf "should connect to %s", $dsn;
+
+ is $dbh->{Name}, ($policy->skip_connect_check)
+ ? $driver_dsn
+ : $remote_driver_dsn;
+
+ END { unlink glob "fruit.???" }
+ ok $dbh->do("DROP TABLE IF EXISTS fruit");
+ ok $dbh->do("CREATE TABLE fruit (dKey INT, dVal VARCHAR(10))");
+ die "$test_run_tag aborted ($DBI::errstr)\n" if $DBI::err;
+
+ my $sth = do {
+ local $dbh->{RaiseError} = 0;
+ $dbh->prepare("complete non-sql gibberish");
+ };
+ ($policy->skip_prepare_check)
+ ? isa_ok $sth, 'DBI::st'
+ : is $sth, undef, 'should detect prepare failure';
+
+ ok my $ins_sth = $dbh->prepare("INSERT INTO fruit VALUES (?,?)");
+ ok $ins_sth->execute(1, 'oranges');
+ ok $ins_sth->execute(2, 'oranges');
+
+ my $rowset;
+ ok $rowset = $dbh->selectall_arrayref("SELECT dKey, dVal FROM fruit ORDER BY dKey");
+ is_deeply($rowset, [ [ '1', 'oranges' ], [ '2', 'oranges' ] ]);
+
+ ok $dbh->do("UPDATE fruit SET dVal='apples' WHERE dVal='oranges'");
+ ok $dbh->{go_response}->executed_flag_set, 'go_response executed flag should be true'
+ unless $skip_gofer_checks && pass();
+
+ ok $sth = $dbh->prepare("SELECT dKey, dVal FROM fruit");
+ ok $sth->execute;
+ ok $rowset = $sth->fetchall_hashref('dKey');
+ is_deeply($rowset, { '1' => { dKey=>1, dVal=>'apples' }, 2 => { dKey=>2, dVal=>'apples' } });
+
+ if ($opt_count and $transport ne 'pipeone') {
+ note "performance check - $opt_count selects and inserts";
+ my $start = dbi_time();
+ $dbh->selectall_arrayref("SELECT dKey, dVal FROM fruit")
+ for (1000..1000+$opt_count);
+ $durations{select}{"$transport+$policy_name"} = dbi_time() - $start;
+
+ # some rows in to get a (*very* rough) idea of overheads
+ $start = dbi_time();
+ $ins_sth->execute($_, 'speed')
+ for (1000..1000+$opt_count);
+ $durations{insert}{"$transport+$policy_name"} = dbi_time() - $start;
+ }
+
+ note "Testing go_request_count and caching of simple values";
+ my $go_request_count = $dbh->{go_request_count};
+ ok $go_request_count
+ unless $skip_gofer_checks && pass();
+
+ ok $dbh->do("DROP TABLE fruit");
+ is ++$go_request_count, $dbh->{go_request_count}
+ unless $skip_gofer_checks && pass();
+
+ # tests go_request_count, caching, and skip_default_methods policy
+ my $use_remote = ($policy->skip_default_methods) ? 0 : 1;
+ note sprintf "use_remote=%s (policy=%s, transport=%s) %s",
+ $use_remote, $policy_name, $transport, $dbh->{dbi_default_methods}||'';
+
+SKIP: {
+ skip "skip_default_methods checking doesn't work with Gofer over Gofer", 3
+ if $ENV{DBI_AUTOPROXY} or $skip_gofer_checks;
+ $dbh->data_sources({ foo_bar => $go_request_count });
+ is $dbh->{go_request_count}, $go_request_count + 1*$use_remote;
+ $dbh->data_sources({ foo_bar => $go_request_count }); # should use cache
+ is $dbh->{go_request_count}, $go_request_count + 1*$use_remote;
+ @_=$dbh->data_sources({ foo_bar => $go_request_count }); # no cached yet due to wantarray
+ is $dbh->{go_request_count}, $go_request_count + 2*$use_remote;
+}
+
+SKIP: {
+ skip "caching of metadata methods returning sth not yet implemented", 2;
+ note "Testing go_request_count and caching of sth";
+ $go_request_count = $dbh->{go_request_count};
+ my $sth_ti1 = $dbh->table_info("%", "%", "%", "TABLE", { foo_bar => $go_request_count });
+ is $go_request_count + 1, $dbh->{go_request_count};
+
+ my $sth_ti2 = $dbh->table_info("%", "%", "%", "TABLE", { foo_bar => $go_request_count }); # should use cache
+ is $go_request_count + 1, $dbh->{go_request_count};
+}
+
+ ok $dbh->disconnect;
+}
+
+sub get_policy {
+ my ($policy_class) = @_;
+ $policy_class = "DBD::Gofer::Policy::$policy_class" unless $policy_class =~ /::/;
+ _load_class($policy_class) or die $@;
+ return $policy_class->new();
+}
+
+sub _load_class { # return true or false+$@
+ my $class = shift;
+ (my $pm = $class) =~ s{::}{/}g;
+ $pm .= ".pm";
+ return 1 if eval { require $pm };
+ delete $INC{$pm}; # shouldn't be needed (perl bug?) and assigning undef isn't enough
+ undef; # error in $@
+}
+
+done_testing;
+
+1;
diff --git a/t/86gofer_fail.t b/t/86gofer_fail.t
new file mode 100644
index 0000000..9a7b82b
--- /dev/null
+++ b/t/86gofer_fail.t
@@ -0,0 +1,168 @@
+#!perl -w # -*- perl -*-
+# vim:sw=4:ts=8
+$|=1;
+
+use strict;
+use warnings;
+
+use DBI;
+use Data::Dumper;
+use Test::More;
+sub between_ok;
+
+# here we test the DBI_GOFER_RANDOM mechanism
+# and how gofer deals with failures
+
+plan skip_all => "requires Callbacks which are not supported with PurePerl" if $DBI::PurePerl;
+
+if (my $ap = $ENV{DBI_AUTOPROXY}) { # limit the insanity
+ plan skip_all => "Gofer DBI_AUTOPROXY" if $ap =~ /^dbi:Gofer/i;
+
+ # this means we have DBD::Gofer => DBD::Gofer => DBD::whatever
+ # rather than disable it we let it run because we're twisted
+ # and because it helps find more bugs (though debugging can be painful)
+ warn "\n$0 is running with DBI_AUTOPROXY enabled ($ENV{DBI_AUTOPROXY})\n"
+ unless $0 =~ /\bzv/; # don't warn for t/zvg_85gofer.t
+}
+
+plan 'no_plan';
+
+my $tmp;
+my $dbh;
+my $fails;
+
+# we'll use the null transport for simplicity and speed
+# and the rush policy to limit the number of interactions with the gofer executor
+
+# silence the "DBI_GOFER_RANDOM..." warnings
+my @warns;
+$SIG{__WARN__} = sub { ("@_" =~ /^DBI_GOFER_RANDOM/) ? push(@warns, @_) : warn @_; };
+
+# --- 100% failure rate
+
+($fails, $dbh) = trial_impact("fail=100%,do", 10, "", sub { $_->do("set foo=1") });
+is $fails, 100, 'should fail 100% of the time';
+ok $@, '$@ should be set';
+like $@, '/fake error from do method induced by DBI_GOFER_RANDOM/';
+ok $dbh->errstr, 'errstr should be set';
+like $dbh->errstr, '/DBI_GOFER_RANDOM/', 'errstr should contain DBI_GOFER_RANDOM';
+ok !$dbh->{go_response}->executed_flag_set, 'go_response executed flag should be false';
+
+
+# XXX randomness can't be predicted, so it's just possible these will fail
+srand(42); # try to limit occasional failures (effect will vary by platform etc)
+
+sub trial_impact {
+ my ($spec, $count, $dsn_attr, $code, $verbose) = @_;
+ local $ENV{DBI_GOFER_RANDOM} = $spec;
+ my $dbh = dbi_connect("policy=rush;$dsn_attr");
+ local $_ = $dbh;
+ my $fail_percent = percentage_exceptions(200, $code, $verbose);
+ return $fail_percent unless wantarray;
+ return ($fail_percent, $dbh);
+}
+
+# --- 50% failure rate, with no retries
+
+$fails = trial_impact("fail=50%,do", 200, "retry_limit=0", sub { $_->do("set foo=1") });
+print "target approx 50% random failures, got $fails%\n";
+between_ok $fails, 10, 90, "should fail about 50% of the time, but at least between 10% and 90%";
+
+# --- 50% failure rate, with many retries (should yield low failure rate)
+
+$fails = trial_impact("fail=50%,prepare", 200, "retry_limit=5", sub { $_->prepare("set foo=1") });
+print "target less than 20% effective random failures (ideally 0), got $fails%\n";
+cmp_ok $fails, '<', 20, 'should fail < 20%';
+
+# --- 10% failure rate, with many retries (should yield zero failure rate)
+
+$fails = trial_impact("fail=10,do", 200, "retry_limit=10", sub { $_->do("set foo=1") });
+cmp_ok $fails, '<', 1, 'should fail < 1%';
+
+# --- 50% failure rate, test is_idempotent
+
+$ENV{DBI_GOFER_RANDOM} = "fail=50%,do"; # 50%
+
+# test go_retry_hook and that ReadOnly => 1 retries a non-idempotent statement
+ok my $dbh_50r1ro = dbi_connect("policy=rush;retry_limit=1", {
+ go_retry_hook => sub { return ($_[0]->is_idempotent) ? 1 : 0 },
+ ReadOnly => 1,
+} );
+between_ok percentage_exceptions(100, sub { $dbh_50r1ro->do("set foo=1") }),
+ 10, 40, 'should fail ~25% (ie 50% with one retry)';
+between_ok $dbh_50r1ro->{go_transport}->meta->{request_retry_count},
+ 20, 80, 'transport request_retry_count should be around 50';
+
+# test as above but with ReadOnly => 0
+ok my $dbh_50r1rw = dbi_connect("policy=rush;retry_limit=1", {
+ go_retry_hook => sub { return ($_[0]->is_idempotent) ? 1 : 0 },
+ ReadOnly => 0,
+} );
+between_ok percentage_exceptions(100, sub { $dbh_50r1rw->do("set foo=1") }),
+ 20, 80, 'should fail ~50%, ie no retries';
+ok !$dbh_50r1rw->{go_transport}->meta->{request_retry_count},
+ 'transport request_retry_count should be zero or undef';
+
+
+# --- check random is random and non-random is non-random
+
+my %fail_percents;
+for (1..5) {
+ $fails = trial_impact("fail=50%,do", 10, "", sub { $_->do("set foo=1") });
+ ++$fail_percents{$fails};
+}
+cmp_ok scalar keys %fail_percents, '>=', 2, 'positive percentage should fail randomly';
+
+%fail_percents = ();
+for (1..5) {
+ $fails = trial_impact("fail=-50%,do", 10, "", sub { $_->do("set foo=1") });
+ ++$fail_percents{$fails};
+}
+is scalar keys %fail_percents, 1, 'negative percentage should fail non-randomly';
+
+# ---
+print "Testing random delay\n";
+
+$ENV{DBI_GOFER_RANDOM} = "delay0.1=51%,do"; # odd percentage to force warn()s
+@warns = ();
+ok $dbh = dbi_connect("policy=rush;retry_limit=0");
+is percentage_exceptions(20, sub { $dbh->do("set foo=1") }),
+ 0, "should not fail for DBI_GOFER_RANDOM='$ENV{DBI_GOFER_RANDOM}'";
+my $delays = grep { m/delaying execution/ } @warns;
+between_ok $delays, 1, 19, 'should be delayed around 5 times';
+
+exit 0;
+
+# --- subs ---
+#
+sub between_ok {
+ my ($got, $min, $max, $label) = @_;
+ local $Test::Builder::Level = 2;
+ cmp_ok $got, '>=', $min, "$label (got $got)";
+ cmp_ok $got, '<=', $max, "$label (got $got)";
+}
+
+sub dbi_connect {
+ my ($gdsn, $attr) = @_;
+ return DBI->connect("dbi:Gofer:transport=null;$gdsn;dsn=dbi:ExampleP:", 0, 0, {
+ RaiseError => 1, PrintError => 0, ($attr) ? %$attr : ()
+ });
+}
+
+sub percentage_exceptions {
+ my ($count, $sub, $verbose) = @_;
+ my $i = $count;
+ my $exceptions = 0;
+ while ($i--) {
+ eval { $sub->() };
+ warn sprintf("percentage_exceptions $i: %s\n", $@|| $DBI::errstr || '') if $verbose;
+ if ($@) {
+ die "Unexpected failure: $@" unless $@ =~ /DBI_GOFER_RANDOM/;
+ ++$exceptions;
+ }
+ }
+ warn sprintf "percentage_exceptions %f/%f*100 = %f\n",
+ $exceptions, $count, $exceptions/$count*100
+ if $verbose;
+ return $exceptions/$count*100;
+}
diff --git a/t/87gofer_cache.t b/t/87gofer_cache.t
new file mode 100644
index 0000000..9ad2aeb
--- /dev/null
+++ b/t/87gofer_cache.t
@@ -0,0 +1,108 @@
+#!perl -w # -*- perl -*-
+# vim:sw=4:ts=8
+$|=1;
+
+use strict;
+use warnings;
+
+use DBI;
+use Data::Dumper;
+use Test::More;
+use DBI::Util::CacheMemory;
+
+plan skip_all => "Gofer DBI_AUTOPROXY" if (($ENV{DBI_AUTOPROXY}||'') =~ /^dbi:Gofer/i);
+
+plan 'no_plan';
+
+
+my $dsn = "dbi:Gofer:transport=null;policy=classic;dsn=dbi:ExampleP:";
+
+my @cache_classes = qw(DBI::Util::CacheMemory);
+push @cache_classes, "Cache::Memory" if eval { require Cache::Memory };
+push @cache_classes, "1"; # test alias for DBI::Util::CacheMemory
+
+for my $cache_class (@cache_classes) {
+ my $cache_obj = ($cache_class eq "1") ? $cache_class : $cache_class->new();
+ run_tests($cache_obj);
+}
+
+
+sub run_tests {
+ my $cache_obj = shift;
+
+ my $tmp;
+ print " using $cache_obj for $dsn\n";
+
+ my $dbh = DBI->connect($dsn, undef, undef, {
+ go_cache => $cache_obj,
+ RaiseError => 1, PrintError => 0, ShowErrorStatement => 1,
+ } );
+ ok my $go_transport = $dbh->{go_transport};
+ ok my $go_cache = $go_transport->go_cache;
+
+ # setup
+ $go_cache->clear;
+ is $go_cache->count, 0, 'cache should be empty after clear';
+
+ $go_transport->transmit_count(0);
+ is $go_transport->transmit_count, 0, 'transmit_count should be 0';
+
+ $go_transport->cache_hit(0);
+ $go_transport->cache_miss(0);
+ $go_transport->cache_store(0);
+
+ # request 1
+ ok my $rows1 = $dbh->selectall_arrayref("select name from ?", {}, ".");
+ cmp_ok $go_cache->count, '>', 0, 'cache should not be empty after select';
+
+ my $expected = ($ENV{DBI_AUTOPROXY}) ? 2 : 1;
+ is $go_transport->cache_hit, 0;
+ is $go_transport->cache_miss, $expected;
+ is $go_transport->cache_store, $expected;
+
+ is $go_transport->transmit_count, $expected, 'should make 1 round trip';
+ $go_transport->transmit_count(0);
+ is $go_transport->transmit_count, 0, 'transmit_count should be 0';
+
+ # request 2
+ ok my $rows2 = $dbh->selectall_arrayref("select name from ?", {}, ".");
+ is_deeply $rows2, $rows1;
+ is $go_transport->transmit_count, 0, 'should make 1 round trip';
+
+ is $go_transport->cache_hit, $expected;
+ is $go_transport->cache_miss, $expected;
+ is $go_transport->cache_store, $expected;
+}
+
+
+print "test per-sth go_cache\n";
+
+my $dbh = DBI->connect($dsn, undef, undef, {
+ go_cache => 1,
+ RaiseError => 1, PrintError => 0, ShowErrorStatement => 1,
+} );
+ok my $go_transport = $dbh->{go_transport};
+ok my $dbh_cache = $go_transport->go_cache;
+$dbh_cache->clear; # discard ping from connect
+
+my $cache2 = DBI::Util::CacheMemory->new( namespace => "foo2" );
+ok $cache2;
+ok $cache2 != $dbh_cache;
+
+my $sth1 = $dbh->prepare("select name from ?");
+is $sth1->go_cache, $dbh_cache;
+is $dbh_cache->size, 0;
+ok $dbh->selectall_arrayref($sth1, undef, ".");
+ok $dbh_cache->size;
+
+my $sth2 = $dbh->prepare("select * from ?", { go_cache => $cache2 });
+is $sth2->go_cache, $cache2;
+is $cache2->size, 0;
+ok $dbh->selectall_arrayref($sth2, undef, ".");
+ok $cache2->size;
+
+cmp_ok $cache2->size, '>', $dbh_cache->size;
+
+
+
+1;
diff --git a/t/90sql_type_cast.t b/t/90sql_type_cast.t
new file mode 100644
index 0000000..45a91d4
--- /dev/null
+++ b/t/90sql_type_cast.t
@@ -0,0 +1,148 @@
+# $Id: 90sql_type_cast.t 13911 2010-04-22 10:41:37Z timbo $
+# Test DBI::sql_type_cast
+use strict;
+#use warnings; this script generate warnings deliberately as part of the test
+use Test::More;
+use DBI qw(:sql_types :utils);
+use Config;
+
+my $jx = eval {require JSON::XS;};
+my $dp = eval {require Data::Peek;};
+my $pp = $DBI::PurePerl && $DBI::PurePerl; # doubled to avoid typo warning
+
+# NOTE: would have liked to use DBI::neat to test the cast value is what
+# we expect but unfortunately neat uses SvNIOK(sv) so anything that looks
+# like a number is printed as a number without quotes even if it has
+# a pv.
+
+use constant INVALID_TYPE => -2;
+use constant SV_IS_UNDEF => -1;
+use constant NO_CAST_STRICT => 0;
+use constant NO_CAST_NO_STRICT => 1;
+use constant CAST_OK => 2;
+
+my @tests = (
+ ['undef', undef, SQL_INTEGER, SV_IS_UNDEF, -1, q{[null]}],
+ ['invalid sql type', '99', 123456789, 0, INVALID_TYPE, q{["99"]}],
+ ['non numeric cast to int', 'aa', SQL_INTEGER, 0, NO_CAST_NO_STRICT,
+ q{["aa"]}],
+ ['non numeric cast to int (strict)', 'aa', SQL_INTEGER,
+ DBIstcf_STRICT, NO_CAST_STRICT, q{["aa"]}],
+ ['small int cast to int', "99", SQL_INTEGER, 0, CAST_OK, q{["99"]}],
+ ['2 byte max signed int cast to int', "32767", SQL_INTEGER, 0,
+ CAST_OK, q{["32767"]}],
+ ['2 byte max unsigned int cast to int', "65535",
+ SQL_INTEGER, 0, CAST_OK, q{["65535"]}],
+ ['4 byte max signed int cast to int', "2147483647",
+ SQL_INTEGER, 0, CAST_OK, q{["2147483647"]}],
+ ['4 byte max unsigned int cast to int', "4294967295",
+ SQL_INTEGER, 0, CAST_OK, q{["4294967295"]}],
+ ['small int cast to int (discard)',
+ '99', SQL_INTEGER, DBIstcf_DISCARD_STRING, CAST_OK, q{[99]}],
+
+ ['non numeric cast to numeric', 'aa', SQL_NUMERIC,
+ 0, NO_CAST_NO_STRICT, q{["aa"]}],
+ ['non numeric cast to numeric (strict)', 'aa', SQL_NUMERIC,
+ DBIstcf_STRICT, NO_CAST_STRICT, q{["aa"]}],
+ );
+
+if (!$pp) {
+ # some tests cannot be performed with PurePerl as numbers don't
+ # overflow in the same way as XS.
+ push @tests,
+ (
+ ['very large int cast to int',
+ '99999999999999999999', SQL_INTEGER, 0, NO_CAST_NO_STRICT,
+ q{["99999999999999999999"]}],
+ ['very large int cast to int (strict)',
+ '99999999999999999999', SQL_INTEGER, DBIstcf_STRICT,
+ NO_CAST_STRICT, q{["99999999999999999999"]}],
+ ['float cast to int', '99.99', SQL_INTEGER, 0,
+ NO_CAST_NO_STRICT, q{["99.99"]}],
+ ['float cast to int (strict)', '99.99', SQL_INTEGER, DBIstcf_STRICT,
+ NO_CAST_STRICT, q{["99.99"]}],
+ ['float cast to double', '99.99', SQL_DOUBLE, 0, CAST_OK,
+ q{["99.99"]}]
+ );
+ if ($Config{ivsize} == 4) {
+ push @tests,
+ ['4 byte max unsigned int cast to int (ivsize=4)', "4294967296",
+ SQL_INTEGER, 0, NO_CAST_NO_STRICT, q{["4294967296"]}];
+ } elsif ($Config{ivsize} >= 8) {
+ push @tests,
+ ['4 byte max unsigned int cast to int (ivsize>8)', "4294967296",
+ SQL_INTEGER, 0, CAST_OK, q{["4294967296"]}];
+ }
+}
+
+if ($] >= 5.010001) {
+ # Some numeric tests fail the return value test on Perls before 5.10.1
+ # because sv_2nv leaves NOK set - changed in 5.10.1 probably via the
+ # following change:
+ # The public IV and NV flags are now not set if the string
+ # value has trailing "garbage". This behaviour is consistent with not
+ # setting the public IV or NV flags if the value is out of range for the
+ # type.
+ push @tests, (
+ ['non numeric cast to double', 'aabb', SQL_DOUBLE, 0,
+ NO_CAST_NO_STRICT, q{["aabb"]}],
+ ['non numeric cast to double (strict)', 'aabb', SQL_DOUBLE,
+ DBIstcf_STRICT, NO_CAST_STRICT, q{["aabb"]}]
+ );
+}
+
+my $tests = @tests;
+$tests *= 2 if $jx;
+foreach (@tests) {
+ $tests++ if ($dp) && ($_->[3] & DBIstcf_DISCARD_STRING);
+ $tests++ if ($dp) && ($_->[2] == SQL_DOUBLE);
+}
+
+plan tests => $tests;
+
+foreach my $test(@tests) {
+ my $val = $test->[1];
+ #diag(join(",", map {neat($_)} Data::Peek::DDual($val)));
+ my $result;
+ {
+ no warnings; # lexical but also affects XS sub
+ local $^W = 0; # needed for PurePerl tests
+ $result = sql_type_cast($val, $test->[2], $test->[3]);
+ }
+ is($result, $test->[4], "result, $test->[0]");
+ if ($jx) {
+
+ SKIP: {
+ skip 'DiscardString not supported in PurePerl', 1
+ if $pp && ($test->[3] & DBIstcf_DISCARD_STRING);
+
+ my $json = JSON::XS->new->encode([$val]);
+ #diag(neat($val), ",", $json);
+ is($json, $test->[5], "json $test->[0]");
+ };
+ }
+
+ my ($pv, $iv, $nv, $rv, $hm);
+ ($pv, $iv, $nv, $rv, $hm) = Data::Peek::DDual($val) if $dp;
+
+ if ($dp && ($test->[3] & DBIstcf_DISCARD_STRING)) {
+ #diag("D::P ",neat($pv), ",", neat($iv), ",", neat($nv),
+ # ",", neat($rv));
+ SKIP: {
+ skip 'DiscardString not supported in PurePerl', 1 if $pp;
+
+ ok(!defined($pv), "discard works, $test->[0]") if $dp;
+ };
+ }
+ if (($test->[2] == SQL_DOUBLE) && ($dp)) {
+ #diag("D::P ", neat($pv), ",", neat($iv), ",", neat($nv),
+ # ",", neat($rv));
+ if ($test->[4] == CAST_OK) {
+ ok(defined($nv), "nv defined $test->[0]");
+ } else {
+ ok(!defined($nv) || !$nv, "nv not defined $test->[0]");
+ }
+ }
+}
+
+1;
diff --git a/t/lib.pl b/t/lib.pl
new file mode 100644
index 0000000..e1512c6
--- /dev/null
+++ b/t/lib.pl
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+
+# lib.pl is the file where database specific things should live,
+# whereever possible. For example, you define certain constants
+# here and the like.
+
+use strict;
+
+use File::Basename;
+use File::Path;
+use File::Spec;
+
+my $test_dir;
+END { defined( $test_dir ) and rmtree $test_dir }
+
+sub test_dir
+{
+ unless( defined( $test_dir ) )
+ {
+ $test_dir = File::Spec->rel2abs( File::Spec->curdir () );
+ $test_dir = File::Spec->catdir ( $test_dir, "test_output_" . $$ );
+ $test_dir = VMS::Filespec::unixify($test_dir) if $^O eq 'VMS';
+ rmtree $test_dir;
+ mkpath $test_dir;
+ # There must be at least one directory in the test directory,
+ # and nothing guarantees that dot or dot-dot directories will exist.
+ mkpath ( File::Spec->catdir( $test_dir, '000_just_testing' ) );
+ }
+
+ return $test_dir;
+}
+
+1;
diff --git a/t/pod-coverage.t b/t/pod-coverage.t
new file mode 100644
index 0000000..64c2d58
--- /dev/null
+++ b/t/pod-coverage.t
@@ -0,0 +1,8 @@
+#!perl -T
+
+use Test::More;
+eval "use Test::Pod::Coverage 1.04";
+plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@;
+plan skip_all => "Currently a developer-only test" unless -d '.svn' || -d ".git";
+plan skip_all => "Currently FAILS FOR MANY MODULES!";
+all_pod_coverage_ok();
diff --git a/t/pod.t b/t/pod.t
new file mode 100644
index 0000000..23acc7d
--- /dev/null
+++ b/t/pod.t
@@ -0,0 +1,8 @@
+#!perl -w
+
+use Test::More;
+eval "use Test::Pod 1.00";
+plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
+all_pod_files_ok();
+
+1;
diff --git a/test.pl b/test.pl
new file mode 100755
index 0000000..294431b
--- /dev/null
+++ b/test.pl
@@ -0,0 +1,201 @@
+#!/usr/local/bin/perl -w
+
+# $Id: test.pl 12537 2009-02-24 22:45:40Z timbo $
+#
+# Copyright (c) 1994-1998 Tim Bunce
+#
+# See COPYRIGHT section in DBI.pm for usage and distribution rights.
+
+
+# This is now mostly an empty shell I experiment with.
+# The real tests have moved to t/*.t
+# See t/*.t for more detailed tests.
+
+
+BEGIN {
+ print "$0 @ARGV\n";
+ print q{DBI test application $Revision: 12537 $}."\n";
+ $| = 1;
+}
+
+use blib;
+
+use DBI;
+
+use DBI::DBD; # simple test to make sure it's okay
+
+use Config;
+use Getopt::Long;
+use strict;
+
+our $has_devel_leak = eval {
+ local $^W = 0; # silence "Use of uninitialized value $DynaLoader::args[0] in subroutine entry";
+ require Devel::Leak;
+};
+
+$::opt_d = 0;
+$::opt_l = '';
+$::opt_h = 0;
+$::opt_m = 0; # basic memory leak test: "perl test.pl -m NullP"
+$::opt_t = 0; # thread test
+$::opt_n = 0; # counter for other options
+
+GetOptions(qw(d=i h=i l=s m=i t=i n=i))
+ or die "Usage: $0 [-d n] [-h n] [-m n] [-t n] [-n n] [drivername]\n";
+
+my $count = 0;
+my $ps = (-d '/proc') ? "ps -lp " : "ps -l";
+my $driver = $ARGV[0] || ($::opt_m ? 'NullP' : 'ExampleP');
+
+# Now ask for some information from the DBI Switch
+my $switch = DBI->internal;
+$switch->trace($::opt_h); # 2=detailed handle trace
+
+DBI->trace($::opt_d, $::opt_l) if $::opt_d || $::opt_l;
+
+print "Switch: $switch->{'Attribution'}, $switch->{'Version'}\n";
+
+print "Available Drivers: ",join(", ",DBI->available_drivers(1)),"\n";
+
+
+my $dbh = DBI->connect("dbi:$driver:", '', '', { RaiseError=>1 }) or die;
+$dbh->trace($::opt_h);
+
+if (0) {
+ DBI->trace(3);
+ my $h = DBI->connect('dbi:NullP:','','', { RootClass=>'MyTestDBI', DbTypeSubclass=>'foo, bar' });
+ DBI->trace(0);
+ { # only works after 5.004_04:
+ warn "RaiseError= '$h->{RaiseError}' (pre local)\n";
+ local($h->{RaiseError});# = undef;
+ warn "RaiseError= '$h->{RaiseError}' (post local)\n";
+ }
+ warn "RaiseError= '$h->{RaiseError}' (post local block)\n";
+ exit 1;
+}
+
+if ($::opt_m) {
+ #$dbh->trace(9);
+ my $level = $::opt_m;
+ my $cnt = $::opt_n || 10000;
+
+ print "Using $driver, same dbh...\n";
+ for (my $i=0; $i<$cnt; ++$i) { mem_test($dbh, undef, $level, undef, undef, undef) }
+
+ print "Using NullP, reconnecting each time...\n";
+ for (my $i=0; $i<$cnt; ++$i) { mem_test(undef, ['dbi:NullP:'], $level, undef, undef, undef) }
+
+ print "Using ExampleP, reconnecting each time...\n";
+ my $r_develleak = 0;
+ mem_test(undef, ['dbi:NullP:'], $level, undef, undef, \$r_develleak) while 1;
+ #mem_test(undef, ['dbi:mysql:VC'], $level, "select * from campaigns where length(?)>0", 0, undef) while 1;
+}
+elsif ($::opt_t) {
+ thread_test();
+}
+else {
+
+ # new experimental connect_test_perf method
+ DBI->connect_test_perf("dbi:$driver:", '', '', {
+ dbi_loops=>3, dbi_par=>20, dbi_verb=>1
+ });
+
+ require Benchmark;
+ print "Testing handle creation speed...\n";
+ my $null_dbh = DBI->connect('dbi:NullP:','','');
+ my $null_sth = $null_dbh->prepare(''); # create one to warm up
+ $count = 20_000;
+ $count /= 10 if $ENV{DBI_AUTOPROXY};
+ my $i = $count;
+ my $t1 = new Benchmark;
+ $null_dbh->prepare('') while $i--;
+ my $td = Benchmark::timediff(Benchmark->new, $t1);
+ my $tds= Benchmark::timestr($td);
+ my $dur = $td->cpu_a || (1/$count); # fudge if cpu_a==0
+
+ printf "%5d NullP sth/s perl %8s %s (%s %s %s) %fs\n\n",
+ $count/$dur, $], $Config{archname},
+ $Config{gccversion} ? 'gcc' : $Config{cc},
+ (split / /, $Config{gccversion}||$Config{ccversion}||'')[0]||'',
+ $Config{optimize},
+ $dur/$count;
+
+ $null_dbh->disconnect;
+}
+
+$dbh->disconnect;
+
+#DBI->trace(4);
+print "$0 done\n";
+exit 0;
+
+
+sub mem_test { # harness to help find basic leaks
+ my ($orig_dbh, $connect, $level, $select, $params, $r_develleak) = @_;
+ $select ||= "select mode,ino,name from ?";
+ $params ||= [ '.' ];
+
+ # this can be used to force a 'leak' to check memory use reporting
+ #$main::leak .= " " x 1000;
+ system("echo $count; $ps$$") if (($count++ % 2000) == 0);
+
+ my $dbh = $orig_dbh || do {
+ my ($dsn, $u, $p, $attr) = @$connect;
+ $attr->{RaiseError} = 1;
+ DBI->connect($dsn, $u, $p, $attr);
+ };
+ my $cursor_a;
+
+ my ($dl_count, $dl_handle);
+ if ($has_devel_leak && $$r_develleak++) {
+ $dbh->trace(2);
+ $dl_count = Devel::Leak::NoteSV($dl_handle);
+ }
+
+ my $rows;
+ $cursor_a = $dbh->prepare($select) if $level >= 2;
+ $cursor_a->execute(@$params) if $level >= 3;
+ $cursor_a->fetchrow_hashref() if $level >= 4;
+ $rows = $cursor_a->fetchall_arrayref({}) if $level >= 4;
+ $cursor_a->finish if $cursor_a && $cursor_a->{Active};
+ undef $cursor_a;
+
+ @{$dbh->{ChildHandles}} = ();
+
+ die Devel::Leak::CheckSV($dl_handle)-$dl_count
+ if $dl_handle;
+
+ $dbh->disconnect unless $orig_dbh;
+ undef $dbh;
+
+}
+
+
+sub thread_test {
+ require Thread;
+ my $dbh = DBI->connect("dbi:ExampleP:.", "", "") || die $DBI::err;
+ #$dbh->trace(4);
+ my @t;
+ print "Starting $::opt_t threads:\n";
+ foreach(1..$::opt_t) {
+ print "$_\n";
+ push @t, Thread->new(\&thread_test_loop, $dbh, $::opt_n||99);
+ }
+ print "Small sleep to allow threads to progress\n";
+ sleep 2;
+ print "Joining threads:\n";
+ foreach(@t) {
+ print "$_\n";
+ $_->join
+ }
+}
+
+sub thread_test_loop {
+ my $dbh = shift;
+ my $i = shift || 10;
+ while($i-- > 0) {
+ $dbh->selectall_arrayref("select * from ?", undef, ".");
+ }
+}
+
+# end.
diff --git a/typemap b/typemap
new file mode 100644
index 0000000..c06ec2f
--- /dev/null
+++ b/typemap
@@ -0,0 +1,3 @@
+const char * T_PV
+imp_xxh_t * T_PTROBJ
+DBI_imp_data_ * T_PTROBJ