diff options
author | Mason James <mtj@kohaaloha.com> | 2023-01-26 18:45:35 +1300 |
---|---|---|
committer | Mason James <mtj@kohaaloha.com> | 2023-01-26 18:45:35 +1300 |
commit | 9c0f8c2a1b5b8e01ff0f39e64a160e7859ed8283 (patch) | |
tree | be1c312cdd0e166cf79e14d4d211625b3e4cbaac |
Import original source of Catmandu-DBI 0.12
-rw-r--r-- | Build.PL | 56 | ||||
-rw-r--r-- | Changes | 145 | ||||
-rw-r--r-- | LICENSE | 379 | ||||
-rw-r--r-- | MANIFEST | 31 | ||||
-rw-r--r-- | META.json | 89 | ||||
-rw-r--r-- | META.yml | 51 | ||||
-rw-r--r-- | README | 60 | ||||
-rw-r--r-- | cpanfile | 12 | ||||
-rw-r--r-- | cpanfile.snapshot | 2013 | ||||
-rw-r--r-- | dist.ini | 4 | ||||
-rw-r--r-- | lib/Catmandu/DBI.pm | 67 | ||||
-rw-r--r-- | lib/Catmandu/Importer/DBI.pm | 143 | ||||
-rw-r--r-- | lib/Catmandu/Serializer/json_string.pm | 44 | ||||
-rw-r--r-- | lib/Catmandu/Store/DBI.pm | 418 | ||||
-rw-r--r-- | lib/Catmandu/Store/DBI/Bag.pm | 396 | ||||
-rw-r--r-- | lib/Catmandu/Store/DBI/Handler.pm | 64 | ||||
-rw-r--r-- | lib/Catmandu/Store/DBI/Handler/MySQL.pm | 117 | ||||
-rw-r--r-- | lib/Catmandu/Store/DBI/Handler/Pg.pm | 188 | ||||
-rw-r--r-- | lib/Catmandu/Store/DBI/Handler/SQLite.pm | 81 | ||||
-rw-r--r-- | lib/Catmandu/Store/DBI/Iterator.pm | 173 | ||||
-rw-r--r-- | t/00-load.t | 16 | ||||
-rw-r--r-- | t/01-load.t | 14 | ||||
-rw-r--r-- | t/02-bag.t | 96 | ||||
-rw-r--r-- | t/04-slice.t | 64 | ||||
-rw-r--r-- | t/05-importer.t | 122 | ||||
-rw-r--r-- | t/06-sqlite.t | 182 | ||||
-rw-r--r-- | t/07-mysql.t | 223 | ||||
-rw-r--r-- | t/08-postgres.t | 240 | ||||
-rw-r--r-- | t/09-serializer-json-string.t | 51 | ||||
-rw-r--r-- | t/author-pod-syntax.t | 15 |
30 files changed, 5554 insertions, 0 deletions
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; @@ -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 @@ -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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + 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. + + <signature of Ty Coon>, 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 <jakob@nichtich.de>", + "Nicolas Franck <Nicolas.Franck@ugent.be>", + "Nicolas Franck <njfranck@ca20c526.ugent.be>", + "Nicolas Franck <njfranck@ca20c533.ugent.be>", + "Nicolas Steenlant <nicolas.steenlant@gmail.com>", + "Nicolas Steenlant <nicolas.steenlant@ugent.be>", + "njfranck <njfranck@ca20c521.ugent.be>", + "Patrick Hochstenbach <patrick.hochstenbach@ugent.be>", + "vpeil <vitali.peil@uni-bielefeld.de>" + ], + "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 <jakob@nichtich.de>' + - 'Nicolas Franck <Nicolas.Franck@ugent.be>' + - 'Nicolas Franck <njfranck@ca20c526.ugent.be>' + - 'Nicolas Franck <njfranck@ca20c533.ugent.be>' + - 'Nicolas Steenlant <nicolas.steenlant@gmail.com>' + - 'Nicolas Steenlant <nicolas.steenlant@ugent.be>' + - 'njfranck <njfranck@ca20c521.ugent.be>' + - 'Patrick Hochstenbach <patrick.hochstenbach@ugent.be>' + - 'vpeil <vitali.peil@uni-bielefeld.de>' +x_generated_by_perl: v5.30.0 +x_serialization_backend: 'YAML::Tiny version 1.73' +x_static_install: 0 @@ -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 <nicolas.franck at ugent.be> + + Patrick Hochstenbach <patrick.hochstenbach at ugent.be> + + Vitali Peil <vitali.peil at uni-bielefeld.de> + + Nicolas Steenlant <nicolas.steenlant at ugent.be> + +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<Catmandu::Importer::DBI> + +L<Catmandu::Store::DBI> + +=head1 AUTHORS + +Nicolas Franck C<< <nicolas.franck at ugent.be> >> + +Patrick Hochstenbach C<< <patrick.hochstenbach at ugent.be> >> + +Vitali Peil C<< <vitali.peil at uni-bielefeld.de> >> + +Nicolas Steenlant C<< <nicolas.steenlant at ugent.be> >> + +=head1 SEE ALSO + +L<Catmandu>, L<Catmandu::Importer> , L<Catmandu::Store::DBI> + +=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<Catmandu::Importer> 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<DBI> 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<Catmandu>, L<Catmandu::Importer> , L<Catmandu::Store::DBI> + +=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<Catmandu::Serializer> + +=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<Catmandu::Store>. Databases tables are 'bags' (L<Catmandu::Bag>). + +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<DBI> 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<Catmandu::Bag> +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<Catmandu::Store::DBI::Bag> provides +a more efficiënt way to query records. + +See L<Catmandu::Store::DBI::Bag> 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<Catmandu::Serializer::json_string>, + +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<Catmandu::Serializer::json> 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<unique> 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<Catmandu::Error> 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<Catmandu::Bag>, L<DBI> + +=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<Catmandu::Store::DBI::Iterator>. + + # the select statement of $iterator2 does not have a specialized query, so it's a generic L<Catmandu::Iterator>. + # 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<store> accessor, but ensures that the table for this bag exists. + +=head2 select($key => $val) + +Overrides equivalent method in L<Catmandu::Bag>. + +Either returns a generic L<Catmandu::Iterator> or a more efficient L<Catmandu::Store::DBI::Iterator>. + +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<Catmandu::Store::DBI::Iterator>, instead of the generic L<Catmandu::Iterator>. + +=item + +the key does not have a corresponding table column configured + +The returned object is a generic L<Catmandu::Iterator>. + +This iterator can only loop over the records provided by the previous L<Catmandu::Iterable>. + +=back + +A few important notes: + +=over 4 + +=item + +A select statement only results in a L<Catmandu::Store::DBI::Iterator>, when it has a mapped key, +and the previous iterator is either a L<Catmandu::Store::DBI::Bag> or a L<Catmandu::Store::DBI::Iterator>. + +=item + +As soon as the returned object is a generic L<Catmandu::Iterator>, any following select statement +with mapped columns will not make a more efficient L<Catmandu::Store::DBI::Iterator>. + +=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<Catmandu::Store::DBI>. + +=head2 detect($key => $val) + +Overrides equivalent method in L<Catmandu::Bag>. + +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<Catmandu::Bag>. + +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<Catmandu::Bag>. + +When the source is a L<Catmandu::Store::DBI::Bag>, or a L<Catmandu::Store::DBI::Iterator>, +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 = <<SQL; +DO \$\$ +BEGIN + +IF NOT EXISTS ( + SELECT 1 + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = '${name}_${col}_idx' + AND n.nspname = 'public' + ) THEN + + CREATE INDEX ${name}_${col}_idx ON public.${name} (${q_col}); +END IF; + +END\$\$; +SQL +} + +sub create_table { + my ($self, $bag) = @_; + my $mapping = $bag->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(); |