DBIx-MultiStatementDo-1.00009000755001750001750 011524460461 15641 5ustar00emazepemazep000000000000README000644001750001750 3417111524460461 16627 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009NAME DBIx::MultiStatementDo - Multiple SQL statements in a single do() call with any DBI driver VERSION version 1.00009 SYNOPSIS use DBI; use DBIx::MultiStatementDo; # Multiple SQL statements in a single string my $sql_code = <<'SQL'; CREATE TABLE parent (a, b, c , d ); CREATE TABLE child (x, y, "w;", "z;z"); /* C-style comment; */ CREATE TRIGGER "check;delete;parent;" BEFORE DELETE ON parent WHEN EXISTS (SELECT 1 FROM child WHERE old.a = x AND old.b = y) BEGIN SELECT RAISE(ABORT, 'constraint failed;'); -- Inlined SQL comment END; -- Standalone SQL; comment; w/ semicolons; INSERT INTO parent (a, b, c, d) VALUES ('pippo;', 'pluto;', NULL, NULL); SQL my $dbh = DBI->connect( 'dbi:SQLite:dbname=my.db', '', '' ); my $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); # Multiple SQL statements in a single call my @results = $batch->do( $sql_code ) or die $batch->dbh->errstr; print scalar(@results) . ' statements successfully executed!'; # 4 statements successfully executed! DESCRIPTION Some DBI drivers don't support the execution of multiple statements in a single "do()" call. This module tries to overcome such limitation, letting you execute any number of SQL statements (of any kind, not only DDL statements) in a single batch, with any DBI driver. Here is how DBIx::MultiStatementDo works: behind the scenes it parses the SQL code, splits it into the atomic statements it is composed of and executes them one by one. To split the SQL code SQL::SplitStatement is used, which uses a more sophisticated logic than a raw "split" on the ";" (semicolon) character: first, various different statement terminator *tokens* are recognized, then SQL::SplitStatement is able to correctly handle the presence of said tokens inside identifiers, values, comments, "BEGIN ... END" blocks (even nested), *dollar-quoted* strings, MySQL custom "DELIMITER"s, procedural code etc., as (partially) exemplified in the "SYNOPSIS" above. Automatic transactions support is offered by default, so that you'll have the *all-or-nothing* behaviour you would probably expect; if you prefer, you can anyway disable it and manage the transactions yourself. METHODS "new" * "DBIx::MultiStatementDo->new( %options )" * "DBIx::MultiStatementDo->new( \%options )" It creates and returns a new DBIx::MultiStatementDo object. It accepts its options either as an hash or an hashref. The following options are recognized: * "dbh" The database handle object as returned by DBI::connect(). This option is required. * "rollback" A Boolean option which enables (when true) or disables (when false) automatic transactions. It is set to a true value by default. * "splitter_options" This is the options hashref which is passed unaltered to "SQL::SplitStatement->new()" to build the *splitter object*, which is then internally used by DBIx::MultiStatementDo to split the given SQL string. It defaults to "undef", which should be the best value if the given SQL string contains only standard SQL. If it contains contains also procedural code, you may need to fine tune this option. Please refer to SQL::SplitStatement::new() to see the options it takes. "do" * "$batch->do( $sql_string | \@sql_statements )" * "$batch->do( $sql_string | \@sql_statements , \%attr )" * "$batch->do( $sql_string | \@sql_statements , \%attr, \@bind_values | @bind_values )" This is the method which actually executes the SQL statements against your db. As its first (mandatory) argument, it takes an SQL string containing one or more SQL statements. The SQL string is split into its atomic statements, which are then executed one-by-one, in the same order they appear in the given string. The first argument can also be a reference to a list of (already split) statements, in which case no split is performed and the statements are executed as they appear in the list. The list can also be a two-elements list, where the first element is the statements listref as above, and the second is the *placeholder numbers* listref, exactly as returned by the SQL::SplitStatement::split_with_placeholders() method. Analogously to DBI's "do()", it optionally also takes an hashref of attributes (which is passed unaltered to "$batch->dbh->do()" for each atomic statement), and the *bind values*, either as a listref or a flat list (see below for the difference). In list context, "do" returns a list containing the values returned by the "$batch->dbh->do()" call on each single atomic statement. If the "rollback" option has been set (and therefore automatic transactions are enabled), in case one of the atomic statements fails, all the other succeeding statements executed so far, if any, are rolled back and the method (immediately) returns an empty list (since no statements have actually been committed). If the "rollback" option is set to a false value (and therefore automatic transactions are disabled), the method immediately returns at the first failing statement as above, but it does not roll back any prior succeeding statement, and therefore a list containing the values returned by the statements (successfully) executed so far is returned (and these statements are actually committed to the db, if "$dbh->{AutoCommit}" is set). In scalar context it returns, regardless of the value of the "rollback" option, "undef" if any of the atomic statements failed, or a true value if all of the atomic statements succeeded. Note that to activate the automatic transactions you don't have to do anything more than setting the "rollback" option to a true value (or simply do nothing, as it is the default): DBIx::MultiStatementDo will automatically (and temporarily, via "local") set "$dbh->{AutoCommit}" and "$dbh->{RaiseError}" as needed. No other DBI db handle attribute is ever touched, so that you can for example set "$dbh->{PrintError}" and enjoy its effects in case of a failing statement. If you want to disable the automatic transactions and manage them by yourself, you can do something along this: my $batch = DBIx::MultiStatementDo->new( dbh => $dbh, rollback => 0 ); my @results; $batch->dbh->{AutoCommit} = 0; $batch->dbh->{RaiseError} = 1; eval { @results = $batch->do( $sql_string ); $batch->dbh->commit; 1 } or eval { $batch->dbh->rollback }; Bind Values as a List Reference The bind values can be passed as a reference to a list of listrefs, each of which contains the bind values for the atomic statement it corresponds to. The bind values *inner* lists must match the corresponding atomic statements as returned by the internal *splitter object*, with "undef" (or empty listref) elements where the corresponding atomic statements have no *placeholders*. Here is an example: # 7 statements (SQLite valid SQL) my $sql_code = <<'SQL'; CREATE TABLE state (id, name); INSERT INTO state (id, name) VALUES (?, ?); CREATE TABLE city (id, name, state_id); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); DROP TABLE city; DROP TABLE state SQL # Only 5 elements are required in the bind values list my $bind_values = [ undef , # or [] [ 1, 'Nevada' ] , [] , # or undef [ 1, 'Las Vegas' , 1 ], [ 2, 'Carson City', 1 ] ]; my $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); my @results = $batch->do( $sql_code, undef, $bind_values ) or die $batch->dbh->errstr; If the last statements have no placeholders, the corresponding "undef"s don't need to be present in the bind values list, as shown above. The bind values list can also have more elements than the number of the atomic statements, in which case the excess elements will simply be ignored. Bind Values as a Flat List This is a much more powerful feature of "do": when it gets the bind values as a flat list, it automatically assigns them to the corresponding placeholders (no *interleaving* "undef"s are necessary in this case). In other words, you can regard the given SQL code as a single big statement and pass the bind values exactly as you would do with the ordinary DBI "do" method. For example, given $sql_code from the example above, you could simply do: my @bind_values = ( 1, 'Nevada', 1, 'Las Vegas', 1, 2, 'Carson City', 1 ); my @results = $batch->do( $sql_code, undef, @bind_values ) or die $batch->dbh->errstr; and get exactly the same result. Difference between Bind Values as a List Reference and as a Flat List If you want to pass the bind values as a flat list as described above, you must pass the first parameter to "do" either as a string (so that the internal splitting is performed) or, if you want to disable the internal splitting, as a reference to the two-elements list containing both the statements and the placeholder numbers listrefs (as described above in do). In other words, you can't pass the bind values as a flat list and pass at the same time the (already split) statements without the placeholder numbers listref. To do so, you need to pass the bind values as a list reference instead, otherwise "do" throws an exception. To summarize, bind values as a flat list is easier to use but it suffers from this subtle limitation, while bind values as a list reference is a little bit more cumbersome to use, but it has no limitations and can therefore always be used. Recognized Placeholders The recognized placeholders are: * *question mark* placeholders, represented by the "?" character; * *dollar sign numbered* placeholders, represented by the "$1, $2, ..., $n" strings; * *named parameters*, such as ":foo", ":bar", ":baz" etc. "dbh" * "$batch->dbh" * "$batch->dbh( $new_dbh )" Getter/setter method for the "dbh" option explained above. "rollback" * "$batch->rollback" * "$batch->rollback( $boolean )" Getter/setter method for the "rollback" option explained above. "splitter_options" * "$batch->splitter_options" * "$batch->splitter_options( \%options )" Getter/setter method for the "splitter_options" option explained above. "split" and "split_with_placeholders" * "$batch->split( $sql_code )" * "$batch->split_with_placeholders( $sql_code )" These are the methods used internally to split the given SQL code. They call respectively "split" and "split_with_placeholders" on a SQL::SplitStatement instance built with the "splitter_options" described above. Normally they shouldn't be used directly, but they could be useful if you want to see how your SQL code has been split. If you want instead to see how your SQL code *will be* split, that is before executing "do", you can use SQL::SplitStatement by yourself: use SQL::SplitStatement; my $splitter = SQL::SplitStatement->new( \%splitter_options ); my @statements = $splitter->split( $sql_code ); # Now you can check @statements if you want... and then you can execute your statements preventing "do" from performing the splitting again, by passing "\@statements" to it: my $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); my @results = $batch->do( \@statements ); # This does not perform the splitting again. Warning! In previous versions, the "split_with_placeholders" (public) method documented above did not work, so there is the possibility that someone used the (private, undocumented) "_split_with_placeholders" method instead (which worked correctly). In this case, please start using the public method (which now works as advertised), since the private method will be removed in future versions. LIMITATIONS Please look at: SQL::SplitStatement LIMITATIONS DEPENDENCIES DBIx::MultiStatementDo depends on the following modules: * SQL::SplitStatement 0.10000 or newer * Moose AUTHOR Emanuele Zeppieri, "" BUGS No known bugs so far. Please report any bugs or feature requests to "bug-dbix-MultiStatementDo at rt.cpan.org", or through the web interface at . I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. SUPPORT You can find documentation for this module with the perldoc command. perldoc DBIx::MultiStatementDo You can also look for information at: * RT: CPAN's request tracker * AnnoCPAN: Annotated CPAN documentation * CPAN Ratings * Search CPAN ACKNOWLEDGEMENTS Matt S Trout, for having suggested a much more suitable name for this module. SEE ALSO * SQL::SplitStatement * DBI LICENSE AND COPYRIGHT Copyright 2010-2011 Emanuele Zeppieri. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation, or the Artistic License. See http://dev.perl.org/licenses/ for more information. Changes000600001750001750 650711524460461 17214 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009Revision history for DBIx-MultiStatementDo 1.00009 2011-02-09 10:41:02 Europe/Rome * POD formatting fixes to obtain a better (automatically generated) README.mkdn. * Minimum SQL::SplitStatement required version bumped to 1.00009 1.00000 2011-02-09 01:50:43 Europe/Rome * Doc updates about the new placeholders types now recognized (/dollar sign numbers/ and /named parameters/). * Minimum SQL::SplitStatement required version bumped to 1.00000 * Markdown README added (via Dist::Zilla::Plugin::ReadmeMarkdownFromPod). 0.30000 2011-01-23 11:03:12 Europe/Rome * Minimum SQL::SplitStatement required version bumped to 0.30000 * Minor cosmetic doc fixes. 0.20000 2011-01-21 19:14:03 Europe/Rome * Missing DBD::SQLite dependence for testing fixed. * Minimum SQL::SplitStatement required version has been bumped to 0.20000 0.10000 2011-01-20 07:00:00 Europe/Rome * Minimum SQL::SplitStatement required version has been bumped to 0.10000 * Clarified the difference between bind values as a list reference and as a flat list. * Tests for this. * _split_with_placeholders removed for good (end of deprecation cycle). * Various code refactorings. 0.06001 2010-06-23 06:10:00 Europe/Rome * Simply a bug in this Changes file! (The method "split_with_placeholders" described below, was called "splitter_with_placeholders"). 0.06000 2010-06-23 06:00:00 Europe/Rome * By an unfortunate accident, the advertised "split_with_placeholders" (instance) method did not exist, since it had been renamed to the (undocumented) "_split_with_placeholders". Now "split_with_placeholders" has been restored (and works exaclty as documented), while "_split_with_placeholders" has been left just in case someone who did not find the public method was using it. * Minimum SQL::SplitStatement required version has been bumped to 0.05003. * Minor docs and tests additions. * End of deprecation cycle on "split" as a class method. 0.05000 2010-06-17 05:00:00 Europe/Rome * "do()" can now take also a list of (already split) statements as its first parameter, in which case no split is performed. * Now SQL-SplitStatement-0.05000 is required, which has many enhancements. 0.03000 2010-05-30 04:00:00 Europe/Rome # NEVER RELEASED TO CPAN * "do()" can now take /flat/ "@bind_values" exaclty as DBI's "do()". * Tests and docs for this. * SQL-SplitStatement-0.03000 is now required, which has the "split_with_placeholders" method. * Assorted minor code and docs enhancements. 0.02000 2010-05-29 05:00:00 Europe/Rome * "do()" can now take "\%attr" and "\@bind_values" as optional arguments, like DBI's "do()". * Added tests and docs for this. 0.01002 2010-05-28 19:15:00 Europe/Rome * Now SQL-SplitStatement-0.01001 is required, which has the ability to discard comments. * Added tests and docs for this. 0.01001 2010-05-27 08:20:00 Europe/Rome * Removed "use strict" and "use warnings" from MultiStatementDo.pm, as Moose already activates them. * The splitting functionality has been extracted and put in a standalone module (SQL::SplitStatement). - Tests and POD modified accordingly. - Starting of deprecation cycle of DBIx::MultiStatementDo->split. 0.01000 2010-05-22 02:55:00 Europe/Rome * Initial release (renamed from DBIx-DoMore). LICENSE000644001750001750 4353311524460461 16756 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009This software is copyright (c) 2011 by Emanuele Zeppieri. 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) 2011 by Emanuele Zeppieri. 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. 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2011 by Emanuele Zeppieri. 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 INSTALL000644001750001750 177111524460461 16760 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009 This is the Perl distribution DBIx-MultiStatementDo. Installing DBIx-MultiStatementDo is straightforward. ## Installation with cpanm If you have cpanm, you only need one line: % cpanm DBIx::MultiStatementDo If you are installing into a system-wide directory, you may need to pass the "-S" flag to cpanm, which uses sudo to install the module: % cpanm -S DBIx::MultiStatementDo ## Installing with the CPAN shell Alternatively, if your CPAN shell is set up, you should just be able to do: % cpan DBIx::MultiStatementDo ## Manual installation As a last resort, you can manually install it. Download the tarball, untar it, then build it: % perl Makefile.PL % make && make test Then install it: % make install If you are installing into a system-wide directory, you may need to run: % sudo make install ## Documentation DBIx-MultiStatementDo documentation is available as POD. You can run perldoc from a shell to read the documentation: % perldoc DBIx::MultiStatementDo META.yml000644001750001750 152211524460461 17172 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009--- abstract: 'Multiple SQL statements in a single do() call with any DBI driver' author: - 'Emanuele Zeppieri ' build_requires: DBD::SQLite: 0 DBI: 0 English: 0 File::Find: 0 File::Temp: 0 Test::Exception: 0.27 Test::More: 0.7 configure_requires: ExtUtils::MakeMaker: 6.31 dynamic_config: 0 generated_by: 'Dist::Zilla version 4.200003, CPAN::Meta::Converter version 2.110360' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: DBIx-MultiStatementDo requires: Carp: 0 Moose: 0 SQL::SplitStatement: 1.00009 perl: 5.006 resources: bugtracker: http://rt.cpan.org/NoAuth/Bugs.html?Dist=DBIx-MultiStatementDo homepage: http://search.cpan.org/dist/DBIx-MultiStatementDo repository: git://github.com/emazep/DBIx-MultiStatementDo.git version: 1.00009 MANIFEST000644001750001750 75311524460461 17037 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009Changes INSTALL LICENSE MANIFEST META.json META.yml Makefile.PL README lib/DBIx/MultiStatementDo.pm t/00-compile.t t/00-load.t t/author-critic.t t/bind_values_flat.t t/bind_values_ref.t t/bind_values_ref_flat_exception.t t/do_firstarg_as_list.t t/live_manual_rollback.t t/live_no_rollback.t t/live_rollback.t t/my_synopsis.t t/release-distmeta.t t/release-kwalitee.t t/release-no-tabs.t t/release-pod-coverage.t t/release-pod-syntax.t t/release-unused-vars.t t/split_hard.t t/split_simple.t META.json000644001750001750 305011524460461 17340 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009{ "abstract" : "Multiple SQL statements in a single do() call with any DBI driver", "author" : [ "Emanuele Zeppieri " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 4.200003, CPAN::Meta::Converter version 2.110360", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "DBIx-MultiStatementDo", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.31" } }, "runtime" : { "requires" : { "Carp" : 0, "Moose" : 0, "SQL::SplitStatement" : "1.00009", "perl" : "5.006" } }, "test" : { "requires" : { "DBD::SQLite" : 0, "DBI" : 0, "English" : 0, "File::Find" : 0, "File::Temp" : 0, "Test::Exception" : "0.27", "Test::More" : "0.7" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "mailto" : "bug-DBIx-MultiStatementDo@rt.cpan.org", "web" : "http://rt.cpan.org/NoAuth/Bugs.html?Dist=DBIx-MultiStatementDo" }, "homepage" : "http://search.cpan.org/dist/DBIx-MultiStatementDo", "repository" : { "type" : "git", "url" : "git://github.com/emazep/DBIx-MultiStatementDo.git", "web" : "http://github.com/emazep/DBIx-MultiStatementDo" } }, "version" : "1.00009" } t000755001750001750 011524460461 16025 5ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.0000900-load.t000600001750001750 32311524460461 17453 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!/usr/bin/env perl use Test::More tests => 1; BEGIN { use_ok( 'DBIx::MultiStatementDo' ) || print "Bail out! "; } diag( "Testing DBIx::MultiStatementDo $DBIx::MultiStatementDo::VERSION, Perl $], $^X" ); Makefile.PL000644001750001750 242411524460461 17675 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009 use strict; use warnings; BEGIN { require 5.006; } use ExtUtils::MakeMaker 6.31; my %WriteMakefileArgs = ( 'ABSTRACT' => 'Multiple SQL statements in a single do() call with any DBI driver', 'AUTHOR' => 'Emanuele Zeppieri ', 'BUILD_REQUIRES' => { 'DBD::SQLite' => '0', 'DBI' => '0', 'English' => '0', 'File::Find' => '0', 'File::Temp' => '0', 'Test::Exception' => '0.27', 'Test::More' => '0.7' }, 'CONFIGURE_REQUIRES' => { 'ExtUtils::MakeMaker' => '6.31' }, 'DISTNAME' => 'DBIx-MultiStatementDo', 'EXE_FILES' => [], 'LICENSE' => 'perl', 'NAME' => 'DBIx::MultiStatementDo', 'PREREQ_PM' => { 'Carp' => '0', 'Moose' => '0', 'SQL::SplitStatement' => '1.00009' }, 'VERSION' => '1.00009', 'test' => { 'TESTS' => 't/*.t' } ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.56) } ) { my $br = delete $WriteMakefileArgs{BUILD_REQUIRES}; my $pp = $WriteMakefileArgs{PREREQ_PM}; for my $mod ( keys %$br ) { if ( exists $pp->{$mod} ) { $pp->{$mod} = $br->{$mod} if $br->{$mod} > $pp->{$mod}; } else { $pp->{$mod} = $br->{$mod}; } } } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); split_hard.t000600001750001750 236411524460461 20477 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!/usr/bin/env perl use strict; use warnings; use DBI; use DBIx::MultiStatementDo; use Test::More tests => 3; my @statements; my $sql = <<'SQL'; CREATE TABLE child( x, y, "w;", "z;z", FOREIGN KEY (x, y) REFERENCES parent (a,b) ); -- SQL; comment; CREATE TABLE parent( a, b, c, d, PRIMARY KEY(a, b) ); CREATE TRIGGER genfkey1_delete_referenced BEFORE DELETE ON "parent" WHEN EXISTS (SELECT 1 FROM "child" WHERE old."a" == "x" AND old."b" == "y") BEGIN SELECT RAISE(ABORT, 'constraint failed'); -- Inlined comment END SQL my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', '', '' ); @statements = @{ ( DBIx::MultiStatementDo->new(dbh => $dbh)->split_with_placeholders($sql) )[0] }; cmp_ok ( @statements, '==', 3, 'correct number of statements - class method' ); my $sql_splitter = DBIx::MultiStatementDo->new( dbh => $dbh, splitter_options => { keep_terminator => 1, keep_extra_spaces => 1, keep_comments => 1, keep_empty_statements => 1 } ); @statements = $sql_splitter->split($sql); cmp_ok ( scalar(@statements), '==', 3, 'correct number of statements - instance method' ); is ( join('', @statements), $sql, 'code successfully rebuilt - instance method' ); 00-compile.t000644001750001750 204111524460461 20213 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!perl use strict; use warnings; use Test::More; use File::Find; use File::Temp qw{ tempdir }; my @modules; find( sub { return if $File::Find::name !~ /\.pm\z/; my $found = $File::Find::name; $found =~ s{^lib/}{}; $found =~ s{[/\\]}{::}g; $found =~ s/\.pm$//; # nothing to skip push @modules, $found; }, 'lib', ); my @scripts = glob "bin/*"; my $plan = scalar(@modules) + scalar(@scripts); $plan ? (plan tests => $plan) : (plan skip_all => "no tests to run"); { # fake home for cpan-testers # no fake requested ## local $ENV{HOME} = tempdir( CLEANUP => 1 ); like( qx{ $^X -Ilib -e "require $_; print '$_ ok'" }, qr/^\s*$_ ok/s, "$_ loaded ok" ) for sort @modules; SKIP: { eval "use Test::Script 1.05; 1;"; skip "Test::Script needed to test script compilation", scalar(@scripts) if $@; foreach my $file ( @scripts ) { my $script = $file; $script =~ s!.*/!!; script_compiles( $file, "$script script compiles" ); } } } my_synopsis.t000600001750001750 137211524460461 20740 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/tuse strict; use warnings; use DBI; use DBIx::MultiStatementDo; use Test::More tests => 1; my $create = <<'SQL'; CREATE TABLE parent(a, b, c , d ); CREATE TABLE child (x, y, "w;", "z;z"); CREATE TRIGGER "check;delete;parent;" BEFORE DELETE ON parent WHEN EXISTS (SELECT 1 FROM child WHERE old.a = x AND old.b = y) BEGIN SELECT RAISE(ABORT, 'constraint failed;'); END; INSERT INTO parent (a, b, c, d) VALUES ('pippo;', 'pluto;', NULL, NULL) SQL my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', '', '' ); my $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); # Multiple SQL statements in a single call my @results = $batch->do($create); #diag scalar(@results) . ' statements successfully executed'; ok ( @results == 4, 'check success' ); split_simple.t000600001750001750 243411524460461 21050 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!/usr/bin/env perl use strict; use warnings; use DBI; use DBIx::MultiStatementDo; use Test::More tests => 4; my @statements_w_placeholders; my @statements_wo_placeholders; my $sql = <<'SQL'; CREATE TABLE foo ( foo_field_1 VARCHAR, foo_field_2 VARCHAR ); CREATE TABLE bar ( bar_field_1 VARCHAR, bar_field_2 VARCHAR ); SQL my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', '', '' ); my $splitter_options = { keep_terminators => 1, keep_extra_spaces => 1, keep_empty_statements => 1 }; my $sql_splitter = DBIx::MultiStatementDo->new( dbh => $dbh, splitter_options => $splitter_options ); @statements_w_placeholders = @{ ( $sql_splitter->split_with_placeholders($sql) )[0] }; ok ( @statements_w_placeholders == 3, 'correct number of statements - instance method all set' ); is ( join('', @statements_w_placeholders), $sql, 'code successfully rebuilt - instance method all set' ); @statements_wo_placeholders = $sql_splitter->new( dbh => $dbh, splitter_options => $splitter_options )->split($sql); cmp_ok ( scalar(@statements_wo_placeholders), '==', 3, 'number of statements returned by split' ); is_deeply( \@statements_w_placeholders, \@statements_wo_placeholders, 'statements w/ and w/o placeholders match' ) live_rollback.t000600001750001750 437511524460461 21162 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!/usr/bin/env perl use strict; use warnings; use DBI; use DBIx::MultiStatementDo; use Test::More tests => 11; my $create = <<'SQL'; CREATE TABLE parent( a, b, c, d, PRIMARY KEY(a, b) ); CREATE TABLE child( x, y, "w;", "z;z", FOREIGN KEY (x, y) REFERENCES parent (a,b) ); CREATE TRIGGER "foreign;key" BEFORE DELETE ON "parent" WHEN EXISTS (SELECT 1 FROM "child" WHERE old."a" == "x" AND old."b" == "y") BEGIN SELECT RAISE(ABORT, 'constraint failed;'); END; SQL my $drop = <<'SQL'; DROP TRIGGER "foreign;key"; DROP TABLE child; DROP TABLE parent; SQL my $insert_correct = <<'SQL'; INSERT INTO parent (a, b, c, d) VALUES ('pippo1;', ';pluto1', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo2;', ';pluto2', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo3;', ';pluto3', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo4;', ';pluto4', NULL, NULL); SQL my $insert_bad = <<'SQL'; INSERT INTO parent (a, b, c, d) VALUES ('pippo5', 'pluto5', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo6', 'pluto6' ; SQL my $insert_bad2 = <<'SQL'; INSERT INTO parent (a, b, c, d) VALUES ('pippo7', 'pluto7', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo8', 'pluto8' ; SQL my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', '', '', { PrintError => 0 }); my $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); my @results; my $result; ok ( @results = $batch->do($create), 'multiple create on sqlite' ); cmp_ok ( scalar(@results), '==', 3, 'check success' ); ok ( @results = $batch->do($insert_correct), 'multiple correct INSERTs on sqlite' ); cmp_ok ( scalar(@results), '==', 4, 'check success' ); @results = $batch->do($insert_bad); cmp_ok ( scalar(@results), '==', 0, 'check failure' ); $result = $batch->do($insert_bad2); ok ( ! $result, 'multiple mixed INSERTs, check failure in scalar context' ); @results = $batch->do($insert_bad2); ok ( ! @results, 'multiple mixed INSERTs, check failure in list context' ); ok ( @results = $batch->do($drop), 'multiple drop on sqlite' ); cmp_ok ( scalar(@results), '==', 3, 'check success' ); ok ( $batch->dbh->{AutoCommit} , '$dbh->{AutoCommit} automatically restored' ); ok ( ! $batch->dbh->{RaiseError}, '$dbh->{RaiseError} automatically restored' ); author-critic.t000644001750001750 66611524460461 21116 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for testing by the author'); } } use strict; use warnings; use Test::More; use English qw(-no_match_vars); eval "use Test::Perl::Critic"; plan skip_all => 'Test::Perl::Critic required to criticise code' if $@; Test::Perl::Critic->import( -profile => "perlcritic.rc" ) if -e "perlcritic.rc"; all_critic_ok(); bind_values_ref.t000600001750001750 416611524460461 21477 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!/usr/bin/env perl use strict; use warnings; use DBI; use DBIx::MultiStatementDo; use SQL::SplitStatement; use Test::More tests => 6; my $sql_code; my @bind_values; my @results; my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', '', '', { PrintError => 0 }); my $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); # SQLite valid SQL $sql_code = <<'SQL'; CREATE TABLE state (id, name); INSERT INTO state (id, name) VALUES (?, ?); CREATE TABLE city (id, name, state_id); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?) SQL @bind_values = ( undef , # or [] [ 1, 'New York' ] , [] , # or undef [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ], undef , [] ); ok ( @results = $batch->do( $sql_code, undef, \@bind_values ), 'mixed statements w/ bind values on sqlite' ); cmp_ok ( scalar(@results), '==', 5, 'check execution' ); my $cities = $dbh->selectall_arrayref( 'SELECT id, name, state_id FROM city ORDER BY id' ); is_deeply( $cities, [ [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ] ], 'CREATEs mixed with INSERTs with bind values' ); # --- # Now check the case of a list of already split statement passed to do() # --- $sql_code = <<'SQL'; DROP TABLE state; DROP TABLE city; CREATE TABLE state (id, name); INSERT INTO state (id, name) VALUES (?, ?); CREATE TABLE city (id, name, state_id); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?) SQL @bind_values = ( undef , [] , undef , [ 1, 'New York' ] , [] , [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ], undef , [] ); my $splitter = SQL::SplitStatement->new; my @statements; @statements = $splitter->split($sql_code); cmp_ok ( scalar(@statements), '==', 7, 'check splitting' ); ok ( @results = $batch->do( \@statements, undef, \@bind_values ), 'mixed already split statements w/ bind values on sqlite' ); cmp_ok ( scalar(@results), '==', 7, 'check execution' ); release-no-tabs.t000644001750001750 45011524460461 21311 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use strict; use warnings; use Test::More; eval 'use Test::NoTabs'; plan skip_all => 'Test::NoTabs required' if $@; all_perl_files_ok(); bind_values_flat.t000600001750001750 206511524460461 21645 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!/usr/bin/env perl use strict; use warnings; use DBI; use DBIx::MultiStatementDo; use Test::More tests => 3; my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', '', '', { PrintError => 0 }) or die $DBI::errstr; my $sql_code; my @bind_values; my $batch; my $cities; $sql_code = <<'SQL'; CREATE TABLE state (id, name); INSERT INTO state (id, name) VALUES (?, ?); CREATE TABLE city (id, name, state_id); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); DROP TABLE state; SQL @bind_values = (1, 'New York', 1, 'Albany', 1, 2, 'Buffalo', 1); $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); my @results; ok ( @results = $batch->do( $sql_code, undef, @bind_values ), 'mixed statements w/ bind values on sqlite' ); cmp_ok ( scalar(@results), '==', 6, 'check success' ); $cities = $dbh->selectall_arrayref( 'SELECT id, name, state_id FROM city ORDER BY id' ); is_deeply( $cities, [ [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ] ], 'CREATEs mixed with INSERTs with bind values' ) live_no_rollback.t000600001750001750 377111524460461 21655 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!/usr/bin/env perl use strict; use warnings; use DBI; use DBIx::MultiStatementDo; use Test::More tests => 9; my $create = <<'SQL'; CREATE TABLE parent( a, b, c, d, PRIMARY KEY(a, b) ); CREATE TABLE child( x, y, "w;", "z;z"); CREATE TRIGGER "foreign;key" BEFORE DELETE ON "parent" WHEN EXISTS (SELECT 1 FROM "child" WHERE old."a" == "x" AND old."b" == "y") BEGIN SELECT RAISE(ABORT, 'constraint failed;'); END; SQL my $drop = <<'SQL'; DROP TRIGGER "foreign;key"; DROP TABLE child; DROP TABLE parent; SQL my $insert_correct = <<'SQL'; INSERT INTO parent (a, b, c, d) VALUES ('pippo1;', ';pluto1', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo2;', ';pluto2', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo3;', ';pluto3', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo4;', ';pluto4', NULL, NULL); SQL my $insert_bad = <<'SQL'; INSERT INTO parent (a, b, c, d) VALUES ('pippo5', 'pluto5', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo6', 'pluto6' ; SQL my $insert_bad2 = <<'SQL'; INSERT INTO parent (a, b, c, d) VALUES ('pippo7', 'pluto7', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo8', 'pluto8' ; SQL my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', '', '', { PrintError => 0 }); my $batch = DBIx::MultiStatementDo->new( dbh => $dbh, rollback => 0 ); my @results; my $result; ok ( @results = $batch->do($create), 'multiple create on sqlite' ); cmp_ok ( scalar(@results), '==', 3, 'check success' ); ok ( @results = $batch->do($insert_correct), 'multiple correct INSERTs on sqlite' ); cmp_ok ( scalar(@results), '==', 4, 'check success' ); ok ( @results = $batch->do($insert_bad), 'multiple mixed INSERTs on sqlite' ); cmp_ok ( scalar(@results), '==', 1, 'check success' ); $result = $batch->do($insert_bad2); ok ( ! $result, 'multiple mixed INSERTs, check failure in scalar context' ); ok ( @results = $batch->do($drop), 'multiple drop on sqlite' ); cmp_ok ( scalar(@results), '==', 3, 'check success' ); release-distmeta.t000644001750001750 45511524460461 21565 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::CPAN::Meta"; plan skip_all => "Test::CPAN::Meta required for testing META.yml" if $@; meta_yaml_ok(); release-kwalitee.t000644001750001750 43311524460461 21554 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::Kwalitee"; plan skip_all => "Test::Kwalitee required for testing kwalitee" if $@; release-pod-syntax.t000644001750001750 45011524460461 22054 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::Pod 1.41"; plan skip_all => "Test::Pod 1.41 required for testing POD" if $@; all_pod_files_ok(); do_firstarg_as_list.t000600001750001750 231511524460461 22363 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!/usr/bin/env perl use strict; use warnings; use DBI; use DBIx::MultiStatementDo; use SQL::SplitStatement; use Test::More tests => 3; # SQLite valid SQL my $sql_code = <<'SQL'; CREATE TABLE state (id, name); INSERT INTO state (id, name) VALUES (?, ?); CREATE TABLE city (id, name, state_id); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); DROP TABLE state; SQL my @bind_values = (1, 'New York', 1, 'Albany', 1, 2, 'Buffalo', 1); my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', '', '', { PrintError => 0 }); my $splitter = SQL::SplitStatement->new; # Two value listref. my @statements_and_placeholders = $splitter->split_with_placeholders( $sql_code ); my $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); my @results; ok ( @results = $batch->do( \@statements_and_placeholders, undef, @bind_values ), 'mixed statements w/ bind values on sqlite' ); cmp_ok ( scalar(@results), '==', 6, 'check success' ); my $cities = $dbh->selectall_arrayref( 'SELECT id, name, state_id FROM city ORDER BY id' ); is_deeply( $cities, [ [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ] ], 'CREATEs mixed with INSERTs with bind values' ) release-unused-vars.t000644001750001750 44511524460461 22226 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::Vars"; plan skip_all => "Test::Vars required for testing unused vars" if $@; all_vars_ok(); live_manual_rollback.t000600001750001750 245011524460461 22507 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!/usr/bin/env perl use strict; use warnings; use DBI; use DBIx::MultiStatementDo; use Test::More tests => 4; my $create = <<'SQL'; CREATE TABLE parent( a, b, c, d, PRIMARY KEY(a, b) ); CREATE TABLE child( x, y, "w;", "z;z", FOREIGN KEY (x, y) REFERENCES parent (a,b) ); CREATE TRIGGER "foreign;key" BEFORE DELETE ON "parent" WHEN EXISTS (SELECT 1 FROM "child" WHERE old."a" == "x" AND old."b" == "y") BEGIN SELECT RAISE(ABORT, 'constraint failed;'); END; SQL my $insert_bad = <<'SQL'; INSERT INTO parent (a, b, c, d) VALUES ('pippo1', 'pluto1', NULL, NULL); INSERT INTO parent (a, b, c, d) VALUES ('pippo2', 'pluto2' ; SQL my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', '', '', { PrintError => 0 }); my $batch = DBIx::MultiStatementDo->new( dbh => $dbh, rollback => 0 ); my @results; my $result; ok ( @results = $batch->do($create), 'multiple create on sqlite' ); cmp_ok ( scalar(@results), '==', 3, 'check success' ); @results = (); my $rollback_done; $batch->dbh->{AutoCommit} = 0; $batch->dbh->{RaiseError} = 1; eval { @results = $batch->do( $insert_bad ); $batch->dbh->commit; 1 } or do { eval { $batch->dbh->rollback }; $rollback_done = 1 }; cmp_ok ( scalar(@results), '==', 0, 'check failure' ); ok ( $rollback_done, 'check rollback' ); release-pod-coverage.t000644001750001750 76511524460461 22332 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::Pod::Coverage 1.08"; plan skip_all => "Test::Pod::Coverage 1.08 required for testing POD coverage" if $@; eval "use Pod::Coverage::TrustPod"; plan skip_all => "Pod::Coverage::TrustPod required for testing POD coverage" if $@; all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' }); DBIx000755001750001750 011524460461 17116 5ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/libMultiStatementDo.pm000600001750001750 4102011524460461 23062 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/lib/DBIx## no critic package DBIx::MultiStatementDo; BEGIN { $DBIx::MultiStatementDo::VERSION = '1.00009'; } ## use critic use Moose; use Carp qw(croak); use SQL::SplitStatement 1.00009; has 'dbh' => ( is => 'rw', isa => 'DBI::db', required => 1 ); has 'splitter_options' => ( is => 'rw', isa => 'Maybe[HashRef[Bool]]', trigger => \&_set_splitter, default => undef ); sub _set_splitter { my ($self, $new_options) = @_; $self->_splitter( SQL::SplitStatement->new($new_options) ) } has '_splitter' => ( is => 'rw', isa => 'SQL::SplitStatement', handles => [ qw(split split_with_placeholders) ], lazy => 1, default => sub { SQL::SplitStatement->new } ); has 'rollback' => ( is => 'rw', isa => 'Bool', default => 1 ); sub do { my ($self, $code, $attr, @bind_values) = @_; my ( $statements, $placeholders ) = ! ref($code) ? $self->split_with_placeholders($code) : ref( $code->[0] ) eq 'ARRAY' ? @$code : ( $code, undef ); my @compound_bind_values; if ( @bind_values >= 1 ) { if ( ! ref $bind_values[0] ) { # @bind_values was a FLAT LIST ref($placeholders) ne 'ARRAY' and croak( q[Bind values as a flat list require the placeholder numbers listref to be passed as well] ); push @compound_bind_values, [ splice @bind_values, 0, $_ ] foreach @$placeholders } else { @compound_bind_values = @{ $bind_values[0] } } } my $dbh = $self->dbh; my @results; if ( $self->rollback ) { local $dbh->{AutoCommit} = 0; local $dbh->{RaiseError} = 1; eval { @results = $self->_do_statements( $statements, $attr, \@compound_bind_values ); $dbh->commit; 1 } or eval { $dbh->rollback } } else { @results = $self->_do_statements( $statements, $attr, \@compound_bind_values ) } return @results if wantarray; # List context. return 1 if @results == @$statements; # Scalar context and success. return # Scalar context and failure. } sub _do_statements { my ($self, $statements, $attr, $compound_bind_values) = @_; my @results; my $dbh = $self->dbh; for my $statement ( @$statements ) { my $result = $dbh->do( $statement, $attr, @{ shift(@$compound_bind_values) || [] } ); last unless $result; push @results, $result } return @results } no Moose; __PACKAGE__->meta->make_immutable; 1; __END__ =head1 NAME DBIx::MultiStatementDo - Multiple SQL statements in a single do() call with any DBI driver =head1 VERSION version 1.00009 =head1 SYNOPSIS use DBI; use DBIx::MultiStatementDo; # Multiple SQL statements in a single string my $sql_code = <<'SQL'; CREATE TABLE parent (a, b, c , d ); CREATE TABLE child (x, y, "w;", "z;z"); /* C-style comment; */ CREATE TRIGGER "check;delete;parent;" BEFORE DELETE ON parent WHEN EXISTS (SELECT 1 FROM child WHERE old.a = x AND old.b = y) BEGIN SELECT RAISE(ABORT, 'constraint failed;'); -- Inlined SQL comment END; -- Standalone SQL; comment; w/ semicolons; INSERT INTO parent (a, b, c, d) VALUES ('pippo;', 'pluto;', NULL, NULL); SQL my $dbh = DBI->connect( 'dbi:SQLite:dbname=my.db', '', '' ); my $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); # Multiple SQL statements in a single call my @results = $batch->do( $sql_code ) or die $batch->dbh->errstr; print scalar(@results) . ' statements successfully executed!'; # 4 statements successfully executed! =head1 DESCRIPTION Some DBI drivers don't support the execution of multiple statements in a single C call. This module tries to overcome such limitation, letting you execute any number of SQL statements (of any kind, not only DDL statements) in a single batch, with any DBI driver. Here is how DBIx::MultiStatementDo works: behind the scenes it parses the SQL code, splits it into the atomic statements it is composed of and executes them one by one. To split the SQL code L is used, which uses a more sophisticated logic than a raw C on the C<;> (semicolon) character: first, various different statement terminator I are recognized, then L is able to correctly handle the presence of said tokens inside identifiers, values, comments, C blocks (even nested), I strings, MySQL custom Cs, procedural code etc., as (partially) exemplified in the L above. Automatic transactions support is offered by default, so that you'll have the I behaviour you would probably expect; if you prefer, you can anyway disable it and manage the transactions yourself. =head1 METHODS =head2 C =over 4 =item * C<< DBIx::MultiStatementDo->new( %options ) >> =item * C<< DBIx::MultiStatementDo->new( \%options ) >> =back It creates and returns a new DBIx::MultiStatementDo object. It accepts its options either as an hash or an hashref. The following options are recognized: =over 4 =item * C The database handle object as returned by L. This option B. =item * C A Boolean option which enables (when true) or disables (when false) automatic transactions. It is set to a true value by default. =item * C This is the options hashref which is passed unaltered to C<< SQL::SplitStatement->new() >> to build the I, which is then internally used by DBIx::MultiStatementDo to split the given SQL string. It defaults to C, which should be the best value if the given SQL string contains only standard SQL. If it contains contains also procedural code, you may need to fine tune this option. Please refer to L<< SQL::SplitStatement::new()|SQL::SplitStatement/new >> to see the options it takes. =back =head2 C =over 4 =item * C<< $batch->do( $sql_string | \@sql_statements ) >> =item * C<< $batch->do( $sql_string | \@sql_statements , \%attr ) >> =item * C<< $batch->do( $sql_string | \@sql_statements , \%attr, \@bind_values | @bind_values ) >> =back This is the method which actually executes the SQL statements against your db. As its first (mandatory) argument, it takes an SQL string containing one or more SQL statements. The SQL string is split into its atomic statements, which are then executed one-by-one, in the same order they appear in the given string. The first argument can also be a reference to a list of (already split) statements, in which case no split is performed and the statements are executed as they appear in the list. The list can also be a two-elements list, where the first element is the statements listref as above, and the second is the I listref, exactly as returned by the L<< SQL::SplitStatement::split_with_placeholders()|SQL::SplitStatement/split_with_placeholders >> method. Analogously to DBI's C, it optionally also takes an hashref of attributes (which is passed unaltered to C<< $batch->dbh->do() >> for each atomic statement), and the I, either as a listref or a flat list (see below for the difference). In list context, C returns a list containing the values returned by the C<< $batch->dbh->do() >> call on each single atomic statement. If the C option has been set (and therefore automatic transactions are enabled), in case one of the atomic statements fails, all the other succeeding statements executed so far, if any, are rolled back and the method (immediately) returns an empty list (since no statements have actually been committed). If the C option is set to a false value (and therefore automatic transactions are disabled), the method immediately returns at the first failing statement as above, but it does not roll back any prior succeeding statement, and therefore a list containing the values returned by the statements (successfully) executed so far is returned (and these statements are actually committed to the db, if C<< $dbh->{AutoCommit} >> is set). In scalar context it returns, regardless of the value of the C option, C if any of the atomic statements failed, or a true value if all of the atomic statements succeeded. Note that to activate the automatic transactions you don't have to do anything more than setting the C option to a true value (or simply do nothing, as it is the default): DBIx::MultiStatementDo will automatically (and temporarily, via C) set C<< $dbh->{AutoCommit} >> and C<< $dbh->{RaiseError} >> as needed. No other DBI db handle attribute is ever touched, so that you can for example set C<< $dbh->{PrintError} >> and enjoy its effects in case of a failing statement. If you want to disable the automatic transactions and manage them by yourself, you can do something along this: my $batch = DBIx::MultiStatementDo->new( dbh => $dbh, rollback => 0 ); my @results; $batch->dbh->{AutoCommit} = 0; $batch->dbh->{RaiseError} = 1; eval { @results = $batch->do( $sql_string ); $batch->dbh->commit; 1 } or eval { $batch->dbh->rollback }; =head3 Bind Values as a List Reference The bind values can be passed as a reference to a list of listrefs, each of which contains the bind values for the atomic statement it corresponds to. The bind values I lists must match the corresponding atomic statements as returned by the internal I, with C (or empty listref) elements where the corresponding atomic statements have no I. Here is an example: # 7 statements (SQLite valid SQL) my $sql_code = <<'SQL'; CREATE TABLE state (id, name); INSERT INTO state (id, name) VALUES (?, ?); CREATE TABLE city (id, name, state_id); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); DROP TABLE city; DROP TABLE state SQL # Only 5 elements are required in the bind values list my $bind_values = [ undef , # or [] [ 1, 'Nevada' ] , [] , # or undef [ 1, 'Las Vegas' , 1 ], [ 2, 'Carson City', 1 ] ]; my $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); my @results = $batch->do( $sql_code, undef, $bind_values ) or die $batch->dbh->errstr; If the last statements have no placeholders, the corresponding Cs don't need to be present in the bind values list, as shown above. The bind values list can also have more elements than the number of the atomic statements, in which case the excess elements will simply be ignored. =head3 Bind Values as a Flat List This is a much more powerful feature of C: when it gets the bind values as a flat list, it automatically assigns them to the corresponding placeholders (no I Cs are necessary in this case). In other words, you can regard the given SQL code as a single big statement and pass the bind values exactly as you would do with the ordinary DBI C method. For example, given C<$sql_code> from the example above, you could simply do: my @bind_values = ( 1, 'Nevada', 1, 'Las Vegas', 1, 2, 'Carson City', 1 ); my @results = $batch->do( $sql_code, undef, @bind_values ) or die $batch->dbh->errstr; and get exactly the same result. =head3 Difference between Bind Values as a List Reference and as a Flat List If you want to pass the bind values as a flat list as described above, you must pass the first parameter to C either as a string (so that the internal splitting is performed) or, if you want to disable the internal splitting, as a reference to the two-elements list containing both the statements and the placeholder numbers listrefs (as described above in L). In other words, you can't pass the bind values as a flat list and pass at the same time the (already split) statements without the placeholder numbers listref. To do so, you need to pass the bind values as a list reference instead, otherwise C throws an exception. To summarize, bind values as a flat list is easier to use but it suffers from this subtle limitation, while bind values as a list reference is a little bit more cumbersome to use, but it has no limitations and can therefore always be used. =head3 Recognized Placeholders The recognized placeholders are: =over 4 =item * I placeholders, represented by the C character; =item * I placeholders, represented by the C<$1, $2, ..., $n> strings; =item * I, such as C<:foo>, C<:bar>, C<:baz> etc. =back =head2 C =over 4 =item * C<< $batch->dbh >> =item * C<< $batch->dbh( $new_dbh ) >> Getter/setter method for the C option explained above. =back =head2 C =over 4 =item * C<< $batch->rollback >> =item * C<< $batch->rollback( $boolean ) >> Getter/setter method for the C option explained above. =back =head2 C =over 4 =item * C<< $batch->splitter_options >> =item * C<< $batch->splitter_options( \%options ) >> Getter/setter method for the C option explained above. =back =head2 C and C =over 4 =item * C<< $batch->split( $sql_code ) >> =item * C<< $batch->split_with_placeholders( $sql_code ) >> =back These are the methods used internally to split the given SQL code. They call respectively C and C on a SQL::SplitStatement instance built with the C described above. Normally they shouldn't be used directly, but they could be useful if you want to see how your SQL code has been split. If you want instead to see how your SQL code I split, that is before executing C, you can use SQL::SplitStatement by yourself: use SQL::SplitStatement; my $splitter = SQL::SplitStatement->new( \%splitter_options ); my @statements = $splitter->split( $sql_code ); # Now you can check @statements if you want... and then you can execute your statements preventing C from performing the splitting again, by passing C<\@statements> to it: my $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); my @results = $batch->do( \@statements ); # This does not perform the splitting again. B In previous versions, the C (public) method documented above did not work, so there is the possibility that someone used the (private, undocumented) C<_split_with_placeholders> method instead (which worked correctly). In this case, please start using the public method (which now works as advertised), since the private method will be removed in future versions. =head1 LIMITATIONS Please look at: L<< SQL::SplitStatement LIMITATIONS|SQL::SplitStatement/LIMITATIONS >> =head1 DEPENDENCIES DBIx::MultiStatementDo depends on the following modules: =over 4 =item * L 0.10000 or newer =item * L =back =head1 AUTHOR Emanuele Zeppieri, C<< >> =head1 BUGS No known bugs so far. Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc DBIx::MultiStatementDo You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 ACKNOWLEDGEMENTS Matt S Trout, for having suggested a much more suitable name for this module. =head1 SEE ALSO =over 4 =item * L =item * L =back =head1 LICENSE AND COPYRIGHT Copyright 2010-2011 Emanuele Zeppieri. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation, or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cutbind_values_ref_flat_exception.t000644001750001750 754411524460461 24576 0ustar00emazepemazep000000000000DBIx-MultiStatementDo-1.00009/t#!/usr/bin/env perl use strict; use warnings; use DBI; use DBIx::MultiStatementDo; use Test::More; BEGIN { unless ( eval 'use Test::Exception; 1' ) { plan skip_all => "please install Test::Exception to run these tests" } } plan tests => 16; my @sql_statements; my @flat_bind_values; my @compound_bind_values; my @placeholder_numbers; my $batch; my $cities; @sql_statements = ( 'CREATE TABLE state (id, name)', 'INSERT INTO state (id, name) VALUES (?, ?)', 'CREATE TABLE city (id, name, state_id)', 'INSERT INTO city (id, name, state_id) VALUES (?, ?, ?)', 'INSERT INTO city (id, name, state_id) VALUES (?, ?, ?)', 'DROP TABLE state' ); @compound_bind_values = ( undef , # or [] [ 1, 'New York' ] , [] , # or undef [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ], undef, [] ); @flat_bind_values = (1, 'New York', 1, 'Albany', 1, 2, 'Buffalo', 1); my $dbh = DBI->connect( 'dbi:SQLite:dbname=:memory:', '', '', { PrintError => 1 }) or die $DBI::errstr; $batch = DBIx::MultiStatementDo->new( dbh => $dbh ); my @results; throws_ok { $batch->do( \@sql_statements, undef, @flat_bind_values ) } qr/Bind values as a flat list require the placeholder numbers listref/, 'Bind values as a flat list require the placeholder numbers listref'; ok ( @results = $batch->do( \@sql_statements, undef, \@compound_bind_values ), 'mixed statements w/ bind values as compound list' ); cmp_ok ( scalar(@results), '==', 6, 'check success' ); $cities = $dbh->selectall_arrayref( 'SELECT id, name, state_id FROM city ORDER BY id' ); is_deeply( $cities, [ [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ] ], 'check insertions' ); unshift @sql_statements, 'DROP TABLE city'; @placeholder_numbers = (0, 0, 2, 0, 3, 3, 0); ok ( @results = $batch->do( [ \@sql_statements, \@placeholder_numbers ], undef, @flat_bind_values ), 'mixed statements w/ bind values as flat list' ); cmp_ok ( scalar(@results), '==', 7, 'check success' ); $cities = $dbh->selectall_arrayref( 'SELECT id, name, state_id FROM city ORDER BY id' ); is_deeply( $cities, [ [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ] ], 'check insertions' ); @compound_bind_values = ( undef , # or [] undef , # or [] [ 1, 'New York' ] , [] , # or undef [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ], [], [] ); ok ( @results = $batch->do( [ \@sql_statements, \@placeholder_numbers ], undef, \@compound_bind_values ), 'mixed statements w/ compound bind values and placeholder numbers' ); cmp_ok ( scalar(@results), '==', 7, 'check success' ); $cities = $dbh->selectall_arrayref( 'SELECT id, name, state_id FROM city ORDER BY id' ); is_deeply( $cities, [ [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ] ], 'check insertions' ); $batch = DBIx::MultiStatementDo->new( dbh => $dbh, rollback => 0 ); ok ( @results = $batch->do( [ \@sql_statements, \@placeholder_numbers ], undef, @flat_bind_values ), 'mixed statements w/ bind values as flat list, no rollback' ); cmp_ok ( scalar(@results), '==', 7, 'check success' ); $cities = $dbh->selectall_arrayref( 'SELECT id, name, state_id FROM city ORDER BY id' ); is_deeply( $cities, [ [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ] ], 'check insertions' ); ok ( @results = $batch->do( [ \@sql_statements, \@placeholder_numbers ], undef, \@compound_bind_values ), 'mixed statements w/ compound bind values and placeholder numbers, no rollback' ); cmp_ok ( scalar(@results), '==', 7, 'check success' ); $cities = $dbh->selectall_arrayref( 'SELECT id, name, state_id FROM city ORDER BY id' ); is_deeply( $cities, [ [ 1, 'Albany' , 1 ], [ 2, 'Buffalo', 1 ] ], 'check insertions' );