From 9c0f8c2a1b5b8e01ff0f39e64a160e7859ed8283 Mon Sep 17 00:00:00 2001 From: Mason James Date: Thu, 26 Jan 2023 18:45:35 +1300 Subject: Import original source of Catmandu-DBI 0.12 --- Build.PL | 56 + Changes | 145 +++ LICENSE | 379 ++++++ MANIFEST | 31 + META.json | 89 ++ META.yml | 51 + README | 60 + cpanfile | 12 + cpanfile.snapshot | 2013 ++++++++++++++++++++++++++++++ dist.ini | 4 + lib/Catmandu/DBI.pm | 67 + lib/Catmandu/Importer/DBI.pm | 143 +++ lib/Catmandu/Serializer/json_string.pm | 44 + lib/Catmandu/Store/DBI.pm | 418 +++++++ lib/Catmandu/Store/DBI/Bag.pm | 396 ++++++ lib/Catmandu/Store/DBI/Handler.pm | 64 + lib/Catmandu/Store/DBI/Handler/MySQL.pm | 117 ++ lib/Catmandu/Store/DBI/Handler/Pg.pm | 188 +++ lib/Catmandu/Store/DBI/Handler/SQLite.pm | 81 ++ lib/Catmandu/Store/DBI/Iterator.pm | 173 +++ t/00-load.t | 16 + t/01-load.t | 14 + t/02-bag.t | 96 ++ t/04-slice.t | 64 + t/05-importer.t | 122 ++ t/06-sqlite.t | 182 +++ t/07-mysql.t | 223 ++++ t/08-postgres.t | 240 ++++ t/09-serializer-json-string.t | 51 + t/author-pod-syntax.t | 15 + 30 files changed, 5554 insertions(+) create mode 100644 Build.PL create mode 100644 Changes create mode 100644 LICENSE create mode 100644 MANIFEST create mode 100644 META.json create mode 100644 META.yml create mode 100644 README create mode 100644 cpanfile create mode 100644 cpanfile.snapshot create mode 100644 dist.ini create mode 100644 lib/Catmandu/DBI.pm create mode 100644 lib/Catmandu/Importer/DBI.pm create mode 100644 lib/Catmandu/Serializer/json_string.pm create mode 100644 lib/Catmandu/Store/DBI.pm create mode 100644 lib/Catmandu/Store/DBI/Bag.pm create mode 100644 lib/Catmandu/Store/DBI/Handler.pm create mode 100644 lib/Catmandu/Store/DBI/Handler/MySQL.pm create mode 100644 lib/Catmandu/Store/DBI/Handler/Pg.pm create mode 100644 lib/Catmandu/Store/DBI/Handler/SQLite.pm create mode 100644 lib/Catmandu/Store/DBI/Iterator.pm create mode 100644 t/00-load.t create mode 100644 t/01-load.t create mode 100644 t/02-bag.t create mode 100644 t/04-slice.t create mode 100644 t/05-importer.t create mode 100644 t/06-sqlite.t create mode 100644 t/07-mysql.t create mode 100644 t/08-postgres.t create mode 100644 t/09-serializer-json-string.t create mode 100644 t/author-pod-syntax.t diff --git a/Build.PL b/Build.PL new file mode 100644 index 0000000..0e334eb --- /dev/null +++ b/Build.PL @@ -0,0 +1,56 @@ + +# This file was automatically generated by Dist::Zilla::Plugin::ModuleBuild v6.012. +use strict; +use warnings; + +use Module::Build 0.28; + + +my %module_build_args = ( + "build_requires" => { + "Module::Build" => "0.28" + }, + "configure_requires" => { + "Module::Build" => "0.28" + }, + "dist_abstract" => "Catmandu tools to communicate with DBI based interfaces", + "dist_author" => [ + "Nicolas Franck" + ], + "dist_name" => "Catmandu-DBI", + "dist_version" => "0.12", + "license" => "perl", + "module_name" => "Catmandu::DBI", + "recursive_test_files" => 1, + "requires" => { + "Catmandu" => "1.0", + "DBI" => "1.630", + "JSON" => 0, + "Moo" => "1.004006", + "MooX::Aliases" => "0.001006", + "namespace::clean" => "0.24", + "perl" => "v5.10.1" + }, + "test_requires" => { + "Test::Exception" => 0, + "Test::More" => 0 + } +); + + +my %fallback_build_requires = ( + "Module::Build" => "0.28", + "Test::Exception" => 0, + "Test::More" => 0 +); + + +unless ( eval { Module::Build->VERSION(0.4004) } ) { + delete $module_build_args{test_requires}; + $module_build_args{build_requires} = \%fallback_build_requires; +} + +my $build = Module::Build->new(%module_build_args); + + +$build->create_build_script; diff --git a/Changes b/Changes new file mode 100644 index 0000000..5bf9f6c --- /dev/null +++ b/Changes @@ -0,0 +1,145 @@ +Revision history for Catmandu-DBI + +0.12 2022-08-23 11:19:52 CEST + - optimize bag generator by reducing number of queries by 1 + +0.11 2021-10-13 12:04:11 CEST + - bump version + +0.10.1 2021-10-11 09:27:51 CEST + - auto reconnect + +0.10 2021-10-11 09:17:28 CEST + - auto reconnect + +0.09 2019-06-05 11:39:38 CEST + - lazily create tables + +0.08_01 2019-06-05 11:14:40 CEST + - lazily create tables + +0.08 2018-11-08 14:28:34 CET + - optimize mysql select with large offsets + - fix Catmandu::Importer::DBI utf-8 handling + - The Pg handler now supports the jsonb type + +0.0702 2018-04-10 11:30:09 CEST + - document index option + +0.0701 2017-11-13 09:05:26 CET + - fix utf8 string bug + +0.07 2017-11-09 13:40:42 CET + - revert the changes made in 0.06 because of high memory usage in mysql and + postgresql + +0.06 2017-09-01 12:39:01 CEST + - retain sth for faster iteration + +0.0511 2017-01-09 13:56:38 CET + - more pod + +0.0510 2016-10-04 15:20:17 CEST + - use installer ModuleBuild to make Travis happy + +0.0509 2016-10-04 14:59:51 CEST + - META file + +0.0508 2016-10-04 14:29:12 CEST + - set sqlite_use_immediate_transaction to true by default to allow safer + multiprocess access + +0.0507 2016-06-21 14:20:23 CEST + - remove files MYMETA.json and MYMETA.yml + +0.0506 2016-06-08 14:51:05 CEST + - add prefix 'DBI:' to data_source automatically + - fix memory leak in Catmandu::Store::DBI::Iterator + +0.0505 2016-04-20 16:40:09 CEST + - Add more documentation + +0.0504 2016-02-19 12:31:06 CET + - support field 'datetime' + +0.0503 2015-10-13 16:13:54 CEST + - Fix typo + +0.0502 2015-10-13 16:07:07 CEST + - Fix Pg quoting on index creation + +0.0501 2015-10-13 11:26:42 CEST + - Shallow copy default mapping + - define VERSION in all packages + +0.05 2015-10-08 11:12:04 CEST + - Column mapping + - Specialized Iterator + - Synchronize VERSION's + +0.0135 2015-04-14 + - BUG in Catmandu::Store::DBI for MySQL. identifier 'id' is of type + 'varchar', which is case insensitive. This can easily be solved with the + sql statement "alter table scans modify id varchar(255) binary". + +0.0134 2015-01-26 + - BUG in Catmandu::Store::DBI: method 'dbh' crashes during global destruction + (after call to DEMOLISH). Fixed by not accessing DBI interface during this fase. + +0.0133 2015-01-16 + - BUG in Catmandu::Store::DBI::Bag: a reference to the database handle is + stored in a variable outside the callback in function _build_create_*, and + then used inside the callback. This only works as long as the database handle + is active. When working with web applications, where these stores are kept in + memory, and not used for a long time, this can lead to inactive database + handles. Fix: direct reference from the callback to the function "dbh" of the + Catmandu::Store::DBI, that automatically reconnects if necessary. + +0.0132 2015-01-09 + - BUG for DBD::Pg. When inserting data, column 'data' is not properly escaped + als PG_BYTEA (see _build_add_postgres in Catmandu::Store::DBI::Bag) + +0.0131 2015-01-09 + - Incorrect use of 'state' in function Catmandu::Store::DBI::dbh. Due to this + bug, only one dbh could be stored. Now also the version in + Catmandu::Store::DBI is raised + +0.013 2015-01-08 + - Incorrect use of 'state' in function Catmandu::Store::DBI::dbh. Due to this + bug, only one dbh could be stored. + +0.012 2014-10-02 + - Use of function tempfile in test scripts must set the option EXLOCK to '0', + to avoid locking problems on BSD. This temporary file is used by SQLite, + but EXLOCK is set to '1' on some systems. + +0.011 2014-10-01 + - Switched to Dist::Milla + +0.01 2014-09-26 + - Catmandu-Store-DBI and Catmandu-Importer-DBI merged into one package + Catmandu-DBI + +Revision history for Catmandu-Importer-DBI + +0.03 2014-03-26 + - adding support for accessing the database handle + +0.02 2014-02-21 + - deleted internal test + +0.01 2014-02-21 + - initial version + +Revision history for Catmandu-Store-DBI + +0.041 2014-09-25 + - add support for timeout and auto reconnect + +0.03 2014-03-07 + - add support for postgres >= 9.1 + - optimized slice implementation + - mysql auto reconnect + +0.02 2013-06-12 + - initial release of Catmandu::Store::DBI as own package diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..64ca3a6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,379 @@ +This software is copyright (c) 2022 by Nicolas Steenlant. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +Terms of the Perl programming language system itself + +a) the GNU General Public License as published by the Free + Software Foundation; either version 1, or (at your option) any + later version, or +b) the "Artistic License" + +--- The GNU General Public License, Version 1, February 1989 --- + +This software is Copyright (c) 2022 by Nicolas Steenlant. + +This is free software, licensed under: + + The GNU General Public License, Version 1, February 1989 + + GNU GENERAL PUBLIC LICENSE + Version 1, February 1989 + + Copyright (C) 1989 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The license agreements of most software companies try to keep users +at the mercy of those companies. By contrast, our General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. The +General Public License applies to the Free Software Foundation's +software and to any other program whose authors commit to using it. +You can use it for your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Specifically, the General Public License is designed to make +sure that you have the freedom to give away or sell copies of free +software, that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of a such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must tell them their rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work based +on the Program" means either the Program or any work containing the +Program or a portion of it, either verbatim or with modifications. Each +licensee is addressed as "you". + + 1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +General Public License and to the absence of any warranty; and give any +other recipients of the Program a copy of this General Public License +along with the Program. You may charge a fee for the physical act of +transferring a copy. + + 2. You may modify your copy or copies of the Program or any portion of +it, and copy and distribute such modifications under the terms of Paragraph +1 above, provided that you also do the following: + + a) cause the modified files to carry prominent notices stating that + you changed the files and the date of any change; and + + b) cause the whole of any work that you distribute or publish, that + in whole or in part contains the Program or any part thereof, either + with or without modifications, to be licensed at no charge to all + third parties under the terms of this General Public License (except + that you may choose to grant warranty protection to some or all + third parties, at your option). + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the simplest and most usual way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this General + Public License. + + d) You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + +Mere aggregation of another independent work with the Program (or its +derivative) on a volume of a storage or distribution medium does not bring +the other work under the scope of these terms. + + 3. You may copy and distribute the Program (or a portion or derivative of +it, under Paragraph 2) in object code or executable form under the terms of +Paragraphs 1 and 2 above provided that you also do one of the following: + + a) accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of + Paragraphs 1 and 2 above; or, + + b) accompany it with a written offer, valid for at least three + years, to give any third party free (except for a nominal charge + for the cost of distribution) a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of + Paragraphs 1 and 2 above; or, + + c) accompany it with the information you received as to where the + corresponding source code may be obtained. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form alone.) + +Source code for a work means the preferred form of the work for making +modifications to it. For an executable file, complete source code means +all the source code for all modules it contains; but, as a special +exception, it need not include source code for modules which are standard +libraries that accompany the operating system on which the executable +file runs, or for standard header files or definitions files that +accompany that operating system. + + 4. You may not copy, modify, sublicense, distribute or transfer the +Program except as expressly provided under this General Public License. +Any attempt otherwise to copy, modify, sublicense, distribute or transfer +the Program is void, and will automatically terminate your rights to use +the Program under this License. However, parties who have received +copies, or rights to use copies, from you under this General Public +License will not have their licenses terminated so long as such parties +remain in full compliance. + + 5. By copying, distributing or modifying the Program (or any work based +on the Program) you indicate your acceptance of this license to do so, +and all its terms and conditions. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these +terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. + + 7. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of the license which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +the license, you may choose any version ever published by the Free Software +Foundation. + + 8. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to humanity, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19xx name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and `show +c'; they could even be mouse-clicks or menu items--whatever suits your +program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (a program to direct compilers to make passes + at assemblers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +That's all there is to it! + + +--- The Artistic License 1.0 --- + +This software is Copyright (c) 2022 by Nicolas Steenlant. + +This is free software, licensed under: + + The Artistic License 1.0 + +The Artistic License + +Preamble + +The intent of this document is to state the conditions under which a Package +may be copied, such that the Copyright Holder maintains some semblance of +artistic control over the development of the package, while giving the users of +the package the right to use and distribute the Package in a more-or-less +customary fashion, plus the right to make reasonable modifications. + +Definitions: + + - "Package" refers to the collection of files distributed by the Copyright + Holder, and derivatives of that collection of files created through + textual modification. + - "Standard Version" refers to such a Package if it has not been modified, + or has been modified in accordance with the wishes of the Copyright + Holder. + - "Copyright Holder" is whoever is named in the copyright or copyrights for + the package. + - "You" is you, if you're thinking about copying or distributing this Package. + - "Reasonable copying fee" is whatever you can justify on the basis of media + cost, duplication charges, time of people involved, and so on. (You will + not be required to justify it to the Copyright Holder, but only to the + computing community at large as a market that must bear the fee.) + - "Freely Available" means that no fee is charged for the item itself, though + there may be fees involved in handling the item. It also means that + recipients of the item may redistribute it under the same conditions they + received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications derived +from the Public Domain or from the Copyright Holder. A Package modified in such +a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided that +you insert a prominent notice in each changed file stating how and when you +changed that file, and provided that you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or an + equivalent medium, or placing the modifications on a major archive site + such as ftp.uu.net, or by allowing the Copyright Holder to include your + modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict with + standard executables, which must also be provided, and provide a separate + manual page for each non-standard executable that clearly documents how it + differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or executable +form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where to + get the Standard Version. + + b) accompany the distribution with the machine-readable source of the Package + with your modifications. + + c) accompany any non-standard executables with their corresponding Standard + Version executables, giving the non-standard executables non-standard + names, and clearly documenting the differences in manual pages (or + equivalent), together with instructions on where to get the Standard + Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this Package. You +may not charge a fee for this Package itself. However, you may distribute this +Package in aggregate with other (possibly commercial) programs as part of a +larger (possibly commercial) software distribution provided that you do not +advertise this Package as a product of your own. + +6. The scripts and library files supplied as input to or produced as output +from the programs of this Package do not automatically fall under the copyright +of this Package, but belong to whomever generated them, and may be sold +commercially, and may be aggregated with this Package. + +7. C or perl subroutines supplied by you and linked into this Package shall not +be considered part of this Package. + +8. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +The End + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..d94dce8 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,31 @@ +# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.012. +Build.PL +Changes +LICENSE +MANIFEST +META.json +META.yml +README +cpanfile +cpanfile.snapshot +dist.ini +lib/Catmandu/DBI.pm +lib/Catmandu/Importer/DBI.pm +lib/Catmandu/Serializer/json_string.pm +lib/Catmandu/Store/DBI.pm +lib/Catmandu/Store/DBI/Bag.pm +lib/Catmandu/Store/DBI/Handler.pm +lib/Catmandu/Store/DBI/Handler/MySQL.pm +lib/Catmandu/Store/DBI/Handler/Pg.pm +lib/Catmandu/Store/DBI/Handler/SQLite.pm +lib/Catmandu/Store/DBI/Iterator.pm +t/00-load.t +t/01-load.t +t/02-bag.t +t/04-slice.t +t/05-importer.t +t/06-sqlite.t +t/07-mysql.t +t/08-postgres.t +t/09-serializer-json-string.t +t/author-pod-syntax.t diff --git a/META.json b/META.json new file mode 100644 index 0000000..ece5e5c --- /dev/null +++ b/META.json @@ -0,0 +1,89 @@ +{ + "abstract" : "Catmandu tools to communicate with DBI based interfaces", + "author" : [ + "Nicolas Franck" + ], + "dynamic_config" : 0, + "generated_by" : "Dist::Milla version v1.0.20, Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010", + "license" : [ + "perl_5" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : 2 + }, + "name" : "Catmandu-DBI", + "no_index" : { + "directory" : [ + "eg", + "examples", + "inc", + "share", + "t", + "xt" + ] + }, + "prereqs" : { + "build" : { + "requires" : { + "Module::Build" : "0.28" + } + }, + "configure" : { + "requires" : { + "Module::Build" : "0.28" + } + }, + "develop" : { + "requires" : { + "Dist::Milla" : "v1.0.20", + "Test::Pod" : "1.41" + } + }, + "runtime" : { + "requires" : { + "Catmandu" : "1.0", + "DBI" : "1.630", + "JSON" : "0", + "Moo" : "1.004006", + "MooX::Aliases" : "0.001006", + "namespace::clean" : "0.24", + "perl" : "v5.10.1" + } + }, + "test" : { + "requires" : { + "Test::Exception" : "0", + "Test::More" : "0" + } + } + }, + "release_status" : "stable", + "resources" : { + "bugtracker" : { + "web" : "https://github.com/LibreCat/Catmandu-DBI/issues" + }, + "homepage" : "https://github.com/LibreCat/Catmandu-DBI", + "repository" : { + "type" : "git", + "url" : "https://github.com/LibreCat/Catmandu-DBI.git", + "web" : "https://github.com/LibreCat/Catmandu-DBI" + } + }, + "version" : "0.12", + "x_contributors" : [ + "Jakob Voss ", + "Nicolas Franck ", + "Nicolas Franck ", + "Nicolas Franck ", + "Nicolas Steenlant ", + "Nicolas Steenlant ", + "njfranck ", + "Patrick Hochstenbach ", + "vpeil " + ], + "x_generated_by_perl" : "v5.30.0", + "x_serialization_backend" : "Cpanel::JSON::XS version 4.27", + "x_static_install" : 0 +} + diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..2121b5f --- /dev/null +++ b/META.yml @@ -0,0 +1,51 @@ +--- +abstract: 'Catmandu tools to communicate with DBI based interfaces' +author: + - 'Nicolas Franck' +build_requires: + Module::Build: '0.28' + Test::Exception: '0' + Test::More: '0' +configure_requires: + Module::Build: '0.28' +dynamic_config: 0 +generated_by: 'Dist::Milla version v1.0.20, Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010' +license: perl +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: '1.4' +name: Catmandu-DBI +no_index: + directory: + - eg + - examples + - inc + - share + - t + - xt +requires: + Catmandu: '1.0' + DBI: '1.630' + JSON: '0' + Moo: '1.004006' + MooX::Aliases: '0.001006' + namespace::clean: '0.24' + perl: v5.10.1 +resources: + bugtracker: https://github.com/LibreCat/Catmandu-DBI/issues + homepage: https://github.com/LibreCat/Catmandu-DBI + repository: https://github.com/LibreCat/Catmandu-DBI.git +version: '0.12' +x_contributors: + - 'Jakob Voss ' + - 'Nicolas Franck ' + - 'Nicolas Franck ' + - 'Nicolas Franck ' + - 'Nicolas Steenlant ' + - 'Nicolas Steenlant ' + - 'njfranck ' + - 'Patrick Hochstenbach ' + - 'vpeil ' +x_generated_by_perl: v5.30.0 +x_serialization_backend: 'YAML::Tiny version 1.73' +x_static_install: 0 diff --git a/README b/README new file mode 100644 index 0000000..e15653b --- /dev/null +++ b/README @@ -0,0 +1,60 @@ +NAME + + Catmandu::DBI - Catmandu tools to communicate with DBI based interfaces + +SYNOPSIS + + # From the command line + + # Export data from a relational database + $ catmandu convert DBI --dsn dbi:mysql:foobar --user foo --password bar --query "select * from table" + + # Import data into a relational database + $ catmandu import JSON to DBI --data_source dbi:SQLite:mydb.sqlite < data.json + + # Export data from a relational database + $ catmandu export DBI --data_source dbi:SQLite:mydb.sqlite to JSON + + # Or via a configuration file + $ cat catmandu.yml + --- + store: + mydb: + package: DBI + options: + data_source: "dbi:mysql:database=mydb" + username: xyz + password: xyz + ... + $ catmandu import JSON to mydb < data.json + $ catmandu export mydb to YAML > data.yml + + # Export one record + $ catmandu export mydb --id 012E929E-FF44-11E6-B956-AE2804ED5190 to JSON > record.json + + # Count the number of records + $ catmandu count mydb + + # Delete data + $ catmandy delete mydb + +MODULES + + Catmandu::Importer::DBI + + Catmandu::Store::DBI + +AUTHORS + + Nicolas Franck + + Patrick Hochstenbach + + Vitali Peil + + Nicolas Steenlant + +SEE ALSO + + Catmandu, Catmandu::Importer , Catmandu::Store::DBI + diff --git a/cpanfile b/cpanfile new file mode 100644 index 0000000..9525ba8 --- /dev/null +++ b/cpanfile @@ -0,0 +1,12 @@ +requires 'perl','5.10.1'; +requires 'Catmandu','>=1.0'; +requires 'namespace::clean','>=0.24'; +requires 'DBI','>=1.630'; +requires 'Moo', '>=1.004006'; +requires 'MooX::Aliases', '>=0.001006'; +requires 'JSON'; + +on 'test', sub { + requires 'Test::Exception','0'; + requires 'Test::More','0'; +}; diff --git a/cpanfile.snapshot b/cpanfile.snapshot new file mode 100644 index 0000000..aeb0715 --- /dev/null +++ b/cpanfile.snapshot @@ -0,0 +1,2013 @@ +# carton snapshot format: version 1.0 +DISTRIBUTIONS + Algorithm-Diff-1.1903 + pathname: T/TY/TYEMQ/Algorithm-Diff-1.1903.tar.gz + provides: + Algorithm::Diff 1.1903 + Algorithm::Diff::_impl 1.1903 + requirements: + ExtUtils::MakeMaker 0 + Any-URI-Escape-0.01 + pathname: P/PH/PHRED/Any-URI-Escape-0.01.tar.gz + provides: + Any::URI::Escape 0.01 + requirements: + ExtUtils::MakeMaker 0 + URI::Escape 0 + App-Cmd-0.331 + pathname: R/RJ/RJBS/App-Cmd-0.331.tar.gz + provides: + App::Cmd 0.331 + App::Cmd::ArgProcessor 0.331 + App::Cmd::Command 0.331 + App::Cmd::Command::commands 0.331 + App::Cmd::Command::help 0.331 + App::Cmd::Command::version 0.331 + App::Cmd::Plugin 0.331 + App::Cmd::Setup 0.331 + App::Cmd::Simple 0.331 + App::Cmd::Subdispatch 0.331 + App::Cmd::Subdispatch::DashedStyle 0.331 + App::Cmd::Tester 0.331 + App::Cmd::Tester::CaptureExternal 0.331 + App::Cmd::Tester::Exited 0.331 + App::Cmd::Tester::Result 0.331 + requirements: + Capture::Tiny 0.13 + Carp 0 + Class::Load 0.06 + Data::OptList 0 + ExtUtils::MakeMaker 0 + File::Basename 0 + Getopt::Long 2.39 + Getopt::Long::Descriptive 0.084 + IO::TieCombine 0 + Module::Pluggable::Object 0 + Pod::Usage 1.61 + String::RewritePrefix 0 + Sub::Exporter 0 + Sub::Exporter::Util 0 + Sub::Install 0 + Text::Abbrev 0 + constant 0 + parent 0 + perl 5.006 + strict 0 + warnings 0 + B-Hooks-EndOfScope-0.21 + pathname: E/ET/ETHER/B-Hooks-EndOfScope-0.21.tar.gz + provides: + B::Hooks::EndOfScope 0.21 + B::Hooks::EndOfScope::PP 0.21 + B::Hooks::EndOfScope::XS 0.21 + requirements: + ExtUtils::MakeMaker 0 + Module::Implementation 0.05 + Sub::Exporter::Progressive 0.001006 + Text::ParseWords 0 + Variable::Magic 0.48 + perl 5.008001 + strict 0 + warnings 0 + B-Hooks-OP-Check-0.22 + pathname: E/ET/ETHER/B-Hooks-OP-Check-0.22.tar.gz + provides: + B::Hooks::OP::Check 0.22 + requirements: + DynaLoader 0 + ExtUtils::Depends 0.302 + ExtUtils::MakeMaker 0 + parent 0 + perl 5.008001 + strict 0 + warnings 0 + B-Utils-0.27 + pathname: E/ET/ETHER/B-Utils-0.27.tar.gz + provides: + B::Utils 0.27 + B::Utils::Install::Files undef + B::Utils::OP 0.27 + requirements: + Exporter 0 + ExtUtils::Depends 0.302 + ExtUtils::MakeMaker 0 + Scalar::Util 0 + Task::Weaken 0 + perl 5.006 + CGI-Expand-2.05 + pathname: B/BO/BOWMANBS/CGI-Expand-2.05.tar.gz + provides: + CGI::Expand 2.05 + requirements: + ExtUtils::MakeMaker 0 + Test::Exception 0 + Test::More 0 + Capture-Tiny-0.46 + pathname: D/DA/DAGOLDEN/Capture-Tiny-0.46.tar.gz + provides: + Capture::Tiny 0.46 + requirements: + Carp 0 + Exporter 0 + ExtUtils::MakeMaker 6.17 + File::Spec 0 + File::Temp 0 + IO::Handle 0 + Scalar::Util 0 + perl 5.006 + strict 0 + warnings 0 + Catmandu-1.08 + pathname: N/NI/NICS/Catmandu-1.08.tar.gz + provides: + Catmandu 1.08 + Catmandu::Addable 1.08 + Catmandu::ArrayIterator 1.08 + Catmandu::BadArg 1.08 + Catmandu::BadFixArg 1.08 + Catmandu::BadVal 1.08 + Catmandu::Bag 1.08 + Catmandu::Bag::IdGenerator 1.08 + Catmandu::Bag::IdGenerator::Mock 1.08 + Catmandu::Bag::IdGenerator::UUID 1.08 + Catmandu::Buffer 1.08 + Catmandu::CLI 1.08 + Catmandu::CQLSearchable 1.08 + Catmandu::Cmd 1.08 + Catmandu::Cmd::compile 1.08 + Catmandu::Cmd::config 1.08 + Catmandu::Cmd::convert 1.08 + Catmandu::Cmd::copy 1.08 + Catmandu::Cmd::count 1.08 + Catmandu::Cmd::delete 1.08 + Catmandu::Cmd::drop 1.08 + Catmandu::Cmd::export 1.08 + Catmandu::Cmd::help 1.08 + Catmandu::Cmd::import 1.08 + Catmandu::Cmd::info 1.08 + Catmandu::Cmd::run 1.08 + Catmandu::Cmd::stream 1.0505 + Catmandu::Cmd::touch 1.08 + Catmandu::Counter 1.08 + Catmandu::Droppable 1.08 + Catmandu::Env 1.08 + Catmandu::Error 1.08 + Catmandu::Error::Source 1.08 + Catmandu::Expander 1.08 + Catmandu::Exporter 1.08 + Catmandu::Exporter::CSV 1.08 + Catmandu::Exporter::Count 1.08 + Catmandu::Exporter::JSON 1.08 + Catmandu::Exporter::Mock 1.08 + Catmandu::Exporter::Multi 1.08 + Catmandu::Exporter::Null 1.08 + Catmandu::Exporter::TSV 1.08 + Catmandu::Exporter::Text 1.08 + Catmandu::Exporter::YAML 1.08 + Catmandu::FileBag 1.08 + Catmandu::FileBag::Index 1.08 + Catmandu::FileStore 1.08 + Catmandu::Fix 1.0201 + Catmandu::Fix::Base 1.08 + Catmandu::Fix::Bind 1.08 + Catmandu::Fix::Bind::Group 1.08 + Catmandu::Fix::Bind::benchmark 1.08 + Catmandu::Fix::Bind::each 1.08 + Catmandu::Fix::Bind::hashmap 1.08 + Catmandu::Fix::Bind::identity 1.08 + Catmandu::Fix::Bind::importer 1.08 + Catmandu::Fix::Bind::iterate 1.08 + Catmandu::Fix::Bind::list 1.08 + Catmandu::Fix::Bind::maybe 1.08 + Catmandu::Fix::Bind::timeout 1.08 + Catmandu::Fix::Bind::visitor 1.08 + Catmandu::Fix::Bind::with 1.08 + Catmandu::Fix::Condition 1.08 + Catmandu::Fix::Condition::SimpleAllTest 1.08 + Catmandu::Fix::Condition::SimpleAnyTest 1.08 + Catmandu::Fix::Condition::SimpleCompareTest 1.08 + Catmandu::Fix::Condition::all_equal 1.08 + Catmandu::Fix::Condition::all_match 1.08 + Catmandu::Fix::Condition::any_equal 1.08 + Catmandu::Fix::Condition::any_match 1.08 + Catmandu::Fix::Condition::exists 1.08 + Catmandu::Fix::Condition::greater_than 1.08 + Catmandu::Fix::Condition::in 1.08 + Catmandu::Fix::Condition::is_array 1.08 + Catmandu::Fix::Condition::is_false 1.08 + Catmandu::Fix::Condition::is_null 1.08 + Catmandu::Fix::Condition::is_number 1.08 + Catmandu::Fix::Condition::is_object 1.08 + Catmandu::Fix::Condition::is_string 1.08 + Catmandu::Fix::Condition::is_true 1.08 + Catmandu::Fix::Condition::less_than 1.08 + Catmandu::Fix::Condition::valid 1.08 + Catmandu::Fix::Has 1.08 + Catmandu::Fix::Inlineable 1.08 + Catmandu::Fix::Parser 1.08 + Catmandu::Fix::SimpleGetValue 1.08 + Catmandu::Fix::add 1.08 + Catmandu::Fix::add_field 1.08 + Catmandu::Fix::add_to_exporter 1.08 + Catmandu::Fix::add_to_store 1.08 + Catmandu::Fix::append 1.08 + Catmandu::Fix::array 1.08 + Catmandu::Fix::assoc 1.08 + Catmandu::Fix::capitalize 1.08 + Catmandu::Fix::clone 1.08 + Catmandu::Fix::code 1.08 + Catmandu::Fix::collapse 1.08 + Catmandu::Fix::compact undef + Catmandu::Fix::copy 1.08 + Catmandu::Fix::copy_field 1.08 + Catmandu::Fix::count 1.08 + Catmandu::Fix::downcase 1.08 + Catmandu::Fix::error 1.08 + Catmandu::Fix::expand 1.08 + Catmandu::Fix::expand_date 1.08 + Catmandu::Fix::export_to_string 1.08 + Catmandu::Fix::filter 1.08 + Catmandu::Fix::flatten 1.08 + Catmandu::Fix::format 1.08 + Catmandu::Fix::from_json 1.08 + Catmandu::Fix::hash 1.08 + Catmandu::Fix::import 1.08 + Catmandu::Fix::import_from_string 1.08 + Catmandu::Fix::include 1.08 + Catmandu::Fix::index 1.08 + Catmandu::Fix::int 1.08 + Catmandu::Fix::join 1.08 + Catmandu::Fix::join_field 1.08 + Catmandu::Fix::log 1.08 + Catmandu::Fix::lookup 1.08 + Catmandu::Fix::lookup_in_store 1.08 + Catmandu::Fix::move 1.08 + Catmandu::Fix::move_field 1.08 + Catmandu::Fix::nothing 1.08 + Catmandu::Fix::parse_text 1.08 + Catmandu::Fix::paste 1.08 + Catmandu::Fix::perlcode 1.08 + Catmandu::Fix::prepend 1.08 + Catmandu::Fix::random 1.08 + Catmandu::Fix::reject 1.08 + Catmandu::Fix::remove 1.08 + Catmandu::Fix::remove_field 1.08 + Catmandu::Fix::rename 1.08 + Catmandu::Fix::replace_all 1.08 + Catmandu::Fix::retain 1.08 + Catmandu::Fix::retain_field 1.08 + Catmandu::Fix::reverse 1.08 + Catmandu::Fix::search_in_store undef + Catmandu::Fix::set 1.08 + Catmandu::Fix::set_array 1.08 + Catmandu::Fix::set_field 1.08 + Catmandu::Fix::set_hash 1.08 + Catmandu::Fix::sleep 1.08 + Catmandu::Fix::sort 1.08 + Catmandu::Fix::sort_field 1.08 + Catmandu::Fix::split 1.08 + Catmandu::Fix::split_field 1.08 + Catmandu::Fix::string 1.08 + Catmandu::Fix::substring 1.08 + Catmandu::Fix::sum 1.08 + Catmandu::Fix::to_json 1.08 + Catmandu::Fix::trim 1.08 + Catmandu::Fix::uniq 1.08 + Catmandu::Fix::upcase 1.08 + Catmandu::Fix::uri_decode 1.08 + Catmandu::Fix::uri_encode 1.08 + Catmandu::Fix::vacuum 1.08 + Catmandu::FixError 1.08 + Catmandu::FixParseError 1.08 + Catmandu::Fixable 1.08 + Catmandu::HTTPError 1.08 + Catmandu::Hits 1.08 + Catmandu::IdGenerator 1.08 + Catmandu::IdGenerator::Mock 1.08 + Catmandu::IdGenerator::UUID 1.08 + Catmandu::Importer 1.08 + Catmandu::Importer::CSV 1.08 + Catmandu::Importer::DKVP 1.08 + Catmandu::Importer::JSON 1.08 + Catmandu::Importer::Mock 1.08 + Catmandu::Importer::Modules 1.08 + Catmandu::Importer::Multi 1.08 + Catmandu::Importer::Null 1.08 + Catmandu::Importer::TSV 1.08 + Catmandu::Importer::Text 1.08 + Catmandu::Importer::YAML 1.08 + Catmandu::Interactive 1.08 + Catmandu::Iterable 1.08 + Catmandu::Iterator 1.08 + Catmandu::Logger 1.08 + Catmandu::MultiIterator 1.08 + Catmandu::NoSuchFixPackage 1.08 + Catmandu::NoSuchPackage 1.08 + Catmandu::NotImplemented 1.08 + Catmandu::Paged 1.08 + Catmandu::Pluggable 1.08 + Catmandu::Plugin::Datestamps 1.08 + Catmandu::Plugin::Readonly 1.08 + Catmandu::Plugin::SideCar 1.08 + Catmandu::Plugin::Versioning 1.08 + Catmandu::Sane 1.08 + Catmandu::Searchable 1.08 + Catmandu::Serializer 1.08 + Catmandu::Serializer::json 1.08 + Catmandu::Store 1.08 + Catmandu::Store::File::Memory 1.08 + Catmandu::Store::File::Memory::Bag 1.08 + Catmandu::Store::File::Memory::Index 1.08 + Catmandu::Store::File::Multi 1.08 + Catmandu::Store::File::Multi::Bag 1.08 + Catmandu::Store::File::Multi::Index 1.08 + Catmandu::Store::File::Simple 1.08 + Catmandu::Store::File::Simple::Bag 1.08 + Catmandu::Store::File::Simple::Index 1.08 + Catmandu::Store::Hash 1.08 + Catmandu::Store::Hash::Bag 1.08 + Catmandu::Store::Multi 1.08 + Catmandu::Store::Multi::Bag 1.08 + Catmandu::TabularExporter 1.08 + Catmandu::Transactional 1.08 + Catmandu::Util 1.08 + Catmandu::Validator 1.08 + Catmandu::Validator::Simple 1.08 + requirements: + Any::URI::Escape 0 + App::Cmd 0.33 + CGI::Expand 2.02 + Clone 0.31 + Config::Onion 1.004 + Cpanel::JSON::XS 3.0213 + Data::Compare 1.22 + Data::UUID 1.217 + Data::Util 0.66 + Hash::Merge::Simple 0 + IO::Handle::Util 0.01 + LWP::UserAgent 0 + List::MoreUtils 0.33 + List::MoreUtils::XS 0 + Log::Any 0 + Log::Any::Adapter 0 + MIME::Types 0 + Module::Build 0.28 + Module::Info 0 + Moo 1.004006 + MooX::Aliases 0.001006 + Parser::MGC 0.15 + Path::Iterator::Rule 0 + Path::Tiny 0 + String::CamelCase 0 + Sub::Exporter 0.982 + Sub::Quote 0 + Text::CSV 1.21 + Text::Hogan::Compiler 1.02 + Throwable 0.200004 + Time::HiRes 0 + Try::Tiny::ByClass 0.01 + URI 0 + URI::Template 0.22 + Unicode::Normalize 0 + YAML::XS 0.41 + asa 0 + namespace::clean 0.24 + perl v5.10.1 + Class-Data-Inheritable-0.08 + pathname: T/TM/TMTM/Class-Data-Inheritable-0.08.tar.gz + provides: + Class::Data::Inheritable 0.08 + requirements: + ExtUtils::MakeMaker 0 + Class-Load-0.24 + pathname: E/ET/ETHER/Class-Load-0.24.tar.gz + provides: + Class::Load 0.24 + Class::Load::PP 0.24 + requirements: + Carp 0 + Data::OptList 0 + Exporter 0 + ExtUtils::MakeMaker 0 + Module::Implementation 0.04 + Module::Runtime 0.012 + Package::Stash 0.14 + Scalar::Util 0 + Try::Tiny 0 + base 0 + perl 5.006 + strict 0 + warnings 0 + Class-Method-Modifiers-2.12 + pathname: E/ET/ETHER/Class-Method-Modifiers-2.12.tar.gz + provides: + Class::Method::Modifiers 2.12 + requirements: + B 0 + Carp 0 + Exporter 0 + ExtUtils::MakeMaker 0 + base 0 + perl 5.006 + strict 0 + warnings 0 + Clone-0.39 + pathname: G/GA/GARU/Clone-0.39.tar.gz + provides: + Clone 0.39 + requirements: + ExtUtils::MakeMaker 0 + Test::More 0 + Config-Any-0.32 + pathname: H/HA/HAARG/Config-Any-0.32.tar.gz + provides: + Config::Any 0.32 + Config::Any::Base undef + Config::Any::General undef + Config::Any::INI undef + Config::Any::JSON undef + Config::Any::Perl undef + Config::Any::XML undef + Config::Any::YAML undef + requirements: + Module::Pluggable::Object 3.6 + Config-Onion-1.007 + pathname: D/DS/DSHEROH/Config-Onion-1.007.tar.gz + provides: + Config::Onion 1.007 + Config::Onion::Simple 1.004 + requirements: + Config::Any 0 + Exporter 0 + ExtUtils::MakeMaker 0 + Hash::Merge::Simple 0 + Moo 0 + base 0 + strict 0 + warnings 0 + Cpanel-JSON-XS-4.01 + pathname: R/RU/RURBAN/Cpanel-JSON-XS-4.01.tar.gz + provides: + Cpanel::JSON::XS 4.01 + Cpanel::JSON::XS::Type undef + requirements: + ExtUtils::MakeMaker 0 + Pod::Text 2.08 + DBI-1.640 + pathname: T/TI/TIMB/DBI-1.640.tar.gz + provides: + Bundle::DBI 12.008696 + DBD::DBM 0.08 + DBD::DBM::Statement 0.08 + DBD::DBM::Table 0.08 + DBD::DBM::db 0.08 + DBD::DBM::dr 0.08 + DBD::DBM::st 0.08 + DBD::ExampleP 12.014311 + DBD::ExampleP::db 12.014311 + DBD::ExampleP::dr 12.014311 + DBD::ExampleP::st 12.014311 + DBD::File 0.44 + DBD::File::DataSource::File 0.44 + DBD::File::DataSource::Stream 0.44 + DBD::File::Statement 0.44 + DBD::File::Table 0.44 + DBD::File::TableSource::FileSystem 0.44 + DBD::File::db 0.44 + DBD::File::dr 0.44 + DBD::File::st 0.44 + DBD::Gofer 0.015327 + DBD::Gofer::Policy::Base 0.010088 + DBD::Gofer::Policy::classic 0.010088 + DBD::Gofer::Policy::pedantic 0.010088 + DBD::Gofer::Policy::rush 0.010088 + DBD::Gofer::Transport::Base 0.014121 + DBD::Gofer::Transport::corostream undef + DBD::Gofer::Transport::null 0.010088 + DBD::Gofer::Transport::pipeone 0.010088 + DBD::Gofer::Transport::stream 0.014599 + DBD::Gofer::db 0.015327 + DBD::Gofer::dr 0.015327 + DBD::Gofer::st 0.015327 + DBD::Mem 0.001 + DBD::Mem::DataSource 0.001 + DBD::Mem::Statement 0.001 + DBD::Mem::Table 0.001 + DBD::Mem::db 0.001 + DBD::Mem::dr 0.001 + DBD::Mem::st 0.001 + DBD::NullP 12.014715 + DBD::NullP::db 12.014715 + DBD::NullP::dr 12.014715 + DBD::NullP::st 12.014715 + DBD::Proxy 0.2004 + DBD::Proxy::RPC::PlClient 0.2004 + DBD::Proxy::db 0.2004 + DBD::Proxy::dr 0.2004 + DBD::Proxy::st 0.2004 + DBD::Sponge 12.010003 + DBD::Sponge::db 12.010003 + DBD::Sponge::dr 12.010003 + DBD::Sponge::st 12.010003 + DBDI 12.015129 + DBI 1.640 + DBI::Const::GetInfo::ANSI 2.008697 + DBI::Const::GetInfo::ODBC 2.011374 + DBI::Const::GetInfoReturn 2.008697 + DBI::Const::GetInfoType 2.008697 + DBI::DBD 12.015129 + DBI::DBD::Metadata 2.014214 + DBI::DBD::SqlEngine 0.06 + DBI::DBD::SqlEngine::DataSource 0.06 + DBI::DBD::SqlEngine::Statement 0.06 + DBI::DBD::SqlEngine::Table 0.06 + DBI::DBD::SqlEngine::TableSource 0.06 + DBI::DBD::SqlEngine::TieMeta 0.06 + DBI::DBD::SqlEngine::TieTables 0.06 + DBI::DBD::SqlEngine::db 0.06 + DBI::DBD::SqlEngine::dr 0.06 + DBI::DBD::SqlEngine::st 0.06 + DBI::Gofer::Execute 0.014283 + DBI::Gofer::Request 0.012537 + DBI::Gofer::Response 0.011566 + DBI::Gofer::Serializer::Base 0.009950 + DBI::Gofer::Serializer::DataDumper 0.009950 + DBI::Gofer::Serializer::Storable 0.015586 + DBI::Gofer::Transport::Base 0.012537 + DBI::Gofer::Transport::pipeone 0.012537 + DBI::Gofer::Transport::stream 0.012537 + DBI::Profile 2.015065 + DBI::ProfileData 2.010008 + DBI::ProfileDumper 2.015325 + DBI::ProfileDumper::Apache 2.014121 + DBI::ProfileSubs 0.009396 + DBI::ProxyServer 0.3005 + DBI::ProxyServer::db 0.3005 + DBI::ProxyServer::dr 0.3005 + DBI::ProxyServer::st 0.3005 + DBI::SQL::Nano 1.015544 + DBI::SQL::Nano::Statement_ 1.015544 + DBI::SQL::Nano::Table_ 1.015544 + DBI::Util::CacheMemory 0.010315 + DBI::Util::_accessor 0.009479 + DBI::common 1.640 + requirements: + ExtUtils::MakeMaker 6.48 + Storable 2.16 + Test::Simple 0.90 + perl 5.008 + Data-Compare-1.25 + pathname: D/DC/DCANTRELL/Data-Compare-1.25.tar.gz + provides: + Data::Compare 1.25 + Data::Compare::Plugins::Scalar::Properties 1 + requirements: + ExtUtils::MakeMaker 0 + File::Find::Rule 0.1 + Scalar::Util 0 + Data-OptList-0.110 + pathname: R/RJ/RJBS/Data-OptList-0.110.tar.gz + provides: + Data::OptList 0.110 + requirements: + ExtUtils::MakeMaker 0 + List::Util 0 + Params::Util 0 + Sub::Install 0.921 + strict 0 + warnings 0 + Data-UUID-1.221 + pathname: R/RJ/RJBS/Data-UUID-1.221.tar.gz + provides: + Data::UUID 1.221 + requirements: + Digest::MD5 0 + ExtUtils::MakeMaker 0 + Data-Util-0.66 + pathname: S/SY/SYOHEX/Data-Util-0.66.tar.gz + provides: + Data::Util 0.66 + Data::Util::Error undef + Data::Util::PurePerl undef + requirements: + Devel::PPPort 3.19 + ExtUtils::CBuilder 0 + ExtUtils::MakeMaker 6.59 + ExtUtils::ParseXS 3.18 + Hash::Util::FieldHash::Compat 0 + Module::Build 0.4005 + Module::Build::XSUtil 0.18 + Scope::Guard 0 + Test::Exception 0.27 + Test::More 0.62 + XSLoader 0.02 + perl 5.010 + Devel-CheckCompiler-0.07 + pathname: S/SY/SYOHEX/Devel-CheckCompiler-0.07.tar.gz + provides: + Devel::AssertC99 undef + Devel::CheckCompiler 0.07 + requirements: + Exporter 0 + ExtUtils::CBuilder 0 + File::Temp 0 + Module::Build::Tiny 0.035 + Test::More 0.98 + parent 0 + perl 5.008001 + Devel-GlobalDestruction-0.14 + pathname: H/HA/HAARG/Devel-GlobalDestruction-0.14.tar.gz + provides: + Devel::GlobalDestruction 0.14 + requirements: + ExtUtils::MakeMaker 0 + Sub::Exporter::Progressive 0.001011 + perl 5.006 + Devel-StackTrace-2.03 + pathname: D/DR/DROLSKY/Devel-StackTrace-2.03.tar.gz + provides: + Devel::StackTrace 2.03 + Devel::StackTrace::Frame 2.03 + requirements: + ExtUtils::MakeMaker 0 + File::Spec 0 + Scalar::Util 0 + overload 0 + perl 5.006 + strict 0 + warnings 0 + Dispatch-Class-0.02 + pathname: M/MA/MAUKE/Dispatch-Class-0.02.tar.gz + provides: + Dispatch::Class 0.02 + requirements: + Exporter::Tiny 0 + ExtUtils::MakeMaker 6.48 + Scalar::Util 0 + perl 5.006000 + strict 0 + warnings 0 + Dist-CheckConflicts-0.11 + pathname: D/DO/DOY/Dist-CheckConflicts-0.11.tar.gz + provides: + Dist::CheckConflicts 0.11 + requirements: + Carp 0 + Exporter 0 + ExtUtils::MakeMaker 6.30 + Module::Runtime 0.009 + base 0 + strict 0 + warnings 0 + Encode-Locale-1.05 + pathname: G/GA/GAAS/Encode-Locale-1.05.tar.gz + provides: + Encode::Locale 1.05 + requirements: + Encode 2 + Encode::Alias 0 + ExtUtils::MakeMaker 0 + perl 5.008 + Exception-Class-1.44 + pathname: D/DR/DROLSKY/Exception-Class-1.44.tar.gz + provides: + Exception::Class 1.44 + Exception::Class::Base 1.44 + requirements: + Class::Data::Inheritable 0.02 + Devel::StackTrace 2.00 + ExtUtils::MakeMaker 0 + Scalar::Util 0 + base 0 + overload 0 + perl 5.008001 + strict 0 + warnings 0 + Exporter-Tiny-1.000000 + pathname: T/TO/TOBYINK/Exporter-Tiny-1.000000.tar.gz + provides: + Exporter::Shiny 1.000000 + Exporter::Tiny 1.000000 + requirements: + ExtUtils::MakeMaker 6.17 + perl 5.006001 + ExtUtils-Config-0.008 + pathname: L/LE/LEONT/ExtUtils-Config-0.008.tar.gz + provides: + ExtUtils::Config 0.008 + requirements: + Data::Dumper 0 + ExtUtils::MakeMaker 6.30 + strict 0 + warnings 0 + ExtUtils-Depends-0.405 + pathname: X/XA/XAOC/ExtUtils-Depends-0.405.tar.gz + provides: + ExtUtils::Depends 0.405 + requirements: + Data::Dumper 0 + ExtUtils::MakeMaker 0 + File::Spec 0 + IO::File 0 + perl 5.006 + ExtUtils-Helpers-0.026 + pathname: L/LE/LEONT/ExtUtils-Helpers-0.026.tar.gz + provides: + ExtUtils::Helpers 0.026 + ExtUtils::Helpers::Unix 0.026 + ExtUtils::Helpers::VMS 0.026 + ExtUtils::Helpers::Windows 0.026 + requirements: + Carp 0 + Exporter 5.57 + ExtUtils::MakeMaker 0 + File::Basename 0 + File::Copy 0 + File::Spec::Functions 0 + Text::ParseWords 3.24 + perl 5.006 + strict 0 + warnings 0 + ExtUtils-InstallPaths-0.011 + pathname: L/LE/LEONT/ExtUtils-InstallPaths-0.011.tar.gz + provides: + ExtUtils::InstallPaths 0.011 + requirements: + Carp 0 + ExtUtils::Config 0.002 + ExtUtils::MakeMaker 0 + File::Spec 0 + perl 5.006 + strict 0 + warnings 0 + File-Find-Rule-0.34 + pathname: R/RC/RCLAMP/File-Find-Rule-0.34.tar.gz + provides: + File::Find::Rule 0.34 + File::Find::Rule::Test::ATeam undef + requirements: + ExtUtils::MakeMaker 0 + File::Find 0 + File::Spec 0 + Number::Compare 0 + Test::More 0 + Text::Glob 0.07 + File-Listing-6.04 + pathname: G/GA/GAAS/File-Listing-6.04.tar.gz + provides: + File::Listing 6.04 + File::Listing::apache 6.04 + File::Listing::dosftp 6.04 + File::Listing::netware 6.04 + File::Listing::unix 6.04 + File::Listing::vms 6.04 + requirements: + ExtUtils::MakeMaker 0 + HTTP::Date 6 + perl 5.006002 + File-Slurp-Tiny-0.004 + pathname: L/LE/LEONT/File-Slurp-Tiny-0.004.tar.gz + provides: + File::Slurp::Tiny 0.004 + requirements: + Carp 0 + Exporter 5.57 + ExtUtils::MakeMaker 0 + File::Spec::Functions 0 + FileHandle 0 + perl 5.008001 + strict 0 + warnings 0 + Getopt-Long-Descriptive-0.101 + pathname: R/RJ/RJBS/Getopt-Long-Descriptive-0.101.tar.gz + provides: + Getopt::Long::Descriptive 0.101 + Getopt::Long::Descriptive::Opts 0.101 + Getopt::Long::Descriptive::Usage 0.101 + requirements: + Carp 0 + ExtUtils::MakeMaker 0 + File::Basename 0 + Getopt::Long 2.33 + List::Util 0 + Params::Validate 0.97 + Scalar::Util 0 + Sub::Exporter 0.972 + Sub::Exporter::Util 0 + overload 0 + strict 0 + warnings 0 + HTML-Parser-3.72 + pathname: G/GA/GAAS/HTML-Parser-3.72.tar.gz + provides: + HTML::Entities 3.69 + HTML::Filter 3.72 + HTML::HeadParser 3.71 + HTML::LinkExtor 3.69 + HTML::Parser 3.72 + HTML::PullParser 3.57 + HTML::TokeParser 3.69 + requirements: + ExtUtils::MakeMaker 0 + HTML::Tagset 3 + XSLoader 0 + perl 5.008 + HTML-Tagset-3.20 + pathname: P/PE/PETDANCE/HTML-Tagset-3.20.tar.gz + provides: + HTML::Tagset 3.20 + requirements: + ExtUtils::MakeMaker 0 + HTTP-Cookies-6.04 + pathname: O/OA/OALDERS/HTTP-Cookies-6.04.tar.gz + provides: + HTTP::Cookies 6.04 + HTTP::Cookies::Microsoft 6.04 + HTTP::Cookies::Netscape 6.04 + requirements: + Carp 0 + ExtUtils::MakeMaker 0 + HTTP::Date 6 + HTTP::Headers::Util 6 + HTTP::Request 0 + Time::Local 0 + locale 0 + perl 5.008001 + strict 0 + vars 0 + HTTP-Daemon-6.01 + pathname: G/GA/GAAS/HTTP-Daemon-6.01.tar.gz + provides: + HTTP::Daemon 6.01 + HTTP::Daemon::ClientConn 6.01 + requirements: + ExtUtils::MakeMaker 0 + HTTP::Date 6 + HTTP::Request 6 + HTTP::Response 6 + HTTP::Status 6 + IO::Socket 0 + LWP::MediaTypes 6 + Sys::Hostname 0 + perl 5.008001 + HTTP-Date-6.02 + pathname: G/GA/GAAS/HTTP-Date-6.02.tar.gz + provides: + HTTP::Date 6.02 + requirements: + ExtUtils::MakeMaker 0 + Time::Local 0 + perl 5.006002 + HTTP-Message-6.14 + pathname: O/OA/OALDERS/HTTP-Message-6.14.tar.gz + provides: + HTTP::Config 6.14 + HTTP::Headers 6.14 + HTTP::Headers::Auth 6.14 + HTTP::Headers::ETag 6.14 + HTTP::Headers::Util 6.14 + HTTP::Message 6.14 + HTTP::Request 6.14 + HTTP::Request::Common 6.14 + HTTP::Response 6.14 + HTTP::Status 6.14 + requirements: + Carp 0 + Compress::Raw::Zlib 0 + Encode 2.21 + Encode::Locale 1 + Exporter 5.57 + ExtUtils::MakeMaker 0 + HTTP::Date 6 + IO::Compress::Bzip2 2.021 + IO::Compress::Deflate 0 + IO::Compress::Gzip 0 + IO::HTML 0 + IO::Uncompress::Bunzip2 2.021 + IO::Uncompress::Gunzip 0 + IO::Uncompress::Inflate 0 + IO::Uncompress::RawInflate 0 + LWP::MediaTypes 6 + MIME::Base64 2.1 + MIME::QuotedPrint 0 + Storable 0 + URI 1.10 + base 0 + perl 5.008001 + strict 0 + warnings 0 + HTTP-Negotiate-6.01 + pathname: G/GA/GAAS/HTTP-Negotiate-6.01.tar.gz + provides: + HTTP::Negotiate 6.01 + requirements: + ExtUtils::MakeMaker 0 + HTTP::Headers 6 + perl 5.008001 + Hash-Merge-Simple-0.051 + pathname: R/RO/ROKR/Hash-Merge-Simple-0.051.tar.gz + provides: + Hash::Merge::Simple 0.051 + requirements: + Clone 0 + ExtUtils::MakeMaker 6.31 + Storable 0 + Test::Most 0 + Hash-Util-FieldHash-Compat-0.11 + pathname: E/ET/ETHER/Hash-Util-FieldHash-Compat-0.11.tar.gz + provides: + Hash::Util::FieldHash::Compat 0.11 + Hash::Util::FieldHash::Compat::Heavy 0.11 + requirements: + Exporter 0 + ExtUtils::MakeMaker 0 + constant 0 + parent 0 + perl 5.006 + strict 0 + warnings 0 + IO-HTML-1.001 + pathname: C/CJ/CJM/IO-HTML-1.001.tar.gz + provides: + IO::HTML 1.001 + requirements: + Carp 0 + Encode 2.10 + Exporter 5.57 + ExtUtils::MakeMaker 6.30 + IO-Handle-Util-0.01 + pathname: N/NU/NUFFIN/IO-Handle-Util-0.01.tar.gz + provides: + IO::Handle::Iterator undef + IO::Handle::Iterator::Buffered undef + IO::Handle::Prototype undef + IO::Handle::Prototype::Fallback undef + IO::Handle::Util 0.01 + IO::Handle::Util::Overloading undef + IO::Handle::Util::Tie undef + requirements: + ExtUtils::MakeMaker 0 + IO::String 0 + Scalar::Util 0 + Sub::Exporter 0 + Test::More 0.88 + Test::use::ok 0 + asa 0 + parent 0 + IO-String-1.08 + pathname: G/GA/GAAS/IO-String-1.08.tar.gz + provides: + IO::String 1.08 + requirements: + ExtUtils::MakeMaker 0 + IO-TieCombine-1.005 + pathname: R/RJ/RJBS/IO-TieCombine-1.005.tar.gz + provides: + IO::TieCombine 1.005 + IO::TieCombine::Handle 1.005 + IO::TieCombine::Scalar 1.005 + requirements: + Carp 0 + ExtUtils::MakeMaker 0 + Symbol 0 + strict 0 + warnings 0 + LWP-MediaTypes-6.02 + pathname: G/GA/GAAS/LWP-MediaTypes-6.02.tar.gz + provides: + LWP::MediaTypes 6.02 + requirements: + ExtUtils::MakeMaker 0 + perl 5.006002 + Lexical-SealRequireHints-0.011 + pathname: Z/ZE/ZEFRAM/Lexical-SealRequireHints-0.011.tar.gz + provides: + Lexical::SealRequireHints 0.011 + requirements: + Module::Build 0 + Test::More 0.41 + perl 5.006 + strict 0 + warnings 0 + List-MoreUtils-0.428 + pathname: R/RE/REHSACK/List-MoreUtils-0.428.tar.gz + provides: + List::MoreUtils 0.428 + List::MoreUtils::PP 0.428 + requirements: + Exporter::Tiny 0.038 + ExtUtils::MakeMaker 0 + List::MoreUtils::XS 0.426 + List-MoreUtils-XS-0.428 + pathname: R/RE/REHSACK/List-MoreUtils-XS-0.428.tar.gz + provides: + List::MoreUtils::XS 0.428 + requirements: + Carp 0 + ExtUtils::MakeMaker 0 + File::Basename 0 + File::Copy 0 + File::Path 0 + File::Spec 0 + IPC::Cmd 0 + XSLoader 0.22 + base 0 + Log-Any-1.705 + pathname: P/PR/PREACTION/Log-Any-1.705.tar.gz + provides: + Log::Any 1.705 + Log::Any::Adapter 1.705 + Log::Any::Adapter::Base 1.705 + Log::Any::Adapter::File 1.705 + Log::Any::Adapter::Null 1.705 + Log::Any::Adapter::Stderr 1.705 + Log::Any::Adapter::Stdout 1.705 + Log::Any::Adapter::Syslog 1.705 + Log::Any::Adapter::Test 1.705 + Log::Any::Adapter::Util 1.705 + Log::Any::Manager 1.705 + Log::Any::Proxy 1.705 + Log::Any::Proxy::Null 1.705 + Log::Any::Proxy::Test 1.705 + Log::Any::Test 1.705 + requirements: + B 0 + Carp 0 + Data::Dumper 0 + Exporter 0 + ExtUtils::MakeMaker 0 + Fcntl 0 + File::Basename 0 + FindBin 0 + IO::File 0 + Storable 0 + Sys::Syslog 0 + Test::Builder 0 + constant 0 + strict 0 + warnings 0 + MIME-Types-2.17 + pathname: M/MA/MARKOV/MIME-Types-2.17.tar.gz + provides: + MIME::Type 2.17 + MIME::Types 2.17 + MojoX::MIME::Types 2.17 + requirements: + ExtUtils::MakeMaker 0 + File::Basename 0 + File::Spec 0 + List::Util 0 + Test::More 0.47 + Module-Build-0.4224 + pathname: L/LE/LEONT/Module-Build-0.4224.tar.gz + provides: + Module::Build 0.4224 + Module::Build::Base 0.4224 + Module::Build::Compat 0.4224 + Module::Build::Config 0.4224 + Module::Build::Cookbook 0.4224 + Module::Build::Dumper 0.4224 + Module::Build::Notes 0.4224 + Module::Build::PPMMaker 0.4224 + Module::Build::Platform::Default 0.4224 + Module::Build::Platform::MacOS 0.4224 + Module::Build::Platform::Unix 0.4224 + Module::Build::Platform::VMS 0.4224 + Module::Build::Platform::VOS 0.4224 + Module::Build::Platform::Windows 0.4224 + Module::Build::Platform::aix 0.4224 + Module::Build::Platform::cygwin 0.4224 + Module::Build::Platform::darwin 0.4224 + Module::Build::Platform::os2 0.4224 + Module::Build::PodParser 0.4224 + requirements: + CPAN::Meta 2.142060 + CPAN::Meta::YAML 0.003 + Cwd 0 + Data::Dumper 0 + ExtUtils::CBuilder 0.27 + ExtUtils::Install 0 + ExtUtils::Manifest 0 + ExtUtils::Mkbootstrap 0 + ExtUtils::ParseXS 2.21 + File::Basename 0 + File::Compare 0 + File::Copy 0 + File::Find 0 + File::Path 0 + File::Spec 0.82 + File::Temp 0.15 + Getopt::Long 0 + Module::Metadata 1.000002 + Parse::CPAN::Meta 1.4401 + Perl::OSType 1 + Pod::Man 2.17 + TAP::Harness 3.29 + Test::More 0.49 + Text::Abbrev 0 + Text::ParseWords 0 + perl 5.006001 + version 0.87 + Module-Build-Tiny-0.039 + pathname: L/LE/LEONT/Module-Build-Tiny-0.039.tar.gz + provides: + Module::Build::Tiny 0.039 + requirements: + CPAN::Meta 0 + DynaLoader 0 + Exporter 5.57 + ExtUtils::CBuilder 0 + ExtUtils::Config 0.003 + ExtUtils::Helpers 0.020 + ExtUtils::Install 0 + ExtUtils::InstallPaths 0.002 + ExtUtils::ParseXS 0 + File::Basename 0 + File::Find 0 + File::Path 0 + File::Spec::Functions 0 + Getopt::Long 2.36 + JSON::PP 2 + Pod::Man 0 + TAP::Harness::Env 0 + perl 5.006 + strict 0 + warnings 0 + Module-Build-XSUtil-0.18 + pathname: H/HI/HIDEAKIO/Module-Build-XSUtil-0.18.tar.gz + provides: + Module::Build::XSUtil 0.18 + requirements: + Devel::CheckCompiler 0 + Devel::PPPort 0 + Exporter 0 + ExtUtils::CBuilder 0 + File::Basename 0 + File::Path 0 + Module::Build 0.4005 + XSLoader 0 + parent 0 + perl 5.008005 + Module-Implementation-0.09 + pathname: D/DR/DROLSKY/Module-Implementation-0.09.tar.gz + provides: + Module::Implementation 0.09 + requirements: + Carp 0 + ExtUtils::MakeMaker 0 + Module::Runtime 0.012 + Try::Tiny 0 + strict 0 + warnings 0 + Module-Info-0.37 + pathname: N/NE/NEILB/Module-Info-0.37.tar.gz + provides: + B::Module::Info 0.37 + Module::Info 0.370 + Module::Info::Safe 0.370 + Module::Info::Unsafe 0.370 + Module::Info::_version 0.370 + requirements: + B 0 + B::Utils 0.27 + Carp 0 + ExtUtils::MakeMaker 0 + File::Spec 0.8 + perl 5.006 + strict 0 + Module-Pluggable-5.2 + pathname: S/SI/SIMONW/Module-Pluggable-5.2.tar.gz + provides: + Devel::InnerPackage 0.4 + Module::Pluggable 5.2 + Module::Pluggable::Object 5.2 + requirements: + Exporter 5.57 + ExtUtils::MakeMaker 0 + File::Basename 0 + File::Find 0 + File::Spec 3.00 + File::Spec::Functions 0 + if 0 + perl 5.00503 + strict 0 + Module-Runtime-0.016 + pathname: Z/ZE/ZEFRAM/Module-Runtime-0.016.tar.gz + provides: + Module::Runtime 0.016 + requirements: + Module::Build 0 + Test::More 0.41 + perl 5.006 + strict 0 + warnings 0 + Moo-2.003004 + pathname: H/HA/HAARG/Moo-2.003004.tar.gz + provides: + Method::Generate::Accessor undef + Method::Generate::BuildAll undef + Method::Generate::Constructor undef + Method::Generate::DemolishAll undef + Moo 2.003004 + Moo::HandleMoose undef + Moo::HandleMoose::FakeConstructor undef + Moo::HandleMoose::FakeMetaClass undef + Moo::HandleMoose::_TypeMap undef + Moo::Object undef + Moo::Role 2.003004 + Moo::_Utils undef + Moo::_mro undef + Moo::_strictures undef + Moo::sification undef + oo undef + requirements: + Class::Method::Modifiers 1.1 + Devel::GlobalDestruction 0.11 + Exporter 5.57 + ExtUtils::MakeMaker 0 + Module::Runtime 0.014 + Role::Tiny 2.000004 + Scalar::Util 0 + Sub::Defer 2.003001 + Sub::Quote 2.003001 + perl 5.006 + MooX-Aliases-0.001006 + pathname: H/HA/HAARG/MooX-Aliases-0.001006.tar.gz + provides: + MooX::Aliases 0.001006 + requirements: + Class::Method::Modifiers 1.05 + Moo 1.001 + perl 5.006 + strictures 1 + Net-HTTP-6.17 + pathname: O/OA/OALDERS/Net-HTTP-6.17.tar.gz + provides: + Net::HTTP 6.17 + Net::HTTP::Methods 6.17 + Net::HTTP::NB 6.17 + Net::HTTPS 6.17 + requirements: + Carp 0 + Compress::Raw::Zlib 0 + ExtUtils::MakeMaker 0 + IO::Socket::INET 0 + IO::Uncompress::Gunzip 0 + URI 0 + base 0 + perl 5.006002 + strict 0 + vars 0 + warnings 0 + Number-Compare-0.03 + pathname: R/RC/RCLAMP/Number-Compare-0.03.tar.gz + provides: + Number::Compare 0.03 + requirements: + ExtUtils::MakeMaker 0 + Test::More 0 + Package-Stash-0.37 + pathname: D/DO/DOY/Package-Stash-0.37.tar.gz + provides: + Package::Stash 0.37 + Package::Stash::PP 0.37 + requirements: + B 0 + Carp 0 + Config 0 + Dist::CheckConflicts 0.02 + ExtUtils::MakeMaker 0 + File::Spec 0 + Getopt::Long 0 + Module::Implementation 0.06 + Package::Stash::XS 0.26 + Scalar::Util 0 + Symbol 0 + Text::ParseWords 0 + constant 0 + strict 0 + warnings 0 + Package-Stash-XS-0.28 + pathname: D/DO/DOY/Package-Stash-XS-0.28.tar.gz + provides: + Package::Stash::XS 0.28 + requirements: + ExtUtils::MakeMaker 6.30 + XSLoader 0 + strict 0 + warnings 0 + Params-Util-1.07 + pathname: A/AD/ADAMK/Params-Util-1.07.tar.gz + provides: + Params::Util 1.07 + requirements: + ExtUtils::CBuilder 0.27 + ExtUtils::MakeMaker 6.52 + File::Spec 0.80 + Scalar::Util 1.18 + Test::More 0.42 + perl 5.00503 + Params-Validate-1.29 + pathname: D/DR/DROLSKY/Params-Validate-1.29.tar.gz + provides: + Params::Validate 1.29 + Params::Validate::Constants 1.29 + Params::Validate::PP 1.29 + Params::Validate::XS 1.29 + requirements: + Carp 0 + Exporter 0 + ExtUtils::CBuilder 0 + Module::Build 0.28 + Module::Implementation 0 + Scalar::Util 1.10 + XSLoader 0 + perl 5.008001 + strict 0 + vars 0 + warnings 0 + Parser-MGC-0.16 + pathname: P/PE/PEVANS/Parser-MGC-0.16.tar.gz + provides: + Parser::MGC 0.16 + Parser::MGC::Examples::EvaluateExpression undef + requirements: + File::Slurp::Tiny 0 + Module::Build 0.4004 + Scalar::Util 0 + Path-Iterator-Rule-1.012 + pathname: D/DA/DAGOLDEN/Path-Iterator-Rule-1.012.tar.gz + provides: + PIR 1.012 + Path::Iterator::Rule 1.012 + requirements: + Carp 0 + ExtUtils::MakeMaker 6.17 + File::Basename 0 + File::Spec 0 + List::Util 0 + Number::Compare 0.02 + Scalar::Util 0 + Text::Glob 0 + Try::Tiny 0 + if 0 + perl 5.008001 + strict 0 + warnings 0 + warnings::register 0 + Path-Tiny-0.104 + pathname: D/DA/DAGOLDEN/Path-Tiny-0.104.tar.gz + provides: + Path::Tiny 0.104 + Path::Tiny::Error 0.104 + requirements: + Carp 0 + Cwd 0 + Digest 1.03 + Digest::SHA 5.45 + Exporter 5.57 + ExtUtils::MakeMaker 6.17 + Fcntl 0 + File::Copy 0 + File::Glob 0 + File::Path 2.07 + File::Spec 0.86 + File::Temp 0.19 + File::stat 0 + constant 0 + if 0 + overload 0 + perl 5.008001 + strict 0 + warnings 0 + Role-Tiny-2.000006 + pathname: H/HA/HAARG/Role-Tiny-2.000006.tar.gz + provides: + Role::Tiny 2.000006 + Role::Tiny::With 2.000006 + requirements: + Exporter 5.57 + perl 5.006 + Scope-Guard-0.21 + pathname: C/CH/CHOCOLATE/Scope-Guard-0.21.tar.gz + provides: + Scope::Guard 0.21 + requirements: + ExtUtils::MakeMaker 0 + Test::More 0 + perl 5.006001 + String-CamelCase-0.03 + pathname: H/HI/HIO/String-CamelCase-0.03.tar.gz + provides: + String::CamelCase undef + requirements: + ExtUtils::MakeMaker 0 + Test::More 0 + String-RewritePrefix-0.007 + pathname: R/RJ/RJBS/String-RewritePrefix-0.007.tar.gz + provides: + String::RewritePrefix 0.007 + requirements: + Carp 0 + ExtUtils::MakeMaker 6.30 + Sub::Exporter 0.972 + strict 0 + warnings 0 + Sub-Exporter-0.987 + pathname: R/RJ/RJBS/Sub-Exporter-0.987.tar.gz + provides: + Sub::Exporter 0.987 + Sub::Exporter::Util 0.987 + requirements: + Carp 0 + Data::OptList 0.100 + ExtUtils::MakeMaker 6.30 + Params::Util 0.14 + Sub::Install 0.92 + strict 0 + warnings 0 + Sub-Exporter-Progressive-0.001013 + pathname: F/FR/FREW/Sub-Exporter-Progressive-0.001013.tar.gz + provides: + Sub::Exporter::Progressive 0.001013 + requirements: + ExtUtils::MakeMaker 0 + Sub-Install-0.928 + pathname: R/RJ/RJBS/Sub-Install-0.928.tar.gz + provides: + Sub::Install 0.928 + requirements: + B 0 + Carp 0 + ExtUtils::MakeMaker 6.30 + Scalar::Util 0 + strict 0 + warnings 0 + Sub-Quote-2.005000 + pathname: H/HA/HAARG/Sub-Quote-2.005000.tar.gz + provides: + Sub::Defer 2.005000 + Sub::Quote 2.005000 + requirements: + ExtUtils::MakeMaker 0 + Scalar::Util 0 + perl 5.006 + Sub-Uplevel-0.2800 + pathname: D/DA/DAGOLDEN/Sub-Uplevel-0.2800.tar.gz + provides: + Sub::Uplevel 0.2800 + requirements: + Carp 0 + ExtUtils::MakeMaker 6.17 + constant 0 + perl 5.006 + strict 0 + warnings 0 + Task-Weaken-1.05 + pathname: E/ET/ETHER/Task-Weaken-1.05.tar.gz + provides: + Task::Weaken 1.05 + requirements: + Config 0 + ExtUtils::MakeMaker 0 + File::Spec 0 + Scalar::Util 1.14 + perl 5.006 + strict 0 + Test-Deep-1.127 + pathname: R/RJ/RJBS/Test-Deep-1.127.tar.gz + provides: + Test::Deep 1.127 + Test::Deep::All undef + Test::Deep::Any undef + Test::Deep::Array undef + Test::Deep::ArrayEach undef + Test::Deep::ArrayElementsOnly undef + Test::Deep::ArrayLength undef + Test::Deep::ArrayLengthOnly undef + Test::Deep::Blessed undef + Test::Deep::Boolean undef + Test::Deep::Cache undef + Test::Deep::Cache::Simple undef + Test::Deep::Class undef + Test::Deep::Cmp undef + Test::Deep::Code undef + Test::Deep::Hash undef + Test::Deep::HashEach undef + Test::Deep::HashElements undef + Test::Deep::HashKeys undef + Test::Deep::HashKeysOnly undef + Test::Deep::Ignore undef + Test::Deep::Isa undef + Test::Deep::ListMethods undef + Test::Deep::MM undef + Test::Deep::Methods undef + Test::Deep::NoTest undef + Test::Deep::None undef + Test::Deep::Number undef + Test::Deep::Obj undef + Test::Deep::Ref undef + Test::Deep::RefType undef + Test::Deep::Regexp undef + Test::Deep::RegexpMatches undef + Test::Deep::RegexpOnly undef + Test::Deep::RegexpRef undef + Test::Deep::RegexpRefOnly undef + Test::Deep::RegexpVersion undef + Test::Deep::ScalarRef undef + Test::Deep::ScalarRefOnly undef + Test::Deep::Set undef + Test::Deep::Shallow undef + Test::Deep::Stack undef + Test::Deep::String undef + Test::Deep::SubHash undef + Test::Deep::SubHashElements undef + Test::Deep::SubHashKeys undef + Test::Deep::SubHashKeysOnly undef + Test::Deep::SuperHash undef + Test::Deep::SuperHashElements undef + Test::Deep::SuperHashKeys undef + Test::Deep::SuperHashKeysOnly undef + requirements: + ExtUtils::MakeMaker 0 + List::Util 1.09 + Scalar::Util 1.09 + Test::Builder 0 + Test-Differences-0.64 + pathname: D/DC/DCANTRELL/Test-Differences-0.64.tar.gz + provides: + Test::Differences 0.64 + requirements: + Capture::Tiny 0.24 + Data::Dumper 2.126 + Test::More 0.88 + Text::Diff 0.35 + Test-Exception-0.43 + pathname: E/EX/EXODIST/Test-Exception-0.43.tar.gz + provides: + Test::Exception 0.43 + requirements: + Carp 0 + Exporter 0 + ExtUtils::MakeMaker 0 + Sub::Uplevel 0.18 + Test::Builder 0.7 + Test::Builder::Tester 1.07 + Test::Harness 2.03 + base 0 + perl 5.006001 + strict 0 + warnings 0 + Test-Most-0.35 + pathname: O/OV/OVID/Test-Most-0.35.tar.gz + provides: + Test::Most 0.35 + Test::Most::Exception 0.35 + requirements: + Exception::Class 1.14 + ExtUtils::MakeMaker 0 + Test::Deep 0.119 + Test::Differences 0.64 + Test::Exception 0.43 + Test::Harness 3.35 + Test::More 1.302047 + Test::Warn 0.30 + Test-Simple-1.302122 + pathname: E/EX/EXODIST/Test-Simple-1.302122.tar.gz + provides: + Test2 1.302122 + Test2::API 1.302122 + Test2::API::Breakage 1.302122 + Test2::API::Context 1.302122 + Test2::API::Instance 1.302122 + Test2::API::Stack 1.302122 + Test2::Event 1.302122 + Test2::Event::Bail 1.302122 + Test2::Event::Diag 1.302122 + Test2::Event::Encoding 1.302122 + Test2::Event::Exception 1.302122 + Test2::Event::Fail 1.302122 + Test2::Event::Generic 1.302122 + Test2::Event::Note 1.302122 + Test2::Event::Ok 1.302122 + Test2::Event::Pass 1.302122 + Test2::Event::Plan 1.302122 + Test2::Event::Skip 1.302122 + Test2::Event::Subtest 1.302122 + Test2::Event::TAP::Version 1.302122 + Test2::Event::Waiting 1.302122 + Test2::EventFacet 1.302122 + Test2::EventFacet::About 1.302122 + Test2::EventFacet::Amnesty 1.302122 + Test2::EventFacet::Assert 1.302122 + Test2::EventFacet::Control 1.302122 + Test2::EventFacet::Error 1.302122 + Test2::EventFacet::Info 1.302122 + Test2::EventFacet::Meta 1.302122 + Test2::EventFacet::Parent 1.302122 + Test2::EventFacet::Plan 1.302122 + Test2::EventFacet::Render 1.302122 + Test2::EventFacet::Trace 1.302122 + Test2::Formatter 1.302122 + Test2::Formatter::TAP 1.302122 + Test2::Hub 1.302122 + Test2::Hub::Interceptor 1.302122 + Test2::Hub::Interceptor::Terminator 1.302122 + Test2::Hub::Subtest 1.302122 + Test2::IPC 1.302122 + Test2::IPC::Driver 1.302122 + Test2::IPC::Driver::Files 1.302122 + Test2::Tools::Tiny 1.302122 + Test2::Util 1.302122 + Test2::Util::ExternalMeta 1.302122 + Test2::Util::Facets2Legacy 1.302122 + Test2::Util::HashBase 1.302122 + Test2::Util::Trace 1.302122 + Test::Builder 1.302122 + Test::Builder::Formatter 1.302122 + Test::Builder::IO::Scalar 2.114 + Test::Builder::Module 1.302122 + Test::Builder::Tester 1.302122 + Test::Builder::Tester::Color 1.302122 + Test::Builder::Tester::Tie 1.302122 + Test::Builder::TodoDiag 1.302122 + Test::More 1.302122 + Test::Simple 1.302122 + Test::Tester 1.302122 + Test::Tester::Capture 1.302122 + Test::Tester::CaptureRunner 1.302122 + Test::Tester::Delegate 1.302122 + Test::use::ok 1.302122 + ok 1.302122 + requirements: + ExtUtils::MakeMaker 0 + File::Spec 0 + File::Temp 0 + Scalar::Util 1.13 + Storable 0 + perl 5.006002 + utf8 0 + Test-Warn-0.32 + pathname: B/BI/BIGJ/Test-Warn-0.32.tar.gz + provides: + Test::Warn 0.32 + Test::Warn::Categorization 0.32 + requirements: + Carp 1.22 + ExtUtils::MakeMaker 0 + Sub::Uplevel 0.12 + Test::Builder 0.13 + Test::Builder::Tester 1.02 + perl 5.006 + Text-CSV-1.95 + pathname: I/IS/ISHIGAKI/Text-CSV-1.95.tar.gz + provides: + Text::CSV 1.95 + Text::CSV::ErrorDiag 1.95 + Text::CSV_PP 1.95 + requirements: + ExtUtils::MakeMaker 0 + IO::Handle 0 + Test::Harness 0 + Test::More 0.71 + Text-Diff-1.45 + pathname: N/NE/NEILB/Text-Diff-1.45.tar.gz + provides: + Text::Diff 1.45 + Text::Diff::Base 1.45 + Text::Diff::Config 1.44 + Text::Diff::Table 1.44 + requirements: + Algorithm::Diff 1.19 + Exporter 0 + ExtUtils::MakeMaker 0 + perl 5.006 + Text-Glob-0.11 + pathname: R/RC/RCLAMP/Text-Glob-0.11.tar.gz + provides: + Text::Glob 0.11 + requirements: + Exporter 0 + ExtUtils::MakeMaker 0 + constant 0 + perl 5.00503 + Text-Hogan-1.04 + pathname: K/KA/KAORU/Text-Hogan-1.04.tar.gz + provides: + Text::Hogan 1.04 + Text::Hogan::Compiler 1.04 + Text::Hogan::Template 1.04 + requirements: + Clone 0.37 + ExtUtils::MakeMaker 0 + Scalar::Util 1.41 + Text::Trim 1.02 + perl 5.006 + Text-Trim-1.02 + pathname: M/MA/MATTLAW/Text-Trim-1.02.tar.gz + provides: + Text::Trim 1.02 + requirements: + Test::More 0 + perl 5 + Throwable-0.200013 + pathname: R/RJ/RJBS/Throwable-0.200013.tar.gz + provides: + StackTrace::Auto 0.200013 + Throwable 0.200013 + Throwable::Error 0.200013 + requirements: + Carp 0 + Devel::StackTrace 1.32 + ExtUtils::MakeMaker 0 + Module::Runtime 0.002 + Moo 1.000001 + Moo::Role 0 + Scalar::Util 0 + Sub::Quote 0 + overload 0 + Try-Tiny-0.30 + pathname: E/ET/ETHER/Try-Tiny-0.30.tar.gz + provides: + Try::Tiny 0.30 + requirements: + Carp 0 + Exporter 5.57 + ExtUtils::MakeMaker 0 + constant 0 + perl 5.006 + strict 0 + warnings 0 + Try-Tiny-ByClass-0.01 + pathname: M/MA/MAUKE/Try-Tiny-ByClass-0.01.tar.gz + provides: + Try::Tiny::ByClass 0.01 + requirements: + Dispatch::Class 0 + Exporter 0 + ExtUtils::MakeMaker 6.56 + IO::Handle 0 + Test::More 0 + Try::Tiny 0 + base 0 + perl 5.006000 + strict 0 + warnings 0 + URI-1.73 + pathname: E/ET/ETHER/URI-1.73.tar.gz + provides: + URI 1.73 + URI::Escape 3.31 + URI::Heuristic 4.20 + URI::IRI 1.73 + URI::QueryParam 1.73 + URI::Split 1.73 + URI::URL 5.04 + URI::WithBase 2.20 + URI::data 1.73 + URI::file 4.21 + URI::file::Base 1.73 + URI::file::FAT 1.73 + URI::file::Mac 1.73 + URI::file::OS2 1.73 + URI::file::QNX 1.73 + URI::file::Unix 1.73 + URI::file::Win32 1.73 + URI::ftp 1.73 + URI::gopher 1.73 + URI::http 1.73 + URI::https 1.73 + URI::ldap 1.73 + URI::ldapi 1.73 + URI::ldaps 1.73 + URI::mailto 1.73 + URI::mms 1.73 + URI::news 1.73 + URI::nntp 1.73 + URI::pop 1.73 + URI::rlogin 1.73 + URI::rsync 1.73 + URI::rtsp 1.73 + URI::rtspu 1.73 + URI::sftp 1.73 + URI::sip 1.73 + URI::sips 1.73 + URI::snews 1.73 + URI::ssh 1.73 + URI::telnet 1.73 + URI::tn3270 1.73 + URI::urn 1.73 + URI::urn::isbn 1.73 + URI::urn::oid 1.73 + requirements: + Carp 0 + Cwd 0 + Data::Dumper 0 + Encode 0 + Exporter 5.57 + ExtUtils::MakeMaker 0 + MIME::Base64 2 + Net::Domain 0 + Scalar::Util 0 + constant 0 + integer 0 + overload 0 + parent 0 + perl 5.008001 + strict 0 + utf8 0 + warnings 0 + URI-Template-0.22 + pathname: B/BR/BRICAS/URI-Template-0.22.tar.gz + provides: + URI::Template 0.22 + requirements: + ExtUtils::MakeMaker 6.59 + Test::More 0 + URI 0 + URI::Escape 0 + Unicode::Normalize 0 + perl 5.006 + Variable-Magic-0.62 + pathname: V/VP/VPIT/Variable-Magic-0.62.tar.gz + provides: + Variable::Magic 0.62 + requirements: + Carp 0 + Config 0 + Exporter 0 + ExtUtils::MakeMaker 0 + IO::Handle 0 + IO::Select 0 + IPC::Open3 0 + POSIX 0 + Socket 0 + Test::More 0 + XSLoader 0 + base 0 + lib 0 + perl 5.008 + WWW-RobotRules-6.02 + pathname: G/GA/GAAS/WWW-RobotRules-6.02.tar.gz + provides: + WWW::RobotRules 6.02 + WWW::RobotRules::AnyDBM_File 6.00 + WWW::RobotRules::InCore 6.02 + requirements: + AnyDBM_File 0 + ExtUtils::MakeMaker 0 + Fcntl 0 + URI 1.10 + perl 5.008001 + XSLoader-0.24 + pathname: S/SA/SAPER/XSLoader-0.24.tar.gz + provides: + XSLoader 0.24 + requirements: + ExtUtils::MakeMaker 0 + Test::More 0.47 + YAML-LibYAML-0.69 + pathname: T/TI/TINITA/YAML-LibYAML-0.69.tar.gz + provides: + YAML::LibYAML 0.69 + YAML::XS 0.69 + YAML::XS::LibYAML undef + requirements: + ExtUtils::MakeMaker 0 + perl 5.008001 + asa-1.03 + pathname: A/AD/ADAMK/asa-1.03.tar.gz + provides: + asa 1.03 + requirements: + ExtUtils::MakeMaker 6.42 + File::Spec 0.80 + Test::More 0.47 + base 0 + perl 5.005 + bareword-filehandles-0.005 + pathname: I/IL/ILMARI/bareword-filehandles-0.005.tar.gz + provides: + bareword::filehandles 0.005 + requirements: + B::Hooks::OP::Check 0 + ExtUtils::Depends 0 + ExtUtils::MakeMaker 0 + Lexical::SealRequireHints 0 + Test::More 0.88 + XSLoader 0 + perl 5.008001 + strict 0 + warnings 0 + indirect-0.38 + pathname: V/VP/VPIT/indirect-0.38.tar.gz + provides: + indirect 0.38 + requirements: + Carp 0 + Config 0 + ExtUtils::MakeMaker 0 + File::Spec 0 + IO::Handle 0 + IO::Select 0 + IPC::Open3 0 + POSIX 0 + Socket 0 + Test::More 0 + XSLoader 0 + lib 0 + perl 5.008001 + libwww-perl-6.31 + pathname: E/ET/ETHER/libwww-perl-6.31.tar.gz + provides: + LWP 6.31 + LWP::Authen::Basic 6.31 + LWP::Authen::Digest 6.31 + LWP::Authen::Ntlm 6.31 + LWP::ConnCache 6.31 + LWP::Debug 6.31 + LWP::Debug::TraceHTTP 6.31 + LWP::DebugFile 6.31 + LWP::MemberMixin 6.31 + LWP::Protocol 6.31 + LWP::Protocol::cpan 6.31 + LWP::Protocol::data 6.31 + LWP::Protocol::file 6.31 + LWP::Protocol::ftp 6.31 + LWP::Protocol::gopher 6.31 + LWP::Protocol::http 6.31 + LWP::Protocol::loopback 6.31 + LWP::Protocol::mailto 6.31 + LWP::Protocol::nntp 6.31 + LWP::Protocol::nogo 6.31 + LWP::RobotUA 6.31 + LWP::Simple 6.31 + LWP::UserAgent 6.31 + libwww::perl undef + requirements: + Digest::MD5 0 + Encode 2.12 + Encode::Locale 0 + ExtUtils::MakeMaker 0 + File::Copy 0 + File::Listing 6 + Getopt::Long 0 + HTML::Entities 0 + HTML::HeadParser 0 + HTTP::Cookies 6 + HTTP::Daemon 6 + HTTP::Date 6 + HTTP::Negotiate 6 + HTTP::Request 6 + HTTP::Request::Common 6 + HTTP::Response 6 + HTTP::Status 6 + IO::Select 0 + IO::Socket 0 + LWP::MediaTypes 6 + MIME::Base64 2.1 + Net::FTP 2.58 + Net::HTTP 6.07 + Scalar::Util 0 + Try::Tiny 0 + URI 1.10 + URI::Escape 0 + WWW::RobotRules 6 + base 0 + perl 5.008001 + strict 0 + warnings 0 + multidimensional-0.013 + pathname: I/IL/ILMARI/multidimensional-0.013.tar.gz + provides: + multidimensional 0.013 + requirements: + B::Hooks::OP::Check 0.19 + CPAN::Meta 2.112580 + ExtUtils::Depends 0 + ExtUtils::MakeMaker 0 + Lexical::SealRequireHints 0.005 + Test::More 0.88 + XSLoader 0 + perl 5.008 + strict 0 + warnings 0 + namespace-clean-0.27 + pathname: R/RI/RIBASUSHI/namespace-clean-0.27.tar.gz + provides: + namespace::clean 0.27 + requirements: + B::Hooks::EndOfScope 0.12 + ExtUtils::MakeMaker 0 + Package::Stash 0.23 + perl 5.008001 + strictures-2.000003 + pathname: H/HA/HAARG/strictures-2.000003.tar.gz + provides: + strictures 2.000003 + strictures::extra undef + requirements: + bareword::filehandles 0 + indirect 0 + multidimensional 0 + perl 5.006 diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..87a51dd --- /dev/null +++ b/dist.ini @@ -0,0 +1,4 @@ +author = Nicolas Franck +[@Milla] +installer = ModuleBuild + diff --git a/lib/Catmandu/DBI.pm b/lib/Catmandu/DBI.pm new file mode 100644 index 0000000..8c997c8 --- /dev/null +++ b/lib/Catmandu/DBI.pm @@ -0,0 +1,67 @@ +package Catmandu::DBI; + +our $VERSION = "0.12"; + +=head1 NAME + +Catmandu::DBI - Catmandu tools to communicate with DBI based interfaces + +=head1 SYNOPSIS + + # From the command line + + # Export data from a relational database + $ catmandu convert DBI --dsn dbi:mysql:foobar --user foo --password bar --query "select * from table" + + # Import data into a relational database + $ catmandu import JSON to DBI --data_source dbi:SQLite:mydb.sqlite < data.json + + # Export data from a relational database + $ catmandu export DBI --data_source dbi:SQLite:mydb.sqlite to JSON + + # Or via a configuration file + $ cat catmandu.yml + --- + store: + mydb: + package: DBI + options: + data_source: "dbi:mysql:database=mydb" + username: xyz + password: xyz + ... + $ catmandu import JSON to mydb < data.json + $ catmandu export mydb to YAML > data.yml + + # Export one record + $ catmandu export mydb --id 012E929E-FF44-11E6-B956-AE2804ED5190 to JSON > record.json + + # Count the number of records + $ catmandu count mydb + + # Delete data + $ catmandy delete mydb + +=head1 MODULES + +L + +L + +=head1 AUTHORS + +Nicolas Franck C<< >> + +Patrick Hochstenbach C<< >> + +Vitali Peil C<< >> + +Nicolas Steenlant C<< >> + +=head1 SEE ALSO + +L, L , L + +=cut + +1; diff --git a/lib/Catmandu/Importer/DBI.pm b/lib/Catmandu/Importer/DBI.pm new file mode 100644 index 0000000..b6e3e10 --- /dev/null +++ b/lib/Catmandu/Importer/DBI.pm @@ -0,0 +1,143 @@ +package Catmandu::Importer::DBI; + +use Catmandu::Sane; +use DBI; +use Moo; +use MooX::Aliases; +use namespace::clean; + +our $VERSION = '0.12'; + +with 'Catmandu::Importer'; + +has data_source => (is => 'ro', required => 1, alias => 'dsn'); +has username => (is => 'ro', alias => 'user'); +has password => (is => 'ro', alias => 'pass'); +has query => (is => 'ro', required => 1); +has dbh => + (is => 'ro', init_arg => undef, lazy => 1, builder => '_build_dbh',); +has sth => + (is => 'ro', init_arg => undef, lazy => 1, builder => '_build_sth',); + +sub _build_dbh { + my $self = $_[0]; + DBI->connect( + $self->dsn, + $self->user, + $self->password, + { + AutoCommit => 1, + RaiseError => 1, + mysql_auto_reconnect => 1, + mysql_enable_utf8 => 1, + pg_utf8_strings => 1, + sqlite_use_immediate_transaction => 1, + sqlite_unicode => 1, + } + ); +} + +sub _build_sth { + my $self = $_[0]; + my $sth = $self->dbh->prepare($self->query); + $sth->execute; + $sth; +} + +sub generator { + my ($self) = @_; + + return sub { + $self->sth->fetchrow_hashref(); + } +} + +sub DESTROY { + my ($self) = @_; + $self->sth->finish; + $self->dbh->disconnect; +} + +=head1 NAME + +Catmandu::Importer::DBI - Catmandu module to import data from any DBI source + +=head1 LIMITATIONS + +Text columns are assumed to be utf-8. + +=head1 SYNOPSIS + + # From the command line + + $ catmandu convert DBI --dsn dbi:mysql:foobar --user foo --password bar --query "select * from table" + + # From Perl code + + use Catmandu; + + my %attrs = ( + dsn => 'dbi:mysql:foobar' , + user => 'foo' , + password => 'bar' , + query => 'select * from table' + ); + + my $importer = Catmandu->importer('DBI',%attrs); + + # Optional set extra parameters on the database handle + # $importer->dbh->{LongReadLen} = 1024 * 64; + + $importer->each(sub { + my $row_hash = shift; + ... + }); + +=head1 DESCRIPTION + +This L can be used to access data stored in a relational database. +Given a database handle and a SQL query an export of hits will be exported. + +=head1 CONFIGURATION + +=over + +=item dsn + +Required. The connection parameters to the database. See L for more information. + +Examples: + + dbi:mysql:foobar <= a local mysql database 'foobar' + dbi:Pg:dbname=foobar;host=myserver.org;port=5432 <= a remote PostGres database + dbi:SQLite:mydb.sqlite <= a local SQLLite file based database mydb.sqlite + dbi:Oracle:host=myserver.org;sid=data01 <= a remote Oracle database + +Drivers for each database need to be available on your computer. Install then with: + + cpanm DBD::mysql + cpanm DBD::Pg + cpanm DBD::SQLite + cpanm DBD::Oracle + +=item user + +Optional. A user name to connect to the database + +=item password + +Optional. A password for connecting to the database + +=item query + +Required. An SQL query to be executed against the datbase. + +=back + +=head1 SEE ALSO + +L, L , L + +=cut + +1; diff --git a/lib/Catmandu/Serializer/json_string.pm b/lib/Catmandu/Serializer/json_string.pm new file mode 100644 index 0000000..3a6d506 --- /dev/null +++ b/lib/Catmandu/Serializer/json_string.pm @@ -0,0 +1,44 @@ +package Catmandu::Serializer::json_string; + +use Catmandu::Sane; +use JSON qw(); +use Moo; + +has json => ( + is => "ro", + lazy => 1, + init_arg => undef, + default => sub {JSON->new()->utf8(0);} +); + +sub serialize { + $_[0]->json()->encode($_[1]); +} + +sub deserialize { + $_[0]->json()->decode($_[1]); +} + +1; + +__END__ + +=pod + +=head1 NAME + +Catmandu::Serializer - A (de)serializer from and to json strings + +=head1 DESCRIPTION + + serializer 'json' returns a binary utf-8 string, + which only makes sense if you send your data to column of type 'binary' + + use this serializer if your data column is a text field or a subtype of text + (like json or jsonb in postgres) + +=head1 SEE ALSO + +L + +=cut diff --git a/lib/Catmandu/Store/DBI.pm b/lib/Catmandu/Store/DBI.pm new file mode 100644 index 0000000..9cb48f9 --- /dev/null +++ b/lib/Catmandu/Store/DBI.pm @@ -0,0 +1,418 @@ +package Catmandu::Store::DBI; + +use Catmandu::Sane; +use Catmandu::Util qw(require_package); +use DBI; +use Catmandu::Store::DBI::Bag; +use Moo; +use MooX::Aliases; +use Catmandu::Error; +use namespace::clean; + +our $VERSION = "0.12"; + +with 'Catmandu::Store'; +with 'Catmandu::Transactional'; + +has data_source => ( + is => 'ro', + required => 1, + alias => 'dsn', + trigger => sub { + my $ds = $_[0]->{data_source}; + $ds = $ds =~ /^DBI:/i ? $ds : "DBI:$ds"; + $_[0]->{data_source} = $ds; + }, +); +has username => (is => 'ro', default => sub {''}, alias => 'user'); +has password => (is => 'ro', default => sub {''}, alias => 'pass'); +has default_order => (is => 'ro', default => sub {'ID'}); +has handler => (is => 'lazy'); +has _in_transaction => (is => 'rw', writer => '_set_in_transaction',); +has _dbh => (is => 'lazy', builder => '_build_dbh', writer => '_set_dbh',); + +# DEPRECATED methods. Were only invented to tackle of problem of reconnection +sub timeout { + warn "method timeout has been replaced by auto reconnect"; +} + +sub has_timeout { + warn "method has_timeout has been replaced by auto reconnect"; + 0; +} + +sub reconnect_after_timeout { + warn "method reconnect_after_timeout has been replaced by auto reconnect"; +} + +sub handler_namespace { + 'Catmandu::Store::DBI::Handler'; +} + +sub _build_handler { + my ($self) = @_; + my $driver = $self->dbh->{Driver}{Name} // ''; + my $ns = $self->handler_namespace; + my $pkg; + if ($driver =~ /pg/i) { + $pkg = 'Pg'; + } + elsif ($driver =~ /sqlite/i) { + $pkg = 'SQLite'; + } + elsif ($driver =~ /mysql/i) { + $pkg = 'MySQL'; + } + else { + Catmandu::NotImplemented->throw( + 'Only Pg, SQLite and MySQL are supported.'); + } + require_package($pkg, $ns)->new; +} + +sub _build_dbh { + my ($self) = @_; + my $opts = { + AutoCommit => 1, + RaiseError => 1, + mysql_auto_reconnect => 1, + mysql_enable_utf8 => 1, + pg_utf8_strings => 1, + sqlite_use_immediate_transaction => 1, + sqlite_unicode => 1, + }; + my $dbh + = DBI->connect($self->data_source, $self->username, $self->password, + $opts,); + $dbh; +} + +sub dbh { + + my $self = $_[0]; + my $dbh = $self->_dbh; + + # reconnect when dbh is not set (should never happen) + return $self->reconnect + unless defined $dbh; + + # check validity of dbh + # for performance reasons only check every second + if ( defined( $self->{last_ping_t} ) ) { + + return $dbh if (time - $self->{last_ping_t}) < 1; + + } + + $self->{last_ping_t} = time; + return $dbh if $dbh->ping; + + # one should never reconnect to a database during a transaction + # because that would initiate a new transaction + Catmandu::Error->throw("Connection to DBI backend lost, and cannot reconnect during a transaction") + unless $dbh->{AutoCommit}; + + # reconnect and return dbh + # note: mysql_auto_reconnect only works when AutoCommit is 1 + $self->reconnect; + +} + +sub reconnect { + + my $self = $_[0]; + my $dbh = $self->_dbh; + $dbh->disconnect if defined($dbh); + $self->_set_dbh($self->_build_dbh); + $self->_dbh; + +} + +sub transaction { + my ($self, $sub) = @_; + + if ($self->_in_transaction) { + return $sub->(); + } + + my $dbh = $self->dbh; + my @res; + + eval { + $self->_set_in_transaction(1); + $dbh->begin_work; + @res = $sub->(); + $dbh->commit; + $self->_set_in_transaction(0); + 1; + } or do { + my $err = $@; + eval {$dbh->rollback}; + $self->_set_in_transaction(0); + die $err; + }; + + @res; +} + +sub DEMOLISH { + my ($self) = @_; + $self->{_dbh}->disconnect if $self->{_dbh}; +} + +1; + +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +Catmandu::Store::DBI - A Catmandu::Store backed by DBI + +=head1 VERSION + +Version 0.0424 + +=head1 SYNOPSIS + + # From the command line + $ catmandu import JSON to DBI --data_source SQLite:mydb.sqlite < data.json + + # Or via a configuration file + $ cat catmandu.yml + --- + store: + mydb: + package: DBI + options: + data_source: "dbi:mysql:database=mydb" + username: xyz + password: xyz + ... + $ catmandu import JSON to mydb < data.json + $ catmandu export mydb to YAML > data.yml + $ catmandu export mydb --id 012E929E-FF44-11E6-B956-AE2804ED5190 to JSON > record.json + $ catmandu count mydb + $ catmandy delete mydb + + # From perl + use Catmandu::Store::DBI; + + my $store = Catmandu::Store::DBI->new( + data_source => 'DBI:mysql:database=mydb', # prefix "DBI:" optional + username => 'xyz', # optional + password => 'xyz', # optional + ); + + my $obj1 = $store->bag->add({ name => 'Patrick' }); + + printf "obj1 stored as %s\n" , $obj1->{_id}; + + # Force an id in the store + my $obj2 = $store->bag->add({ _id => 'test123' , name => 'Nicolas' }); + + my $obj3 = $store->bag->get('test123'); + + $store->bag->delete('test123'); + + $store->bag->delete_all; + + # All bags are iterators + $store->bag->each(sub { ... }); + $store->bag->take(10)->each(sub { ... }); + +=head1 DESCRIPTION + +A Catmandu::Store::DBI is a Perl package that can store data into +DBI backed databases. The database as a whole is a 'store' +L. Databases tables are 'bags' (L). + +Databases need to be preconfigured for accepting Catmandu data. When +no specialized Catmandu tables exist in a database then Catmandu will +create them automatically. See "DATABASE CONFIGURATION" below. + +DO NOT USE Catmandu::Store::DBI on an existing database! Tables and +data can be deleted and changed. + +=head1 LIMITATIONS + +Currently only MySQL, Postgres and SQLite are supported. Text columns are also +assumed to be utf-8. + +=head1 CONFIGURATION + +=over + +=item data_source + +Required. The connection parameters to the database. See L for more information. + +Examples: + + dbi:mysql:foobar <= a local mysql database 'foobar' + dbi:Pg:dbname=foobar;host=myserver.org;port=5432 <= a remote PostGres database + dbi:SQLite:mydb.sqlite <= a local SQLLite file based database mydb.sqlite + dbi:Oracle:host=myserver.org;sid=data01 <= a remote Oracle database + +Drivers for each database need to be available on your computer. Install then with: + + cpanm DBD::mysql + cpanm DBD::Pg + cpanm DBD::SQLite + +=item user + +Optional. A user name to connect to the database + +=item password + +Optional. A password for connecting to the database + +=item default_order + +Optional. Default the default sorting of results when returning an iterator. +Choose 'ID' to order on the configured identifier field, 'NONE' to skip all +ordering, or "$field" where $field is the name of a table column. By default +set to 'ID'. + +=back + +=head1 DATABASE CONFIGURATION + +When no tables exists for storing data in the database, then Catmandu +will create them. By default tables are created for each L +which contain an '_id' and 'data' column. + +This behavior can be changed with mapping option: + + my $store = Catmandu::Store::DBI->new( + data_source => 'DBI:mysql:database=test', + bags => { + # books table + books => { + mapping => { + # these keys will be directly mapped to columns + # all other keys will be serialized in the data column + title => {type => 'string', required => 1, column => 'book_title'}, + isbn => {type => 'string', unique => 1}, + authors => {type => 'string', array => 1} + } + } + } + ); + +For keys that have a corresponding table column configured, the method 'select' of class L provides +a more efficiënt way to query records. + +See L for more information. + +=head2 Column types + +=over + +=item string + +=item integer + +=item binary + +=item datetime + +Only MySQL, PostgreSQL + +=item datetime_milli + +Only MySQL, PostgreSQL + +=item json + +Only PostgreSQL + +This is mapped internally to postgres field of type "jsonb". + +Please use the serializer L, + +if you choose to store the perl data structure into this type of field. + +Reasons: + +* there are several types of serializers. E.g. serializer "messagepack" + produces a string that is not accepted by a jsonb field in postgres + +* the default serializer L converts the perl data structure to a binary json string, + and the DBI client reencodes that utf8 string (because jsonb is a sort of text field), + so you end up having a double encoded string. + +=back + +=head2 Column options + +=over + +=item column + +Name of the table column if it differs from the key in your data. + +=item array + +Boolean option, default is C<0>. Note that this is only supported for PostgreSQL. + +=item unique + +Boolean option, default is C<0>. + +=item index + +Boolean option, default is C<0>. Ignored if C is true. + +=item required + +Boolean option, default is C<0>. + +=back + +=head1 AUTO RECONNECT + +This library automatically connects to the underlying + +database, and reconnects when that connection is lost. + +There is one exception though: when the connection is lost + +in the middle of a transaction, this is skipped and + +a L is thrown. Reconnecting during a + +transaction would have returned a new transaction, + +and (probably?) committed the lost transaction + +contrary to your expectation. There is actually no way to + +recover from that, so throwing an error seemed + +liked to a "good" way to solve that. + + +In order to avoid this situation, try to avoid + +a big time lap between database actions during + +a transaction, as your server may have thrown + +you out. + +P.S. the mysql option C<< mysql_auto_reconnect >> + +does NOT automatically reconnect during a transaction + +exactly for this reason. + +=head1 SEE ALSO + +L, L + +=cut diff --git a/lib/Catmandu/Store/DBI/Bag.pm b/lib/Catmandu/Store/DBI/Bag.pm new file mode 100644 index 0000000..6307428 --- /dev/null +++ b/lib/Catmandu/Store/DBI/Bag.pm @@ -0,0 +1,396 @@ +package Catmandu::Store::DBI::Bag; + +use Catmandu::Sane; +use Moo; +use Catmandu::Store::DBI::Iterator; +use namespace::clean; + +our $VERSION = "0.12"; + +my $default_mapping = { + _id => { + column => 'id', + type => 'string', + index => 1, + required => 1, + unique => 1, + }, + _data => {column => 'data', type => 'binary', serialize => 'all',} +}; + +has mapping => (is => 'ro', default => sub {+{%$default_mapping}},); +has default_order => (is => 'ro'); + +has _iterator => ( + is => 'ro', + lazy => 1, + builder => '_build_iterator', + handles => [ + qw( + generator + count + slice + select + detect + first + ) + ], +); + +has store_with_table => (is => 'lazy'); + +with 'Catmandu::Bag'; +with 'Catmandu::Serializer'; + +sub BUILD { + my ($self) = @_; + $self->_normalize_mapping; +} + +sub _normalize_mapping { + my ($self) = @_; + my $mapping = $self->mapping; + + $mapping->{_id} ||= $default_mapping->{_id}; + + for my $key (keys %$mapping) { + my $map = $mapping->{$key}; + $map->{type} ||= 'string'; + $map->{column} ||= $key; + } + + $mapping; +} + +sub _build_iterator { + my ($self) = @_; + Catmandu::Store::DBI::Iterator->new(bag => $self); +} + +sub _build_store_with_table { + my ($self) = @_; + my $store = $self->store; + $store->handler->create_table($self); + $store; +} + +sub get { + my ($self, $id) = @_; + my $store = $self->store_with_table; + my $dbh = $store->dbh; + my $q_name = $dbh->quote_identifier($self->name); + my $q_id_field = $dbh->quote_identifier($self->mapping->{_id}->{column}); + my $sth + = $dbh->prepare_cached( + "SELECT * FROM ${q_name} WHERE ${q_id_field}=?") + or Catmandu::Error->throw($dbh->errstr); + $sth->execute($id) or Catmandu::Error->throw($sth->errstr); + my $row = $sth->fetchrow_hashref; + $sth->finish; + $self->_row_to_data($row // return); +} + +sub add { + my ($self, $data) = @_; + $self->store_with_table->handler->add_row($self, + $self->_data_to_row($data)); + $data; +} + +sub delete { + my ($self, $id) = @_; + my $store = $self->store_with_table; + my $dbh = $store->dbh; + my $q_name = $dbh->quote_identifier($self->name); + my $q_id_field = $dbh->quote_identifier($self->mapping->{_id}->{column}); + my $sth + = $dbh->prepare_cached("DELETE FROM ${q_name} WHERE ${q_id_field}=?") + or Catmandu::Error->throw($dbh->errstr); + $sth->execute($id) or Catmandu::Error->throw($sth->errstr); + $sth->finish; +} + +sub delete_all { + my ($self) = @_; + my $store = $self->store_with_table; + my $dbh = $store->dbh; + my $q_name = $dbh->quote_identifier($self->name); + my $sth = $dbh->prepare_cached("DELETE FROM ${q_name}") + or Catmandu::Error->throw($dbh->errstr); + $sth->execute or Catmandu::Error->throw($sth->errstr); + $sth->finish; +} + +sub _row_to_data { + my ($self, $row) = @_; + my $mapping = $self->mapping; + my $data = {}; + + for my $key (keys %$mapping) { + my $map = $mapping->{$key}; + my $val = $row->{$map->{column}} // next; + if ($map->{serialize}) { + $val = $self->deserialize($val); + if ($map->{serialize} eq 'all') { + for my $k (keys %$val) { + $data->{$k} = $val->{$k} // next; + } + next; + } + } + if ($map->{type} eq "datetime") { + + my ($date, $time) = split ' ', $val; + $val = "${date}T${time}Z"; + + } + $data->{$key} = $val; + } + + $data; +} + +sub _data_to_row { + my ($self, $data) = @_; + $data = {%$data}; + my $mapping = $self->mapping; + my $row = {}; + my $serialize_all_column; + + for my $key (keys %$mapping) { + my $map = $mapping->{$key}; + my $val = delete($data->{$key}); + if ($map->{serialize}) { + if ($map->{serialize} eq 'all') { + $serialize_all_column = $map->{column}; + next; + } + $val = $self->serialize($val // next); + } + if ($map->{type} eq "datetime") { + + # Translate ISO dates into datetime format + if ($val && $val =~ /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})/) { + $val = "$1 $2"; + } + } + $row->{$map->{column}} = $val // next; + } + + if ($serialize_all_column) { + $row->{$serialize_all_column} = $self->serialize($data); + } + + $row; +} + +sub _quote_id { + $_[0]->store->dbh->quote_identifier($_[1]); +} + +sub _max_limit { # should be plenty large + use bigint; + state $max_limit = 2**63 - 1; +} + +=head1 NAME + +Catmandu::Store::DBI::Bag - implementation of a Catmandu::Bag for DBI + +=head1 SYNOPSIS + + my $store = Catmandu::Store::DBI->new( + data_source => "dbi:SQLite:dbname=/tmp/test.db", + bags => { + data => { + mapping => { + _id => { + column => 'id', + type => 'string', + index => 1, + unique => 1 + }, + author => { + type => 'string' + }, + subject => { + type => 'string', + }, + _data => { + column => 'data', + type => 'binary', + serialize => 'all' + } + } + } + } + ); + + my $bag = $store->bag('data'); + + #SELECT + { + #SELECT * FROM DATA WHERE author = 'Nicolas' + my $iterator = $bag->select( author => 'Nicolas' ); + } + #CHAINED SELECT + { + #SELECT * FROM DATA WHERE author = 'Nicolas' AND subject = 'ICT' + my $iterator = $bag->select( author => 'Nicolas' )->select( subject => 'ICT' ); + } + #COUNT + { + #SELECT * FROM DATA WHERE author = 'Nicolas' + my $iterator = $bag->select( author => 'Nicolas' ); + + #SELECT COUNT(*) FROM ( SELECT * FROM DATA WHERE author = 'Nicolas' ) + my $count = $iterator->count(); + } + #DETECT + { + #SELECT * FROM DATA WHERE author = 'Nicolas' AND subject = 'ICT' LIMIT 1 + my $record = $bag->select( author => 'Nicolas' )->detect( subject => 'ICT' ); + } + + #NOTES + { + + #This creates an iterator with a specialized SQL query: + + #SELECT * FROM DATA WHERE author = 'Nicolas' + my $iterator = $bag->select( author => 'Nicolas' ); + + #But this does not + my $iterator2 = $iterator->select( title => "Hello world" ); + + #'title' does not have a corresponding table column, so it falls back to the default implementation, + #and loops over every record. + + } + { + + #this is faster.. + my $iterator = $bag->select( author => 'Nicolas' )->select( title => 'Hello world'); + + #..than + my $iterator2 = $bag->select( title => 'Hello world' )->select( author => 'Nicolas' ); + + #reason: + + # the select statement of $iterator creates a specialized query, and so reduces the amount of records to loop over. + # $iterator is a L. + + # the select statement of $iterator2 does not have a specialized query, so it's a generic L. + # the second select statement of $iterator2 receives this generic object as its source, and can only loop over its records. + + } + +=head1 DESCRIPTION + +Catmandu::Store::DBI::Bag provides some method overrides specific for DBI interfaces, +to make querying more efficient. + +=head1 METHODS + +=head2 store_with_table + +Equivalent to the C accessor, but ensures that the table for this bag exists. + +=head2 select($key => $val) + +Overrides equivalent method in L. + +Either returns a generic L or a more efficient L. + +Expect the following behaviour: + +=over 4 + +=item + +the key has a corresponding table column configured + +a SQL where clause is created in the background: + +.. WHERE $key = $val + +Chained select statements with existing table columns result in a combined where clause: + + .. WHERE $key1 = $val1 AND $key2 = $val2 .. + +The returned object is a L, instead of the generic L. + +=item + +the key does not have a corresponding table column configured + +The returned object is a generic L. + +This iterator can only loop over the records provided by the previous L. + +=back + +A few important notes: + +=over 4 + +=item + +A select statement only results in a L, when it has a mapped key, +and the previous iterator is either a L or a L. + +=item + +As soon as the returned object is a generic L, any following select statement +with mapped columns will not make a more efficient L. + +=back + +In order to make your chained statements efficient, do the following: + +=over 4 + +=item + +create indexes on the table columns + +=item + +put select statements with mapped keys in front, and those with non mapped keys at the end. + +=back + +To configure table columns, see L. + +=head2 detect($key => $val) + +Overrides equivalent method in L. + +Also returns first record where $key matches $val. + +Works like the select method above, but adds the SQL statement 'LIMIT 1' to the current SQL query in the background. + +=head2 first() + +Overrides equivalent method in L. + +Also returns first record using the current iterator. + +The parent method uses a generator, but fetches only one record. + +This method adds the SQL statement 'LIMIT 1' to the current SQL query. + +=head2 count() + +Overrides equivalent method in L. + +When the source is a L, or a L, +a specialized SQL query is created: + + SELECT COUNT(*) FROM TABLE WHERE (..) + +The select statement of the source is between the parenthesises. + +=cut + +1; diff --git a/lib/Catmandu/Store/DBI/Handler.pm b/lib/Catmandu/Store/DBI/Handler.pm new file mode 100644 index 0000000..823c3ff --- /dev/null +++ b/lib/Catmandu/Store/DBI/Handler.pm @@ -0,0 +1,64 @@ +package Catmandu::Store::DBI::Handler; + +use Catmandu::Sane; +use Moo::Role; +use namespace::clean; + +our $VERSION = "0.12"; + +requires 'create_table'; +requires 'add_row'; + +sub select_sql { + my ($self, $bag, $start, $limit, $where) = @_; + my $id_field = $bag->mapping->{_id}->{column}; + my $q_id_field = $bag->_quote_id($id_field); + + my $sql = "SELECT * FROM " . $bag->_quote_id($bag->name); + $sql .= " WHERE $where" if $where; + + my $default_order = $bag->default_order // $bag->store->default_order; + + if (defined $default_order) { + if ($default_order eq 'ID') { + $sql .= " ORDER BY $q_id_field"; + } + elsif ($default_order eq 'NONE') { + + # no nothing + } + else { + $sql .= " ORDER BY $default_order"; + } + } + $sql .= " LIMIT $limit OFFSET $start"; + $sql; +} + +sub count_sql { + my ($self, $bag, $start, $total, $where) = @_; + my $name = $bag->name; + + return "SELECT COUNT(*) FROM " . $bag->_quote_id($name) + unless $total || $start || $where; + + my $sql = "SELECT COUNT(*) FROM (SELECT * FROM " . $bag->_quote_id($name); + if ($where) { + $sql .= " WHERE $where"; + } + if ($total) { + $sql .= " LIMIT $total"; + } + elsif ($start) { # no offset without limit + $sql .= " LIMIT " . $bag->_max_limit; + } + if ($start) { + $sql .= " OFFSET $start"; + } + $sql .= ") AS q"; + + $sql; +} + +1; + diff --git a/lib/Catmandu/Store/DBI/Handler/MySQL.pm b/lib/Catmandu/Store/DBI/Handler/MySQL.pm new file mode 100644 index 0000000..be27ff9 --- /dev/null +++ b/lib/Catmandu/Store/DBI/Handler/MySQL.pm @@ -0,0 +1,117 @@ +package Catmandu::Store::DBI::Handler::MySQL; + +use Catmandu::Sane; +use Moo; +use namespace::clean; + +our $VERSION = "0.12"; + +with 'Catmandu::Store::DBI::Handler'; + +# text types are case-insensitive in MySQL +sub _column_sql { + my ($self, $map, $bag) = @_; + my $col = $map->{column}; + my $dbh = $bag->store->dbh; + my $sql = $dbh->quote_identifier($col) . " "; + if ($map->{type} eq 'string' && $map->{unique}) { + $sql .= 'VARCHAR(255) BINARY'; + } + elsif ($map->{type} eq 'string') { + $sql .= 'TEXT BINARY'; + } + elsif ($map->{type} eq 'integer') { + $sql .= 'INTEGER'; + } + elsif ($map->{type} eq 'binary') { + $sql .= 'LONGBLOB'; + } + elsif ($map->{type} eq 'datetime') { + $sql .= 'DATETIME'; + } + elsif ($map->{type} eq 'datetime_milli') { + if ($dbh->{mysql_clientversion} < 50640) { + Catmandu::Error->throw( + "DATETIME(3) type only supported in MySQL 5.6.4 and above"); + } + $sql .= 'DATETIME(3)'; + } + else { + Catmandu::Error->throw("Unknown type '$map->{type}'"); + } + if ($map->{unique}) { + $sql .= " UNIQUE"; + } + if ($map->{required}) { + $sql .= " NOT NULL"; + } + if (!$map->{unique} && $map->{index}) { + if ($map->{type} eq 'string') { + $sql .= ", INDEX($col(255))"; + } + else { + $sql .= ", INDEX($col)"; + } + } + $sql; +} + +# http://devoluk.com/mysql-limit-offset-performance.html +sub select_sql { + my ($self, $bag, $start, $limit, $where) = @_; + + my $q_id_field = $bag->_quote_id($bag->mapping->{_id}->{column}); + my $q_table = $bag->_quote_id($bag->name); + + my $sql = "SELECT * FROM $q_table AS t1"; + $sql .= " JOIN (SELECT $q_id_field FROM $q_table"; + $sql .= " WHERE $where" if $where; + + my $default_order = $bag->default_order // $bag->store->default_order; + + if (defined $default_order && $default_order eq 'ID') { + $sql .= " ORDER BY $q_id_field"; + } + elsif (defined $default_order && $default_order ne 'NONE') { + $sql .= " ORDER BY $default_order"; + } + $sql + .= " LIMIT $limit OFFSET $start) AS t2 ON t1.$q_id_field = t2.$q_id_field"; + + $sql; +} + +sub create_table { + my ($self, $bag) = @_; + my $mapping = $bag->mapping; + my $dbh = $bag->store->dbh; + my $q_name = $dbh->quote_identifier($bag->name); + my $sql + = "CREATE TABLE IF NOT EXISTS $q_name(" + . join(',', map {$self->_column_sql($_, $bag)} values %$mapping) + . ")"; + $dbh->do($sql) or Catmandu::Error->throw($dbh->errstr); +} + +sub add_row { + my ($self, $bag, $row) = @_; + my $dbh = $bag->store->dbh; + my @cols = keys %$row; + my @q_cols = map {$dbh->quote_identifier($_)} @cols; + my @vals = values %$row; + my $q_name = $dbh->quote_identifier($bag->name); + my $sql + = "INSERT INTO $q_name(" + . join(',', @q_cols) + . ") VALUES(" + . join(',', ('?') x @q_cols) + . ") ON DUPLICATE KEY UPDATE " + . join(',', map {"$_=VALUES($_)"} @q_cols); + + my $sth = $dbh->prepare_cached($sql) + or Catmandu::Error->throw($dbh->errstr); + $sth->execute(@vals) or Catmandu::Error->throw($sth->errstr); + $sth->finish; +} + +1; diff --git a/lib/Catmandu/Store/DBI/Handler/Pg.pm b/lib/Catmandu/Store/DBI/Handler/Pg.pm new file mode 100644 index 0000000..af6178a --- /dev/null +++ b/lib/Catmandu/Store/DBI/Handler/Pg.pm @@ -0,0 +1,188 @@ +package Catmandu::Store::DBI::Handler::Pg; + +use Catmandu::Sane; +use DBD::Pg (); +use Moo; +use namespace::clean; + +our $VERSION = "0.12"; + +with 'Catmandu::Store::DBI::Handler'; + +sub _column_sql { + my ($self, $map, $bag) = @_; + my $col = $map->{column}; + my $dbh = $bag->store->dbh; + my $sql = $dbh->quote_identifier($col) . " "; + if ($map->{type} eq 'string') { + $sql .= 'TEXT'; + } + elsif ($map->{type} eq 'integer') { + $sql .= 'INTEGER'; + } + elsif ($map->{type} eq 'binary') { + $sql .= 'BYTEA'; + } + elsif ($map->{type} eq 'json') { + if ($dbh->{pg_server_version} < 90400) { + Catmandu::Error->throw( + "JSONB type only supported in PostgreSQL 9.4 and above"); + } + $sql .= 'JSONB'; + } + elsif ($map->{type} eq 'datetime') { + $sql .= 'TIMESTAMP(0)'; + } + elsif ($map->{type} eq 'datetime_milli') { + $sql .= 'TIMESTAMP(3)'; + } + else { + Catmandu::Error->throw("Unknown type '$map->{type}'"); + } + if ($map->{array}) { + $sql .= '[]'; + } + if ($map->{unique}) { + $sql .= " UNIQUE"; + } + if ($map->{required}) { + $sql .= " NOT NULL"; + } + $sql; +} + +sub _create_index_sql { + my ($self, $bag, $map) = @_; + my $name = $bag->name; + my $col = $map->{column}; + my $dbh = $bag->store->dbh; + my $q_col = $dbh->quote_identifier($col); + my $sql = <mapping; + my $name = $bag->name; + my $dbh = $bag->store->dbh; + my $q_name = $dbh->quote_identifier($name); + + my $sql + = "CREATE TABLE IF NOT EXISTS $q_name(" + . join(',', map {$self->_column_sql($_, $bag)} values %$mapping) + . ");"; + + for my $map (values %$mapping) { + next if $map->{unique} || !$map->{index}; + $sql .= $self->_create_index_sql($bag, $map); + } + + local $SIG{__WARN__} = sub { + my $msg = $_[0]; + if ($msg !~ /^NOTICE: relation "$name" already exists/) { + warn $msg; + } + }; + $dbh->do($sql) or Catmandu::Error->throw($dbh->errstr); +} + +# see +# http://stackoverflow.com/questions/15840922/where-not-exists-in-postgresql-gives-syntax-error +# and +# https://rt.cpan.org/Public/Bug/Display.html?id=13180 +sub add_row { + my ($self, $bag, $row) = @_; + my $mapping = $bag->mapping; + my $dbh = $bag->store->dbh; + my $id_col = $mapping->{_id}{column}; + my $q_id_col = $dbh->quote_identifier($id_col); + my %binary_cols; + my %json_cols; + for my $map (values %$mapping) { + + if ($map->{type} eq 'binary') { + $binary_cols{$map->{column}} = 1; + } + elsif ($map->{type} eq 'json') { + $json_cols{$map->{column}} = 1; + } + } + my $id = $row->{$id_col}; + my @cols = keys %$row; + my @q_cols = map {$dbh->quote_identifier($_)} @cols; + my @vals = values %$row; + my $name = $bag->name; + my $q_name = $dbh->quote_identifier($name); + my $insert_sql + = "INSERT INTO $q_name(" + . join(',', @q_cols) + . ") SELECT " + . join(',', ('?') x @cols) + . " WHERE NOT EXISTS (SELECT 1 FROM $q_name WHERE $q_id_col=?)"; + my $update_sql + = "UPDATE $q_name SET " + . join(',', map {"$_=?"} @q_cols) + . " WHERE $q_id_col=?"; + + my $sth = $dbh->prepare_cached($update_sql) + or Catmandu::Error->throw($dbh->errstr); + my $i = 0; + for (; $i < @cols; $i++) { + my $col = $cols[$i]; + my $val = $vals[$i]; + if ($binary_cols{$col}) { + $sth->bind_param($i + 1, $val, {pg_type => DBD::Pg->PG_BYTEA}); + } + elsif ($json_cols{$col}) { + $sth->bind_param($i + 1, $val, {pg_type => DBD::Pg->PG_JSONB}); + } + else { + $sth->bind_param($i + 1, $val); + } + } + $sth->bind_param($i + 1, $id); + $sth->execute or Catmandu::Error->throw($sth->errstr); + + unless ($sth->rows) { + $sth->finish; + $sth = $dbh->prepare_cached($insert_sql) + or Catmandu::Error->throw($dbh->errstr); + my $i = 0; + for (; $i < @cols; $i++) { + my $col = $cols[$i]; + my $val = $vals[$i]; + if ($binary_cols{$col}) { + $sth->bind_param($i + 1, $val, + {pg_type => DBD::Pg->PG_BYTEA}); + } + elsif ($json_cols{$col}) { + $sth->bind_param($i + 1, $val, + {pg_type => DBD::Pg->PG_JSONB}); + } + else { + $sth->bind_param($i + 1, $val); + } + } + $sth->bind_param($i + 1, $id); + $sth->execute or Catmandu::Error->throw($sth->errstr); + } + $sth->finish; +} + +1; diff --git a/lib/Catmandu/Store/DBI/Handler/SQLite.pm b/lib/Catmandu/Store/DBI/Handler/SQLite.pm new file mode 100644 index 0000000..dba573f --- /dev/null +++ b/lib/Catmandu/Store/DBI/Handler/SQLite.pm @@ -0,0 +1,81 @@ +package Catmandu::Store::DBI::Handler::SQLite; + +use Catmandu::Sane; +use Moo; +use namespace::clean; + +our $VERSION = "0.12"; + +with 'Catmandu::Store::DBI::Handler'; + +sub _column_sql { + my ($self, $map, $bag) = @_; + my $col = $map->{column}; + my $dbh = $bag->store->dbh; + my $sql = $dbh->quote_identifier($col) . " "; + if ($map->{type} eq 'string') { + $sql .= 'TEXT'; + } + elsif ($map->{type} eq 'integer') { + $sql .= 'INTEGER'; + } + elsif ($map->{type} eq 'binary') { + $sql .= 'BLOB'; + } + else { + Catmandu::Error->throw("Unknown type '$map->{type}'"); + } + if ($map->{unique}) { + $sql .= " UNIQUE"; + } + if ($map->{required}) { + $sql .= " NOT NULL"; + } + $sql; +} + +sub create_table { + my ($self, $bag) = @_; + my $mapping = $bag->mapping; + my $dbh = $bag->store->dbh; + my $name = $bag->name; + my $q_name = $dbh->quote_identifier($name); + + my $sql + = "CREATE TABLE IF NOT EXISTS $q_name(" + . join(',', map {$self->_column_sql($_, $bag)} values %$mapping) + . ")"; + + $dbh->do($sql) or Catmandu::Error->throw($dbh->errstr); + + for my $map (values %$mapping) { + next if $map->{unique} || !$map->{index}; + my $col = $map->{column}; + my $q_col = $dbh->quote_identifier($col); + my $q_idx = $dbh->quote_identifier("${name}_${col}_idx"); + my $idx_sql + = "CREATE INDEX IF NOT EXISTS ${q_idx} ON $q_name($q_col)"; + $dbh->do($idx_sql) or Catmandu::Error->throw($dbh->errstr); + } +} + +sub add_row { + my ($self, $bag, $row) = @_; + my $dbh = $bag->store->dbh; + my @cols = keys %$row; + my @q_cols = map {$dbh->quote_identifier($_)} @cols; + my @values = values %$row; + my $q_name = $dbh->quote_identifier($bag->name); + my $sql + = "INSERT OR REPLACE INTO $q_name(" + . join(',', @q_cols) + . ") VALUES(" + . join(',', ('?') x @cols) . ")"; + + my $sth = $dbh->prepare_cached($sql) + or Catmandu::Error->throw($dbh->errstr); + $sth->execute(@values) or Catmandu::Error->throw($sth->errstr); + $sth->finish; +} + +1; diff --git a/lib/Catmandu/Store/DBI/Iterator.pm b/lib/Catmandu/Store/DBI/Iterator.pm new file mode 100644 index 0000000..eb62164 --- /dev/null +++ b/lib/Catmandu/Store/DBI/Iterator.pm @@ -0,0 +1,173 @@ +package Catmandu::Store::DBI::Iterator; + +use Catmandu::Sane; +use Catmandu::Util qw(is_value is_string is_array_ref); +use Moo; +use namespace::clean; + +our $VERSION = "0.12"; + +with 'Catmandu::Iterable'; + +has bag => (is => 'ro', required => 1); +has where => (is => 'ro'); +has binds => (is => 'lazy'); +has total => (is => 'ro'); +has start => (is => 'lazy'); +has limit => (is => 'lazy'); + +sub _build_binds {[]} +sub _build_start {0} + +sub _build_limit { + my ($self) = @_; + my $limit = 100; + my $total = $self->total; + if (defined $total && $total < $limit) { + $limit = $total; + } + $limit; +} + +sub generator { + my ($self) = @_; + my $bag = $self->bag; + my $store = $self->bag->store_with_table; + my $handler = $store->handler; + my $binds = $self->binds; + my $total = $self->total; + my $start = $self->start; + my $limit = $self->limit; + my $where = $self->where; + + sub { + state $rows; + + return if defined $total && $total <= 0; + + unless (defined $rows && @$rows) { + my $dbh = $store->dbh; + +#DO NOT USE prepare_cached as it holds previous data in memory, leading to a memory leak! + my $sth + = $dbh->prepare( + $handler->select_sql($bag, $start, $limit, $where)) + or Catmandu::Error->throw($dbh->errstr); + $sth->execute(@$binds) or Catmandu::Error->throw($sth->errstr); + $rows = $sth->fetchall_arrayref({}); + # less results than requested: the end is near + $total = scalar(@$rows) if scalar(@$rows) < $limit; + $sth->finish; + $start += $limit; + } + + my $data = $bag->_row_to_data(shift(@$rows) // return); + $total-- if defined $total; + $data; + }; +} + +sub count { + my ($self) = @_; + my $bag = $self->bag; + my $binds = $self->binds; + my $store = $bag->store_with_table; + my $dbh = $store->dbh; + my $sth = $dbh->prepare_cached( + $store->handler->count_sql( + $bag, $self->start, $self->total, $self->where + ) + ) or Catmandu::Error->throw($dbh->errstr); + $sth->execute(@$binds) or Catmandu::Error->throw($sth->errstr); + my ($n) = $sth->fetchrow_array; + $sth->finish; + $n; +} + +sub slice { + my ($self, $start, $total) = @_; + ref($self)->new( + { + bag => $self->bag, + where => $self->where, + binds => $self->binds, + total => $total, + start => $self->start + ($start // 0), + } + ); +} + +around select => sub { + my ($orig, $self, $arg1, $arg2) = @_; + my $mapping = $self->bag->mapping; + + if ( is_string($arg1) + && $mapping->{$arg1} + && (is_value($arg2) || is_array_ref($arg2))) + { + my $opts = $self->_scope($arg1, $arg2); + return ref($self)->new($opts); + } + + $self->$orig($arg1, $arg2); +}; + +around detect => sub { + my ($orig, $self, $arg1, $arg2) = @_; + my $mapping = $self->bag->mapping; + + if ( is_string($arg1) + && $mapping->{$arg1} + && (is_value($arg2) || is_array_ref($arg2))) + { + my $opts = $self->_scope($arg1, $arg2); + $opts->{total} = 1; + return ref($self)->new($opts)->generator->(); + } + + $self->$orig($arg1, $arg2); +}; + +sub first { + my ($self) = @_; + ref($self)->new( + { + bag => $self->bag, + where => $self->where, + binds => $self->binds, + total => 1, + start => $self->start, + } + )->generator->(); +} + +sub _scope { + my ($self, $arg1, $arg2) = @_; + my $binds = [@{$self->binds}]; + my $where = is_string($self->where) ? '(' . $self->where . ') AND ' : ''; + my $map = $self->bag->mapping->{$arg1}; + my $column = $map->{column}; + my $q_column = $self->bag->_quote_id($column); + + if ($map->{array}) { + push @$binds, is_value($arg2) ? [$arg2] : $arg2; + $where .= "($q_column && ?)"; + } + elsif (is_value($arg2)) { + push @$binds, $arg2; + $where .= "($q_column=?)"; + } + else { + push @$binds, @$arg2; + $where .= "($q_column IN(" . join(',', ('?') x @$arg2) . '))'; + } + + { + bag => $self->bag, + where => $where, + binds => $binds, + start => $self->start, + }; +} + +1; diff --git a/t/00-load.t b/t/00-load.t new file mode 100644 index 0000000..5d1c2b7 --- /dev/null +++ b/t/00-load.t @@ -0,0 +1,16 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More; +use Test::Exception; + +my $pkg; + +BEGIN { + $pkg = 'Catmandu::Store::DBI'; + use_ok $pkg; +} +require_ok $pkg; + +done_testing 2; diff --git a/t/01-load.t b/t/01-load.t new file mode 100644 index 0000000..a24a9f4 --- /dev/null +++ b/t/01-load.t @@ -0,0 +1,14 @@ +use strict; +use warnings; +use Test::More; + +my $pkg; + +BEGIN { + $pkg = 'Catmandu::Importer::DBI'; + use_ok $pkg; +} + +require_ok $pkg; + +done_testing 2; diff --git a/t/02-bag.t b/t/02-bag.t new file mode 100644 index 0000000..33c1922 --- /dev/null +++ b/t/02-bag.t @@ -0,0 +1,96 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; +use Test::Exception; +use File::Temp qw(tempfile); + +require Catmandu::Store::DBI; + +my $driver_found = 1; +{ + local $@; + eval {require DBD::SQLite;}; + if ($@) { + $driver_found = 0; + } +} + +if (!$driver_found) { + + plan skip_all => "database driver DBD::SQLite not found"; + +} +else { + + my ($fh, $file); + lives_ok( + sub { + #avoid exclusive lock on BSD + ($fh, $file) = tempfile(UNLINK => 1, EXLOCK => 0); + }, + "database file created" + ); + + my $bag; + + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new( + data_source => "dbi:SQLite:dbname=$file")->bag(); + }, + "bag created" + ); + + my $record + = {_id => "test", title => "my little pony", author => "no idea"}; + + #bag add + lives_ok(sub {$bag->add($record)}, "bag add successfull"); + + #bag get + my $new_record; + lives_ok(sub {$new_record = $bag->get($record->{_id});}, + "bag get successfull"); + is_deeply($record, $new_record, "retrieved record equal"); + + #bag delete + lives_ok(sub {$bag->delete($record->{_id})}, "bag delete"); + is($bag->get($record->{_id}), undef, "record deleted successfully"); + + #bag add_many + my @records; + for (1 .. 10) { + push @records, {_id => "test-$_", test => $_}; + } + lives_ok(sub {$bag->add_many(\@records)}, "bag add_many successfull"); + + my $num = 0; + + lives_ok(sub {$num = $bag->count();}, "bag count successfully"); + + is($num, scalar(@records), "bag count equal"); + + #bag delete_all + lives_ok(sub {$bag->delete_all();}, "bag delete successfully"); + is($bag->count(), 0, "all records deleted"); + + #transactions + dies_ok( + sub { + $bag->store->transaction( + sub { + $bag->add({_id => "a"}); + $bag->add({_id => "b"}); + die("failed"); + } + ); + }, + "bag transactions" + ); + + is($bag->count(), 0, "bag transactions"); + + done_testing 14; + +} diff --git a/t/04-slice.t b/t/04-slice.t new file mode 100644 index 0000000..c0a4703 --- /dev/null +++ b/t/04-slice.t @@ -0,0 +1,64 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; +use Test::Exception; +use File::Temp qw(tempfile); + +require Catmandu::Store::DBI; + +my $driver_found = 1; +{ + local $@; + eval {require DBD::SQLite;}; + if ($@) { + $driver_found = 0; + } +} + +if (!$driver_found) { + + plan skip_all => "database driver DBD::SQLite not found"; + +} +else { + + my $timeout = 2; + + my ($fh, $file); + lives_ok( + sub { + #avoid exclusive lock on BSD + ($fh, $file) = tempfile(UNLINK => 1, EXLOCK => 0); + }, + "database file created" + ); + + my $bag; + + lives_ok( + sub { + + $bag = Catmandu::Store::DBI->new( + data_source => "dbi:SQLite:dbname=$file", + timeout => $timeout, + reconnect_after_timeout => 1 + )->bag(); + + }, + "bag created" + ); + + my @records; + for (1 .. 10) { + push @records, {_id => "test-$_", test => $_}; + } + lives_ok(sub {$bag->add_many(\@records)}, "bag add_many successfull"); + + my $iterator = $bag->slice(8, 2); + + is($iterator->count, 2, "sliced results 8-10"); + + done_testing 4; + +} diff --git a/t/05-importer.t b/t/05-importer.t new file mode 100644 index 0000000..adadd6c --- /dev/null +++ b/t/05-importer.t @@ -0,0 +1,122 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; +use Test::Exception; +use File::Temp qw(tempfile); + +sub r {int(rand(100));} + +use DBI; +use Catmandu::Importer::DBI; + +my $driver_found = 1; +{ + local $@; + eval {require DBD::SQLite;}; + if ($@) { + $driver_found = 0; + } +} + +if (!$driver_found) { + + plan skip_all => "database driver DBD::SQLite not found"; + +} +else { + + my @fields = qw(name first_name street street_nr postal_code place); + my @people; + for (1 .. 100) { + my $record = {}; + $record->{$_} = r() for @fields; + $record->{id} = int($_); + push @people, $record; + } + @people = sort {$a->{id} <=> $b->{id}} @people; + + my ($fh, $file); + lives_ok( + sub { + #avoid exclusive lock on BSD + ($fh, $file) = tempfile(UNLINK => 1, EXLOCK => 0); + }, + "database file created" + ); + + #connect + my $dbh; + lives_ok( + sub { + + $dbh = DBI->connect("dbi:SQLite:dbname=$file", "", "", + {AutoCommit => 1, RaiseError => 1}); + $dbh or die($DBI::errstr); + + }, + "dbh created" + ); + + #create table + { + my $sql = "create table people (id integer not null primary key," + . join(',', map {"$_ varchar(255)"} @fields) . ")"; + lives_ok( + sub { + + $dbh->do($sql) or die($dbh->errstr); + + }, + "table created" + ); + } + + #insert data + { + my $sql + = "insert into people(id," + . join(',', @fields) + . ") values(?," + . join(',', ("?") x (scalar(@fields))) . ")"; + my $sth; + lives_ok( + sub { + + $sth = $dbh->prepare($sql) or die($dbh->errstr); + for my $p (@people) { + my @values = @{$p}{"id", @fields}; + $sth->execute(@values) or die($sth->errstr); + } + $sth->finish; + + }, + "data added" + ); + + } + + #import data + my $importer; + + #create importer + lives_ok( + sub { + + $importer = Catmandu::Importer::DBI->new( + dsn => "dbi:SQLite:dbname=$file", + user => "", + password => "", + query => "select * from people order by id asc" + ); + + }, + "importer created" + ); + + is_deeply $importer->to_array, \@people, + "imported data equal to inserted data"; + + done_testing 6; + +} diff --git a/t/06-sqlite.t b/t/06-sqlite.t new file mode 100644 index 0000000..efd1074 --- /dev/null +++ b/t/06-sqlite.t @@ -0,0 +1,182 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; +use Test::Exception; +use utf8; +require Catmandu::Store::DBI; +require Catmandu::Serializer::json; + +my $driver_found = 1; +{ + local $@; + eval {require DBD::SQLite;}; + if ($@) { + $driver_found = 0; + } +} + +if (!$driver_found) { + + plan skip_all => "database driver DBD::SQLite not found"; + +} +else { + + sub get { + my ($dbh, $table, $id_field, $id) = @_; + my $sql + = "SELECT * FROM " + . $dbh->quote_identifier($table) + . " WHERE " + . $dbh->quote_identifier($id_field) . "=?"; + my $sth = $dbh->prepare_cached($sql) or die($dbh->errstr); + $sth->execute($id) or die($sth->errstr); + $sth->fetchrow_hashref; + } + + my $record = { + _id => "彩虹小馬", + title => "My little pony", + author => "孩之寶" + }; + my $serializer = Catmandu::Serializer::json->new(); + +#implicit mapping (old behaviour) => except for the _id that is not stored anymore in 'data' + { + my $bag; + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new( + data_source => "dbi:SQLite:dbname=:memory:")->bag; + }, + "no mapping - bag created" + ); + + lives_ok(sub {$bag->add($record);}, "no mapping - add record"); + + my $row = get($bag->store->dbh, "data", "id", $record->{_id}); + $row->{data} = $serializer->deserialize($row->{data}); + + my $expected = +{id => $record->{_id}, data => {%$record}}; + delete $expected->{data}->{_id}; + + is_deeply($row, $expected, "expected fields created"); + } + + #explicit mapping (no double field _id anymore) + { + my $bag; + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new( + data_source => "dbi:SQLite:dbname=:memory:", + bags => { + data => { + mapping => { + _id => { + column => "_id", + type => "string", + index => 1, + required => 1, + unique => 1 + }, + title => + {column => "title", type => "string"}, + author => + {column => "author", type => "string"} + }, + default_order => 'ID' + } + } + )->bag(); + }, + "mapping given - bag created" + ); + + lives_ok(sub {$bag->add($record);}, "mapping given - add record"); + + my $row = get($bag->store->dbh, "data", "_id", $record->{_id}); + is_deeply $row, $record, "mapping given - expected fields created"; + + lives_ok(sub {$bag->count}, "mapping given - count successfull"); + } + { + my $bag; + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new( + data_source => "dbi:SQLite:dbname=:memory:", + bags => { + data => { + mapping => { + _id => { + column => "_id", + type => "string", + index => 1, + required => 1, + unique => 1 + }, + title => + {column => "title", type => "string"}, + author => + {column => "author", type => "string"} + } + } + } + )->bag(); + }, + "iterator - bag created" + ); + my @d_records = map {+{author => "Dostoyevsky"}} 1 .. 10; + my @t_records = map {+{author => "Tolstoj"}} 1 .. 15; + + $bag->add_many([@d_records, @t_records], + "iterator - added many records"); + + #iterator select + my $iterator; + + lives_ok(sub {$iterator = $bag->select(author => "Tolstoj");}, + "iterator - select(key => value) created"); + cmp_ok($iterator->count, "==", scalar(@t_records), + "iterator - select(key => value) contains correct number of records" + ); + + #iterator slice(start) + lives_ok( + sub { + $iterator = $bag->select(author => "Dostoyevsky")->slice(5); + }, + "iterator - select(key => value)->slice(start) created" + ); + cmp_ok($iterator->count, "==", 5, + "iterator - select(key => value)->slice(start) contains correct number of records" + ); + + #iterator slice(start,limit) + lives_ok( + sub { + $iterator + = $bag->select(author => "Dostoyevsky")->slice(5, 1); + }, + "iterator - select(key => value)->slice(start,limit) created" + ); + cmp_ok($iterator->count, "==", 1, + "iterator - select(key => value)->slice(start,limit) contains correct number of records" + ); + + #first + my $r; + lives_ok( + sub {$r = $bag->select(author => "Dostoyevsky")->first;}, + "iterator - select(key => value)->first created" + ); + isnt($r, undef, + "iterator - select(key => value)->first contains one record"); + + } + + done_testing 16; + +} diff --git a/t/07-mysql.t b/t/07-mysql.t new file mode 100644 index 0000000..291f36e --- /dev/null +++ b/t/07-mysql.t @@ -0,0 +1,223 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; +use Test::Exception; +use utf8; + +require Catmandu::Store::DBI; +require Catmandu::Serializer::json; + +my $driver_found = 1; +{ + local $@; + eval {require DBD::mysql;}; + if ($@) { + $driver_found = 0; + } +} +my %store_args = ( + data_source => $ENV{CATMANDU_DBI_TEST_MYSQL_DSN}, + username => $ENV{CATMANDU_DBI_TEST_MYSQL_USERNAME}, + password => $ENV{CATMANDU_DBI_TEST_MYSQL_PASSWORD} +); + +if (!$driver_found) { + plan skip_all => "database driver DBD::mysql not found"; +} +elsif ( + !( + $ENV{CATMANDU_DBI_TEST_MYSQL_DSN} + // $ENV{CATMANDU_DBI_TEST_MYSQL_USERNAME} + // $ENV{CATMANDU_DBI_TEST_MYSQL_PASSWORD} + ) + ) +{ + plan skip_all => "not all mysql connection details are set"; +} +else { + + sub get { + my ($dbh, $table, $id_field, $id) = @_; + my $sql + = "SELECT * FROM " + . $dbh->quote_identifier($table) + . " WHERE " + . $dbh->quote_identifier($id_field) . "=?"; + my $sth = $dbh->prepare_cached($sql) or die($dbh->errstr); + $sth->execute($id) or die($sth->errstr); + $sth->fetchrow_hashref; + } + + sub drop { + my ($dbh, $table, $id_field, $id) = @_; + my $sql = "DROP TABLE " . $dbh->quote_identifier($table); + $dbh->do($sql); + } + + my $record = { + _id => "彩虹小馬", + title => "My little pony", + author => "孩之寶", + date_updated => "2017-01-01 10:00:00", + number => "42" + }; + my $serializer = Catmandu::Serializer::json->new; + + #implicit mapping (old behaviour) + { + my $bag; + my $bag_name = "data1"; + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new(%store_args)->bag($bag_name); + }, + "no mapping - bag $bag_name created" + ); + lives_ok(sub {$bag->delete_all;}, + "no mapping - bag $bag_name cleared"); + + lives_ok(sub {$bag->add($record);}, "no mapping - add record"); + + my $row = get($bag->store->dbh, $bag_name, "id", $record->{_id}); + $row->{data} = $serializer->deserialize($row->{data}); + + my $expected = +{id => $record->{_id}, data => {%$record}}; + delete $expected->{data}->{_id}; + + is_deeply($row, $expected, "no mapping - expected fields created"); + + drop($bag->store->dbh, $bag_name); + } + + #explicit mapping + { + my $bag; + my $bag_name = "data2"; + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new( + %store_args, + bags => { + $bag_name => { + mapping => { + _id => { + column => "_id", + type => "string", + index => 1, + required => 1, + unique => 1 + }, + title => + {column => "title", type => "string"}, + author => + {column => "author", type => "string"}, + date_updated => { + column => "date_updated", + type => "datetime" + }, + number => + {column => "number", type => "integer"}, + } + } + } + )->bag($bag_name); + }, + "mapping given - bag $bag_name created" + ); + lives_ok(sub {$bag->delete_all;}, + "mapping given - bag $bag_name cleared"); + + lives_ok(sub {$bag->add($record);}, + "mapping given - record added to bag $bag_name"); + + my $row = get($bag->store->dbh, $bag_name, "_id", $record->{_id}); + is_deeply $row, $record, "mapping given - expected fields created"; + + lives_ok(sub {$bag->count}, "mapping given - count ok"); + drop($bag->store->dbh, $bag_name); + } + + { + my $bag; + my $bag_name = "data3"; + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new( + %store_args, + bags => { + $bag_name => { + mapping => { + _id => { + column => "_id", + type => "string", + index => 1, + required => 1, + unique => 1 + }, + title => + {column => "title", type => "string"}, + author => + {column => "author", type => "string"}, + date_updated => { + column => "date_updated", + type => "datetime" + }, + number => + {column => "number", type => "integer"}, + } + } + } + )->bag($bag_name); + }, + "iterator - bag $bag_name created" + ); + lives_ok(sub {$bag->delete_all;}, "iterator - bag $bag_name cleared"); + + my @d_records = map {+{author => "Dostoyevsky"}} 1 .. 10; + my @t_records = map {+{author => "Tolstoj"}} 1 .. 15; + + $bag->add_many([@d_records, @t_records]); + + #iterator select + my $iterator; + + lives_ok(sub {$iterator = $bag->select(author => "Tolstoj");}, + "iterator - select(key => value) created"); + cmp_ok($iterator->count, "==", scalar(@t_records), + "iterator - count contains correct number of records"); + + #iterator slice(start) + lives_ok( + sub { + $iterator = $bag->select(author => "Dostoyevsky")->slice(5); + }, + "iterator - slice(start) created" + ); + cmp_ok($iterator->count, "==", 5, + "slice(start)->count contains correct number of records"); + + #iterator slice(start,limit) + lives_ok( + sub { + $iterator + = $bag->select(author => "Dostoyevsky")->slice(5, 1); + }, + "iterator - slice(start,limit) created" + ); + cmp_ok($iterator->count, "==", 1, + "slice(start,limit)->count contains correct number of records"); + + #first + my $r; + lives_ok( + sub {$r = $bag->select(author => "Dostoyevsky")->first;}, + "iterator - select(key => value)->first created" + ); + isnt($r, undef, + "iterator - select(key => value)->first contains one record"); + drop($bag->store->dbh, $bag_name); + } + + done_testing 19; +} diff --git a/t/08-postgres.t b/t/08-postgres.t new file mode 100644 index 0000000..730a3ba --- /dev/null +++ b/t/08-postgres.t @@ -0,0 +1,240 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; +use Test::Exception; +use utf8; + +require Catmandu::Store::DBI; +require Catmandu::Serializer::json; + +my $driver_found = 1; +{ + local $@; + eval {require DBD::Pg;}; + if ($@) { + $driver_found = 0; + } +} +my %store_args = ( + data_source => $ENV{CATMANDU_DBI_TEST_PG_DSN}, + username => $ENV{CATMANDU_DBI_TEST_PG_USERNAME}, + password => $ENV{CATMANDU_DBI_TEST_PG_PASSWORD} +); + +if (!$driver_found) { + + plan skip_all => "database driver DBD::Pg not found"; + +} +elsif ( + !( + $ENV{CATMANDU_DBI_TEST_PG_DSN} // $ENV{CATMANDU_DBI_TEST_PG_USERNAME} + // $ENV{CATMANDU_DBI_TEST_PG_PASSWORD} + ) + ) +{ + + plan skip_all => "not all postgres connection details are set"; + +} +else { + + sub get { + my ($dbh, $table, $id_field, $id) = @_; + my $sql + = "SELECT * FROM " + . $dbh->quote_identifier($table) + . " WHERE " + . $dbh->quote_identifier($id_field) . "=?"; + my $sth = $dbh->prepare_cached($sql) or die($dbh->errstr); + $sth->execute($id) or die($sth->errstr); + $sth->fetchrow_hashref; + } + + my $record = { + _id => "彩虹小馬", + title => "My little pony", + author => "孩之寶" + }; + my $serializer = Catmandu::Serializer::json->new; + + my $pg_version; + + #implicit mapping (old behaviour) + { + my $bag; + my $bag_name = "data1"; + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new(%store_args)->bag($bag_name); + }, + "no mapping - bag $bag_name created" + ); + + $pg_version = $bag->store->dbh->{pg_server_version}; + + lives_ok(sub {$bag->delete_all;}, + "no mapping - bag $bag_name cleared"); + + lives_ok(sub {$bag->add($record);}, "no mapping - add record"); + + my $row = get($bag->store->dbh, $bag_name, "id", $record->{_id}); + $row->{data} = $serializer->deserialize($row->{data}); + + my $expected = +{id => $record->{_id}, data => {%$record}}; + delete $expected->{data}->{_id}; + + is_deeply($row, $expected, "no mapping - expected fields created"); + } + + #explicit mapping + { + my $bag; + my $bag_name = "data2"; + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new( + %store_args, + bags => { + $bag_name => { + mapping => { + _id => { + column => "_id", + type => "string", + index => 1, + required => 1, + unique => 1 + }, + title => + {column => "title", type => "string"}, + author => + {column => "author", type => "string"} + } + } + } + )->bag($bag_name); + }, + "mapping given - bag $bag_name created" + ); + lives_ok(sub {$bag->delete_all;}, + "mapping given - bag $bag_name cleared"); + + lives_ok(sub {$bag->add($record);}, + "mapping given - record added to bag $bag_name"); + + my $row = get($bag->store->dbh, $bag_name, "_id", $record->{_id}); + is_deeply $row, $record, "mapping given - expected fields created"; + + lives_ok(sub {$bag->count}, "mapping given - count ok"); + } + { + my $bag; + my $bag_name = "data3"; + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new( + %store_args, + bags => { + $bag_name => { + mapping => { + _id => { + column => "_id", + type => "string", + index => 1, + required => 1, + unique => 1 + }, + title => + {column => "title", type => "string"}, + author => + {column => "author", type => "string"} + } + } + } + )->bag($bag_name); + }, + "iterator - bag $bag_name created" + ); + lives_ok(sub {$bag->delete_all;}, "iterator - bag $bag_name cleared"); + + my @d_records = map {+{author => "Dostoyevsky"}} 1 .. 10; + my @t_records = map {+{author => "Tolstoj"}} 1 .. 15; + + $bag->add_many([@d_records, @t_records]); + + #iterator select + my $iterator; + + lives_ok(sub {$iterator = $bag->select(author => "Tolstoj");}, + "iterator - select(key => value) created"); + cmp_ok($iterator->count, "==", scalar(@t_records), + "iterator - count contains correct number of records"); + + #iterator slice(start) + lives_ok( + sub { + $iterator = $bag->select(author => "Dostoyevsky")->slice(5); + }, + "iterator - slice(start) created" + ); + cmp_ok($iterator->count, "==", 5, + "slice(start)->count contains correct number of records"); + + #iterator slice(start,limit) + lives_ok( + sub { + $iterator + = $bag->select(author => "Dostoyevsky")->slice(5, 1); + }, + "iterator - slice(start,limit) created" + ); + cmp_ok($iterator->count, "==", 1, + "slice(start,limit)->count contains correct number of records"); + + #first + my $r; + lives_ok( + sub {$r = $bag->select(author => "Dostoyevsky")->first;}, + "iterator - select(key => value)->first created" + ); + isnt($r, undef, + "iterator - select(key => value)->first contains one record"); + + } + + #test jsonb support in postgres 9.4 and above + if ($pg_version >= 90400) { + my $bag; + my $bag_name = "data4"; + lives_ok( + sub { + $bag = Catmandu::Store::DBI->new( + %store_args, + bags => { + $bag_name => { + mapping => { + _id => { + column => "_id", + type => "string", + index => 1, + required => 1, + unique => 1 + }, + _data => { + column => "data", + type => "json", + serialize => "all", + jsonb => 1 + } + } + } + } + )->bag($bag_name); + }, + "bag $bag_name with jsonb support created" + ); + } + + done_testing; +} diff --git a/t/09-serializer-json-string.t b/t/09-serializer-json-string.t new file mode 100644 index 0000000..7c536e8 --- /dev/null +++ b/t/09-serializer-json-string.t @@ -0,0 +1,51 @@ +use strict; +use warnings; +use Test::More; +use Test::Exception; + +my $pkg; + +BEGIN { + $pkg = 'Catmandu::Serializer::json_string'; + use_ok $pkg; +} + +require_ok $pkg; + +my $serializer; + +lives_ok( + sub { + $serializer = $pkg->new(); + } +); + +{ + my $data = {title => "café"}; + + lives_ok( + sub { + $data = $serializer->serialize({title => "café"}); + } + ); + + is($data, qq({"title":"café"})); + + ok(utf8::is_utf8($data)); +} + +{ + + my $data = qq({"title":"café"}); + + lives_ok( + sub { + $data = $serializer->deserialize($data); + } + ); + + is_deeply($data, {title => "café"}); + +} + +done_testing; diff --git a/t/author-pod-syntax.t b/t/author-pod-syntax.t new file mode 100644 index 0000000..2233af0 --- /dev/null +++ b/t/author-pod-syntax.t @@ -0,0 +1,15 @@ +#!perl + +BEGIN { + unless ($ENV{AUTHOR_TESTING}) { + print qq{1..0 # SKIP these tests are for testing by the author\n}; + exit + } +} + +# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. +use strict; use warnings; +use Test::More; +use Test::Pod 1.41; + +all_pod_files_ok(); -- cgit v1.2.3