SQL-SplitStatement-1.00020000755001750001750 011543335122 15215 5ustar00emazepemazep000000000000README000644001750001750 4134411543335122 16203 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020NAME SQL::SplitStatement - Split any SQL code into atomic statements VERSION version 1.00020 SYNOPSIS # 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;'); -- Inline SQL comment END; -- Standalone SQL; comment; with semicolons; INSERT INTO parent (a, b, c, d) VALUES ('pippo;', 'pluto;', NULL, NULL); SQL use SQL::SplitStatement; my $sql_splitter = SQL::SplitStatement->new; my @statements = $sql_splitter->split($sql_code); # @statements now is: # # ( # '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)' # ) DESCRIPTION This is a simple module which tries to split any SQL code, even including non-standard extensions (for the details see the "SUPPORTED DBMSs" section below), into the atomic statements it is composed of. The logic used to split the SQL code is more sophisticated than a raw "split" on the ";" (semicolon) character: first, various different statement terminator *tokens* are recognized (see below for the list), then this module 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. Consider however that this is by no means a validating parser (technically speaking, it's just a *context-sensitive tokenizer*). It should rather be seen as an in-progress *heuristic* approach, which will gradually improve as test cases will be reported. This also means that, except for the "LIMITATIONS" detailed below, there is no known (to the author) SQL code the most current release of this module can't correctly split. The test suite bundled with the distribution (which now includes the popular *Sakila* and *Pagila* sample db schemata, as detailed in the "SHOWCASE" section below) should give you an idea of the capabilities of this module If your atomic statements are to be fed to a DBMS, you are encouraged to use DBIx::MultiStatementDo instead, which uses this module and also (optionally) offers automatic transactions support, so that you'll have the *all-or-nothing* behavior you would probably want. METHODS "new" * "SQL::SplitStatement->new( %options )" * "SQL::SplitStatement->new( \%options )" It creates and returns a new SQL::SplitStatement object. It accepts its options either as a hash or a hashref. "new" takes the following Boolean options, which for documentation purposes can be grouped in two sets: "Formatting Options" and "DBMSs Specific Options". Formatting Options * "keep_terminators" A Boolean option which causes, when set to a false value (which is the default), the trailing terminator token to be discarded in the returned atomic statements. When set to a true value, the terminators are kept instead. The possible terminators (which are treated as such depending on the context) are: * ";" (the *semicolon* character); * any string defined by the MySQL "DELIMITER" command; * an ";" followed by an "/" (*forward-slash* character) on its own line; * an ";" followed by an "." (*dot* character) on its own line, followed by an "/" on its own line; * an "/" on its own line regardless of the preceding characters (only if the "slash_terminates" option, explained below, is set). The multi-line terminators above are always treated as a single token, that is they are discarded (or returned) as a whole (regardless of the "slash_terminates" option value). If your statements are to be fed to a DBMS, you are advised to keep this option to its default (false) value, since some drivers/DBMSs don't want the terminator to be present at the end of the (single) statement. (Note that the last, possibly empty, statement of a given SQL text, never has a trailing terminator. See below for an example.) * "keep_terminator" An alias for the the "keep_terminators" option explained above. Note that if "keep_terminators" and "keep_terminator" are both passed to "new", an exception is thrown. * "keep_extra_spaces" A Boolean option which causes, when set to a false value (which is the default), the spaces ("\s") around the statements to be trimmed. When set to a true value, these spaces are kept instead. When "keep_terminators" is set to false as well, the terminator is discarded first (regardless of the spaces around it) and the trailing spaces are trimmed then. This ensures that if "keep_extra_spaces" is set to false, the returned statements will never have trailing (nor leading) spaces, regardless of the "keep_terminators" value. * "keep_comments" A Boolean option which causes, when set to a false value (which is the default), the comments to be discarded in the returned statements. When set to a true value, they are kept with the statements instead. Both SQL and multi-line C-style comments are recognized. When kept, each comment is returned in the same string with the atomic statement it belongs to. A comment belongs to a statement if it appears, in the original SQL code, before the end of that statement and after the terminator of the previous statement (if it exists), as shown in this pseudo-SQL snippet: /* This comment will be returned together with statement1 */ ; -- This will go with statement2 -- (note the semicolon which closes statement1) -- This with statement2 as well * "keep_empty_statements" A Boolean option which causes, when set to a false value (which is the default), the empty statements to be discarded. When set to a true value, the empty statements are returned instead. A statement is considered empty when it contains no characters other than the terminator and space characters ("\s"). A statement composed solely of comments is not recognized as empty and may therefore be returned even when "keep_empty_statements" is false. To avoid this, it is sufficient to leave "keep_comments" to false as well. Note instead that an empty statement is recognized as such regardless of the value of the options "keep_terminators" and "keep_extra_spaces". These options are basically to be kept to their default (false) values, especially if the atomic statements are to be given to a DBMS. They are intended mainly for *cosmetic* reasons, or if you want to count by how many atomic statements, including the empty ones, your original SQL code was composed of. Another situation where they are useful (in the general case necessary, really), is when you want to retain the ability to verbatim rebuild the original SQL string from the returned statements: my $verbatim_splitter = SQL::SplitStatement->new( keep_terminators => 1, keep_extra_spaces => 1, keep_comments => 1, keep_empty_statements => 1 ); my @verbatim_statements = $verbatim_splitter->split($sql_string); $sql_string eq join '', @verbatim_statements; # Always true, given the constructor above. Other than this, again, you are recommended to stick with the defaults. DBMSs Specific Options The same syntactic structure can have different semantics across different SQL dialects, so sometimes it is necessary to help the parser to make the right decision. This is the function of these options. * "slash_terminates" A Boolean option which causes, when set to a true value (which is the default), a "/" (*forward-slash*) on its own line, even without a preceding semicolon, to be admitted as a (possible) terminator. If set to false, a forward-slash on its own line is treated as a statement terminator only if preceded by a semicolon or by a dot and a semicolon. If you are dealing with Oracle's SQL, you should let this option set, since a slash (alone, without a preceding semicolon) is sometimes used as a terminator, as it is permitted by SQL*Plus (on non-*block* statements). With SQL dialects other than Oracle, there is the (theoretical) possibility that a slash on its own line can pass the additional checks and be considered a terminator (while it shouldn't). This chance should be really tiny (it has never been observed in real world code indeed). Though negligible, by setting this option to false that risk can anyway be ruled out. "split" * "$sql_splitter->split( $sql_string )" This is the method which actually splits the SQL code into its atomic components. It returns a list containing the atomic statements, in the same order they appear in the original SQL code. The atomic statements are returned according to the options explained above. Note that, as mentioned above, an SQL string which terminates with a terminator token (for example a semicolon), contains a trailing empty statement: this is correct and it is treated accordingly (if "keep_empty_statements" is set to a true value): my $sql_splitter = SQL::SplitStatement->new( keep_empty_statements => 1 ); my @statements = $sql_splitter->split( 'SELECT 1;' ); print 'The SQL code contains ' . scalar(@statements) . ' statements.'; # The SQL code contains 2 statements. "split_with_placeholders" * "$sql_splitter->split_with_placeholders( $sql_string )" It works exactly as the "split" method explained above, except that it returns also a list of integers, each of which is the number of the *placeholders* contained in the corresponding atomic statement. More precisely, its return value is a list of two elements, the first of which is a reference to the list of the atomic statements exactly as returned by the "split" method, while the second is a reference to the list of the number of placeholders as explained above. Here is an example: # 4 statements (valid SQLite 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 (?, ?, ?) SQL my $splitter = SQL::SplitStatement->new; my ( $statements, $placeholders ) = $splitter->split_with_placeholders( $sql_code ); # $placeholders now is: [0, 2, 0, 3] where the returned $placeholders list(ref) is to be read as follows: the first statement contains 0 placeholders, the second 2, the third 0 and the fourth 3. 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. "keep_terminators" * "$sql_splitter->keep_terminators" * "$sql_splitter->keep_terminators( $boolean )" Getter/setter method for the "keep_terminators" option explained above. "keep_terminator" An alias for the "keep_terminators" method explained above. "keep_extra_spaces" * "$sql_splitter->keep_extra_spaces" * "$sql_splitter->keep_extra_spaces( $boolean )" Getter/setter method for the "keep_extra_spaces" option explained above. "keep_comments" * "$sql_splitter->keep_comments" * "$sql_splitter->keep_comments( $boolean )" Getter/setter method for the "keep_comments" option explained above. "keep_empty_statements" * "$sql_splitter->keep_empty_statements" * "$sql_splitter->keep_empty_statements( $boolean )" Getter/setter method for the "keep_empty_statements" option explained above. "slash_terminates" * "$sql_splitter->slash_terminates" * "$sql_splitter->slash_terminates( $boolean )" Getter/setter method for the "slash_terminates" option explained above. SUPPORTED DBMSs SQL::SplitStatement aims to cover the widest possible range of DBMSs, SQL dialects and extensions (even proprietary), in a (nearly) fully transparent way for the user. Currently it has been tested mainly on SQLite, PostgreSQL, MySQL and Oracle. Procedural Extensions Procedural code is by far the most complex to handle. Currently any block of code which start with "FUNCTION", "PROCEDURE", "DECLARE", "CREATE" or "CALL" is correctly recognized, as well as *anonymous* "BEGIN ... END" blocks, *dollar quoted* blocks and blocks delimited by a "DELIMITER"-defined *custom terminator*, therefore a wide range of procedural extensions should be handled correctly. However, only PL/SQL, PL/PgSQL and MySQL code has been tested so far. If you need also other procedural languages to be recognized, please let me know (possibly with some test cases). LIMITATIONS Bound to be plenty, given the heuristic nature of this module (and its ambitious goals). However, no limitations are currently known. Please report any problematic test case. Non-limitations To be split correctly, the given input must, in general, be syntactically valid SQL. For example, an unbalanced "BEGIN" or a misspelled keyword could, under certain circumstances, confuse the parser and make it trip over the next statement terminator, thus returning non-split statements. This should not be seen as a limitation though, as the original (invalid) SQL code would have been unusable anyway (remember that this is NOT a validating parser!) SHOWCASE To test the capabilities of this module, you can run it (or rather run sql-split) on the files t/data/sakila-schema.sql and t/data/pagila-schema.sql included in the distribution, which contain two quite large and complex *real world* db schemata, for MySQL and PostgreSQL respectively. For more information: * Sakila db: * Pagila db: DEPENDENCIES SQL::SplitStatement depends on the following modules: * Carp * Class::Accessor::Fast * List::MoreUtils * Regexp::Common * SQL::Tokenizer 0.22 or newer AUTHOR Emanuele Zeppieri, "" BUGS No known bugs. Please report any bugs or feature requests to "bug-sql-SplitStatement 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 SQL::SplitStatement You can also look for information at: * RT: CPAN's request tracker * AnnoCPAN: Annotated CPAN documentation * CPAN Ratings * Search CPAN ACKNOWLEDGEMENTS Igor Sutton for his excellent SQL::Tokenizer, which made writing this module a joke. SEE ALSO * DBIx::MultiStatementDo * sql-split 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. Changes000644001750001750 1436111543335122 16615 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020Revision history for SQL-SplitStatement 1.00020 2011-03-26 11:05:02 Europe/Rome * Emergency release: fixed several problems caused by the new SQL::Tokenizer (0.21 and above) releases. SQL::Tokenizer 0.22 now required. * Minor doc additions and fixes. 1.00009 2011-02-09 10:37:48 Europe/Rome * Fixed a corner case where an empty statement was not removed (upon request) if it was closed by a /strange/ DELIMITER-defined terminator. * Added a test for this. * Fixed "t/90-mysql_sakila.t" and "t/92-postgresql_pagila.t" for older Perls (pre 5.8.7 it seems). Thank you CPAN Testers! * POD formatting fixes to obtain a better (automatically generated) README.mkdn 1.00000 2011-02-09 01:40:58 Europe/Rome * PL/SQL PACKAGE limitation removed: now a PACKAGE with an initialization block is correctly recognized even if it lacks both the package name at the END and the slash terminator. * PL/SQL parser fully rewritten again. * Added a new terminator: semicolon followed by a period on its own line followed by a forward slash on its own line (PL/SQL). * Extended dollar-quoted strings handling, that is strings like "$TAG$", are now handled correctly (whereas before only "$$" was permitted as a dollar-quote). * Two new types of placeholders added: /dollar sign numbers/ ($1, $2, ..., $n) and /named parameters/ (:foo, :bar, :baz). * PL/SQL "DECLARE CURSOR" handling; * Fixed missing PL/SQL "END CASE" check; * Fixed a bug with DELIMITER-defined terminators when their scope persisted over multiple statements. * Fixed a bug which caused certain DELIMITER-defined terminators not to be removed in the returned statements (when "keep_terminators" was set to false). * Many further improvements and fixes. * Added the new "slash_terminates" option (NOT a change in the default behavior, but please read the docs). * Massive test additions (the two popular Sakila and Pagila sample db now included). * Docs updates. * Markdown README added (via Dist::Zilla::Plugin::ReadmeMarkdownFromPod). 0.30000 2011-01-23 11:00:02 Europe/Rome * Fixed a PL/SQL problem where a FUNCTION or PROCEDURE inside a PACKAGE, could cause the parser to trip over the end of the package. * Tests for this. * keep_terminator as a keep_terminators alias fix. * Minor doc fixes. 0.20000 2011-01-21 18:53:11 Europe/Rome * Removed the limitation on the BEGIN, DECLARE, FUNCTION and PROCEDURE keywords: they can now be used even as unquoted identifiers! :-) * Minor docs enhancements. 0.10000 2011-01-20 07:00:00 Europe/Rome * Moose dropped in favour of Class::Accessor::Fast, since some people think Moose is an overkill for such a simple module. * Command line SQL splitting utility sql-split added. * Added GRANT support: thanks Alexander Sennhauser for the report. * Added MySQL DELIMITER support; closes bug #60401: thanks ed.shrock. * Private methods heavily refactored. * Many improvements and additions on docs and tests. 0.07000 2010-06-26 04:00:00 Europe/Rome * Much improved procedural blocks handling: * dollar quoted blocks handling; * better "CREATE ... FUNCTION|PROCEDURE" handling. * Minimum required SQL::Tokenizer version is now 0.20 (thanks Igor Sutton for this new version). * End of deprecation cycle on the "keep_semicolon" option (has now been removed for good). * Tests and docs additions. 0.05003_1 2010-06-21 08:30:00 Europe/Rome * Fixed a bug which caused a PL/SQL package without the package name after the END to not be split correctly. Kindly reported by Dan Horne: thanks! * Tests for this. * Updated the LIMITATIONS section in the docs to explain that we cannot handle a PL/SQL PACKAGE block _with_ an initialization block inside, if the package lacks _both_ its name after the END and the trailing slash. 0.05003 2010-06-20 00:00:00 Europe/Rome * A bit more robust "CREATE ... PACKAGE" handling, so that it is now accepted the use of the "PACKAGE" keyword as an identifier (even unquoted). * Tests for that. * Better explained LIMITATIONS. * Minor doc fixes. * Cleaned MANIFEST up from a perl stackdump. 0.05002 2010-06-17 08:00:00 Europe/Rome * Now handles also the presence of a so-called "initialization block" inside a "PACKAGE" block. * Tests for that. 0.05001 2010-06-17 06:00:00 Europe/Rome * Now handles also the PL/SQL "CREATE ... PACKAGE" construct (thanks Dan Horne for his info about that!) * Tests for that. 0.05000 2010-06-17 04:00:00 Europe/Rome * Now handles transactions (starting either with "BEGIN" and "START") correclty. Closes bug #58032. Thanks Frew Schmidt. * Now handles procedural code as well. Closes bug #57971. Thanks Dan Horne (thank you Dan also for the info by private mail!) * Now handle also Oracle-style statement terminators (slash and semicolon-newline-slash-newline). * Start of deprecation cycle on the "keep_semicolon" option which has been renamed to "keep_terminator" (as now different terminator tokens are recognized). * Many tests and docs additions. * Switch from Class::Accessor to Moose. 0.03000 2010-05-30 03:00:00 Europe/Rome * "split_with_placeholders" method added. * Tests and docs for "split_with_placeholders". * Assorted minor code and docs enhancements. 0.01002 2010-05-28 20:30:00 Europe/Rome * Removed "use DBI" from some tests, which was left there for error (and caused said tests to fail, where DBI was not installed). * Minor docs enhancements. 0.01001 2010-05-28 19:00:00 Europe/Rome * Added the ability to discard/keep comments. Really, this is a change in the API, since now comments are discarded by default, while before they were returned instead. I hope this won't harm any user, given the young age of this module (furthermore there isn't any difference when feeding the returned statements to a DBMS, the differences are just /cosmetic/). Sorry anyway! * Added tests and docs for this. 0.01000 2010-05-27 08:00:00 Europe/Rome * Initial release. LICENSE000644001750001750 4353311543335122 16332 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020This 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 174411543335122 16334 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020 This is the Perl distribution SQL-SplitStatement. Installing SQL-SplitStatement is straightforward. ## Installation with cpanm If you have cpanm, you only need one line: % cpanm SQL::SplitStatement 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 SQL::SplitStatement ## Installing with the CPAN shell Alternatively, if your CPAN shell is set up, you should just be able to do: % cpan SQL::SplitStatement ## 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 SQL-SplitStatement documentation is available as POD. You can run perldoc from a shell to read the documentation: % perldoc SQL::SplitStatement META.yml000644001750001750 156511543335122 16555 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020--- abstract: 'Split any SQL code into atomic statements' author: - 'Emanuele Zeppieri ' build_requires: 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.200004, CPAN::Meta::Converter version 2.110580' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: SQL-SplitStatement requires: Carp: 0 Class::Accessor::Fast: 0 Getopt::Long: 0 List::MoreUtils: 0 Pod::Usage: 0 Regexp::Common: 0 SQL::Tokenizer: 0.22 constant: 0 perl: 5.008 resources: bugtracker: http://rt.cpan.org/NoAuth/Bugs.html?Dist=SQL-SplitStatement homepage: http://search.cpan.org/dist/SQL-SplitStatement repository: git://github.com/emazep/SQL-SplitStatement.git version: 1.00020 MANIFEST000644001750001750 224611543335122 16432 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020Changes INSTALL LICENSE MANIFEST META.json META.yml Makefile.PL README bin/sql-split lib/SQL/SplitStatement.pm t/00-compile.t t/00-load.t t/05-empty_statements.t t/10-simple.t t/20-hard.t t/25-keywords_as_unquoted_identifiers.t t/30-nested_blocks.t t/40-comments.t t/42-slash_terminates.t t/50-transactions.t t/51-transactions_and_proc.t t/60-placeholders.t t/61-placeholders_empty.t t/70-proc_plsql_mixed.t t/70-proc_plsql_slash_endings.t t/71-proc_plsql_RTbug_57971.t t/72-proc_plsql_alias.t t/72-proc_plsql_function_inside_package.t t/72-proc_plsql_mixed_endings.t t/72-proc_plsql_nested_functions.t t/72-proc_plsql_package.t t/74-proc_plpgsql_dollar_quoted.t t/74-proc_plpgsql_dollar_quoted_w_transactions.t t/75-proc_grant.t t/80-examples.t t/81-examples_procedural.t t/82-synopsis.t t/90-mysql_delimiter.t t/90-mysql_mixed.t t/90-mysql_sakila.t t/92-postgresql_pagila.t t/94-exception.t t/95-script.t t/author-critic.t t/data/create_table.sql t/data/create_table_and_trigger.sql t/data/pagila-schema.sql t/data/sakila-schema.sql 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-synopsis.t t/release-unused-vars.t META.json000644001750001750 315711543335122 16724 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020{ "abstract" : "Split any SQL code into atomic statements", "author" : [ "Emanuele Zeppieri " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 4.200004, CPAN::Meta::Converter version 2.110580", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "SQL-SplitStatement", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.31" } }, "runtime" : { "requires" : { "Carp" : 0, "Class::Accessor::Fast" : 0, "Getopt::Long" : 0, "List::MoreUtils" : 0, "Pod::Usage" : 0, "Regexp::Common" : 0, "SQL::Tokenizer" : "0.22", "constant" : 0, "perl" : "5.008" } }, "test" : { "requires" : { "English" : 0, "File::Find" : 0, "File::Temp" : 0, "Test::Exception" : "0.27", "Test::More" : "0.7" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "mailto" : "bug-SQL-SplitStatement@rt.cpan.org", "web" : "http://rt.cpan.org/NoAuth/Bugs.html?Dist=SQL-SplitStatement" }, "homepage" : "http://search.cpan.org/dist/SQL-SplitStatement", "repository" : { "type" : "git", "url" : "git://github.com/emazep/SQL-SplitStatement.git", "web" : "http://github.com/emazep/SQL-SplitStatement" } }, "version" : "1.00020" } t000755001750001750 011543335122 15401 5ustar00emazepemazep000000000000SQL-SplitStatement-1.0002020-hard.t000644001750001750 141011543335122 17056 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 2; my $sql = <<'SQL'; CREATE TABLE child( x, y, "w;", "z;z", FOREIGN KEY (x, y) REFERENCES parent (a,b) ); 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'); END; SQL chop( my $clean_sql = $sql ); chop $clean_sql; my $sql_splitter = SQL::SplitStatement->new; my @statements = $sql_splitter->split($sql); cmp_ok ( scalar(@statements), '==', 3, 'number of atomic statements' ); is ( join( ";\n", @statements ), $clean_sql, 'SQL code successfully rebuilt' ); 00-load.t000644001750001750 31611543335122 17041 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use Test::More tests => 1; BEGIN { use_ok( 'SQL::SplitStatement' ) || print "Bail out! "; } diag( "Testing SQL::SplitStatement $SQL::SplitStatement::VERSION, Perl $], $^X" ); Makefile.PL000644001750001750 255711543335122 17260 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020 use strict; use warnings; BEGIN { require 5.008; } use ExtUtils::MakeMaker 6.31; my %WriteMakefileArgs = ( 'ABSTRACT' => 'Split any SQL code into atomic statements', 'AUTHOR' => 'Emanuele Zeppieri ', 'BUILD_REQUIRES' => { 'English' => '0', 'File::Find' => '0', 'File::Temp' => '0', 'Test::Exception' => '0.27', 'Test::More' => '0.7' }, 'CONFIGURE_REQUIRES' => { 'ExtUtils::MakeMaker' => '6.31' }, 'DISTNAME' => 'SQL-SplitStatement', 'EXE_FILES' => [ 'bin/sql-split' ], 'LICENSE' => 'perl', 'NAME' => 'SQL::SplitStatement', 'PREREQ_PM' => { 'Carp' => '0', 'Class::Accessor::Fast' => '0', 'Getopt::Long' => '0', 'List::MoreUtils' => '0', 'Pod::Usage' => '0', 'Regexp::Common' => '0', 'SQL::Tokenizer' => '0.22', 'constant' => '0' }, 'VERSION' => '1.00020', '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); bin000755001750001750 011543335122 15706 5ustar00emazepemazep000000000000SQL-SplitStatement-1.00020sql-split000755001750001750 2534211543335122 17751 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/bin#!/usr/bin/env perl ########################################################################### # Copyright 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. # ########################################################################### use strict; use warnings; use SQL::SplitStatement; our $VERSION = $SQL::SplitStatement::VERSION; use Getopt::Long; Getopt::Long::Configure qw( auto_help auto_version bundling require_order gnu_compat ); use Pod::Usage; ### Options ### my $man; my $help; my $keep_terminators; my $keep_comments; my $keep_empty_statements; my $keep_extra_spaces; my $slash_terminates; # Global variables (UPPERCASED) my $OUTPUT_STATEMENT_SEP = "\n--\n"; my $OUTPUT_FILE_SEP = "\n-- >>>*<<< --\n"; my $ON_ERROR = 0; GetOptions( 'help|h|?' => \$help, 'man' => \$man, 'terminators|T!' => \$keep_terminators, 'extra-spaces|spaces|S!' => \$keep_extra_spaces, 'comments|C!' => \$keep_comments, 'empty-statements|empty|E!' => \$keep_empty_statements, 'slash-terminates|slash!' => \$slash_terminates, 'output-statement-separator|oss|s=s' => \$OUTPUT_STATEMENT_SEP, 'output-file-separator|ofs|f=s' => \$OUTPUT_FILE_SEP, 'on-error|error|e=s' => \$ON_ERROR ) or pod2usage(2); pod2usage( -exitstatus => 0, -verbose => 2 ) if $man; pod2usage(2) if $help; die qq[Illegal on_error value: "$ON_ERROR"] if $ON_ERROR !~ /^(stop|continue|no-output|0|1|2)$/i; ### Main code ### *error = $ON_ERROR =~ /^(continue|1)$/i ? sub { warn shift } : sub { die shift }; *process_file = $ON_ERROR =~ /^(no-output|2)$/i ? \&split_and_gather_statements : \&split_and_print_statements; my $SPLITTER = SQL::SplitStatement->new( keep_terminators => $keep_terminators, keep_extra_spaces => $keep_extra_spaces, keep_comments => $keep_comments, keep_empty_statements => $keep_empty_statements, slash_terminates => $slash_terminates ); my $OUTPUT = ''; { local $/, $| = 1; if ( @ARGV ) { while ( my $filename = shift @ARGV ) { if ( $filename eq '-' ) { process_file( \*STDIN ) } elsif ( open my $fh, '<', $filename ) { process_file( $fh, scalar @ARGV ) } else { error( qq[Can't open file "$filename": $!] ) } } } else { split_and_print_statements( \*STDIN ) } print $OUTPUT if $OUTPUT; print "\n" } ### Main code End ### sub split_and_print_statements { my ($fh, $not_last_file) = @_; print join $OUTPUT_STATEMENT_SEP, $SPLITTER->split( <$fh> ); print $OUTPUT_FILE_SEP if $not_last_file } sub split_and_gather_statements { my ($fh, $not_last_file) = @_; $OUTPUT .= join $OUTPUT_STATEMENT_SEP, $SPLITTER->split( <$fh> ); $OUTPUT .= $OUTPUT_FILE_SEP if $not_last_file } __END__ =head1 NAME sql-split - SQL splitting command line utility =head1 VERSION version 1.00020 =head1 SYNOPSIS sql-split [ OPTIONS ] [ FILE(S) ] sql-split --man =head1 DESCRIPTION This program tries to split any SQL code (even containing non-standard and/or procedural extensions, at least the ones from the most popular DBMSs) into the atomic statements it is composed of. The given FILES are read and split one by one, and the resulting statements are printed to the standard output, separated by a customizable string (see below). Each given file must contain only full SQL statements, that is, no single atomic statement can span multiple files. If no file is given, or if one of the file names is a C<-> (dash), the SQL code is read from STDIN, so that this program can be used as a I or even interactively. Consider however that this is by no means a validating parser, so that errors in SQL code will not be detected (and can even lead to incorrect splitting). =head1 OPTIONS =head2 -T, --terminators It causes the trailing terminator tokens to be kept in the returned atomic statements (by default they are discarded instead). The strings currently recognized as terminators (depending on the context) are: =over 4 =item * C<;> (the I character); =item * any string defined by the MySQL C command; =item * an C<;> followed by an C (I character) on its own line; =item * an C<;> followed by an C<.> (I character) on its own line, followed by an C on its own line; =item * an C on its own line regardless of the preceding characters (only if the C option, explained below, is set). =back The multi-line terminators above are always treated as a single token, that is they are discarded (or returned) as a whole (regardless of the C<--no-slash-terminates> option value). =head2 -S, --spaces, --extra-spaces It causes the space characters around the statements, if any, to be kept in the returned atomic statements (by default they are trimmed instead). =head2 -C, --comments It causes the comments, if any, to be kept in the returned atomic statements (by default any comment is discarded instead). Both SQL and multi-line C-style comments are recognized. =head2 -E, --empty, --empty-statements It causes the empty statements to be returned (by default, they are discarded instead). A statement is considered empty when it contains no characters other than the terminator and space characters. A statement composed solely of comments is not recognized as empty and it is therefore returned, if the C<--comments> option is used. Note instead that an empty statement is recognized as such regardless of the use of the C<--terminators> and C<--extra-spaces> options. =head2 --no-slash, --no-slash-terminates By default a C (I) on its own line, even without a preceding semicolon, is admitted as a candidate terminator. When this option is used instead, a forward-slash on its own line is treated as a statement terminator only if preceded by a semicolon or by a dot and a semicolon. If you are dealing with Oracle's SQL, you should not use this option, since a slash (alone, without a preceding semicolon) is often used as a terminator, as it is permitted by SQL*Plus (on non-I statements). With SQL dialects other than Oracle, there is the (theoretical) possibility that a slash on its own line could pass the additional checks and be considered a terminator (while it shouldn't). This chance should be really tiny (it has never been observed in real world code indeed). Though negligible, this option will anyway rule out that risk. =head2 -s, --oss, --output-statement-separator I The string which will be printed between every pair of returned atomic statements. By default, it is a S> (I) on its own line. To use special characters (such as newlines) when passing such string, please consult your shell docs (for example, in Bash the above mentioned default separator could be defined as S>). Note that the last returned statement (for each processed file) will not be followed by such separator. =head2 -f, --ofs, --output-file-separator I The string which will be printed between the groups of statements coming from different files. By default it is the C<<<< -- >>>*<<< -- >>>> string on its own line. Similarly to the statement separator, the file separator will not be printed after the last file. =head2 -e, --error, --on-error I It controls the program behavior in case one of the given files is not accessible. It can take the following values: =over 4 =item * C or C<0>, which causes the program to die at the first file which can not be opened, but it prints all the statements split that far (this is the default); =item * C or C<1>, which causes the program, when it encounters a file error, to just emit a warning (on STDERR) and continue with the next file; =item * C or C<2>, which, just like C, causes the program to die at the first file error, but in this case it does not print any statement, not even those coming from the previous (already read) files; in other words, the statements are printed out only if (and after) all of the given files have been successfully read. =back The above listed string values are case-insensitive. =head2 -h, -?, --help It prints a brief help message and exits. =head2 --man It shows the full man page. =head2 --version It prints the program version and exits. =head1 SUPPORTED DBMSs sql-split aims to cover the widest possible range of DBMSs, SQL dialects and extensions (even proprietary), in a (nearly) fully transparent way for the user. Currently it has been tested mainly on SQLite, PostgreSQL, MySQL and Oracle. =head2 Procedural Extensions Procedural code is by far the most complex to handle. Currently any block of code which start with C, C, C, C or C is correctly recognized, as well as I C blocks, I blocks and blocks delimited by a C-defined I, therefore a wide range of procedural extensions should be handled correctly. However, only PL/SQL, PL/PgSQL and MySQL code has been tested so far. =head1 LIMITATIONS None currently known (other than the lack of tests on SQL dialects different from the ones described above). =head2 Non-limitations To be split correctly, the given input must, in general, be syntactically valid SQL. For example, an unbalanced C or a misspelled keyword could, under certain circumstances, confuse the parser and make it trip over the next statement terminator, thus returning non-split statements. This should not be a problem though, as the original (invalid) SQL code would have been unusable anyway (remember that this is NOT a validating parser!) =head1 SEE ALSO =over 4 =item * L (perldoc SQL::SplitStatement) =back =head1 COPYRIGHT Copyright 2011 I Eemazep@cpan.orgE. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L =head1 NO WARRANTY This program comes with NO WARRANTIES of any kind. It not only may cause loss of data and hardware damaging, but it may also cause several bad diseases to nearby people, including, but not limited to, diarrhoea, gonorrhea and dysmenorrhea. Don't say you haven't been warned. =cut95-script.t000644001750001750 600311543335122 17463 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use Test::More; BEGIN { unless ( eval 'use Test::Script::Run; 1' ) { plan skip_all => "please install Test::Script::Run to run these tests" } } plan tests => 18; ####### use constant { APP => 'bin/sql-split', OSS => "\n--\n", OFS => "\n-- >>>*<<< --\n" }; my @files = qw{ t/data/create_table.sql t/data/create_table_and_trigger.sql }; my $statements; my $stderr; my ($oss, $ofs); ####### run_ok( APP, \@files, 'script invokation' ); ####### ($stderr, $statements) = test_script( \@files ); ok( length($stderr) == 0, 'no warnings' ); cmp_ok ( scalar(@$statements), '==', 2, 'number of files found in output' ); cmp_ok ( scalar( @{ $statements->[0] } ), '==', 2, 'number of statements in the first file' ); cmp_ok ( scalar( @{ $statements->[1] } ), '==', 6, 'number of statements in the second file' ); ####### $oss = "\n--*\n"; $ofs = "\n--***\n"; $statements = test_script( [ '--oss', $oss, '--ofs', $ofs, @files ], $oss, $ofs ); cmp_ok ( scalar(@$statements), '==', 2, 'number of files found in output - custom sep' ); cmp_ok ( scalar( @{ $statements->[0] } ), '==', 2, 'number of statements in the first file - custom sep' ); cmp_ok ( scalar( @{ $statements->[1] } ), '==', 6, 'number of statements in the second file - custom sep' ); ####### $oss = "\n--#\n"; $ofs = "\n--###\n"; $statements = test_script( [ '-s', $oss, '-f', $ofs, '-E', @files ], $oss, $ofs ); cmp_ok ( scalar(@$statements), '==', 2, 'number of files found in output - empty statements' ); cmp_ok ( scalar( @{ $statements->[0] } ), '==', 3, 'number of statements in the first file - empty statements' ); cmp_ok ( scalar( @{ $statements->[1] } ), '==', 7, 'number of statements in the second file - empty statements' ); ####### ($stderr, $statements) = test_script( [ @files, 'non-existent.sql' ] ); ok( length($stderr) > 0, 'file error warnings' ); cmp_ok ( scalar(@$statements), '==', 2, 'number of files found in output - file error' ); cmp_ok ( scalar( @{ $statements->[0] } ), '==', 2, 'number of statements in the first file - file error' ); cmp_ok ( scalar( @{ $statements->[1] } ), '==', 6, 'number of statements in the second file - file error' ); ####### run_not_ok( APP, [ '--on-error=stop', @files, 'non-existent.sql' ], 'script dies on file error' ); ####### run_not_ok( APP, [ '-e', 'CONTINU', @files ], 'script dies on wrong --on-error value' ); ####### run_ok( APP, [ '--error', 'sToP', @files ], 'script accepts case-insensitive --on-error value' ); ####### sub test_script { my ($args, $oss, $ofs) = @_; $oss ||= OSS; $ofs ||= OFS; my $stdout; my $stderr; my @statements; run_script( APP, $args, \$stdout, \$stderr ); push @statements, [ split /\Q$oss/, $_, -1 ] foreach split /\Q$ofs/, $stdout; return ( $stderr, \@statements ) } ####### 10-simple.t000644001750001750 230211543335122 17431 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 5; 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 chomp ( my $clean_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 $sql_splitter = SQL::SplitStatement->new({ keep_terminator => 1, keep_extra_spaces => 1, keep_empty_statements => 1 }); my @statements; @statements = $sql_splitter->split($sql); cmp_ok ( scalar(@statements), '==', 3, 'number of atomic statements w/ semicolon' ); is ( join( '', @statements ), $sql, 'SQL code rebuilt w/ semicolon' ); $sql_splitter->keep_terminators(0); @statements = $sql_splitter->split($sql); is ( join( ';', @statements ), $sql, 'SQL code rebuilt w/o semicolon' ); @statements = $sql_splitter->new->split($sql); cmp_ok ( scalar(@statements), '==', 2, 'number of atomic statements w/o semicolon' ); is ( join( '', @statements ), $clean_sql, 'SQL code rebuilt w/o semicolon' ); 00-compile.t000644001750001750 204111543335122 17567 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/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" ); } } } 80-examples.t000644001750001750 200111543335122 17761 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 3; my $sql_splitter = SQL::SplitStatement->new({ keep_terminator => 1, keep_empty_statements => 1 }); my @statements = $sql_splitter->split( 'SELECT 1;' ); cmp_ok ( scalar(@statements), '==', 2, 'number of atomic statements w/ semicolon' ); is ( join( '', @statements ), 'SELECT 1;', 'SQL code successfully rebuilt w/ semicolon' ); my $sql = <<'SQL'; CREATE TABLE foo ( foo_field_1 VARCHAR, foo_field_2 VARCHAR ); -- Comment with semicolon; CREATE TABLE bar ( bar_field_1 VARCHAR, bar_field_2 VARCHAR ); SQL my $verbatim_splitter = SQL::SplitStatement->new({ keep_terminator => 1, keep_extra_spaces => 1, keep_comments => 1, keep_empty_statements => 1 }); my @verbatim_statements = $verbatim_splitter->split($sql); is ( join( '', @verbatim_statements ), $sql, 'SQL code verbatim rebuilt' ); #$sql eq join '', @verbatim_statements; 82-synopsis.t000644001750001750 232111543335122 20041 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/tuse strict; use warnings; use Test::More tests => 1; 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 use SQL::SplitStatement; my $sql_splitter = SQL::SplitStatement->new; my @statements = $sql_splitter->split($sql_code); cmp_ok ( scalar @statements, '==', 4, 'number of atomic statements' ); #use Data::Dumper::Perltidy; #diag Dumper \@statements; # Adjusted to match @statements, not \@statements. # @statements = ( # '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)' # ); 40-comments.t000644001750001750 275211543335122 20001 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 6; my $sql = <<'SQL'; /* Multiline C-Style Comment! */ CREATE TABLE t1 (a, b); --inline comment; with semicolons; -- standalone comment CREATE TABLE t2 (a, b); -- standalone comment w/ trailing spaces CREATE TABLE t3 (a, b) /* inlined C-style comments... */ /* ... before terminator */ ; CREATE TABLE last; -- Trailing standalone comment SQL my $splitter; my @statements; $splitter = SQL::SplitStatement->new; @statements = $splitter->split($sql); cmp_ok ( scalar(@statements), '==', 4, 'number of atomic statements w/o comments' ); isnt ( join( '', @statements ), $sql, q[SQL code don't rebuild w/o comments] ); $splitter = SQL::SplitStatement->new({ keep_comments => 1 }); @statements = $splitter->split($sql); # We have an extra statement made solely by comments. # It's not recognized as an empty statement, that's right. cmp_ok ( scalar(@statements), '==', 5, 'number of atomic statements w/ comments' ); isnt ( join( '', @statements ), $sql, q[SQL code don't rebuild only w/ comments] ); $splitter = SQL::SplitStatement->new({ keep_terminator => 1, keep_extra_spaces => 1, keep_comments => 1 }); @statements = $splitter->split($sql); cmp_ok ( scalar(@statements), '==', 5, 'number of atomic statements w/ comments' ); is ( join( '', @statements ), $sql, q[SQL code rebuilt w/ comments, semicolon and spaces] ); 94-exception.t000644001750001750 131411543335122 20154 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More; BEGIN { unless ( eval 'use Test::Exception; 1' ) { plan skip_all => "please install Test::Exception to run these tests" } } plan tests => 3; throws_ok { my $sql_splitter = SQL::SplitStatement->new({ keep_terminator => 1, keep_terminators => 1 }) } qr/can't be both assigned/, 'keep_terminator and keep_terminators both assigned'; lives_ok { my $sql_splitter = SQL::SplitStatement->new({ keep_terminator => 1 }) } 'keep_terminator only'; lives_ok { my $sql_splitter = SQL::SplitStatement->new({ keep_terminators => 1 }) } 'keep_terminators only'; 75-proc_grant.t000644001750001750 156211543335122 20320 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 2; # Bug report by Alexander Sennhauser my $sql_code = <<'SQL'; GRANT CREATE PROCEDURE TO test; CREATE OR REPLACE PACKAGE UTIL IS PROCEDURE VERIFY_USER(P_USER_NAME IN VARCHAR2); END UTIL; / CREATE TABLE foo ( foo_field_1 VARCHAR, foo_field_2 VARCHAR ); revoke CREATE PROCEDURE TO test; CREATE TABLE bar ( bar_field_1 VARCHAR, bar_field_2 VARCHAR ) SQL my $splitter; my @statements; $splitter = SQL::SplitStatement->new( keep_terminator => 1, keep_extra_spaces => 1, keep_comments => 1, keep_empty_statements => 1 ); @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 5, 'Statements correctly split' ); is ( join( '', @statements ), $sql_code, 'SQL code verbatim rebuilt' ); author-critic.t000644001750001750 66611543335122 20472 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/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(); 90-mysql_mixed.t000644001750001750 256011543335122 20511 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 9; my $sql_code; $sql_code = <<'SQL'; DELIMITER $$ CREATE PROCEDURE `3blocks`(n INT) BEGIN DECLARE newn INT DEFAULT n; BEGIN DECLARE newn INT DEFAULT n * 2; IF TRUE THEN BEGIN DECLARE newn INT DEFAULT n * 3; SELECT n 'Orig', 3 'Run', newn 'New Factor'; END; END IF; SELECT n 'Orig', 2 'Run', newn 'New Factor'; END; SELECT n 'Orig', 1 'Run', newn 'New Factor'; END$$ DELIMITER ; CALL `3blocks`(10); DELIMITER $nando$ CREATE PROCEDURE `3blocks`(n INT) BEGIN DECLARE newn INT DEFAULT n; BEGIN DECLARE newn INT DEFAULT n * 2; IF TRUE THEN BEGIN DECLARE newn INT DEFAULT n * 3; SELECT n 'Orig', 3 'Run', newn 'New Factor'; END; END IF; SELECT n 'Orig', 2 'Run', newn 'New Factor'; END; SELECT n 'Orig', 1 'Run', newn 'New Factor'; END$nando$ DELIMITER ; CALL `3blocks`(10); SQL my $splitter; my @statements; my @endings; $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 8, 'Statements correctly split' ); @endings = qw| $$ END ; `3blocks`(10) $nando$ END ; `3blocks`(10) |; like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; 50-transactions.t000644001750001750 367311543335122 20670 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 4; my $sql_code; my $splitter; my @statements; $sql_code = <<'SQL'; BEGIN; UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; SAVEPOINT my_savepoint; UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Bob'; -- oops ... forget that and use Wally's account ROLLBACK TO my_savepoint; UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Wally'; COMMIT; SQL $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 7, 'Statements correctly split' ); $splitter = SQL::SplitStatement->new; $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); $sql_code = <<'SQL'; CREATE TABLE table1; -- Now wait... START TRANSACTION; SELECT @A:=SUM(salary) FROM table1 WHERE type=1; UPDATE table2 SET summary=@A WHERE type=1; COMMIT; DROP table1; BEGIN ISOLATION LEVEL SERIALIZABLE; UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; SAVEPOINT my_savepoint; UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Bob'; -- oops ... forget that and use Wally's account ROLLBACK TO my_savepoint; UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Wally'; COMMIT; DROP TABLE accounts; SQL $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 14, 'Statements correctly split' ); $splitter = SQL::SplitStatement->new; $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); 90-mysql_sakila.t000644001750001750 311711543335122 20646 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 57; my $filename; my $sql_code; my $splitter; my @statements; my @endings; $splitter = SQL::SplitStatement->new; $filename = 't/data/sakila-schema.sql'; open my $fh, '<', $filename or die "Can't open file $filename: ", $!; $sql_code = do { local $/; <$fh> }; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 55, 'Statements correctly split' ); @endings = ( q[UNIQUE_CHECKS=0] , q[FOREIGN_KEY_CHECKS=0] , q[SQL_MODE='TRADITIONAL'] , ( 'sakila' ) x 3 , ( 'CHARSET=utf8' ) x 10 , q[;;] , ( 'END' ) x 3 , q[;] , ( 'CHARSET=utf8' ) x 6 , q[country.country_id] , q[film.film_id] , q[film.film_id] , q[country.country_id] , q[c.city] , q[DESC] , q[a.last_name] , q[//] , q[END] , ( ';', '$$', 'END' ) x 5 , q[;] , q[SQL_MODE=@OLD_SQL_MODE] , q[=@OLD_FOREIGN_KEY_CHECKS], q[=@OLD_UNIQUE_CHECKS] ); like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); 60-placeholders.t000644001750001750 357611543335122 20630 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 15; my $expected_placeholders = [0, 2, 0, 3, 4, 0, 4, 0, 0, 2, 0, 3, 4]; my $sql_code = <<'SQL'; CREATE TABLE state (id, "?name?"); INSERT INTO state (id, "?name?") VALUES (?, ?); -- Comment with question mark? CREATE TABLE city (id, name, state_id); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?); /* Comment with $1, $2 etc. */ PREPARE fooplan (int, text, bool, numeric) AS INSERT INTO foo VALUES($1, $2, $3, $4); EXECUTE fooplan(1, 'Hunter Valley', 't', 200.00); --Comment with :foo, :bar, :baz, :qux etc. PREPARE fooplan (int, text, bool, numeric) AS INSERT INTO foo VALUES(:foo, :bar, :baz, :qux); EXECUTE fooplan(1, 'Hunter Valley', 't', 200.00); CREATE TABLE state (id, "?name?"); INSERT INTO state (id, "?name?") VALUES (:1, :2); -- Comment with :1, :2, :3 CREATE TABLE city (id, name, state_id); INSERT INTO city (id, name, state_id) VALUES (:1, :2, :3); CREATE OR REPLACE FUNCTION artificial_test( fib_for integer ) RETURNS integer AS $rocco$ BEGIN /* Comment with $1, $2 etc. */ PREPARE fooplan (int, text, bool, numeric) AS INSERT INTO foo VALUES($1, $2, $3, $4); EXECUTE fooplan(1, 'Hunter Valley', 't', 200.00); RETURN 1; END; $rocco$LANGUAGE plpgsql; SQL my ( $statements, $placeholders ); my @endings; my $splitter = SQL::SplitStatement->new; ( $statements, $placeholders ) = $splitter->split_with_placeholders( $sql_code ); cmp_ok( @$statements, '==', 13, 'Statements correctly split' ); @endings = qw| "?name?") ?) state_id) ?) $4) 200.00) :qux) 200.00) "?name?") :2) state_id) :3) plpgsql |; like( $statements->[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; is_deeply( $placeholders, $expected_placeholders, 'Placeholders count' ); release-no-tabs.t000644001750001750 45011543335122 20665 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/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(); 30-nested_blocks.t000644001750001750 154111543335122 20765 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 2; # This is artificial, not valid SQL. my $sql = <<'SQL'; statement1; DECLARE BEGIN statement2; END; CREATE -- another comment BEGIN CREATE BegiN statement3 ; END; CREATE bEgIn CREATE -- Inlined random comment BEGIN statement4 ; statement5; statement6 end; END ; EnD; -- a comment; /* A multiline comment */ DECLARE BEGIN statement7 END SQL chop( my $clean_sql = $sql ); my $sql_splitter = SQL::SplitStatement->new( keep_comments => 1 ); my @statements = $sql_splitter->split($sql); cmp_ok ( scalar(@statements), '==', 4, 'number of atomic statements' ); is ( join( ";\n", @statements ), $clean_sql, 'SQL code successfully rebuilt' ); release-distmeta.t000644001750001750 45511543335122 21141 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/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 43311543335122 21130 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/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-synopsis.t000644001750001750 46311543335122 21215 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/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::Synopsis"; plan skip_all => "Test::Synopsis required for testing synopses" if $@; all_synopsis_ok('lib'); 90-mysql_delimiter.t000644001750001750 505111543335122 21357 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 3; # Bug report by Alexander Sennhauser my @input_statements; my $sql_code; $input_statements[0] = <<'SQL'; DROP TRIGGER IF EXISTS user_change_password; SQL $input_statements[1] = <<'SQL'; DELIMITER // SQL $input_statements[2] = <<'SQL'; CREATE TRIGGER user_change_password AFTER UPDATE ON user FOR EACH ROW my_block: BEGIN IF NEW.password != OLD.password THEN UPDATE user_authentication_results AS uar SET password_changed = 1 WHERE uar.user_id = NEW.user_id; END IF; END my_block; / -- Illegal, just to check that a / inside a custom delimiter -- can't split the statement. set localvariable datatype; set localvariable = parameter2; select fields from table where field1 = parameter1; // SQL $input_statements[3] = <<'SQL'; delimiter ; SQL $input_statements[4] = <<'SQL'; CREATE TABLE foo ( foo_field_1 VARCHAR, foo_field_2 VARCHAR ); SQL $input_statements[5] = <<'SQL'; CREATE TABLE bar ( bar_field_1 VARCHAR, bar_field_2 VARCHAR ) SQL chomp @input_statements; $sql_code = join '', @input_statements; my $splitter; my @statements; $splitter = SQL::SplitStatement->new( keep_terminator => 1, keep_extra_spaces => 1, keep_comments => 1, keep_empty_statements => 1 ); @statements = $splitter->split( $sql_code ); is_deeply ( \@statements, \@input_statements, 'Popular custom delimiter' ); $input_statements[1] = <<'SQL'; DELIMITER "+123-Wak+ka@#> > >@|*|@< < <#@ak+kaW-321+" SQL $input_statements[2] = <<'SQL'; CREATE TRIGGER user_change_password AFTER UPDATE ON user FOR EACH ROW my_block: BEGIN IF NEW.password != OLD.password THEN UPDATE user_authentication_results AS uar SET password_changed = 1 WHERE uar.user_id = NEW.user_id; END IF; END my_block; set localvariable datatype; set localvariable = parameter2; select fields from table where field1 = parameter1; +123-Wak+ka@#> > >@|*|@< < <#@ak+kaW-321+ SQL chomp( $input_statements[1], $input_statements[2] ); $sql_code = join '', @input_statements; @statements = $splitter->split( $sql_code ); is_deeply ( \@statements, \@input_statements, 'Quoted unusual custom delimiter' ); $input_statements[1] = <<'SQL'; DELIMITER +123-Wak+ka@#> > >@|*|@< < <#@ak+kaW-321+ SQL chomp $input_statements[1]; $sql_code = join '', @input_statements; @statements = $splitter->split( $sql_code ); is_deeply ( \@statements, \@input_statements, 'Unquoted unusual custom delimiter' ); release-pod-syntax.t000644001750001750 45011543335122 21430 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/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(); 70-proc_plsql_mixed.t000644001750001750 567611543335122 21533 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 9; my $sql_code = <<'SQL'; CREATE TABLE sqr_root_sum (num NUMBER, sq_root NUMBER(6,2), sqr NUMBER, sum_sqrs NUMBER); DECLARE s PLS_INTEGER; BEGIN FOR i in 1..100 LOOP s := (i * (i + 1) * (2*i +1)) / 6; -- sum of squares INSERT INTO sqr_root_sum VALUES (i, SQRT(i), i*i, s ); END LOOP; END; / CREATE TABLE temp (tempid NUMBER(6), tempsal NUMBER(8,2), tempname VARCHAR2(25)); DECLARE total NUMBER(9) := 0; counter NUMBER(6) := 0; CURSOR company_cur (id_in IN NUMBER) RETURN company%ROWTYPE IS SELECT * FROM company; BEGIN LOOP counter := counter + 1; total := total + counter * counter; -- exit loop when condition is true EXIT WHEN total > 25000; END LOOP; DBMS_OUTPUT.PUT_LINE('Counter: ' || TO_CHAR(counter) || ' Total: ' || TO_CHAR(total)); END; . / -- including OR REPLACE is more convenient when updating a subprogram CREATE OR REPLACE procEDURE award_bonus (emp_id NUMBER, bonus NUMBER) AS commission REAL; comm_missing EXCEPTION; begIN -- executable part starts here SELECT commission_pct / 100 INTO commission FROM employees WHERE employee_id = emp_id; IF commission IS NULL THEN RAISE comm_missing; ELSE UPDATE employees SET salary = salary + bonus*commission WHERE employee_id = emp_id; END IF; EXCEPTION -- exception-handling part starts here WHEN comm_missing THEN DBMS_OUTPUT.PUT_LINE('This employee does not receive a commission.'); commission := 0; WHEN OTHERS THEN NULL; -- for other exceptions do nothing END award_bonus; / CALL award_bonus(150, 400); SQL my $splitter; my @statements; my @endings; $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 6, 'Statements correctly split' ); $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); # Let's try again, with a different constructor $splitter = SQL::SplitStatement->new( keep_extra_spaces => 1, keep_empty_statements => 1, keep_terminator => 1, keep_comments => 1 ); $sql_code .= ';ALTER TABLE temp'; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 8, 'Statements correctly split' ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); @endings = qw| NUMBER) END VARCHAR2(25)) END award_bonus |; $splitter->keep_extra_spaces(0); $splitter->keep_empty_statements(0); $splitter->keep_terminators(0); $splitter->keep_comments(0); @statements = $splitter->split( $sql_code ); like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; 42-slash_terminates.t000644001750001750 216311543335122 21517 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 5; my $sql_code = <<'SQL'; CREATE TABLE test_tab ( num NUMBER, den NUMBER ); SELECT * from test_tab were 1 = num / den; DROP TABLE test_tab SQL my $splitter; my @statements; my @endings; $splitter = SQL::SplitStatement->new( slash_terminates => undef ); @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 3, 'Statements correctly split' ); $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); $splitter->slash_terminates(undef); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); @endings = qw| ) den test_tab |; $splitter->keep_extra_spaces(0); $splitter->keep_empty_statements(0); $splitter->keep_terminators(0); $splitter->keep_comments(0); $splitter->slash_terminates(0); @statements = $splitter->split( $sql_code ); like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; 72-proc_plsql_alias.t000644001750001750 656111543335122 21512 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 9; my $sql_code = <<'SQL'; CREATE OR REPLACE PACKAGE UTIL IS PROCEDURE VERIFY_USER(P_USER_NAME IN VARCHAR2); END UTIL; / CREATE OR REPLACE PACKAGE BODY OS_UTIL IS PROCEDURE VERIFY_USER(P_USER_NAME IN VARCHAR2) IS a_user varchar2(30); BEGIN SELECT user INTO a_user FROM dual; IF upper(a_user) != upper(p_user_name) THEN RAISE_APPLICATION_ERROR( -20004, 'This code can be run as user <' || p_user_name || '> only!' ); END IF; END; END OS_UTIL; / CREATE TRIGGER check_salary BEFORE INSERT OR UPDATE OF sal, job ON emp FOR EACH ROW WHEN (new.job != 'PRESIDENT') DECLARE minsal NUMBER; maxsal NUMBER; BEGIN /* Get salary range for a given job from table sals. */ SELECT losal, hisal INTO minsal, maxsal FROM sals WHERE job = :new.job; /* If salary is out of range, increase is negative, * * or increase exceeds 10%, raise an exception. */ IF (:new.sal < minsal OR :new.sal > maxsal) THEN raise_application_error(-20225, 'Salary out of range'); ELSIF (:new.sal < :old.sal) THEN raise_application_error(-20320, 'Negative increase'); ELSIF (:new.sal > 1.1 * :old.sal) THEN raise_application_error(-20325, 'Increase exceeds 10%'); END IF; END; begin dbms_java.grant_permission ('RT_TEST', 'java.io.FilePermission', '/usr/bin/ps', 'execute'); dbms_java.grant_permission ('RT_TEST', 'java.lang.RuntimePermission', '*', 'writeFileDescriptor' ); end; / CREATE OR REPLACE FUNCTION nested(some_date DATE) RETURN VARCHAR2 IS yrstr VARCHAR2(4); -- beginning of nested function in declaration section FUNCTION turn_around ( year_string VARCHAR2) RETURN VARCHAR2 IS BEGIN yrstr := TO_CHAR(TO_NUMBER(year_string)*2); RETURN yrstr; END; -- end of nested function in declaration section -- beginning of named function BEGIN yrstr := TO_CHAR(some_date, 'YYYY'); yrstr := turn_around(yrstr); RETURN yrstr; END; -- nested begin dbms_java.grant_permission ('RT_TEST', 'java.io.FilePermission', '/usr/bin/ps', 'execute'); dbms_java.grant_permission ('RT_TEST', 'java.lang.RuntimePermission', '*', 'writeFileDescriptor' ); end; DECLARE PROCEDURE P1 IS BEGIN dbms_output.put_line('From procedure p1'); p2; END P1; PROCEDURE P2 IS BEGIN dbms_output.put_line('From procedure p2'); p3; END P2; PROCEDURE P3 IS BEGIN dbms_output.put_line('From procedure p3'); END P3; BEGIN p1; END; CREATE OR REPLACE PACKAGE UTIL IS PROCEDURE VERIFY_USER(P_USER_NAME IN VARCHAR2); END UTIL; / SQL my $splitter; my @statements; my @endings; $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 8, 'Statements correctly split' ); @endings = qw| UTIL OS_UTIL END end END end END UTIL |; like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; 05-empty_statements.t000644001750001750 247011543335122 21557 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 5; my $sql = <<'SQL'; DELIMITER $strange$ CREATE TABLE foo ( foo_field_1 VARCHAR, foo_field_2 VARCHAR )$strange$ CREATE TABLE bar ( bar_field_1 VARCHAR, bar_field_2 VARCHAR )$strange$ $strange$ SQL chomp ( my $clean_sql = <<'SQL' ); DELIMITER $strange$CREATE TABLE foo ( foo_field_1 VARCHAR, foo_field_2 VARCHAR )CREATE TABLE bar ( bar_field_1 VARCHAR, bar_field_2 VARCHAR ) SQL my $sql_splitter = SQL::SplitStatement->new({ keep_terminator => 1, keep_extra_spaces => 1, keep_empty_statements => 1 }); my @statements; @statements = $sql_splitter->split($sql); cmp_ok ( scalar(@statements), '==', 5, 'number of atomic statements w/ terminator' ); is ( join( '', @statements ), $sql, 'SQL code rebuilt w/ terminator' ); $sql_splitter->keep_terminators(0); @statements = $sql_splitter->split($sql); is ( $statements[0] . join( '$strange$', @statements[1..$#statements] ), $sql, 'SQL code rebuilt w/o terminator' ); @statements = $sql_splitter->new->split($sql); cmp_ok ( scalar(@statements), '==', 3, 'number of atomic statements w/o terminator' ); is ( join( '', @statements ), $clean_sql, 'SQL code rebuilt w/o terminator' ); data000755001750001750 011543335122 16312 5ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/tcreate_table.sql000644001750001750 24111543335122 21561 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t/dataCREATE TABLE foo ( foo_field_1 VARCHAR, foo_field_2 VARCHAR ); -- Internal comment CREATE TABLE bar ( bar_field_1 VARCHAR, bar_field_2 VARCHAR );release-unused-vars.t000644001750001750 44511543335122 21602 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/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(); 92-postgresql_pagila.t000644001750001750 230111543335122 21671 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 38; my $filename; my $sql_code; my $splitter; my @statements; my @endings; $splitter = SQL::SplitStatement->new; $filename = 't/data/pagila-schema.sql'; open my $fh, '<', $filename or die "Can't open file $filename: ", $!; $sql_code = do { local $/; <$fh> }; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 225, 'Statements correctly split' ); @endings = ( qw| 'UTF8' off false warning off schema' plpgsql postgres pg_catalog 1 postgres '' false |, ( ')', 'postgres' ) x 3, qw|IMMUTABLE postgres ) postgres 1 postgres ) postgres 1 postgres|, ( ')', 'postgres' ) x 3, qw|a.last_name| ); like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); sakila-schema.sql000644001750001750 5503311543335122 21722 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t/data-- Sakila Sample Database Schema -- Version 0.8 -- Copyright (c) 2006, MySQL AB -- All rights reserved. -- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- * Neither the name of MySQL AB nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL'; DROP SCHEMA IF EXISTS sakila; CREATE SCHEMA sakila; USE sakila; -- -- Table structure for table `actor` -- CREATE TABLE actor ( actor_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, first_name VARCHAR(45) NOT NULL, last_name VARCHAR(45) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (actor_id), KEY idx_actor_last_name (last_name) )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `address` -- CREATE TABLE address ( address_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, address VARCHAR(50) NOT NULL, address2 VARCHAR(50) DEFAULT NULL, district VARCHAR(20) NOT NULL, city_id SMALLINT UNSIGNED NOT NULL, postal_code VARCHAR(10) DEFAULT NULL, phone VARCHAR(20) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (address_id), KEY idx_fk_city_id (city_id), CONSTRAINT `fk_address_city` FOREIGN KEY (city_id) REFERENCES city (city_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `category` -- CREATE TABLE category ( category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(25) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (category_id) )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `city` -- CREATE TABLE city ( city_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, city VARCHAR(50) NOT NULL, country_id SMALLINT UNSIGNED NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (city_id), KEY idx_fk_country_id (country_id), CONSTRAINT `fk_city_country` FOREIGN KEY (country_id) REFERENCES country (country_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `country` -- CREATE TABLE country ( country_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, country VARCHAR(50) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (country_id) )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `customer` -- CREATE TABLE customer ( customer_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, store_id TINYINT UNSIGNED NOT NULL, first_name VARCHAR(45) NOT NULL, last_name VARCHAR(45) NOT NULL, email VARCHAR(50) DEFAULT NULL, address_id SMALLINT UNSIGNED NOT NULL, active BOOLEAN NOT NULL DEFAULT TRUE, create_date DATETIME NOT NULL, last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (customer_id), KEY idx_fk_store_id (store_id), KEY idx_fk_address_id (address_id), KEY idx_last_name (last_name), CONSTRAINT fk_customer_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_customer_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `film` -- CREATE TABLE film ( film_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL, description TEXT DEFAULT NULL, release_year YEAR DEFAULT NULL, language_id TINYINT UNSIGNED NOT NULL, original_language_id TINYINT UNSIGNED DEFAULT NULL, rental_duration TINYINT UNSIGNED NOT NULL DEFAULT 3, rental_rate DECIMAL(4,2) NOT NULL DEFAULT 4.99, length SMALLINT UNSIGNED DEFAULT NULL, replacement_cost DECIMAL(5,2) NOT NULL DEFAULT 19.99, rating ENUM('G','PG','PG-13','R','NC-17') DEFAULT 'G', special_features SET('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (film_id), KEY idx_title (title), KEY idx_fk_language_id (language_id), KEY idx_fk_original_language_id (original_language_id), CONSTRAINT fk_film_language FOREIGN KEY (language_id) REFERENCES language (language_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_film_language_original FOREIGN KEY (original_language_id) REFERENCES language (language_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `film_actor` -- CREATE TABLE film_actor ( actor_id SMALLINT UNSIGNED NOT NULL, film_id SMALLINT UNSIGNED NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (actor_id,film_id), KEY idx_fk_film_id (`film_id`), CONSTRAINT fk_film_actor_actor FOREIGN KEY (actor_id) REFERENCES actor (actor_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_film_actor_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `film_category` -- CREATE TABLE film_category ( film_id SMALLINT UNSIGNED NOT NULL, category_id TINYINT UNSIGNED NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (film_id, category_id), CONSTRAINT fk_film_category_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_film_category_category FOREIGN KEY (category_id) REFERENCES category (category_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `film_text` -- CREATE TABLE film_text ( film_id SMALLINT NOT NULL, title VARCHAR(255) NOT NULL, description TEXT, PRIMARY KEY (film_id), FULLTEXT KEY idx_title_description (title,description) )ENGINE=MyISAM DEFAULT CHARSET=utf8; -- -- Triggers for loading film_text from film -- DELIMITER ;; CREATE TRIGGER `ins_film` AFTER INSERT ON `film` FOR EACH ROW BEGIN INSERT INTO film_text (film_id, title, description) VALUES (new.film_id, new.title, new.description); END;; CREATE TRIGGER `upd_film` AFTER UPDATE ON `film` FOR EACH ROW BEGIN IF (old.title != new.title) or (old.description != new.description) THEN UPDATE film_text SET title=new.title, description=new.description, film_id=new.film_id WHERE film_id=old.film_id; END IF; END;; CREATE TRIGGER `del_film` AFTER DELETE ON `film` FOR EACH ROW BEGIN DELETE FROM film_text WHERE film_id = old.film_id; END;; DELIMITER ; -- -- Table structure for table `inventory` -- CREATE TABLE inventory ( inventory_id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, film_id SMALLINT UNSIGNED NOT NULL, store_id TINYINT UNSIGNED NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (inventory_id), KEY idx_fk_film_id (film_id), KEY idx_store_id_film_id (store_id,film_id), CONSTRAINT fk_inventory_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_inventory_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `language` -- CREATE TABLE language ( language_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, name CHAR(20) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (language_id) )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `payment` -- CREATE TABLE payment ( payment_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, customer_id SMALLINT UNSIGNED NOT NULL, staff_id TINYINT UNSIGNED NOT NULL, rental_id INT DEFAULT NULL, amount DECIMAL(5,2) NOT NULL, payment_date DATETIME NOT NULL, last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (payment_id), KEY idx_fk_staff_id (staff_id), KEY idx_fk_customer_id (customer_id), CONSTRAINT fk_payment_rental FOREIGN KEY (rental_id) REFERENCES rental (rental_id) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT fk_payment_customer FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_payment_staff FOREIGN KEY (staff_id) REFERENCES staff (staff_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `rental` -- CREATE TABLE rental ( rental_id INT NOT NULL AUTO_INCREMENT, rental_date DATETIME NOT NULL, inventory_id MEDIUMINT UNSIGNED NOT NULL, customer_id SMALLINT UNSIGNED NOT NULL, return_date DATETIME DEFAULT NULL, staff_id TINYINT UNSIGNED NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (rental_id), UNIQUE KEY (rental_date,inventory_id,customer_id), KEY idx_fk_inventory_id (inventory_id), KEY idx_fk_customer_id (customer_id), KEY idx_fk_staff_id (staff_id), CONSTRAINT fk_rental_staff FOREIGN KEY (staff_id) REFERENCES staff (staff_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_rental_inventory FOREIGN KEY (inventory_id) REFERENCES inventory (inventory_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_rental_customer FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `staff` -- CREATE TABLE staff ( staff_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, first_name VARCHAR(45) NOT NULL, last_name VARCHAR(45) NOT NULL, address_id SMALLINT UNSIGNED NOT NULL, picture BLOB DEFAULT NULL, email VARCHAR(50) DEFAULT NULL, store_id TINYINT UNSIGNED NOT NULL, active BOOLEAN NOT NULL DEFAULT TRUE, username VARCHAR(16) NOT NULL, password VARCHAR(40) BINARY DEFAULT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (staff_id), KEY idx_fk_store_id (store_id), KEY idx_fk_address_id (address_id), CONSTRAINT fk_staff_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_staff_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `store` -- CREATE TABLE store ( store_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, manager_staff_id TINYINT UNSIGNED NOT NULL, address_id SMALLINT UNSIGNED NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (store_id), UNIQUE KEY idx_unique_manager (manager_staff_id), KEY idx_fk_address_id (address_id), CONSTRAINT fk_store_staff FOREIGN KEY (manager_staff_id) REFERENCES staff (staff_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_store_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE )ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- View structure for view `customer_list` -- CREATE VIEW customer_list AS SELECT cu.customer_id AS ID, CONCAT(cu.first_name, _utf8' ', cu.last_name) AS name, a.address AS address, a.postal_code AS `zip code`, a.phone AS phone, city.city AS city, country.country AS country, IF(cu.active, _utf8'active',_utf8'') AS notes, cu.store_id AS SID FROM customer AS cu JOIN address AS a ON cu.address_id = a.address_id JOIN city ON a.city_id = city.city_id JOIN country ON city.country_id = country.country_id; -- -- View structure for view `film_list` -- CREATE VIEW film_list AS SELECT film.film_id AS FID, film.title AS title, film.description AS description, category.name AS category, film.rental_rate AS price, film.length AS length, film.rating AS rating, GROUP_CONCAT(CONCAT(actor.first_name, _utf8' ', actor.last_name) SEPARATOR ', ') AS actors FROM category LEFT JOIN film_category ON category.category_id = film_category.category_id LEFT JOIN film ON film_category.film_id = film.film_id JOIN film_actor ON film.film_id = film_actor.film_id JOIN actor ON film_actor.actor_id = actor.actor_id GROUP BY film.film_id; -- -- View structure for view `nicer_but_slower_film_list` -- CREATE VIEW nicer_but_slower_film_list AS SELECT film.film_id AS FID, film.title AS title, film.description AS description, category.name AS category, film.rental_rate AS price, film.length AS length, film.rating AS rating, GROUP_CONCAT(CONCAT(CONCAT(UCASE(SUBSTR(actor.first_name,1,1)), LCASE(SUBSTR(actor.first_name,2,LENGTH(actor.first_name))),_utf8' ',CONCAT(UCASE(SUBSTR(actor.last_name,1,1)), LCASE(SUBSTR(actor.last_name,2,LENGTH(actor.last_name)))))) SEPARATOR ', ') AS actors FROM category LEFT JOIN film_category ON category.category_id = film_category.category_id LEFT JOIN film ON film_category.film_id = film.film_id JOIN film_actor ON film.film_id = film_actor.film_id JOIN actor ON film_actor.actor_id = actor.actor_id GROUP BY film.film_id; -- -- View structure for view `staff_list` -- CREATE VIEW staff_list AS SELECT s.staff_id AS ID, CONCAT(s.first_name, _utf8' ', s.last_name) AS name, a.address AS address, a.postal_code AS `zip code`, a.phone AS phone, city.city AS city, country.country AS country, s.store_id AS SID FROM staff AS s JOIN address AS a ON s.address_id = a.address_id JOIN city ON a.city_id = city.city_id JOIN country ON city.country_id = country.country_id; -- -- View structure for view `sales_by_store` -- CREATE VIEW sales_by_store AS SELECT CONCAT(c.city, _utf8',', cy.country) AS store , CONCAT(m.first_name, _utf8' ', m.last_name) AS manager , SUM(p.amount) AS total_sales FROM payment AS p INNER JOIN rental AS r ON p.rental_id = r.rental_id INNER JOIN inventory AS i ON r.inventory_id = i.inventory_id INNER JOIN store AS s ON i.store_id = s.store_id INNER JOIN address AS a ON s.address_id = a.address_id INNER JOIN city AS c ON a.city_id = c.city_id INNER JOIN country AS cy ON c.country_id = cy.country_id INNER JOIN staff AS m ON s.manager_staff_id = m.staff_id GROUP BY s.store_id ORDER BY cy.country, c.city; -- -- View structure for view `sales_by_film_category` -- -- Note that total sales will add up to >100% because -- some titles belong to more than 1 category -- CREATE VIEW sales_by_film_category AS SELECT c.name AS category , SUM(p.amount) AS total_sales FROM payment AS p INNER JOIN rental AS r ON p.rental_id = r.rental_id INNER JOIN inventory AS i ON r.inventory_id = i.inventory_id INNER JOIN film AS f ON i.film_id = f.film_id INNER JOIN film_category AS fc ON f.film_id = fc.film_id INNER JOIN category AS c ON fc.category_id = c.category_id GROUP BY c.name ORDER BY total_sales DESC; -- -- View structure for view `actor_info` -- CREATE DEFINER=CURRENT_USER SQL SECURITY INVOKER VIEW actor_info AS SELECT a.actor_id, a.first_name, a.last_name, GROUP_CONCAT(DISTINCT CONCAT(c.name, ': ', (SELECT GROUP_CONCAT(f.title ORDER BY f.title SEPARATOR ', ') FROM sakila.film f INNER JOIN sakila.film_category fc ON f.film_id = fc.film_id INNER JOIN sakila.film_actor fa ON f.film_id = fa.film_id WHERE fc.category_id = c.category_id AND fa.actor_id = a.actor_id ) ) ORDER BY c.name SEPARATOR '; ') AS film_info FROM sakila.actor a LEFT JOIN sakila.film_actor fa ON a.actor_id = fa.actor_id LEFT JOIN sakila.film_category fc ON fa.film_id = fc.film_id LEFT JOIN sakila.category c ON fc.category_id = c.category_id GROUP BY a.actor_id, a.first_name, a.last_name; -- -- Procedure structure for procedure `rewards_report` -- DELIMITER // CREATE PROCEDURE rewards_report ( IN min_monthly_purchases TINYINT UNSIGNED , IN min_dollar_amount_purchased DECIMAL(10,2) UNSIGNED , OUT count_rewardees INT ) LANGUAGE SQL NOT DETERMINISTIC READS SQL DATA SQL SECURITY DEFINER COMMENT 'Provides a customizable report on best customers' proc: BEGIN DECLARE last_month_start DATE; DECLARE last_month_end DATE; /* Some sanity checks... */ IF min_monthly_purchases = 0 THEN SELECT 'Minimum monthly purchases parameter must be > 0'; LEAVE proc; END IF; IF min_dollar_amount_purchased = 0.00 THEN SELECT 'Minimum monthly dollar amount purchased parameter must be > $0.00'; LEAVE proc; END IF; /* Determine start and end time periods */ SET last_month_start = DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH); SET last_month_start = STR_TO_DATE(CONCAT(YEAR(last_month_start),'-',MONTH(last_month_start),'-01'),'%Y-%m-%d'); SET last_month_end = LAST_DAY(last_month_start); /* Create a temporary storage area for Customer IDs. */ CREATE TEMPORARY TABLE tmpCustomer (customer_id SMALLINT UNSIGNED NOT NULL PRIMARY KEY); /* Find all customers meeting the monthly purchase requirements */ INSERT INTO tmpCustomer (customer_id) SELECT p.customer_id FROM payment AS p WHERE DATE(p.payment_date) BETWEEN last_month_start AND last_month_end GROUP BY customer_id HAVING SUM(p.amount) > min_dollar_amount_purchased AND COUNT(customer_id) > min_monthly_purchases; /* Populate OUT parameter with count of found customers */ SELECT COUNT(*) FROM tmpCustomer INTO count_rewardees; /* Output ALL customer information of matching rewardees. Customize output as needed. */ SELECT c.* FROM tmpCustomer AS t INNER JOIN customer AS c ON t.customer_id = c.customer_id; /* Clean up */ DROP TABLE tmpCustomer; END // DELIMITER ; DELIMITER $$ CREATE FUNCTION get_customer_balance(p_customer_id INT, p_effective_date DATETIME) RETURNS DECIMAL(5,2) DETERMINISTIC READS SQL DATA BEGIN #OK, WE NEED TO CALCULATE THE CURRENT BALANCE GIVEN A CUSTOMER_ID AND A DATE #THAT WE WANT THE BALANCE TO BE EFFECTIVE FOR. THE BALANCE IS: # 1) RENTAL FEES FOR ALL PREVIOUS RENTALS # 2) ONE DOLLAR FOR EVERY DAY THE PREVIOUS RENTALS ARE OVERDUE # 3) IF A FILM IS MORE THAN RENTAL_DURATION * 2 OVERDUE, CHARGE THE REPLACEMENT_COST # 4) SUBTRACT ALL PAYMENTS MADE BEFORE THE DATE SPECIFIED DECLARE v_rentfees DECIMAL(5,2); #FEES PAID TO RENT THE VIDEOS INITIALLY DECLARE v_overfees INTEGER; #LATE FEES FOR PRIOR RENTALS DECLARE v_payments DECIMAL(5,2); #SUM OF PAYMENTS MADE PREVIOUSLY SELECT IFNULL(SUM(film.rental_rate),0) INTO v_rentfees FROM film, inventory, rental WHERE film.film_id = inventory.film_id AND inventory.inventory_id = rental.inventory_id AND rental.rental_date <= p_effective_date AND rental.customer_id = p_customer_id; SELECT IFNULL(SUM(IF((TO_DAYS(rental.return_date) - TO_DAYS(rental.rental_date)) > film.rental_duration, ((TO_DAYS(rental.return_date) - TO_DAYS(rental.rental_date)) - film.rental_duration),0)),0) INTO v_overfees FROM rental, inventory, film WHERE film.film_id = inventory.film_id AND inventory.inventory_id = rental.inventory_id AND rental.rental_date <= p_effective_date AND rental.customer_id = p_customer_id; SELECT IFNULL(SUM(payment.amount),0) INTO v_payments FROM payment WHERE payment.payment_date <= p_effective_date AND payment.customer_id = p_customer_id; RETURN v_rentfees + v_overfees - v_payments; END $$ DELIMITER ; DELIMITER $$ CREATE PROCEDURE film_in_stock(IN p_film_id INT, IN p_store_id INT, OUT p_film_count INT) READS SQL DATA BEGIN SELECT inventory_id FROM inventory WHERE film_id = p_film_id AND store_id = p_store_id AND inventory_in_stock(inventory_id); SELECT FOUND_ROWS() INTO p_film_count; END $$ DELIMITER ; DELIMITER $$ CREATE PROCEDURE film_not_in_stock(IN p_film_id INT, IN p_store_id INT, OUT p_film_count INT) READS SQL DATA BEGIN SELECT inventory_id FROM inventory WHERE film_id = p_film_id AND store_id = p_store_id AND NOT inventory_in_stock(inventory_id); SELECT FOUND_ROWS() INTO p_film_count; END $$ DELIMITER ; DELIMITER $$ CREATE FUNCTION inventory_held_by_customer(p_inventory_id INT) RETURNS INT READS SQL DATA BEGIN DECLARE v_customer_id INT; DECLARE EXIT HANDLER FOR NOT FOUND RETURN NULL; SELECT customer_id INTO v_customer_id FROM rental WHERE return_date IS NULL AND inventory_id = p_inventory_id; RETURN v_customer_id; END $$ DELIMITER ; DELIMITER $$ CREATE FUNCTION inventory_in_stock(p_inventory_id INT) RETURNS BOOLEAN READS SQL DATA BEGIN DECLARE v_rentals INT; DECLARE v_out INT; #AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE #FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED SELECT COUNT(*) INTO v_rentals FROM rental WHERE inventory_id = p_inventory_id; IF v_rentals = 0 THEN RETURN TRUE; END IF; SELECT COUNT(rental_id) INTO v_out FROM inventory LEFT JOIN rental USING(inventory_id) WHERE inventory.inventory_id = p_inventory_id AND rental.return_date IS NULL; IF v_out > 0 THEN RETURN FALSE; ELSE RETURN TRUE; END IF; END $$ DELIMITER ; SET SQL_MODE=@OLD_SQL_MODE; SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; pagila-schema.sql000644001750001750 14306511543335122 21736 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t/data-- -- PostgreSQL database dump -- SET client_encoding = 'UTF8'; SET standard_conforming_strings = off; SET check_function_bodies = false; SET client_min_messages = warning; SET escape_string_warning = off; -- -- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: postgres -- COMMENT ON SCHEMA public IS 'Standard public schema'; -- -- Name: plpgsql; Type: PROCEDURAL LANGUAGE; Schema: -; Owner: postgres -- CREATE PROCEDURAL LANGUAGE plpgsql; ALTER PROCEDURAL LANGUAGE plpgsql OWNER TO postgres; SET search_path = public, pg_catalog; -- -- Name: actor_actor_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE actor_actor_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.actor_actor_id_seq OWNER TO postgres; SET default_tablespace = ''; SET default_with_oids = false; -- -- Name: actor; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE actor ( actor_id integer DEFAULT nextval('actor_actor_id_seq'::regclass) NOT NULL, first_name character varying(45) NOT NULL, last_name character varying(45) NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.actor OWNER TO postgres; -- -- Name: mpaa_rating; Type: TYPE; Schema: public; Owner: postgres -- CREATE TYPE mpaa_rating AS ENUM ( 'G', 'PG', 'PG-13', 'R', 'NC-17' ); ALTER TYPE public.mpaa_rating OWNER TO postgres; -- -- Name: year; Type: DOMAIN; Schema: public; Owner: postgres -- CREATE DOMAIN year AS integer CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155))); ALTER DOMAIN public.year OWNER TO postgres; -- -- Name: _group_concat(text, text); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION _group_concat(text, text) RETURNS text AS $_$ SELECT CASE WHEN $2 IS NULL THEN $1 WHEN $1 IS NULL THEN $2 ELSE $1 || ', ' || $2 END $_$ LANGUAGE sql IMMUTABLE; ALTER FUNCTION public._group_concat(text, text) OWNER TO postgres; -- -- Name: group_concat(text); Type: AGGREGATE; Schema: public; Owner: postgres -- CREATE AGGREGATE group_concat(text) ( SFUNC = _group_concat, STYPE = text ); ALTER AGGREGATE public.group_concat(text) OWNER TO postgres; -- -- Name: category_category_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE category_category_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.category_category_id_seq OWNER TO postgres; -- -- Name: category; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE category ( category_id integer DEFAULT nextval('category_category_id_seq'::regclass) NOT NULL, name character varying(25) NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.category OWNER TO postgres; -- -- Name: film_film_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE film_film_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.film_film_id_seq OWNER TO postgres; -- -- Name: film; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE film ( film_id integer DEFAULT nextval('film_film_id_seq'::regclass) NOT NULL, title character varying(255) NOT NULL, description text, release_year year, language_id smallint NOT NULL, original_language_id smallint, rental_duration smallint DEFAULT 3 NOT NULL, rental_rate numeric(4,2) DEFAULT 4.99 NOT NULL, length smallint, replacement_cost numeric(5,2) DEFAULT 19.99 NOT NULL, rating mpaa_rating DEFAULT 'G'::mpaa_rating, last_update timestamp without time zone DEFAULT now() NOT NULL, special_features text[], fulltext tsvector NOT NULL ); ALTER TABLE public.film OWNER TO postgres; -- -- Name: film_actor; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE film_actor ( actor_id smallint NOT NULL, film_id smallint NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.film_actor OWNER TO postgres; -- -- Name: film_category; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE film_category ( film_id smallint NOT NULL, category_id smallint NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.film_category OWNER TO postgres; -- -- Name: actor_info; Type: VIEW; Schema: public; Owner: postgres -- CREATE VIEW actor_info AS SELECT a.actor_id, a.first_name, a.last_name, group_concat(DISTINCT (((c.name)::text || ': '::text) || (SELECT group_concat((f.title)::text) AS group_concat FROM ((film f JOIN film_category fc ON ((f.film_id = fc.film_id))) JOIN film_actor fa ON ((f.film_id = fa.film_id))) WHERE ((fc.category_id = c.category_id) AND (fa.actor_id = a.actor_id)) GROUP BY fa.actor_id))) AS film_info FROM (((actor a LEFT JOIN film_actor fa ON ((a.actor_id = fa.actor_id))) LEFT JOIN film_category fc ON ((fa.film_id = fc.film_id))) LEFT JOIN category c ON ((fc.category_id = c.category_id))) GROUP BY a.actor_id, a.first_name, a.last_name; ALTER TABLE public.actor_info OWNER TO postgres; -- -- Name: address_address_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE address_address_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.address_address_id_seq OWNER TO postgres; -- -- Name: address; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE address ( address_id integer DEFAULT nextval('address_address_id_seq'::regclass) NOT NULL, address character varying(50) NOT NULL, address2 character varying(50), district character varying(20) NOT NULL, city_id smallint NOT NULL, postal_code character varying(10), phone character varying(20) NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.address OWNER TO postgres; -- -- Name: city_city_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE city_city_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.city_city_id_seq OWNER TO postgres; -- -- Name: city; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE city ( city_id integer DEFAULT nextval('city_city_id_seq'::regclass) NOT NULL, city character varying(50) NOT NULL, country_id smallint NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.city OWNER TO postgres; -- -- Name: country_country_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE country_country_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.country_country_id_seq OWNER TO postgres; -- -- Name: country; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE country ( country_id integer DEFAULT nextval('country_country_id_seq'::regclass) NOT NULL, country character varying(50) NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.country OWNER TO postgres; -- -- Name: customer_customer_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE customer_customer_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.customer_customer_id_seq OWNER TO postgres; -- -- Name: customer; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE customer ( customer_id integer DEFAULT nextval('customer_customer_id_seq'::regclass) NOT NULL, store_id smallint NOT NULL, first_name character varying(45) NOT NULL, last_name character varying(45) NOT NULL, email character varying(50), address_id smallint NOT NULL, activebool boolean DEFAULT true NOT NULL, create_date date DEFAULT ('now'::text)::date NOT NULL, last_update timestamp without time zone DEFAULT now(), active integer ); ALTER TABLE public.customer OWNER TO postgres; -- -- Name: customer_list; Type: VIEW; Schema: public; Owner: postgres -- CREATE VIEW customer_list AS SELECT cu.customer_id AS id, (((cu.first_name)::text || ' '::text) || (cu.last_name)::text) AS name, a.address, a.postal_code AS "zip code", a.phone, city.city, country.country, CASE WHEN cu.activebool THEN 'active'::text ELSE ''::text END AS notes, cu.store_id AS sid FROM (((customer cu JOIN address a ON ((cu.address_id = a.address_id))) JOIN city ON ((a.city_id = city.city_id))) JOIN country ON ((city.country_id = country.country_id))); ALTER TABLE public.customer_list OWNER TO postgres; -- -- Name: film_list; Type: VIEW; Schema: public; Owner: postgres -- CREATE VIEW film_list AS SELECT film.film_id AS fid, film.title, film.description, category.name AS category, film.rental_rate AS price, film.length, film.rating, group_concat((((actor.first_name)::text || ' '::text) || (actor.last_name)::text)) AS actors FROM ((((category LEFT JOIN film_category ON ((category.category_id = film_category.category_id))) LEFT JOIN film ON ((film_category.film_id = film.film_id))) JOIN film_actor ON ((film.film_id = film_actor.film_id))) JOIN actor ON ((film_actor.actor_id = actor.actor_id))) GROUP BY film.film_id, film.title, film.description, category.name, film.rental_rate, film.length, film.rating; ALTER TABLE public.film_list OWNER TO postgres; -- -- Name: inventory_inventory_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE inventory_inventory_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.inventory_inventory_id_seq OWNER TO postgres; -- -- Name: inventory; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE inventory ( inventory_id integer DEFAULT nextval('inventory_inventory_id_seq'::regclass) NOT NULL, film_id smallint NOT NULL, store_id smallint NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.inventory OWNER TO postgres; -- -- Name: language_language_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE language_language_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.language_language_id_seq OWNER TO postgres; -- -- Name: language; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE language ( language_id integer DEFAULT nextval('language_language_id_seq'::regclass) NOT NULL, name character(20) NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.language OWNER TO postgres; -- -- Name: nicer_but_slower_film_list; Type: VIEW; Schema: public; Owner: postgres -- CREATE VIEW nicer_but_slower_film_list AS SELECT film.film_id AS fid, film.title, film.description, category.name AS category, film.rental_rate AS price, film.length, film.rating, group_concat((((upper("substring"((actor.first_name)::text, 1, 1)) || lower("substring"((actor.first_name)::text, 2))) || upper("substring"((actor.last_name)::text, 1, 1))) || lower("substring"((actor.last_name)::text, 2)))) AS actors FROM ((((category LEFT JOIN film_category ON ((category.category_id = film_category.category_id))) LEFT JOIN film ON ((film_category.film_id = film.film_id))) JOIN film_actor ON ((film.film_id = film_actor.film_id))) JOIN actor ON ((film_actor.actor_id = actor.actor_id))) GROUP BY film.film_id, film.title, film.description, category.name, film.rental_rate, film.length, film.rating; ALTER TABLE public.nicer_but_slower_film_list OWNER TO postgres; -- -- Name: payment_payment_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE payment_payment_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.payment_payment_id_seq OWNER TO postgres; -- -- Name: payment; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE payment ( payment_id integer DEFAULT nextval('payment_payment_id_seq'::regclass) NOT NULL, customer_id smallint NOT NULL, staff_id smallint NOT NULL, rental_id integer NOT NULL, amount numeric(5,2) NOT NULL, payment_date timestamp without time zone NOT NULL ); ALTER TABLE public.payment OWNER TO postgres; -- -- Name: payment_p2007_01; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE payment_p2007_01 (CONSTRAINT payment_p2007_01_payment_date_check CHECK (((payment_date >= '2007-01-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-02-01 00:00:00'::timestamp without time zone))) ) INHERITS (payment); ALTER TABLE public.payment_p2007_01 OWNER TO postgres; -- -- Name: payment_p2007_02; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE payment_p2007_02 (CONSTRAINT payment_p2007_02_payment_date_check CHECK (((payment_date >= '2007-02-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-03-01 00:00:00'::timestamp without time zone))) ) INHERITS (payment); ALTER TABLE public.payment_p2007_02 OWNER TO postgres; -- -- Name: payment_p2007_03; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE payment_p2007_03 (CONSTRAINT payment_p2007_03_payment_date_check CHECK (((payment_date >= '2007-03-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-04-01 00:00:00'::timestamp without time zone))) ) INHERITS (payment); ALTER TABLE public.payment_p2007_03 OWNER TO postgres; -- -- Name: payment_p2007_04; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE payment_p2007_04 (CONSTRAINT payment_p2007_04_payment_date_check CHECK (((payment_date >= '2007-04-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-05-01 00:00:00'::timestamp without time zone))) ) INHERITS (payment); ALTER TABLE public.payment_p2007_04 OWNER TO postgres; -- -- Name: payment_p2007_05; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE payment_p2007_05 (CONSTRAINT payment_p2007_05_payment_date_check CHECK (((payment_date >= '2007-05-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-06-01 00:00:00'::timestamp without time zone))) ) INHERITS (payment); ALTER TABLE public.payment_p2007_05 OWNER TO postgres; -- -- Name: payment_p2007_06; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE payment_p2007_06 (CONSTRAINT payment_p2007_06_payment_date_check CHECK (((payment_date >= '2007-06-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-07-01 00:00:00'::timestamp without time zone))) ) INHERITS (payment); ALTER TABLE public.payment_p2007_06 OWNER TO postgres; -- -- Name: rental_rental_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE rental_rental_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.rental_rental_id_seq OWNER TO postgres; -- -- Name: rental; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE rental ( rental_id integer DEFAULT nextval('rental_rental_id_seq'::regclass) NOT NULL, rental_date timestamp without time zone NOT NULL, inventory_id integer NOT NULL, customer_id smallint NOT NULL, return_date timestamp without time zone, staff_id smallint NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.rental OWNER TO postgres; -- -- Name: sales_by_film_category; Type: VIEW; Schema: public; Owner: postgres -- CREATE VIEW sales_by_film_category AS SELECT c.name AS category, sum(p.amount) AS total_sales FROM (((((payment p JOIN rental r ON ((p.rental_id = r.rental_id))) JOIN inventory i ON ((r.inventory_id = i.inventory_id))) JOIN film f ON ((i.film_id = f.film_id))) JOIN film_category fc ON ((f.film_id = fc.film_id))) JOIN category c ON ((fc.category_id = c.category_id))) GROUP BY c.name ORDER BY sum(p.amount) DESC; ALTER TABLE public.sales_by_film_category OWNER TO postgres; -- -- Name: staff_staff_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE staff_staff_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.staff_staff_id_seq OWNER TO postgres; -- -- Name: staff; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE staff ( staff_id integer DEFAULT nextval('staff_staff_id_seq'::regclass) NOT NULL, first_name character varying(45) NOT NULL, last_name character varying(45) NOT NULL, address_id smallint NOT NULL, email character varying(50), store_id smallint NOT NULL, active boolean DEFAULT true NOT NULL, username character varying(16) NOT NULL, password character varying(40), last_update timestamp without time zone DEFAULT now() NOT NULL, picture bytea ); ALTER TABLE public.staff OWNER TO postgres; -- -- Name: store_store_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE store_store_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER TABLE public.store_store_id_seq OWNER TO postgres; -- -- Name: store; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- CREATE TABLE store ( store_id integer DEFAULT nextval('store_store_id_seq'::regclass) NOT NULL, manager_staff_id smallint NOT NULL, address_id smallint NOT NULL, last_update timestamp without time zone DEFAULT now() NOT NULL ); ALTER TABLE public.store OWNER TO postgres; -- -- Name: sales_by_store; Type: VIEW; Schema: public; Owner: postgres -- CREATE VIEW sales_by_store AS SELECT (((c.city)::text || ','::text) || (cy.country)::text) AS store, (((m.first_name)::text || ' '::text) || (m.last_name)::text) AS manager, sum(p.amount) AS total_sales FROM (((((((payment p JOIN rental r ON ((p.rental_id = r.rental_id))) JOIN inventory i ON ((r.inventory_id = i.inventory_id))) JOIN store s ON ((i.store_id = s.store_id))) JOIN address a ON ((s.address_id = a.address_id))) JOIN city c ON ((a.city_id = c.city_id))) JOIN country cy ON ((c.country_id = cy.country_id))) JOIN staff m ON ((s.manager_staff_id = m.staff_id))) GROUP BY cy.country, c.city, s.store_id, m.first_name, m.last_name ORDER BY cy.country, c.city; ALTER TABLE public.sales_by_store OWNER TO postgres; -- -- Name: staff_list; Type: VIEW; Schema: public; Owner: postgres -- CREATE VIEW staff_list AS SELECT s.staff_id AS id, (((s.first_name)::text || ' '::text) || (s.last_name)::text) AS name, a.address, a.postal_code AS "zip code", a.phone, city.city, country.country, s.store_id AS sid FROM (((staff s JOIN address a ON ((s.address_id = a.address_id))) JOIN city ON ((a.city_id = city.city_id))) JOIN country ON ((city.country_id = country.country_id))); ALTER TABLE public.staff_list OWNER TO postgres; -- -- Name: film_in_stock(integer, integer); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION film_in_stock(p_film_id integer, p_store_id integer, OUT p_film_count integer) RETURNS SETOF integer AS $_$ SELECT inventory_id FROM inventory WHERE film_id = $1 AND store_id = $2 AND inventory_in_stock(inventory_id); $_$ LANGUAGE sql; ALTER FUNCTION public.film_in_stock(p_film_id integer, p_store_id integer, OUT p_film_count integer) OWNER TO postgres; -- -- Name: film_not_in_stock(integer, integer); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION film_not_in_stock(p_film_id integer, p_store_id integer, OUT p_film_count integer) RETURNS SETOF integer AS $_$ SELECT inventory_id FROM inventory WHERE film_id = $1 AND store_id = $2 AND NOT inventory_in_stock(inventory_id); $_$ LANGUAGE sql; ALTER FUNCTION public.film_not_in_stock(p_film_id integer, p_store_id integer, OUT p_film_count integer) OWNER TO postgres; -- -- Name: get_customer_balance(integer, timestamp without time zone); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION get_customer_balance(p_customer_id integer, p_effective_date timestamp without time zone) RETURNS numeric AS $$ --#OK, WE NEED TO CALCULATE THE CURRENT BALANCE GIVEN A CUSTOMER_ID AND A DATE --#THAT WE WANT THE BALANCE TO BE EFFECTIVE FOR. THE BALANCE IS: --# 1) RENTAL FEES FOR ALL PREVIOUS RENTALS --# 2) ONE DOLLAR FOR EVERY DAY THE PREVIOUS RENTALS ARE OVERDUE --# 3) IF A FILM IS MORE THAN RENTAL_DURATION * 2 OVERDUE, CHARGE THE REPLACEMENT_COST --# 4) SUBTRACT ALL PAYMENTS MADE BEFORE THE DATE SPECIFIED DECLARE v_rentfees DECIMAL(5,2); --#FEES PAID TO RENT THE VIDEOS INITIALLY v_overfees INTEGER; --#LATE FEES FOR PRIOR RENTALS v_payments DECIMAL(5,2); --#SUM OF PAYMENTS MADE PREVIOUSLY BEGIN SELECT COALESCE(SUM(film.rental_rate),0) INTO v_rentfees FROM film, inventory, rental WHERE film.film_id = inventory.film_id AND inventory.inventory_id = rental.inventory_id AND rental.rental_date <= p_effective_date AND rental.customer_id = p_customer_id; SELECT COALESCE(SUM(IF((rental.return_date - rental.rental_date) > (film.rental_duration * '1 day'::interval), ((rental.return_date - rental.rental_date) - (film.rental_duration * '1 day'::interval)),0)),0) INTO v_overfees FROM rental, inventory, film WHERE film.film_id = inventory.film_id AND inventory.inventory_id = rental.inventory_id AND rental.rental_date <= p_effective_date AND rental.customer_id = p_customer_id; SELECT COALESCE(SUM(payment.amount),0) INTO v_payments FROM payment WHERE payment.payment_date <= p_effective_date AND payment.customer_id = p_customer_id; RETURN v_rentfees + v_overfees - v_payments; END $$ LANGUAGE plpgsql; ALTER FUNCTION public.get_customer_balance(p_customer_id integer, p_effective_date timestamp without time zone) OWNER TO postgres; -- -- Name: inventory_held_by_customer(integer); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION inventory_held_by_customer(p_inventory_id integer) RETURNS integer AS $$ DECLARE v_customer_id INTEGER; BEGIN SELECT customer_id INTO v_customer_id FROM rental WHERE return_date IS NULL AND inventory_id = p_inventory_id; RETURN v_customer_id; END $$ LANGUAGE plpgsql; ALTER FUNCTION public.inventory_held_by_customer(p_inventory_id integer) OWNER TO postgres; -- -- Name: inventory_in_stock(integer); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION inventory_in_stock(p_inventory_id integer) RETURNS boolean AS $$ DECLARE v_rentals INTEGER; v_out INTEGER; BEGIN -- AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE -- FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED SELECT count(*) INTO v_rentals FROM rental WHERE inventory_id = p_inventory_id; IF v_rentals = 0 THEN RETURN TRUE; END IF; SELECT COUNT(rental_id) INTO v_out FROM inventory LEFT JOIN rental USING(inventory_id) WHERE inventory.inventory_id = p_inventory_id AND rental.return_date IS NULL; IF v_out > 0 THEN RETURN FALSE; ELSE RETURN TRUE; END IF; END $$ LANGUAGE plpgsql; ALTER FUNCTION public.inventory_in_stock(p_inventory_id integer) OWNER TO postgres; -- -- Name: last_day(timestamp without time zone); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION last_day(timestamp without time zone) RETURNS date AS $_$ SELECT CASE WHEN EXTRACT(MONTH FROM $1) = 12 THEN (((EXTRACT(YEAR FROM $1) + 1) operator(pg_catalog.||) '-01-01')::date - INTERVAL '1 day')::date ELSE ((EXTRACT(YEAR FROM $1) operator(pg_catalog.||) '-' operator(pg_catalog.||) (EXTRACT(MONTH FROM $1) + 1) operator(pg_catalog.||) '-01')::date - INTERVAL '1 day')::date END $_$ LANGUAGE sql IMMUTABLE STRICT; ALTER FUNCTION public.last_day(timestamp without time zone) OWNER TO postgres; -- -- Name: last_updated(); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION last_updated() RETURNS trigger AS $$ BEGIN NEW.last_update = CURRENT_TIMESTAMP; RETURN NEW; END $$ LANGUAGE plpgsql; ALTER FUNCTION public.last_updated() OWNER TO postgres; -- -- Name: rewards_report(integer, numeric); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION rewards_report(min_monthly_purchases integer, min_dollar_amount_purchased numeric) RETURNS SETOF customer AS $_$ DECLARE last_month_start DATE; last_month_end DATE; rr RECORD; tmpSQL TEXT; BEGIN /* Some sanity checks... */ IF min_monthly_purchases = 0 THEN RAISE EXCEPTION 'Minimum monthly purchases parameter must be > 0'; END IF; IF min_dollar_amount_purchased = 0.00 THEN RAISE EXCEPTION 'Minimum monthly dollar amount purchased parameter must be > $0.00'; END IF; last_month_start := CURRENT_DATE - '3 month'::interval; last_month_start := to_date((extract(YEAR FROM last_month_start) || '-' || extract(MONTH FROM last_month_start) || '-01'),'YYYY-MM-DD'); last_month_end := LAST_DAY(last_month_start); /* Create a temporary storage area for Customer IDs. */ CREATE TEMPORARY TABLE tmpCustomer (customer_id INTEGER NOT NULL PRIMARY KEY); /* Find all customers meeting the monthly purchase requirements */ tmpSQL := 'INSERT INTO tmpCustomer (customer_id) SELECT p.customer_id FROM payment AS p WHERE DATE(p.payment_date) BETWEEN '||quote_literal(last_month_start) ||' AND '|| quote_literal(last_month_end) || ' GROUP BY customer_id HAVING SUM(p.amount) > '|| min_dollar_amount_purchased || ' AND COUNT(customer_id) > ' ||min_monthly_purchases ; EXECUTE tmpSQL; /* Output ALL customer information of matching rewardees. Customize output as needed. */ FOR rr IN EXECUTE 'SELECT c.* FROM tmpCustomer AS t INNER JOIN customer AS c ON t.customer_id = c.customer_id' LOOP RETURN NEXT rr; END LOOP; /* Clean up */ tmpSQL := 'DROP TABLE tmpCustomer'; EXECUTE tmpSQL; RETURN; END $_$ LANGUAGE plpgsql SECURITY DEFINER; ALTER FUNCTION public.rewards_report(min_monthly_purchases integer, min_dollar_amount_purchased numeric) OWNER TO postgres; -- -- Name: actor_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY actor ADD CONSTRAINT actor_pkey PRIMARY KEY (actor_id); -- -- Name: address_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY address ADD CONSTRAINT address_pkey PRIMARY KEY (address_id); -- -- Name: category_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY category ADD CONSTRAINT category_pkey PRIMARY KEY (category_id); -- -- Name: city_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY city ADD CONSTRAINT city_pkey PRIMARY KEY (city_id); -- -- Name: country_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY country ADD CONSTRAINT country_pkey PRIMARY KEY (country_id); -- -- Name: customer_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY customer ADD CONSTRAINT customer_pkey PRIMARY KEY (customer_id); -- -- Name: film_actor_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY film_actor ADD CONSTRAINT film_actor_pkey PRIMARY KEY (actor_id, film_id); -- -- Name: film_category_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY film_category ADD CONSTRAINT film_category_pkey PRIMARY KEY (film_id, category_id); -- -- Name: film_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY film ADD CONSTRAINT film_pkey PRIMARY KEY (film_id); -- -- Name: inventory_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY inventory ADD CONSTRAINT inventory_pkey PRIMARY KEY (inventory_id); -- -- Name: language_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY language ADD CONSTRAINT language_pkey PRIMARY KEY (language_id); -- -- Name: payment_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY payment ADD CONSTRAINT payment_pkey PRIMARY KEY (payment_id); -- -- Name: rental_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY rental ADD CONSTRAINT rental_pkey PRIMARY KEY (rental_id); -- -- Name: staff_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY staff ADD CONSTRAINT staff_pkey PRIMARY KEY (staff_id); -- -- Name: store_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- ALTER TABLE ONLY store ADD CONSTRAINT store_pkey PRIMARY KEY (store_id); -- -- Name: film_fulltext_idx; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX film_fulltext_idx ON film USING gist (fulltext); -- -- Name: idx_actor_last_name; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_actor_last_name ON actor USING btree (last_name); -- -- Name: idx_fk_address_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_address_id ON customer USING btree (address_id); -- -- Name: idx_fk_city_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_city_id ON address USING btree (city_id); -- -- Name: idx_fk_country_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_country_id ON city USING btree (country_id); -- -- Name: idx_fk_customer_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_customer_id ON payment USING btree (customer_id); -- -- Name: idx_fk_film_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_film_id ON film_actor USING btree (film_id); -- -- Name: idx_fk_inventory_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_inventory_id ON rental USING btree (inventory_id); -- -- Name: idx_fk_language_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_language_id ON film USING btree (language_id); -- -- Name: idx_fk_original_language_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_original_language_id ON film USING btree (original_language_id); -- -- Name: idx_fk_payment_p2007_01_customer_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_01_customer_id ON payment_p2007_01 USING btree (customer_id); -- -- Name: idx_fk_payment_p2007_01_staff_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_01_staff_id ON payment_p2007_01 USING btree (staff_id); -- -- Name: idx_fk_payment_p2007_02_customer_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_02_customer_id ON payment_p2007_02 USING btree (customer_id); -- -- Name: idx_fk_payment_p2007_02_staff_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_02_staff_id ON payment_p2007_02 USING btree (staff_id); -- -- Name: idx_fk_payment_p2007_03_customer_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_03_customer_id ON payment_p2007_03 USING btree (customer_id); -- -- Name: idx_fk_payment_p2007_03_staff_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_03_staff_id ON payment_p2007_03 USING btree (staff_id); -- -- Name: idx_fk_payment_p2007_04_customer_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_04_customer_id ON payment_p2007_04 USING btree (customer_id); -- -- Name: idx_fk_payment_p2007_04_staff_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_04_staff_id ON payment_p2007_04 USING btree (staff_id); -- -- Name: idx_fk_payment_p2007_05_customer_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_05_customer_id ON payment_p2007_05 USING btree (customer_id); -- -- Name: idx_fk_payment_p2007_05_staff_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_05_staff_id ON payment_p2007_05 USING btree (staff_id); -- -- Name: idx_fk_payment_p2007_06_customer_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_06_customer_id ON payment_p2007_06 USING btree (customer_id); -- -- Name: idx_fk_payment_p2007_06_staff_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_payment_p2007_06_staff_id ON payment_p2007_06 USING btree (staff_id); -- -- Name: idx_fk_staff_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_staff_id ON payment USING btree (staff_id); -- -- Name: idx_fk_store_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_fk_store_id ON customer USING btree (store_id); -- -- Name: idx_last_name; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_last_name ON customer USING btree (last_name); -- -- Name: idx_store_id_film_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_store_id_film_id ON inventory USING btree (store_id, film_id); -- -- Name: idx_title; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE INDEX idx_title ON film USING btree (title); -- -- Name: idx_unq_manager_staff_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE UNIQUE INDEX idx_unq_manager_staff_id ON store USING btree (manager_staff_id); -- -- Name: idx_unq_rental_rental_date_inventory_id_customer_id; Type: INDEX; Schema: public; Owner: postgres; Tablespace: -- CREATE UNIQUE INDEX idx_unq_rental_rental_date_inventory_id_customer_id ON rental USING btree (rental_date, inventory_id, customer_id); -- -- Name: payment_insert_p2007_01; Type: RULE; Schema: public; Owner: postgres -- CREATE RULE payment_insert_p2007_01 AS ON INSERT TO payment WHERE ((new.payment_date >= '2007-01-01 00:00:00'::timestamp without time zone) AND (new.payment_date < '2007-02-01 00:00:00'::timestamp without time zone)) DO INSTEAD INSERT INTO payment_p2007_01 (payment_id, customer_id, staff_id, rental_id, amount, payment_date) VALUES (DEFAULT, new.customer_id, new.staff_id, new.rental_id, new.amount, new.payment_date); -- -- Name: payment_insert_p2007_02; Type: RULE; Schema: public; Owner: postgres -- CREATE RULE payment_insert_p2007_02 AS ON INSERT TO payment WHERE ((new.payment_date >= '2007-02-01 00:00:00'::timestamp without time zone) AND (new.payment_date < '2007-03-01 00:00:00'::timestamp without time zone)) DO INSTEAD INSERT INTO payment_p2007_02 (payment_id, customer_id, staff_id, rental_id, amount, payment_date) VALUES (DEFAULT, new.customer_id, new.staff_id, new.rental_id, new.amount, new.payment_date); -- -- Name: payment_insert_p2007_03; Type: RULE; Schema: public; Owner: postgres -- CREATE RULE payment_insert_p2007_03 AS ON INSERT TO payment WHERE ((new.payment_date >= '2007-03-01 00:00:00'::timestamp without time zone) AND (new.payment_date < '2007-04-01 00:00:00'::timestamp without time zone)) DO INSTEAD INSERT INTO payment_p2007_03 (payment_id, customer_id, staff_id, rental_id, amount, payment_date) VALUES (DEFAULT, new.customer_id, new.staff_id, new.rental_id, new.amount, new.payment_date); -- -- Name: payment_insert_p2007_04; Type: RULE; Schema: public; Owner: postgres -- CREATE RULE payment_insert_p2007_04 AS ON INSERT TO payment WHERE ((new.payment_date >= '2007-04-01 00:00:00'::timestamp without time zone) AND (new.payment_date < '2007-05-01 00:00:00'::timestamp without time zone)) DO INSTEAD INSERT INTO payment_p2007_04 (payment_id, customer_id, staff_id, rental_id, amount, payment_date) VALUES (DEFAULT, new.customer_id, new.staff_id, new.rental_id, new.amount, new.payment_date); -- -- Name: payment_insert_p2007_05; Type: RULE; Schema: public; Owner: postgres -- CREATE RULE payment_insert_p2007_05 AS ON INSERT TO payment WHERE ((new.payment_date >= '2007-05-01 00:00:00'::timestamp without time zone) AND (new.payment_date < '2007-06-01 00:00:00'::timestamp without time zone)) DO INSTEAD INSERT INTO payment_p2007_05 (payment_id, customer_id, staff_id, rental_id, amount, payment_date) VALUES (DEFAULT, new.customer_id, new.staff_id, new.rental_id, new.amount, new.payment_date); -- -- Name: payment_insert_p2007_06; Type: RULE; Schema: public; Owner: postgres -- CREATE RULE payment_insert_p2007_06 AS ON INSERT TO payment WHERE ((new.payment_date >= '2007-06-01 00:00:00'::timestamp without time zone) AND (new.payment_date < '2007-07-01 00:00:00'::timestamp without time zone)) DO INSTEAD INSERT INTO payment_p2007_06 (payment_id, customer_id, staff_id, rental_id, amount, payment_date) VALUES (DEFAULT, new.customer_id, new.staff_id, new.rental_id, new.amount, new.payment_date); -- -- Name: film_fulltext_trigger; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER film_fulltext_trigger BEFORE INSERT OR UPDATE ON film FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger('fulltext', 'pg_catalog.english', 'title', 'description'); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON actor FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON address FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON category FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON city FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON country FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON customer FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON film FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON film_actor FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON film_category FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON inventory FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON language FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON rental FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON staff FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: last_updated; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER last_updated BEFORE UPDATE ON store FOR EACH ROW EXECUTE PROCEDURE last_updated(); -- -- Name: address_city_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY address ADD CONSTRAINT address_city_id_fkey FOREIGN KEY (city_id) REFERENCES city(city_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: city_country_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY city ADD CONSTRAINT city_country_id_fkey FOREIGN KEY (country_id) REFERENCES country(country_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: customer_address_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY customer ADD CONSTRAINT customer_address_id_fkey FOREIGN KEY (address_id) REFERENCES address(address_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: customer_store_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY customer ADD CONSTRAINT customer_store_id_fkey FOREIGN KEY (store_id) REFERENCES store(store_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: film_actor_actor_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY film_actor ADD CONSTRAINT film_actor_actor_id_fkey FOREIGN KEY (actor_id) REFERENCES actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: film_actor_film_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY film_actor ADD CONSTRAINT film_actor_film_id_fkey FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: film_category_category_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY film_category ADD CONSTRAINT film_category_category_id_fkey FOREIGN KEY (category_id) REFERENCES category(category_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: film_category_film_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY film_category ADD CONSTRAINT film_category_film_id_fkey FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: film_language_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY film ADD CONSTRAINT film_language_id_fkey FOREIGN KEY (language_id) REFERENCES language(language_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: film_original_language_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY film ADD CONSTRAINT film_original_language_id_fkey FOREIGN KEY (original_language_id) REFERENCES language(language_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: inventory_film_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY inventory ADD CONSTRAINT inventory_film_id_fkey FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: inventory_store_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY inventory ADD CONSTRAINT inventory_store_id_fkey FOREIGN KEY (store_id) REFERENCES store(store_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: payment_customer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment ADD CONSTRAINT payment_customer_id_fkey FOREIGN KEY (customer_id) REFERENCES customer(customer_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: payment_p2007_01_customer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_01 ADD CONSTRAINT payment_p2007_01_customer_id_fkey FOREIGN KEY (customer_id) REFERENCES customer(customer_id); -- -- Name: payment_p2007_01_rental_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_01 ADD CONSTRAINT payment_p2007_01_rental_id_fkey FOREIGN KEY (rental_id) REFERENCES rental(rental_id); -- -- Name: payment_p2007_01_staff_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_01 ADD CONSTRAINT payment_p2007_01_staff_id_fkey FOREIGN KEY (staff_id) REFERENCES staff(staff_id); -- -- Name: payment_p2007_02_customer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_02 ADD CONSTRAINT payment_p2007_02_customer_id_fkey FOREIGN KEY (customer_id) REFERENCES customer(customer_id); -- -- Name: payment_p2007_02_rental_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_02 ADD CONSTRAINT payment_p2007_02_rental_id_fkey FOREIGN KEY (rental_id) REFERENCES rental(rental_id); -- -- Name: payment_p2007_02_staff_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_02 ADD CONSTRAINT payment_p2007_02_staff_id_fkey FOREIGN KEY (staff_id) REFERENCES staff(staff_id); -- -- Name: payment_p2007_03_customer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_03 ADD CONSTRAINT payment_p2007_03_customer_id_fkey FOREIGN KEY (customer_id) REFERENCES customer(customer_id); -- -- Name: payment_p2007_03_rental_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_03 ADD CONSTRAINT payment_p2007_03_rental_id_fkey FOREIGN KEY (rental_id) REFERENCES rental(rental_id); -- -- Name: payment_p2007_03_staff_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_03 ADD CONSTRAINT payment_p2007_03_staff_id_fkey FOREIGN KEY (staff_id) REFERENCES staff(staff_id); -- -- Name: payment_p2007_04_customer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_04 ADD CONSTRAINT payment_p2007_04_customer_id_fkey FOREIGN KEY (customer_id) REFERENCES customer(customer_id); -- -- Name: payment_p2007_04_rental_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_04 ADD CONSTRAINT payment_p2007_04_rental_id_fkey FOREIGN KEY (rental_id) REFERENCES rental(rental_id); -- -- Name: payment_p2007_04_staff_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_04 ADD CONSTRAINT payment_p2007_04_staff_id_fkey FOREIGN KEY (staff_id) REFERENCES staff(staff_id); -- -- Name: payment_p2007_05_customer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_05 ADD CONSTRAINT payment_p2007_05_customer_id_fkey FOREIGN KEY (customer_id) REFERENCES customer(customer_id); -- -- Name: payment_p2007_05_rental_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_05 ADD CONSTRAINT payment_p2007_05_rental_id_fkey FOREIGN KEY (rental_id) REFERENCES rental(rental_id); -- -- Name: payment_p2007_05_staff_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_05 ADD CONSTRAINT payment_p2007_05_staff_id_fkey FOREIGN KEY (staff_id) REFERENCES staff(staff_id); -- -- Name: payment_p2007_06_customer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_06 ADD CONSTRAINT payment_p2007_06_customer_id_fkey FOREIGN KEY (customer_id) REFERENCES customer(customer_id); -- -- Name: payment_p2007_06_rental_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_06 ADD CONSTRAINT payment_p2007_06_rental_id_fkey FOREIGN KEY (rental_id) REFERENCES rental(rental_id); -- -- Name: payment_p2007_06_staff_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment_p2007_06 ADD CONSTRAINT payment_p2007_06_staff_id_fkey FOREIGN KEY (staff_id) REFERENCES staff(staff_id); -- -- Name: payment_rental_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment ADD CONSTRAINT payment_rental_id_fkey FOREIGN KEY (rental_id) REFERENCES rental(rental_id) ON UPDATE CASCADE ON DELETE SET NULL; -- -- Name: payment_staff_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY payment ADD CONSTRAINT payment_staff_id_fkey FOREIGN KEY (staff_id) REFERENCES staff(staff_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: rental_customer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY rental ADD CONSTRAINT rental_customer_id_fkey FOREIGN KEY (customer_id) REFERENCES customer(customer_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: rental_inventory_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY rental ADD CONSTRAINT rental_inventory_id_fkey FOREIGN KEY (inventory_id) REFERENCES inventory(inventory_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: rental_staff_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY rental ADD CONSTRAINT rental_staff_id_fkey FOREIGN KEY (staff_id) REFERENCES staff(staff_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: staff_address_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY staff ADD CONSTRAINT staff_address_id_fkey FOREIGN KEY (address_id) REFERENCES address(address_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: staff_store_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY staff ADD CONSTRAINT staff_store_id_fkey FOREIGN KEY (store_id) REFERENCES store(store_id); -- -- Name: store_address_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY store ADD CONSTRAINT store_address_id_fkey FOREIGN KEY (address_id) REFERENCES address(address_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: store_manager_staff_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY store ADD CONSTRAINT store_manager_staff_id_fkey FOREIGN KEY (manager_staff_id) REFERENCES staff(staff_id) ON UPDATE CASCADE ON DELETE RESTRICT; -- -- Name: public; Type: ACL; Schema: -; Owner: postgres -- REVOKE ALL ON SCHEMA public FROM PUBLIC; GRANT ALL ON SCHEMA public TO postgres; GRANT ALL ON SCHEMA public TO PUBLIC; -- -- PostgreSQL database dump complete -- release-pod-coverage.t000644001750001750 76511543335122 21706 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/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' }); SQL000755001750001750 011543335122 16343 5ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/libSplitStatement.pm000644001750001750 11664611543335122 22076 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/lib/SQL## no critic package SQL::SplitStatement; BEGIN { $SQL::SplitStatement::VERSION = '1.00020'; } ## use critic use strict; use warnings; use base 'Class::Accessor::Fast'; use Carp qw(croak); use SQL::Tokenizer 0.22 qw(tokenize_sql); use List::MoreUtils qw(firstval firstidx each_array); use Regexp::Common qw(delimited); use constant { NEWLINE => "\n", SEMICOLON => ';', DOT => '.', FORWARD_SLASH => '/', QUESTION_MARK => '?', SINGLE_DOLLAR => '$', DOUBLE_DOLLAR => '$$', OPEN_BRACKET => '(', CLOSED_BRACKET => ')', SEMICOLON_TERMINATOR => 1, SLASH_TERMINATOR => 2, CUSTOM_DELIMITER => 3 }; my $transaction_RE = qr[^(?: ; |/ |WORK |TRAN |TRANSACTION |ISOLATION |READ )$]xi; my $procedural_END_RE = qr/^(?:IF|CASE|LOOP)$/i; my $terminator_RE = qr[ ;\s*\n\s*\.\s*\n\s*/\s*\n? |;\s*\n\s*/\s*\n? |\.\s*\n\s*/\s*\n? |\n\s*/\s*\n? |; ]x; my $begin_comment_RE = qr/^(?:--|\/\*)/; my $quoted_RE = $RE{delimited}{ -delim=>q{"'`} }; my $dollar_placeholder_RE = qr/^\$\d+$/; my $inner_identifier_RE = qr/[_a-zA-Z][_a-zA-Z0-9]*/; my $CURSOR_RE = qr/^CURSOR$/i; my $DELIMITER_RE = qr/^DELIMITER$/i; my $DECLARE_RE = qr/^DECLARE$/i; my $PROCEDURE_FUNCTION_RE = qr/^(?:FUNCTION|PROCEDURE)$/i; my $PACKAGE_RE = qr/^PACKAGE$/i; my $BEGIN_RE = qr/^BEGIN$/i; my $END_RE = qr/^END$/i; my $AS_RE = qr/^AS$/i; my $IS_RE = qr/^IS$/i; my $TYPE_RE = qr/^TYPE$/i; my $BODY_RE = qr/^BODY$/i; my $DROP_RE = qr/^DROP$/i; my $CRUD_RE = qr/^(?:DELETE|INSERT|SELECT|UPDATE)$/i; my $GRANT_REVOKE_RE = qr/^(?:GRANT|REVOKE)$/i;; my $CREATE_ALTER_RE = qr/^(?:CREATE|ALTER)$/i; my $CREATE_REPLACE_RE = qr/^(?:CREATE|REPLACE)$/i; my $OR_REPLACE_RE = qr/^(?:OR|REPLACE)$/i; my $OR_REPLACE_PACKAGE_RE = qr/^(?:OR|REPLACE|PACKAGE)$/i; my $pre_identifier_RE = qr/^(?: BODY |CONSTRAINT |CURSOR |DECLARE |FUNCTION |INDEX |PACKAGE |PROCEDURE |REFERENCES |TABLE |[.,(] )$/xi; SQL::SplitStatement->mk_accessors( qw/ keep_terminators keep_extra_spaces keep_empty_statements keep_comments slash_terminates _tokens _current_statement _custom_delimiter _terminators _tokens_in_custom_delimiter /); # keep_terminators alias sub keep_terminator { shift->keep_terminators(@_) } sub new { my $class = shift; my $parameters = @_ > 1 ? { @_ } : $_[0] || {}; if ( exists $parameters->{keep_terminators} ) { croak( q[keep_terminator and keep_terminators can't be both assigned'] ) if exists $parameters->{keep_terminator} } elsif ( exists $parameters->{keep_terminator} ) { $parameters->{keep_terminators} = delete $parameters->{keep_terminator} } $parameters->{slash_terminates} = 1 unless exists $parameters->{slash_terminates}; $class->SUPER::new( $parameters ) } sub split { my ($self, $code) = @_; my ($statements, undef) = $self->split_with_placeholders($code); return @{ $statements } } sub split_with_placeholders { my ($self, $code) = @_; my @placeholders = (); my @statements = (); my $statement_placeholders = 0; my $inside_block = 0; my $inside_brackets = 0; my $inside_sub = 0; my $inside_is_as = 0; my $inside_cursor = 0; my $inside_is_cursor = 0; my $inside_declare = 0; my $inside_package = 0; my $inside_grant_revoke = 0; my $inside_crud = 0; my $extra_end_found = 0; my @sub_names = (); my $package_name = ''; my $dollar_quote; my $dollar_quote_to_add; my $prev_token = ''; my $prev_keyword = ''; my $custom_delimiter_def_found = 0; if ( !defined $code ) { $code = "\n" } else { $code .= "\n" }; $self->_tokens( [ tokenize_sql($code) ] ); $self->_terminators( [] ); # Needed (only) to remove them afterwards # when keep_terminators is false. $self->_current_statement(''); while ( defined( my $token = shift @{ $self->_tokens } ) ) { my $terminator_found = 0; # Skip this token if it's a comment and we don't want to keep it. next if $self->_is_comment($token) && ! $self->keep_comments; # Append the token to the current statement; $self->_add_to_current_statement($token); # The token is gathered even if it was a space-only token, # but in this case we can skip any further analysis. next if $token =~ /^\s+$/; if ( $dollar_quote ) { if ( $self->_dollar_quote_close_found($token, $dollar_quote) ) { $self->_add_to_current_statement($dollar_quote_to_add); undef $dollar_quote; # Saving $prev_token not necessary in this case. $inside_sub = 0; # Silence sub opening before dollar quote. @sub_names = (); $inside_is_as = 0; # Silence is_as opening before dollar quote. $inside_declare = 0; next } } if ( $prev_token =~ $AS_RE and !$dollar_quote and $dollar_quote = $self->_dollar_quote_open_found($token) ) { ( $dollar_quote_to_add = $dollar_quote ) =~ s/^\Q$token//; $self->_add_to_current_statement($dollar_quote_to_add) } elsif ( $token =~ $DELIMITER_RE && !$prev_token ) { my $tokens_to_shift = $self->_custom_delimiter_def_found; $self->_add_to_current_statement( join '', splice @{ $self->_tokens }, 0, $tokens_to_shift ); $custom_delimiter_def_found = 1; $self->_custom_delimiter(undef) if $self->_custom_delimiter eq SEMICOLON } elsif ( $token eq OPEN_BRACKET ) { $inside_brackets++ } elsif ( $token eq CLOSED_BRACKET ) { $inside_brackets-- } elsif ( $self->_is_BEGIN_of_block($token, $prev_token) ) { $extra_end_found = 0 if $extra_end_found; $inside_block++ } elsif ( $token =~ $CREATE_ALTER_RE ) { my $next_token = $self->_peek_at_next_significant_token( $OR_REPLACE_RE ); if ( $next_token =~ $PACKAGE_RE ) { $inside_package = 1; $package_name = $self->_peek_at_package_name } } elsif ( $token =~ $PROCEDURE_FUNCTION_RE || $token =~ $BODY_RE && $prev_token =~ $TYPE_RE ) { if ( !$inside_block && !$inside_brackets && $prev_token !~ $DROP_RE && $prev_token !~ $pre_identifier_RE ) { $inside_sub++; $prev_keyword = $token; push @sub_names, $self->_peek_at_next_significant_token } } elsif ( $token =~ /$IS_RE|$AS_RE/ ) { if ( $prev_keyword =~ /$PROCEDURE_FUNCTION_RE|$BODY_RE/ && !$inside_block && $prev_token !~ $pre_identifier_RE ) { $inside_is_as++; $prev_keyword = '' } $inside_is_cursor = 1 if $inside_declare && $inside_cursor } elsif ( $token =~ $DECLARE_RE ) { # In MySQL a declare can only appear inside a BEGIN ... END block. $inside_declare = 1 if !$inside_block && $prev_token !~ $pre_identifier_RE } elsif ( $token =~ $CURSOR_RE ) { $inside_cursor = 1 if $inside_declare && $prev_token !~ $DROP_RE && $prev_token !~ $pre_identifier_RE } elsif ( $token =~ /$GRANT_REVOKE_RE/ ) { $inside_grant_revoke = 1 unless $prev_token } elsif ( defined ( my $name = $self->_is_END_of_block($token) ) ) { $extra_end_found = 1 if !$inside_block; $inside_block-- if $inside_block; if ( !$inside_block ) { # $name contains the next (significant) token. if ( $name eq SEMICOLON ) { # Keep this order! if ( $inside_sub && $inside_is_as ) { $inside_sub--; $inside_is_as--; pop @sub_names if $inside_sub < @sub_names } elsif ( $inside_declare ) { $inside_declare = 0 } elsif ( $inside_package ) { $inside_package = 0; $package_name = '' } } if ( $inside_sub && @sub_names && $name eq $sub_names[-1] ) { $inside_sub--; pop @sub_names if $inside_sub < @sub_names } if ( $inside_package && $name eq $package_name ) { $inside_package = 0; $package_name = '' } } } elsif ( $token =~ $CRUD_RE ) { $inside_crud = 1 } elsif ( $inside_crud && ( my $placeholder_token = $self->_questionmark_placeholder_found($token) || $self->_named_placeholder_found($token) || $self->_dollar_placeholder_found($token) ) ) { $statement_placeholders++ if !$self->_custom_delimiter || $self->_custom_delimiter ne $placeholder_token; # Needed by SQL::Tokenizer pre-0.21 # The only multi-token placeholder is a dollar placeholder. # if ( ( my $token_to_add = $placeholder_token ) =~ s[^\$][] ) { # $self->_add_to_current_statement($token_to_add) # } } else { $terminator_found = $self->_is_terminator($token); if ( $terminator_found && $terminator_found == SEMICOLON_TERMINATOR && !$inside_brackets ) { if ( $inside_sub && !$inside_is_as && !$inside_block ) { # Needed to close PL/SQL sub forward declarations such as: # PROCEDURE proc(number1 NUMBER); $inside_sub-- } if ( $inside_declare && $inside_cursor && !$inside_is_cursor ) { # Needed to close CURSOR decl. other than those in PL/SQL # inside a DECLARE; $inside_declare = 0 } $inside_crud = 0 if $inside_crud } } $prev_token = $token if $token =~ /\S/ && ! $self->_is_comment($token); # If we've just found a new custom DELIMITER definition, we certainly # have a new statement (and no terminator). unless ( $custom_delimiter_def_found || $terminator_found && $terminator_found == CUSTOM_DELIMITER ) { # Let's examine any condition that can make us remain in the # current statement. next if !$terminator_found || $dollar_quote || $inside_brackets || $self->_custom_delimiter; next if $terminator_found && $terminator_found == SEMICOLON_TERMINATOR && ( $inside_block || $inside_sub || $inside_declare || $inside_package || $inside_crud ) && !$inside_grant_revoke && !$extra_end_found } # Whenever we get this far, we have a new statement. push @statements, $self->_current_statement; push @placeholders, $statement_placeholders; # If $terminator_found == CUSTOM_DELIMITER # @{ $self->_terminators } element has already been pushed, # so we have to set it only in the case tested below. push @{ $self->_terminators }, [ $terminator_found, undef ] if ( $terminator_found == SEMICOLON_TERMINATOR || $terminator_found == SLASH_TERMINATOR ); $self->_current_statement(''); $statement_placeholders = 0; $prev_token = ''; $prev_keyword = ''; $inside_brackets = 0; $inside_block = 0; $inside_cursor = 0; $inside_is_cursor = 0; $inside_sub = 0; $inside_is_as = 0; $inside_declare = 0; $inside_package = 0; $inside_grant_revoke = 0; $inside_crud = 0; $extra_end_found = 0; @sub_names = (); $custom_delimiter_def_found = 0 } # Last statement. chomp( my $last_statement = $self->_current_statement ); push @statements, $last_statement; push @{ $self->_terminators }, [undef, undef]; push @placeholders, $statement_placeholders; my @filtered_statements; my @filtered_terminators; my @filtered_placeholders; if ( $self->keep_empty_statements ) { @filtered_statements = @statements; @filtered_terminators = @{ $self->_terminators }; @filtered_placeholders = @placeholders } else { my $sp = each_array( @statements, @{ $self->_terminators }, @placeholders ); while ( my ($statement, $terminator, $placeholder_num) = $sp->() ) { my $only_terminator_RE = $terminator->[0] && $terminator->[0] == CUSTOM_DELIMITER ? qr/^\s*\Q$terminator->[1]\E?\s*$/ : qr/^\s*$terminator_RE?\z/; unless ( $statement =~ $only_terminator_RE ) { push @filtered_statements, $statement; push @filtered_terminators, $terminator; push @filtered_placeholders, $placeholder_num } } } unless ( $self->keep_terminators ) { for ( my $i = 0; $i < @filtered_statements; $i++ ) { my $terminator = $filtered_terminators[$i]; if ( $terminator->[0] ) { if ( $terminator->[0] == CUSTOM_DELIMITER ) { $filtered_statements[$i] =~ s/\Q$terminator->[1]\E$// } else { $filtered_statements[$i] =~ s/$terminator_RE$// } } } } unless ( $self->keep_extra_spaces ) { s/^\s+|\s+$//g foreach @filtered_statements } return ( \@filtered_statements, \@filtered_placeholders ) } sub _add_to_current_statement { my ($self, $token) = @_; $self->_current_statement( $self->_current_statement() . $token ) } sub _is_comment { my ($self, $token) = @_; return $token =~ $begin_comment_RE } sub _is_BEGIN_of_block { my ($self, $token, $prev_token) = @_; return $token =~ $BEGIN_RE && $prev_token !~ $pre_identifier_RE && $self->_peek_at_next_significant_token !~ $transaction_RE } sub _is_END_of_block { my ($self, $token) = @_; my $next_token = $self->_peek_at_next_significant_token; # Return possible package name. if ( $token =~ $END_RE && ( !defined($next_token) || $next_token !~ $procedural_END_RE ) ) { return defined $next_token ? $next_token : '' } return } sub _dollar_placeholder_found { my ($self, $token) = @_; return $token =~ $dollar_placeholder_RE ? $token : ''; # Needed by SQL::Tokenizer pre-0.21 # return '' if $token ne SINGLE_DOLLAR; # # # $token must be: '$' # my $tokens = $self->_tokens; # # return $tokens->[0] =~ /^\d+$/ && $tokens->[1] !~ /^\$/ # ? $token . shift( @$tokens ) : '' } sub _named_placeholder_found { my ($self, $token) = @_; return $token =~ /^:(?:\d+|[_a-z][_a-z\d]*)$/ ? $token : '' } sub _questionmark_placeholder_found { my ($self, $token) = @_; return $token eq QUESTION_MARK ? $token : '' } sub _dollar_quote_open_found { my ($self, $token) = @_; return '' if $token !~ /^\$/; # Includes the DOUBLE_DOLLAR case return $token if $token =~ /^\$$inner_identifier_RE?\$$/; # Used with SQL::Tokenizer pre-0.21 # return $token if $token eq DOUBLE_DOLLAR; # $token must be: '$' or '$1', '$2' etc. return '' if $token =~ $dollar_placeholder_RE; # $token must be: '$' my $tokens = $self->_tokens; # False alarm! return '' if $tokens->[1] !~ /^\$/; return $token . shift( @$tokens ) . shift( @$tokens ) if $tokens->[0] =~ /^$inner_identifier_RE$/ && $tokens->[1] eq SINGLE_DOLLAR; # $tokens->[1] must match: /$.+/ my $quote = $token . shift( @$tokens ) . '$'; $tokens->[0] = substr $tokens->[0], 1; return $quote } sub _dollar_quote_close_found { my ($self, $token, $dollar_quote) = @_; return if $token !~ /^\$/; return 1 if $token eq $dollar_quote; # $token matches /$.*$/ # $token must be: '$' or '$1', '$2' etc. return if $token =~ $dollar_placeholder_RE; # $token must be: '$' my $tokens = $self->_tokens; # False alarm! return if $tokens->[1] !~ /^\$/; if ( $dollar_quote eq $token . $tokens->[0] . $tokens->[1] ) { shift( @$tokens ); shift( @$tokens ); return 1 } # $tokens->[1] must match: /$.+/ if ( $dollar_quote eq $token . $tokens->[0] . '$' ) { shift( @$tokens ); $tokens->[0] = substr $tokens->[0], 1; return 1 } return } sub _peek_at_package_name { shift->_peek_at_next_significant_token( qr/$OR_REPLACE_PACKAGE_RE|$BODY_RE/ ) } sub _custom_delimiter_def_found { my $self = shift; my $tokens = $self->_tokens; my $base_index = 0; $base_index++ while $tokens->[$base_index] =~ /^\s$/; my $first_token_in_delimiter = $tokens->[$base_index]; my $delimiter = ''; my $tokens_in_delimiter; my $tokens_to_shift; if ( $first_token_in_delimiter =~ $quoted_RE ) { # Quoted custom delimiter: it's just a single token (to shift)... $tokens_to_shift = $base_index + 1; # ... However it can be composed by several tokens # (according to SQL::Tokenizer), once removed the quotes. $delimiter = substr $first_token_in_delimiter, 1, -1; $tokens_in_delimiter =()= tokenize_sql($delimiter) } else { # Gather an unquoted custom delimiter, which could be composed # by several tokens (that's the SQL::Tokenizer behaviour). foreach ( $base_index .. $#{ $tokens } ) { last if $tokens->[$_] =~ /^\s+$/; $delimiter .= $tokens->[$_]; $tokens_in_delimiter++ } $tokens_to_shift = $base_index + $tokens_in_delimiter } $self->_custom_delimiter($delimiter); # We've just found a custom delimiter definition, # which means that this statement has no (additional) terminator, # therefore we won't have to delete anything. push @{ $self->_terminators }, [undef, undef]; $self->_tokens_in_custom_delimiter($tokens_in_delimiter); return $tokens_to_shift } sub _is_custom_delimiter { my ($self, $token) = @_; my $tokens = $self->_tokens; my @delimiter_tokens = splice @{$tokens}, 0, $self->_tokens_in_custom_delimiter() - 1; my $lookahead_delimiter = join '', @delimiter_tokens; if ( $self->_custom_delimiter eq $token . $lookahead_delimiter ) { $self->_add_to_current_statement($lookahead_delimiter); push @{ $self->_terminators }, [ CUSTOM_DELIMITER, $self->_custom_delimiter ]; return 1 } else { unshift @{$tokens}, @delimiter_tokens; return } } sub _is_terminator { my ($self, $token) = @_; # This is the first test to perform! if ( $self->_custom_delimiter ) { # If a custom delimiter is currently defined, # no other token can terminate a statement. return CUSTOM_DELIMITER if $self->_is_custom_delimiter($token); return } return if $token ne FORWARD_SLASH && $token ne SEMICOLON; my $tokens = $self->_tokens; if ( $token eq FORWARD_SLASH ) { # Remove the trailing FORWARD_SLASH from the current statement chop( my $current_statement = $self->_current_statement ); my $next_token = $tokens->[0]; my $next_next_token = $tokens->[1]; if ( !defined($next_token) || $next_token eq NEWLINE || $next_token =~ /^\s+$/ && $next_next_token eq NEWLINE ) { return SLASH_TERMINATOR if $current_statement =~ /;\s*\n\s*\z/ || $current_statement =~ /\n\s*\.\s*\n\s*\z/; # Slash with no preceding semicolon or period: # this is to be treated as a semicolon terminator... my $next_significant_token_idx = $self->_next_significant_token_idx; # ... provided that it's not a division operator # (at least not a blatant one ;-) return SEMICOLON_TERMINATOR if $self->slash_terminates && $current_statement =~ /\n\s*\z/ && ( $next_significant_token_idx == -1 || $tokens->[$next_significant_token_idx] ne OPEN_BRACKET && $tokens->[$next_significant_token_idx] !~ /^\d/ && !( $tokens->[$next_significant_token_idx] eq DOT && $tokens->[$next_significant_token_idx + 1] =~ /^\d/ ) ) } return } # $token eq SEMICOLON. my $next_code_portion = ''; my $i = 0; $next_code_portion .= $tokens->[$i++] while $i <= 8 && defined $tokens->[$i]; return SEMICOLON_TERMINATOR if $token eq SEMICOLON && $next_code_portion !~ m#\A\s*\n\s*/\s*$#m && $next_code_portion !~ m#\A\s*\n\s*\.\s*\n\s*/\s*$#m; # there is a FORWARD_SLASH next: let's wait for it to terminate. return } sub _peek_at_next_significant_token { my ($self, $skiptoken_RE) = @_; my $tokens = $self->_tokens; my $next_significant_token = $skiptoken_RE ? firstval { /\S/ && ! $self->_is_comment($_) && ! /$skiptoken_RE/ } @{ $tokens } : firstval { /\S/ && ! $self->_is_comment($_) } @{ $tokens }; return $next_significant_token if defined $next_significant_token; return '' } sub _next_significant_token_idx { my ($self, $skiptoken_RE) = @_; my $tokens = $self->_tokens; return $skiptoken_RE ? firstidx { /\S/ && ! $self->_is_comment($_) && ! /$skiptoken_RE/ } @{ $tokens } : firstidx { /\S/ && ! $self->_is_comment($_) } @{ $tokens } } 1; __END__ =head1 NAME SQL::SplitStatement - Split any SQL code into atomic statements =head1 VERSION version 1.00020 =head1 SYNOPSIS # 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;'); -- Inline SQL comment END; -- Standalone SQL; comment; with semicolons; INSERT INTO parent (a, b, c, d) VALUES ('pippo;', 'pluto;', NULL, NULL); SQL use SQL::SplitStatement; my $sql_splitter = SQL::SplitStatement->new; my @statements = $sql_splitter->split($sql_code); # @statements now is: # # ( # '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)' # ) =head1 DESCRIPTION This is a simple module which tries to split any SQL code, even including non-standard extensions (for the details see the L section below), into the atomic statements it is composed of. The logic used to split the SQL code is more sophisticated than a raw C on the C<;> (semicolon) character: first, various different statement terminator I are recognized (see below for the list), then this module 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. Consider however that this is by no means a validating parser (technically speaking, it's just a I). It should rather be seen as an in-progress I approach, which will gradually improve as test cases will be reported. This also means that, except for the L detailed below, there is no known (to the author) SQL code the most current release of this module can't correctly split. The test suite bundled with the distribution (which now includes the popular I and I sample db schemata, as detailed in the L section below) should give you an idea of the capabilities of this module If your atomic statements are to be fed to a DBMS, you are encouraged to use L instead, which uses this module and also (optionally) offers automatic transactions support, so that you'll have the I behavior you would probably want. =head1 METHODS =head2 C =over 4 =item * C<< SQL::SplitStatement->new( %options ) >> =item * C<< SQL::SplitStatement->new( \%options ) >> =back It creates and returns a new SQL::SplitStatement object. It accepts its options either as a hash or a hashref. C takes the following Boolean options, which for documentation purposes can be grouped in two sets: L and L. =head3 Formatting Options =over 4 =item * C A Boolean option which causes, when set to a false value (which is the default), the trailing terminator token to be discarded in the returned atomic statements. When set to a true value, the terminators are kept instead. The possible terminators (which are treated as such depending on the context) are: =over 4 =item * C<;> (the I character); =item * any string defined by the MySQL C command; =item * an C<;> followed by an C (I character) on its own line; =item * an C<;> followed by an C<.> (I character) on its own line, followed by an C on its own line; =item * an C on its own line regardless of the preceding characters (only if the C option, explained below, is set). =back The multi-line terminators above are always treated as a single token, that is they are discarded (or returned) as a whole (regardless of the C option value). If your statements are to be fed to a DBMS, you are advised to keep this option to its default (false) value, since some drivers/DBMSs don't want the terminator to be present at the end of the (single) statement. (Note that the last, possibly empty, statement of a given SQL text, never has a trailing terminator. See below for an example.) =item * C An alias for the the C option explained above. Note that if C and C are both passed to C, an exception is thrown. =item * C A Boolean option which causes, when set to a false value (which is the default), the spaces (C<\s>) around the statements to be trimmed. When set to a true value, these spaces are kept instead. When C is set to false as well, the terminator is discarded first (regardless of the spaces around it) and the trailing spaces are trimmed then. This ensures that if C is set to false, the returned statements will never have trailing (nor leading) spaces, regardless of the C value. =item * C A Boolean option which causes, when set to a false value (which is the default), the comments to be discarded in the returned statements. When set to a true value, they are kept with the statements instead. Both SQL and multi-line C-style comments are recognized. When kept, each comment is returned in the same string with the atomic statement it belongs to. A comment belongs to a statement if it appears, in the original SQL code, before the end of that statement and after the terminator of the previous statement (if it exists), as shown in this pseudo-SQL snippet: /* This comment will be returned together with statement1 */ ; -- This will go with statement2 -- (note the semicolon which closes statement1) -- This with statement2 as well =item * C A Boolean option which causes, when set to a false value (which is the default), the empty statements to be discarded. When set to a true value, the empty statements are returned instead. A statement is considered empty when it contains no characters other than the terminator and space characters (C<\s>). A statement composed solely of comments is not recognized as empty and may therefore be returned even when C is false. To avoid this, it is sufficient to leave C to false as well. Note instead that an empty statement is recognized as such regardless of the value of the options C and C. =back These options are basically to be kept to their default (false) values, especially if the atomic statements are to be given to a DBMS. They are intended mainly for I reasons, or if you want to count by how many atomic statements, including the empty ones, your original SQL code was composed of. Another situation where they are useful (in the general case necessary, really), is when you want to retain the ability to verbatim rebuild the original SQL string from the returned statements: my $verbatim_splitter = SQL::SplitStatement->new( keep_terminators => 1, keep_extra_spaces => 1, keep_comments => 1, keep_empty_statements => 1 ); my @verbatim_statements = $verbatim_splitter->split($sql_string); $sql_string eq join '', @verbatim_statements; # Always true, given the constructor above. Other than this, again, you are recommended to stick with the defaults. =head3 DBMSs Specific Options The same syntactic structure can have different semantics across different SQL dialects, so sometimes it is necessary to help the parser to make the right decision. This is the function of these options. =over 4 =item * C A Boolean option which causes, when set to a true value (which is the default), a C (I) on its own line, even without a preceding semicolon, to be admitted as a (possible) terminator. If set to false, a forward-slash on its own line is treated as a statement terminator only if preceded by a semicolon or by a dot and a semicolon. If you are dealing with Oracle's SQL, you should let this option set, since a slash (alone, without a preceding semicolon) is sometimes used as a terminator, as it is permitted by SQL*Plus (on non-I statements). With SQL dialects other than Oracle, there is the (theoretical) possibility that a slash on its own line can pass the additional checks and be considered a terminator (while it shouldn't). This chance should be really tiny (it has never been observed in real world code indeed). Though negligible, by setting this option to false that risk can anyway be ruled out. =back =head2 C =over 4 =item * C<< $sql_splitter->split( $sql_string ) >> =back This is the method which actually splits the SQL code into its atomic components. It returns a list containing the atomic statements, in the same order they appear in the original SQL code. The atomic statements are returned according to the options explained above. Note that, as mentioned above, an SQL string which terminates with a terminator token (for example a semicolon), contains a trailing empty statement: this is correct and it is treated accordingly (if C is set to a true value): my $sql_splitter = SQL::SplitStatement->new( keep_empty_statements => 1 ); my @statements = $sql_splitter->split( 'SELECT 1;' ); print 'The SQL code contains ' . scalar(@statements) . ' statements.'; # The SQL code contains 2 statements. =head2 C =over 4 =item * C<< $sql_splitter->split_with_placeholders( $sql_string ) >> =back It works exactly as the C method explained above, except that it returns also a list of integers, each of which is the number of the I contained in the corresponding atomic statement. More precisely, its return value is a list of two elements, the first of which is a reference to the list of the atomic statements exactly as returned by the C method, while the second is a reference to the list of the number of placeholders as explained above. Here is an example: # 4 statements (valid SQLite 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 (?, ?, ?) SQL my $splitter = SQL::SplitStatement->new; my ( $statements, $placeholders ) = $splitter->split_with_placeholders( $sql_code ); # $placeholders now is: [0, 2, 0, 3] where the returned C<$placeholders> list(ref) is to be read as follows: the first statement contains 0 placeholders, the second 2, the third 0 and the fourth 3. 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<< $sql_splitter->keep_terminators >> =item * C<< $sql_splitter->keep_terminators( $boolean ) >> Getter/setter method for the C option explained above. =back =head2 C An alias for the C method explained above. =head2 C =over 4 =item * C<< $sql_splitter->keep_extra_spaces >> =item * C<< $sql_splitter->keep_extra_spaces( $boolean ) >> Getter/setter method for the C option explained above. =back =head2 C =over 4 =item * C<< $sql_splitter->keep_comments >> =item * C<< $sql_splitter->keep_comments( $boolean ) >> Getter/setter method for the C option explained above. =back =head2 C =over 4 =item * C<< $sql_splitter->keep_empty_statements >> =item * C<< $sql_splitter->keep_empty_statements( $boolean ) >> Getter/setter method for the C option explained above. =back =head2 C =over 4 =item * C<< $sql_splitter->slash_terminates >> =item * C<< $sql_splitter->slash_terminates( $boolean ) >> Getter/setter method for the C option explained above. =back =head1 SUPPORTED DBMSs SQL::SplitStatement aims to cover the widest possible range of DBMSs, SQL dialects and extensions (even proprietary), in a (nearly) fully transparent way for the user. Currently it has been tested mainly on SQLite, PostgreSQL, MySQL and Oracle. =head2 Procedural Extensions Procedural code is by far the most complex to handle. Currently any block of code which start with C, C, C, C or C is correctly recognized, as well as I C blocks, I blocks and blocks delimited by a C-defined I, therefore a wide range of procedural extensions should be handled correctly. However, only PL/SQL, PL/PgSQL and MySQL code has been tested so far. If you need also other procedural languages to be recognized, please let me know (possibly with some test cases). =head1 LIMITATIONS Bound to be plenty, given the heuristic nature of this module (and its ambitious goals). However, no limitations are currently known. Please report any problematic test case. =head2 Non-limitations To be split correctly, the given input must, in general, be syntactically valid SQL. For example, an unbalanced C or a misspelled keyword could, under certain circumstances, confuse the parser and make it trip over the next statement terminator, thus returning non-split statements. This should not be seen as a limitation though, as the original (invalid) SQL code would have been unusable anyway (remember that this is NOT a validating parser!) =head1 SHOWCASE To test the capabilities of this module, you can run it (or rather run L) on the files F and F included in the distribution, which contain two quite large and complex I db schemata, for MySQL and PostgreSQL respectively. For more information: =over 4 =item * Sakila db: L =item * Pagila db: L =back =head1 DEPENDENCIES SQL::SplitStatement depends on the following modules: =over 4 =item * L =item * L =item * L =item * L =item * L 0.22 or newer =back =head1 AUTHOR Emanuele Zeppieri, C<< >> =head1 BUGS No known bugs. 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 SQL::SplitStatement 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 Igor Sutton for his excellent L, which made writing this module a joke. =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. =cut72-proc_plsql_package.t000644001750001750 1710711543335122 22032 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 16; my $sql_code; my $splitter; my @statements; $sql_code = <<'SQL'; CREATE TABLE sqr_root_sum (num NUMBER, sq_root NUMBER(6,2), sqr NUMBER, sum_sqrs NUMBER); CREATE PaCkaGe BODY emp_actions AS -- body CURSOR desc_salary RETURN EmpRecTyp IS SELECT empno, sal FROM emp ORDER BY sal DESC; PROCEDURE hire_employee ( ename VARCHAR2, job VARCHAR2, mgr NUMBER, sal NUMBER, comm NUMBER, deptno NUMBER) IS BEGIN INSERT INTO emp VALUES (empno_seq.NEXTVAL, ename, job, mgr, SYSDATE, sal, comm, deptno); END hire_employee; PROCEDURE fire_employee (emp_id NUMBER) IS BEGIN DELETE FROM emp WHERE empno = emp_id; END fire_employee; END emp_actions; DECLARE s PLS_INTEGER; BEGIN FOR i in 1..100 LOOP s := (i * (i + 1) * (2*i +1)) / 6; -- sum of squares INSERT INTO sqr_root_sum VALUES (i, SQRT(i), i*i, s ); END LOOP; END; / DROP TABLE sqr_root_sum SQL $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 4, 'Statements split' ); $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code rebuilt' ); # Let's try again, with a different constructor $splitter = SQL::SplitStatement->new( keep_extra_spaces => 1, keep_empty_statements => 1, keep_terminator => 1, keep_comments => 1 ); $sql_code .= ';ALTER TABLE temp'; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 5, 'Statements split' ); is( join( '', @statements ), $sql_code, 'SQL code rebuilt' ); $sql_code = <<'SQL'; CREATE TABLE sqr_root_sum (num NUMBER, sq_root NUMBER(6,2), sqr NUMBER, sum_sqrs NUMBER); CREATE OR REPLACE PACKAGE BODY emp_actions_w_init AS -- body CURSOR desc_salary RETURN EmpRecTyp IS SELECT empno, sal FROM emp ORDER BY sal DESC; PROCEDURE hire_employee ( ename VARCHAR2, job VARCHAR2, mgr NUMBER, sal NUMBER, comm NUMBER, deptno NUMBER) IS BEGIN INSERT INTO emp VALUES (empno_seq.NEXTVAL, ename, job, mgr, SYSDATE, sal, comm, deptno); END hire_employee; PROCEDURE fire_employee (emp_id NUMBER) IS BEGIN DELETE FROM emp WHERE empno = emp_id; END fire_employee; BEGIN -- initialization part starts here INSERT INTO emp_audit VALUES (SYSDATE, USER, 'EMP_ACTIONS'); number_hired := 0; END emp_actions_w_init; / DECLARE s PLS_INTEGER; BEGIN FOR i in 1..100 LOOP s := (i * (i + 1) * (2*i +1)) / 6; -- sum of squares INSERT INTO sqr_root_sum VALUES (i, SQRT(i), i*i, s ); END LOOP; END; / DROP TABLE sqr_root_sum SQL $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 4, 'Statements w/ initialization split' ); $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code w/ initialization rebuilt' ); # Let's try again, with a different constructor $splitter = SQL::SplitStatement->new( keep_extra_spaces => 1, keep_empty_statements => 1, keep_terminator => 1, keep_comments => 1 ); $sql_code .= ';ALTER TABLE temp'; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 5, 'Statements w/ initialization split' ); is( join( '', @statements ), $sql_code, 'SQL code w/ initialization rebuilt' ); $sql_code = <<'SQL'; CREATE TABLE sqr_root_sum (num NUMBER, sq_root NUMBER(6,2), sqr NUMBER, sum_sqrs NUMBER); CREATE PaCkaGe BODY emp_actions AS -- body CURSOR desc_salary RETURN EmpRecTyp IS SELECT empno, sal FROM emp ORDER BY sal DESC; PROCEDURE hire_employee ( ename VARCHAR2, job VARCHAR2, mgr NUMBER, sal NUMBER, comm NUMBER, deptno NUMBER) IS BEGIN INSERT INTO emp VALUES (empno_seq.NEXTVAL, ename, job, mgr, SYSDATE, sal, comm, deptno); END hire_employee; PROCEDURE fire_employee (emp_id NUMBER) IS BEGIN DELETE FROM emp WHERE empno = emp_id; END fire_employee; END; / DECLARE s PLS_INTEGER; BEGIN FOR i in 1..100 LOOP s := (i * (i + 1) * (2*i +1)) / 6; -- sum of squares INSERT INTO sqr_root_sum VALUES (i, SQRT(i), i*i, s ); END LOOP; END; / DROP TABLE sqr_root_sum SQL $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 4, 'Statements w/o package name split' ); $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code w/o package name rebuilt' ); # Let's try again, with a different constructor $splitter = SQL::SplitStatement->new( keep_extra_spaces => 1, keep_empty_statements => 1, keep_terminator => 1, keep_comments => 1 ); $sql_code .= ';ALTER TABLE temp'; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 5, 'Statements w/o package name split' ); is( join( '', @statements ), $sql_code, 'SQL code w/o package name rebuilt' ); $sql_code = <<'SQL'; CREATE TABLE sqr_root_sum (num NUMBER, sq_root NUMBER(6,2), sqr NUMBER, sum_sqrs NUMBER); CREATE OR REPLACE PACKAGE BODY emp_actions_w_init AS -- body CURSOR desc_salary RETURN EmpRecTyp IS SELECT empno, sal FROM emp ORDER BY sal DESC; PROCEDURE hire_employee ( ename VARCHAR2, job VARCHAR2, mgr NUMBER, sal NUMBER, comm NUMBER, deptno NUMBER) IS BEGIN INSERT INTO emp VALUES (empno_seq.NEXTVAL, ename, job, mgr, SYSDATE, sal, comm, deptno); END hire_employee; PROCEDURE fire_employee (emp_id NUMBER) IS BEGIN DELETE FROM emp WHERE empno = emp_id; END fire_employee; BEGIN -- initialization part starts here INSERT INTO emp_audit VALUES (SYSDATE, USER, 'EMP_ACTIONS'); number_hired := 0; END; / DECLARE s PLS_INTEGER; BEGIN FOR i in 1..100 LOOP s := (i * (i + 1) * (2*i +1)) / 6; -- sum of squares INSERT INTO sqr_root_sum VALUES (i, SQRT(i), i*i, s ); END LOOP; END; / DROP TABLE sqr_root_sum SQL $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 4, 'Statements w/o package name w/ initialization split' ); $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code w/o package name w/ initialization rebuilt' ); # Let's try again, with a different constructor $splitter = SQL::SplitStatement->new( keep_extra_spaces => 1, keep_empty_statements => 1, keep_terminator => 1, keep_comments => 1 ); $sql_code .= ';ALTER TABLE temp'; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 5, 'Statements w/o package name w/ initialization split' ); is( join( '', @statements ), $sql_code, 'SQL code w/o package name w/ initialization rebuilt' ); 61-placeholders_empty.t000644001750001750 330611543335122 22036 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 7; my $original_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 (?, ?, ?)' ]; my $expected_placeholders; my $sql_code = <<'SQL'; CREATE TABLE state (id, "?name?"); INSERT INTO state (id, "?name?") VALUES (?, ?); ; ; -- Two Empty statements CREATE TABLE city (id, name, state_id); INSERT INTO city (id, name, state_id) VALUES (?, ?, ?) ; -- Final empty statement SQL my ( $statements, $placeholders ); my $splitter; $splitter = SQL::SplitStatement->new; ( $statements, $placeholders ) = $splitter->split_with_placeholders( $sql_code ); cmp_ok( @$statements, '==', @$placeholders, 'Same number of statements and placeholders numbers' ); cmp_ok( @$statements, '==', 4, 'Count number of statements' ); is_deeply( $statements, $original_statements, 'Statements correctly split' ); $expected_placeholders = [0, 2, 0, 3]; is_deeply( $placeholders, $expected_placeholders, 'Placeholders count' ); $splitter = SQL::SplitStatement->new( keep_empty_statements => 1 ); ( $statements, $placeholders ) = $splitter->split_with_placeholders( $sql_code ); cmp_ok( @$statements, '==', @$placeholders, 'Same number of statements and placeholders numbers' ); cmp_ok( @$statements, '==', 7, 'Count number of statements' ); $expected_placeholders = [0, 2, 0, 0, 0, 3, 0]; is_deeply( $placeholders, $expected_placeholders, 'Placeholders correctly calculated' ); 81-examples_procedural.t000644001750001750 177411543335122 22222 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 2; my $sql_code = <<'SQL'; CREATE TABLE sqr_root_sum (num NUMBER, sq_root NUMBER(6,2), sqr NUMBER, sum_sqrs NUMBER); DECLARE s PLS_INTEGER; -- inline sql comment s2 PLS_INTEGER; /* Multiline C-style comment */ BEGIN s2 := 10*i; FOR i in 1..100 LOOP s := (i * (i + 1) * (2*i +1)) / 6; -- sum of squares INSERT INTO sqr_root_sum VALUES (i, SQRT(i), i*i, s*s2); END LOOP; END; / DROP TABLE sqr_root_sum SQL my $splitter; my @statements; $splitter = SQL::SplitStatement->new; @statements = $splitter->split($sql_code); cmp_ok ( scalar(@statements), '==', 3, 'Correct number of atomic statements' ); $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); 51-transactions_and_proc.t000644001750001750 361111543335122 22526 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 2; my $sql_code = <<'SQL'; BEGIN; UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; SAVEPOINT my_savepoint; DECLARE s PLS_INTEGER; BEGIN FOR i in 1..100 LOOP s := (i * (i + 1) * (2*i +1)) / 6; -- sum of squares INSERT INTO sqr_root_sum VALUES (i, SQRT(i), i*i, s ); END LOOP; END; / UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Bob'; -- oops ... forget that and use Wally's account ROLLBACK TO my_savepoint; -- including OR REPLACE is more convenient when updating a subprogram CREATE OR REPLACE PROCEDURE award_bonus (emp_id NUMBER, bonus NUMBER) AS commission REAL; comm_missing EXCEPTION; BEGIN -- executable part starts here SELECT commission_pct / 100 INTO commission FROM employees WHERE employee_id = emp_id; IF commission IS NULL THEN RAISE comm_missing; ELSE UPDATE employees SET salary = salary + bonus*commission WHERE employee_id = emp_id; END IF; EXCEPTION -- exception-handling part starts here WHEN comm_missing THEN DBMS_OUTPUT.PUT_LINE('This employee does not receive a commission.'); commission := 0; WHEN OTHERS THEN NULL; -- for other exceptions do nothing END award_bonus; / CALL award_bonus(150, 400); UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Wally'; COMMIT; SQL my $splitter; my @statements; $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 10, 'Statements correctly split' ); $splitter = SQL::SplitStatement->new({ keep_extra_spaces => 1, keep_empty_statements => 1, keep_terminator => 1, keep_comments => 1 }); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); 71-proc_plsql_RTbug_57971.t000644001750001750 116611543335122 22213 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 1; # Bug report (by Dan Horne): # https://rt.cpan.org/Public/Bug/Display.html?id=57971 my $sql_code = <<'SQL'; create or replace procedure test (num1 number) is v_test varchar2 is begin select col1 into v_test from my_tab; end; / create table my_tab( col1 varchar2(30), col2 number ); insert into my_tab(col1, col2) values ('hello', 3); SQL my $splitter; my @statements; $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 3, 'Statements correctly split' ); 72-proc_plsql_mixed_endings.t000644001750001750 430011543335122 23223 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 7; # Bug report (Alexander Sennhauser ): my $sql_code = <<'SQL'; BEGIN dbms_output.put_line('Hi Ma, I can write PL/SQL'); END; BEGIN dbms_output.put_line('Hi Ma, I can write PL/SQL'); END; / BEGIN dbms_output.put_line('Hi Ma, I can write PL/SQL'); END; . / CREATE PACKAGE BODY emp_actions AS number_hired INT; -- visible only in this package CURSOR trans_cursor IS SELECT acct_id, kind, amount FROM transactions WHERE status = 'Pending' ORDER BY time_tag FOR UPDATE OF status; -- to lock rows /* Fully define subprograms specified in package. */ FUNCTION hire_employee ( ename VARCHAR2, job VARCHAR2, mgr REAL, sal REAL, comm REAL, deptno REAL) RETURN INT IS new_empno INT; BEGIN SELECT empno_seq.NEXTVAL INTO new_empno FROM dual; INSERT INTO emp VALUES (new_empno, ename, job, mgr, SYSDATE, sal, comm, deptno); number_hired := number_hired + 1; RETURN new_empno; END hire_employee; BEGIN -- initialization part starts here INSERT INTO emp_audit VALUES (SYSDATE, USER, 'EMP_ACTIONS'); number_hired := 0; END; CREATE OR REPLACE FUNCTION to_date_check_null(dateString IN VARCHAR2, dateFormat IN VARCHAR2) RETURN DATE IS BEGIN IF dateString IS NULL THEN return NULL; ELSE return to_date(dateString, dateFormat); END IF; END; . / CREATE OR REPLACE FUNCTION to_date_check_null(dateString IN VARCHAR2, dateFormat IN VARCHAR2) RETURN DATE IS BEGIN IF dateString IS NULL THEN return NULL; ELSE return to_date(dateString, dateFormat); END IF; END to_date_check_null; / SQL my $splitter; my @statements; my @endings; $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 6, 'Statements correctly split' ); @endings = qw| END END END END END to_date_check_null |; like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; 70-proc_plsql_slash_endings.t000644001750001750 643211543335122 23235 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 18; my $sql_code = <<'SQL'; create or replace type Address_Type as object ( street_addr1 varchar2(25), street_addr2 varchar2(25), city varchar2(30), state varchar2(2), zip_code number, member function toString return varchar2, map member function mapping_function return varchar2 ) / create or replace type body Address_Type as member function toString return varchar2 is begin if ( street_addr2 is not NULL ) then return street_addr1 || ' ' || street_addr2 || ' ' || city || ', ' || state || ' ' || zip_code; else return street_addr1 || ' ' || city || ', ' || state || ' ' || zip_code; end if; end; map member function mapping_function return varchar2 is begin return to_char( nvl(zip_code,0), 'fm00000' ) || lpad( nvl(city,' '), 30 ) || lpad( nvl(street_addr1,' '), 25 ) || lpad( nvl(street_addr2,' '), 25 ); end; end; create table people ( name varchar2(10), home_address address_type, work_address address_type ) / create or replace type Address_Array_Type as varray(25) of Address_Type / alter table people add previous_addresses Address_Array_Type / CREATE TYPE varchar2_4000_array AS TABLE OF VARCHAR2(4000) / DROP TABLE test_tab / CREATE TABLE test_tab ( id NUMBER, PNOTETEXT VARCHAR2_4000_ARRAY ) nested table PNOTETEXT store as PNOTETEXT_NEST ; CREATE INDEX i_test_tab_pk ON test_tab (id) / SELECT count(*) from test_tab / SELECT id FROM mytable WHERE 4 < id / .2 ; SELECT id FROM mytable WHERE 4 < id / 3 ; SELECT id FROM mytable WHERE 4 < id / (3+4) ; CREATE SEQUENCE TEST_TAB_SEQ MINVALUE 1 MAXVALUE 9999999 START WITH 1 INCREMENT BY 1 NOCACHE ; DECLARE vCollection varchar2_4000_array := varchar2_4000_array(); vID NUMBER; BEGIN -- get a new id SELECT TEST_TAB_SEQ.NEXTVAL INTO vID FROM dual; SELECT pnotetext INTO vCollection FROM test_tab WHERE id = vID; -- loop round all the collection variable elements and print them out FOR q IN 1 .. vCollection.count LOOP dbms_output.put_line(q||' - tab : '||vCollection(q)); END LOOP; END; DROP TABLE test_tab / SQL my $splitter; my @statements; my @endings; $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 16, 'Statements correctly split' ); $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); @endings = qw| ) end ) Address_Type Address_Array_Type VARCHAR2(4000) test_tab PNOTETEXT_NEST (id) test_tab 2 3 ) NOCACHE END test_tab |; $splitter->keep_extra_spaces(0); $splitter->keep_empty_statements(0); $splitter->keep_terminators(0); $splitter->keep_comments(0); @statements = $splitter->split( $sql_code ); like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; 74-proc_plpgsql_dollar_quoted.t000644001750001750 545411543335122 23610 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 12; my $sql_code = <<'SQL'; CREATE LANGUAGE 'plpgsql' HANDLER plpgsql_call_handler LANCOMPILER 'PL/pgSQL'; PREPARE some_insert(integer, integer) AS INSERT INTO fib_cache (num, fib) VALUES ($1, $2); EXECUTE some_insert(fib_for, ret); DECLARE liahona CURSOR FOR SELECT * FROM films; CREATE OR REPLACE FUNCTION fib_fast( fib_for integer ) RETURNS integer AS $rocco$ DECLARE ret integer := 0; nxt integer := 1; tmp integer; BEGIN FOR num IN 1..fib_for LOOP tmp := ret; ret := nxt; nxt := tmp + nxt; END LOOP; PREPARE fooplan (int, text, bool, numeric) AS INSERT INTO foo VALUES($1, $2, $3, $4); EXECUTE fooplan(1, 'Hunter Valley', 't', 200.00); RETURN ret; END; $rocco$LANGUAGE plpgsql; DROP FUNCTION fib_fast(integer); CREATE FUNCTION somefunc() RETURNS integer AS $$ label DECLARE liahona CURSOR FOR SELECT * FROM films; quantity integer := 30; BEGIN RAISE NOTICE 'Quantity here is %', quantity; -- Prints 30 quantity := 50; -- -- Create a subblock -- DECLARE quantity integer := 80; BEGIN RAISE NOTICE 'Quantity here is %', quantity; -- Prints 80 RAISE NOTICE 'Outer quantity here is %', outerblock.quantity; -- Prints 50 END; RAISE NOTICE 'Quantity here is %', quantity; -- Prints 50 PREPARE fooplan (int, text, bool, numeric) AS INSERT INTO foo VALUES($1, $2, $3, $4); EXECUTE fooplan(1, 'Hunter Valley', 't', 200.00); / -- Illegal, just to check that a / inside dollar-quotes can't split the statement RETURN quantity; END label; $$ LANGUAGE plpgsql; DECLARE liahona CURSOR FOR SELECT * FROM films; DROP FUNCTION somefunc(integer); CREATE FUNCTION funcname (argument-types) RETURNS return-type AS $$ # PL/Perl function body $$ LANGUAGE plperl; SQL my $splitter; my @statements; my @endings; $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 10, 'Statements correctly split' ); $splitter = SQL::SplitStatement->new; $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); $splitter->keep_extra_spaces(0); $splitter->keep_empty_statements(0); $splitter->keep_terminators(0); $splitter->keep_comments(0); @statements = $splitter->split( $sql_code ); @endings = qw| 'PL/pgSQL' $2) ret) films plpgsql fib_fast(integer) plpgsql films somefunc(integer) plperl |; like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; 72-proc_plsql_nested_functions.t000644001750001750 761211543335122 23771 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 5; my $sql_code; my $splitter; my @statements; my @endings; $sql_code = <<'SQL'; CREATE OR REPLACE FUNCTION nested(some_date DATE) RETURN VARCHAR2 IS yrstr VARCHAR2(4); -- beginning of nested function in declaration section FUNCTION turn_around ( year_string VARCHAR2) RETURN VARCHAR2 IS BEGIN yrstr := TO_CHAR(TO_NUMBER(year_string)*2); RETURN yrstr; END; -- end of nested function in declaration section -- beginning of named function BEGIN yrstr := TO_CHAR(some_date, 'YYYY'); yrstr := turn_around(yrstr); RETURN yrstr; END nested; CREATE PaCkaGe BODY emp_actions AS -- body CURSOR desc_salary RETURN EmpRecTyp IS SELECT empno, sal FROM emp ORDER BY sal DESC; PROCEDURE hire_employee ( ename VARCHAR2, job VARCHAR2, mgr NUMBER, sal NUMBER, comm NUMBER, deptno NUMBER) IS BEGIN INSERT INTO emp VALUES (empno_seq.NEXTVAL, ename, job, mgr, SYSDATE, sal, comm, deptno); END hire_employee; PROCEDURE fire_employee (emp_id NUMBER) IS BEGIN DELETE FROM emp WHERE empno = emp_id; END fire_employee; FUNCTION nested(some_date DATE) RETURN VARCHAR2 IS yrstr VARCHAR2(4); -- beginning of nested function in declaration section FUNCTION turn_around ( year_string VARCHAR2) RETURN VARCHAR2 IS BEGIN yrstr := TO_CHAR(TO_NUMBER(year_string)*2); RETURN yrstr; END; -- end of nested function in declaration section -- beginning of named function BEGIN yrstr := TO_CHAR(some_date, 'YYYY'); yrstr := turn_around(yrstr); RETURN yrstr; END nested; END; CREATE PACKAGE BODY emp_actions2 AS number_hired INT; -- visible only in this package /* Fully define subprograms specified in package. */ FUNCTION hire_employee ( ename VARCHAR2, job VARCHAR2, mgr REAL, sal REAL, comm REAL, deptno REAL) RETURN INT IS new_empno INT; BEGIN SELECT empno_seq.NEXTVAL INTO new_empno FROM dual; INSERT INTO emp VALUES (new_empno, ename, job, mgr, SYSDATE, sal, comm, deptno); number_hired := number_hired + 1; RETURN new_empno; END hire_employee; FUNCTION nested(some_date DATE) RETURN VARCHAR2 IS yrstr VARCHAR2(4); -- beginning of nested function in declaration section FUNCTION turn_around ( year_string VARCHAR2) RETURN VARCHAR2 IS BEGIN yrstr := TO_CHAR(TO_NUMBER(year_string)*2); RETURN yrstr; END; -- end of nested function in declaration section -- beginning of named function BEGIN yrstr := TO_CHAR(some_date, 'YYYY'); yrstr := turn_around(yrstr); RETURN yrstr; END nested; BEGIN -- initialization part starts here INSERT INTO emp_audit VALUES (SYSDATE, USER, 'EMP_ACTIONS'); number_hired := 0; END emp_actions2; SQL $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 3, 'Statements split' ); $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminators(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code rebuilt' ); @endings = qw| nested END emp_actions2 |; $splitter->keep_extra_spaces(0); $splitter->keep_empty_statements(0); $splitter->keep_terminators(0); $splitter->keep_comments(0); @statements = $splitter->split( $sql_code ); like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; create_table_and_trigger.sql000644001750001750 113311543335122 24147 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t/dataDROP TRIGGER IF EXISTS user_change_password; DELIMITER // CREATE TRIGGER user_change_password AFTER UPDATE ON user FOR EACH ROW my_block: BEGIN IF NEW.password != OLD.password THEN UPDATE user_authentication_results AS uar SET password_changed = 1 WHERE uar.user_id = NEW.user_id; END IF; END my_block; set localvariable datatype; set localvariable = parameter2; select fields from table where field1 = parameter1; // delimiter ; CREATE TABLE foo ( foo_field_1 VARCHAR, foo_field_2 VARCHAR ); CREATE TABLE bar ( bar_field_1 VARCHAR, bar_field_2 VARCHAR );25-keywords_as_unquoted_identifiers.t000644001750001750 336611543335122 25024 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 4; my $sql = <<'SQL'; CREATE TABLE begin ( declare VARCHAR, function VARCHAR ); CREATE TABLE procedure ( begin VARCHAR, declare VARCHAR ); DECLARE s PLS_INTEGER; BEGIN FOR i in 1..100 LOOP s := (i * (i + 1) * (2*i +1)) / 6; -- sum of squares INSERT INTO sqr_root_sum VALUES (i, SQRT(i), i*i, s ); END LOOP; END; / CREATE TABLE declare ( declare VARCHAR, function VARCHAR ); CREATE TABLE function ( declare VARCHAR, begin VARCHAR ); SQL chomp ( my $clean_sql = <<'SQL' ); CREATE TABLE begin ( declare VARCHAR, function VARCHAR )CREATE TABLE procedure ( begin VARCHAR, declare VARCHAR )DECLARE s PLS_INTEGER; BEGIN FOR i in 1..100 LOOP s := (i * (i + 1) * (2*i +1)) / 6; -- sum of squares INSERT INTO sqr_root_sum VALUES (i, SQRT(i), i*i, s ); END LOOP; ENDCREATE TABLE declare ( declare VARCHAR, function VARCHAR )CREATE TABLE function ( declare VARCHAR, begin VARCHAR ) SQL my $sql_splitter = SQL::SplitStatement->new({ keep_terminators => 1, keep_extra_spaces => 1, keep_empty_statements => 1, keep_comments => 1 }); my @statements; @statements = $sql_splitter->split($sql); cmp_ok ( scalar(@statements), '==', 6, 'number of atomic statements w/ semicolon' ); is ( join( '', @statements ), $sql, 'SQL code rebuilt w/ semicolon' ); $sql_splitter->keep_terminators(0); $sql_splitter->keep_extra_spaces(0); @statements = $sql_splitter->split($sql); cmp_ok ( scalar(@statements), '==', 6, 'number of atomic statements w/o semicolon' ); is ( join( '', @statements ), $clean_sql, 'SQL code rebuilt w/o semicolon' ); 72-proc_plsql_function_inside_package.t000644001750001750 1437211543335122 25273 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 8; my $sql_code; my $splitter; my @statements; my @endings; $sql_code = <<'SQL'; CREATE PACKAGE emp_actions AS /* Declare externally callable subprograms. */ FUNCTION hire_employee ( ename VARCHAR2, job VARCHAR2, mgr REAL, sal REAL, comm REAL, deptno REAL) RETURN INT; END emp_actions; CREATE PACKAGE bank_transactions AS /* Declare externally visible constant. */ minimum_balance CONSTANT REAL := 100.00; /* Declare externally callable procedures. */ PROCEDURE apply_transactions; PROCEDURE enter_transaction ( acct INT, kind CHAR, amount REAL); END bank_transactions; CREATE PACKAGE BODY bank_transactions AS /* Declare global variable to hold transaction status. */ new_status VARCHAR2(70) := 'Unknown'; /* Use forward declarations because apply_transactions calls credit_account and debit_account, which are not yet declared when the calls are made. */ PROCEDURE credit_account (acct INT, credit REAL); PROCEDURE debit_account (acct INT, debit REAL); /* Fully define procedures specified in package. */ PROCEDURE apply_transactions IS /* Apply pending transactions in transactions table to accounts table. Use cursor to fetch rows. */ CURSOR trans_cursor IS SELECT acct_id, kind, amount FROM transactions WHERE status = 'Pending' ORDER BY time_tag FOR UPDATE OF status; -- to lock rows BEGIN FOR trans IN trans_cursor LOOP IF trans.kind = 'D' THEN debit_account(trans.acct_id, trans.amount); ELSIF trans.kind = 'C' THEN credit_account(trans.acct_id, trans.amount); ELSE new_status := 'Rejected'; END IF; UPDATE transactions SET status = new_status WHERE CURRENT OF trans_cursor; END LOOP; END apply_transactions; PROCEDURE enter_transaction ( /* Add a transaction to transactions table. */ acct INT, kind CHAR, amount REAL) IS BEGIN INSERT INTO transactions VALUES (acct, kind, amount, 'Pending', SYSDATE); END enter_transaction; /* Define local procedures, available only in package. */ PROCEDURE do_journal_entry ( /* Record transaction in journal. */ acct INT, kind CHAR, new_bal REAL) IS BEGIN INSERT INTO journal VALUES (acct, kind, new_bal, sysdate); IF kind = 'D' THEN new_status := 'Debit applied'; ELSE new_status := 'Credit applied'; END IF; END do_journal_entry; PROCEDURE credit_account (acct INT, credit REAL) IS /* Credit account unless account number is bad. */ old_balance REAL; new_balance REAL; BEGIN SELECT balance INTO old_balance FROM accounts WHERE acct_id = acct FOR UPDATE OF balance; -- to lock the row new_balance := old_balance + credit; UPDATE accounts SET balance = new_balance WHERE acct_id = acct; do_journal_entry(acct, 'C', new_balance); EXCEPTION WHEN NO_DATA_FOUND THEN new_status := 'Bad account number'; WHEN OTHERS THEN new_status := SUBSTR(SQLERRM,1,70); END credit_account; PROCEDURE debit_account (acct INT, debit REAL) IS /* Debit account unless account number is bad or account has insufficient funds. */ old_balance REAL; new_balance REAL; insufficient_funds EXCEPTION; BEGIN SELECT balance INTO old_balance FROM accounts WHERE acct_id = acct FOR UPDATE OF balance; -- to lock the row new_balance := old_balance - debit; IF new_balance >= minimum_balance THEN UPDATE accounts SET balance = new_balance WHERE acct_id = acct; do_journal_entry(acct, 'D', new_balance); ELSE RAISE insufficient_funds; END IF; EXCEPTION WHEN NO_DATA_FOUND THEN new_status := 'Bad account number'; WHEN insufficient_funds THEN new_status := 'Insufficient funds'; WHEN OTHERS THEN new_status := SUBSTR(SQLERRM,1,70); END debit_account; END bank_transactions; / CREATE PACKAGE BODY emp_actions AS number_hired INT; -- visible only in this package /* Fully define subprograms specified in package. */ FUNCTION hire_employee ( ename VARCHAR2, job VARCHAR2, mgr REAL, sal REAL, comm REAL, deptno REAL) RETURN INT IS new_empno INT; BEGIN SELECT empno_seq.NEXTVAL INTO new_empno FROM dual; INSERT INTO emp VALUES (new_empno, ename, job, mgr, SYSDATE, sal, comm, deptno); number_hired := number_hired + 1; RETURN new_empno; END hire_employee; BEGIN -- initialization part starts here INSERT INTO emp_audit VALUES (SYSDATE, USER, 'EMP_ACTIONS'); number_hired := 0; END emp_actions; CREATE TABLE foo ( foo_field_1 VARCHAR, foo_field_2 VARCHAR ) SQL $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 5, 'Statements split' ); $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminators(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code rebuilt' ); # Let's try again, with a different constructor $splitter = SQL::SplitStatement->new( keep_extra_spaces => 1, keep_empty_statements => 1, keep_terminator => 1, keep_comments => 1 ); $sql_code .= ';ALTER TABLE temp'; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 6, 'Statements split' ); is( join( '', @statements ), $sql_code, 'SQL code rebuilt' ); @endings = qw| emp_actions bank_transactions bank_transactions emp_actions |; $splitter->keep_extra_spaces(0); $splitter->keep_empty_statements(0); $splitter->keep_terminators(0); $splitter->keep_comments(0); @statements = $splitter->split( $sql_code ); like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings; 74-proc_plpgsql_dollar_quoted_w_transactions.t000644001750001750 603211543335122 26717 0ustar00emazepemazep000000000000SQL-SplitStatement-1.00020/t#!/usr/bin/env perl use strict; use warnings; use SQL::SplitStatement; use Test::More tests => 22; my $sql_code = <<'SQL'; BEGIN; CREATE LANGUAGE 'plpgsql' HANDLER plpgsql_call_handler LANCOMPILER 'PL/pgSQL'; SAVEPOINT my_savepoint; PREPARE some_insert(integer, integer) AS INSERT INTO fib_cache (num, fib) VALUES (?, ?); ROLLBACK TO my_savepoint; EXECUTE some_insert(fib_for, ret); CREATE OR REPLACE FUNCTION fib_fast( fib_for integer ) RETURNS integer AS $$ DECLARE ret integer := 0; nxt integer := 1; tmp integer; BEGIN FOR num IN 1..fib_for LOOP tmp := ret; ret := nxt; nxt := tmp + nxt; END LOOP; RETURN ret; END; $$ LANGUAGE plpgsql; COMMIT; START TRANSACTION; DROP FUNCTION fib_fast(integer); COMMIT; BEGIN ISOLATION LEVEL SERIALIZABLE; CREATE FUNCTION somefunc() RETURNS integer AS $$ label DECLARE quantity integer := 30; BEGIN RAISE NOTICE 'Quantity here is %', quantity; -- Prints 30 quantity := 50; -- -- Create a subblock -- DECLARE quantity integer := 80; BEGIN RAISE NOTICE 'Quantity here is %', quantity; -- Prints 80 RAISE NOTICE 'Outer quantity here is %', outerblock.quantity; -- Prints 50 END; RAISE NOTICE 'Quantity here is %', quantity; -- Prints 50 RETURN quantity; END label; $$ LANGUAGE plpgsql; COMMIT; DROP FUNCTION somefunc(integer); CREATE TABLE t1 (a integer PRIMARY KEY); CREATE FUNCTION test_exception() RETURNS boolean LANGUAGE plpgsql AS $$BEGIN INSERT INTO t1 (a) VALUES (1); INSERT INTO t1 (a) VALUES (2); INSERT INTO t1 (a) VALUES (1); INSERT INTO t1 (a) VALUES (3); RETURN TRUE; EXCEPTION WHEN integrity_constraint_violation THEN RAISE NOTICE 'Rollback to savepoint'; RETURN FALSE; END;$$; BEGIN; SELECT test_exception(); SQL my $splitter; my @statements; my @endings; my ($statement, $placeholders); $splitter = SQL::SplitStatement->new; @statements = $splitter->split( $sql_code ); cmp_ok( @statements, '==', 19, 'Statements correctly split' ); $splitter = SQL::SplitStatement->new; $splitter->keep_extra_spaces(1); $splitter->keep_empty_statements(1); $splitter->keep_terminator(1); $splitter->keep_comments(1); @statements = $splitter->split( $sql_code ); is( join( '', @statements ), $sql_code, 'SQL code correctly rebuilt' ); ($statement, $placeholders) = $splitter->split_with_placeholders( $sql_code ); cmp_ok( $placeholders->[3], '==', 2, 'Statements correctly split' ); @endings = qw| BEGIN 'PL/pgSQL' my_savepoint ?) my_savepoint ret) plpgsql COMMIT TRANSACTION fib_fast(integer) COMMIT SERIALIZABLE plpgsql COMMIT somefunc(integer) KEY) END;$$ BEGIN test_exception() |; $splitter->keep_extra_spaces(0); $splitter->keep_empty_statements(0); $splitter->keep_terminators(0); $splitter->keep_comments(0); @statements = $splitter->split( $sql_code ); like( $statements[$_], qr/\Q$endings[$_]\E$/, 'Statement ' . ($_+1) . ' check' ) for 0..$#endings;