summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMason James <mtj@kohaaloha.com>2023-01-26 18:45:35 +1300
committerMason James <mtj@kohaaloha.com>2023-01-26 18:45:35 +1300
commit9c0f8c2a1b5b8e01ff0f39e64a160e7859ed8283 (patch)
treebe1c312cdd0e166cf79e14d4d211625b3e4cbaac
Import original source of Catmandu-DBI 0.12
-rw-r--r--Build.PL56
-rw-r--r--Changes145
-rw-r--r--LICENSE379
-rw-r--r--MANIFEST31
-rw-r--r--META.json89
-rw-r--r--META.yml51
-rw-r--r--README60
-rw-r--r--cpanfile12
-rw-r--r--cpanfile.snapshot2013
-rw-r--r--dist.ini4
-rw-r--r--lib/Catmandu/DBI.pm67
-rw-r--r--lib/Catmandu/Importer/DBI.pm143
-rw-r--r--lib/Catmandu/Serializer/json_string.pm44
-rw-r--r--lib/Catmandu/Store/DBI.pm418
-rw-r--r--lib/Catmandu/Store/DBI/Bag.pm396
-rw-r--r--lib/Catmandu/Store/DBI/Handler.pm64
-rw-r--r--lib/Catmandu/Store/DBI/Handler/MySQL.pm117
-rw-r--r--lib/Catmandu/Store/DBI/Handler/Pg.pm188
-rw-r--r--lib/Catmandu/Store/DBI/Handler/SQLite.pm81
-rw-r--r--lib/Catmandu/Store/DBI/Iterator.pm173
-rw-r--r--t/00-load.t16
-rw-r--r--t/01-load.t14
-rw-r--r--t/02-bag.t96
-rw-r--r--t/04-slice.t64
-rw-r--r--t/05-importer.t122
-rw-r--r--t/06-sqlite.t182
-rw-r--r--t/07-mysql.t223
-rw-r--r--t/08-postgres.t240
-rw-r--r--t/09-serializer-json-string.t51
-rw-r--r--t/author-pod-syntax.t15
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;
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..5bf9f6c
--- /dev/null
+++ b/Changes
@@ -0,0 +1,145 @@
+Revision history for Catmandu-DBI
+
+0.12 2022-08-23 11:19:52 CEST
+ - optimize bag generator by reducing number of queries by 1
+
+0.11 2021-10-13 12:04:11 CEST
+ - bump version
+
+0.10.1 2021-10-11 09:27:51 CEST
+ - auto reconnect
+
+0.10 2021-10-11 09:17:28 CEST
+ - auto reconnect
+
+0.09 2019-06-05 11:39:38 CEST
+ - lazily create tables
+
+0.08_01 2019-06-05 11:14:40 CEST
+ - lazily create tables
+
+0.08 2018-11-08 14:28:34 CET
+ - optimize mysql select with large offsets
+ - fix Catmandu::Importer::DBI utf-8 handling
+ - The Pg handler now supports the jsonb type
+
+0.0702 2018-04-10 11:30:09 CEST
+ - document index option
+
+0.0701 2017-11-13 09:05:26 CET
+ - fix utf8 string bug
+
+0.07 2017-11-09 13:40:42 CET
+ - revert the changes made in 0.06 because of high memory usage in mysql and
+ postgresql
+
+0.06 2017-09-01 12:39:01 CEST
+ - retain sth for faster iteration
+
+0.0511 2017-01-09 13:56:38 CET
+ - more pod
+
+0.0510 2016-10-04 15:20:17 CEST
+ - use installer ModuleBuild to make Travis happy
+
+0.0509 2016-10-04 14:59:51 CEST
+ - META file
+
+0.0508 2016-10-04 14:29:12 CEST
+ - set sqlite_use_immediate_transaction to true by default to allow safer
+ multiprocess access
+
+0.0507 2016-06-21 14:20:23 CEST
+ - remove files MYMETA.json and MYMETA.yml
+
+0.0506 2016-06-08 14:51:05 CEST
+ - add prefix 'DBI:' to data_source automatically
+ - fix memory leak in Catmandu::Store::DBI::Iterator
+
+0.0505 2016-04-20 16:40:09 CEST
+ - Add more documentation
+
+0.0504 2016-02-19 12:31:06 CET
+ - support field 'datetime'
+
+0.0503 2015-10-13 16:13:54 CEST
+ - Fix typo
+
+0.0502 2015-10-13 16:07:07 CEST
+ - Fix Pg quoting on index creation
+
+0.0501 2015-10-13 11:26:42 CEST
+ - Shallow copy default mapping
+ - define VERSION in all packages
+
+0.05 2015-10-08 11:12:04 CEST
+ - Column mapping
+ - Specialized Iterator
+ - Synchronize VERSION's
+
+0.0135 2015-04-14
+ - BUG in Catmandu::Store::DBI for MySQL. identifier 'id' is of type
+ 'varchar', which is case insensitive. This can easily be solved with the
+ sql statement "alter table scans modify id varchar(255) binary".
+
+0.0134 2015-01-26
+ - BUG in Catmandu::Store::DBI: method 'dbh' crashes during global destruction
+ (after call to DEMOLISH). Fixed by not accessing DBI interface during this fase.
+
+0.0133 2015-01-16
+ - BUG in Catmandu::Store::DBI::Bag: a reference to the database handle is
+ stored in a variable outside the callback in function _build_create_*, and
+ then used inside the callback. This only works as long as the database handle
+ is active. When working with web applications, where these stores are kept in
+ memory, and not used for a long time, this can lead to inactive database
+ handles. Fix: direct reference from the callback to the function "dbh" of the
+ Catmandu::Store::DBI, that automatically reconnects if necessary.
+
+0.0132 2015-01-09
+ - BUG for DBD::Pg. When inserting data, column 'data' is not properly escaped
+ als PG_BYTEA (see _build_add_postgres in Catmandu::Store::DBI::Bag)
+
+0.0131 2015-01-09
+ - Incorrect use of 'state' in function Catmandu::Store::DBI::dbh. Due to this
+ bug, only one dbh could be stored. Now also the version in
+ Catmandu::Store::DBI is raised
+
+0.013 2015-01-08
+ - Incorrect use of 'state' in function Catmandu::Store::DBI::dbh. Due to this
+ bug, only one dbh could be stored.
+
+0.012 2014-10-02
+ - Use of function tempfile in test scripts must set the option EXLOCK to '0',
+ to avoid locking problems on BSD. This temporary file is used by SQLite,
+ but EXLOCK is set to '1' on some systems.
+
+0.011 2014-10-01
+ - Switched to Dist::Milla
+
+0.01 2014-09-26
+ - Catmandu-Store-DBI and Catmandu-Importer-DBI merged into one package
+ Catmandu-DBI
+
+Revision history for Catmandu-Importer-DBI
+
+0.03 2014-03-26
+ - adding support for accessing the database handle
+
+0.02 2014-02-21
+ - deleted internal test
+
+0.01 2014-02-21
+ - initial version
+
+Revision history for Catmandu-Store-DBI
+
+0.041 2014-09-25
+ - add support for timeout and auto reconnect
+
+0.03 2014-03-07
+ - add support for postgres >= 9.1
+ - optimized slice implementation
+ - mysql auto reconnect
+
+0.02 2013-06-12
+ - initial release of Catmandu::Store::DBI as own package
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..64ca3a6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,379 @@
+This software is copyright (c) 2022 by Nicolas Steenlant.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+Terms of the Perl programming language system itself
+
+a) the GNU General Public License as published by the Free
+ Software Foundation; either version 1, or (at your option) any
+ later version, or
+b) the "Artistic License"
+
+--- The GNU General Public License, Version 1, February 1989 ---
+
+This software is Copyright (c) 2022 by Nicolas Steenlant.
+
+This is free software, licensed under:
+
+ The GNU General Public License, Version 1, February 1989
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 1, February 1989
+
+ Copyright (C) 1989 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The license agreements of most software companies try to keep users
+at the mercy of those companies. By contrast, our General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. The
+General Public License applies to the Free Software Foundation's
+software and to any other program whose authors commit to using it.
+You can use it for your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Specifically, the General Public License is designed to make
+sure that you have the freedom to give away or sell copies of free
+software, that you receive source code or can get it if you want it,
+that you can change the software or use pieces of it in new free
+programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of a such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must tell them their rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any program or other work which
+contains a notice placed by the copyright holder saying it may be
+distributed under the terms of this General Public License. The
+"Program", below, refers to any such program or work, and a "work based
+on the Program" means either the Program or any work containing the
+Program or a portion of it, either verbatim or with modifications. Each
+licensee is addressed as "you".
+
+ 1. You may copy and distribute verbatim copies of the Program's source
+code as you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and
+disclaimer of warranty; keep intact all the notices that refer to this
+General Public License and to the absence of any warranty; and give any
+other recipients of the Program a copy of this General Public License
+along with the Program. You may charge a fee for the physical act of
+transferring a copy.
+
+ 2. You may modify your copy or copies of the Program or any portion of
+it, and copy and distribute such modifications under the terms of Paragraph
+1 above, provided that you also do the following:
+
+ a) cause the modified files to carry prominent notices stating that
+ you changed the files and the date of any change; and
+
+ b) cause the whole of any work that you distribute or publish, that
+ in whole or in part contains the Program or any part thereof, either
+ with or without modifications, to be licensed at no charge to all
+ third parties under the terms of this General Public License (except
+ that you may choose to grant warranty protection to some or all
+ third parties, at your option).
+
+ c) If the modified program normally reads commands interactively when
+ run, you must cause it, when started running for such interactive use
+ in the simplest and most usual way, to print or display an
+ announcement including an appropriate copyright notice and a notice
+ that there is no warranty (or else, saying that you provide a
+ warranty) and that users may redistribute the program under these
+ conditions, and telling the user how to view a copy of this General
+ Public License.
+
+ d) You may charge a fee for the physical act of transferring a
+ copy, and you may at your option offer warranty protection in
+ exchange for a fee.
+
+Mere aggregation of another independent work with the Program (or its
+derivative) on a volume of a storage or distribution medium does not bring
+the other work under the scope of these terms.
+
+ 3. You may copy and distribute the Program (or a portion or derivative of
+it, under Paragraph 2) in object code or executable form under the terms of
+Paragraphs 1 and 2 above provided that you also do one of the following:
+
+ a) accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of
+ Paragraphs 1 and 2 above; or,
+
+ b) accompany it with a written offer, valid for at least three
+ years, to give any third party free (except for a nominal charge
+ for the cost of distribution) a complete machine-readable copy of the
+ corresponding source code, to be distributed under the terms of
+ Paragraphs 1 and 2 above; or,
+
+ c) accompany it with the information you received as to where the
+ corresponding source code may be obtained. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form alone.)
+
+Source code for a work means the preferred form of the work for making
+modifications to it. For an executable file, complete source code means
+all the source code for all modules it contains; but, as a special
+exception, it need not include source code for modules which are standard
+libraries that accompany the operating system on which the executable
+file runs, or for standard header files or definitions files that
+accompany that operating system.
+
+ 4. You may not copy, modify, sublicense, distribute or transfer the
+Program except as expressly provided under this General Public License.
+Any attempt otherwise to copy, modify, sublicense, distribute or transfer
+the Program is void, and will automatically terminate your rights to use
+the Program under this License. However, parties who have received
+copies, or rights to use copies, from you under this General Public
+License will not have their licenses terminated so long as such parties
+remain in full compliance.
+
+ 5. By copying, distributing or modifying the Program (or any work based
+on the Program) you indicate your acceptance of this license to do so,
+and all its terms and conditions.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the original
+licensor to copy, distribute or modify the Program subject to these
+terms and conditions. You may not impose any further restrictions on the
+recipients' exercise of the rights granted herein.
+
+ 7. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of the license which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+the license, you may choose any version ever published by the Free Software
+Foundation.
+
+ 8. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ Appendix: How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to humanity, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these
+terms.
+
+ To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively convey
+the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <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
diff --git a/README b/README
new file mode 100644
index 0000000..e15653b
--- /dev/null
+++ b/README
@@ -0,0 +1,60 @@
+NAME
+
+ Catmandu::DBI - Catmandu tools to communicate with DBI based interfaces
+
+SYNOPSIS
+
+ # From the command line
+
+ # Export data from a relational database
+ $ catmandu convert DBI --dsn dbi:mysql:foobar --user foo --password bar --query "select * from table"
+
+ # Import data into a relational database
+ $ catmandu import JSON to DBI --data_source dbi:SQLite:mydb.sqlite < data.json
+
+ # Export data from a relational database
+ $ catmandu export DBI --data_source dbi:SQLite:mydb.sqlite to JSON
+
+ # Or via a configuration file
+ $ cat catmandu.yml
+ ---
+ store:
+ mydb:
+ package: DBI
+ options:
+ data_source: "dbi:mysql:database=mydb"
+ username: xyz
+ password: xyz
+ ...
+ $ catmandu import JSON to mydb < data.json
+ $ catmandu export mydb to YAML > data.yml
+
+ # Export one record
+ $ catmandu export mydb --id 012E929E-FF44-11E6-B956-AE2804ED5190 to JSON > record.json
+
+ # Count the number of records
+ $ catmandu count mydb
+
+ # Delete data
+ $ catmandy delete mydb
+
+MODULES
+
+ Catmandu::Importer::DBI
+
+ Catmandu::Store::DBI
+
+AUTHORS
+
+ Nicolas Franck <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();