SQL::Sp...tatement(3)
π SQL::SplitStatement β the SQL::Sp...tatement(3) manual, bestie β¨
okay so this is the User Contributed Perl Documentation page for SQL::SplitStatement(3), and it is literally about splitting SQL into atomic statements. main character energy fr fr.
πΈ NAME πΈ
SQL::SplitStatement β Split any SQL code into atomic statements.
π¦ SYNOPSIS π¦
bestie, hereβs the whole vibe in code. Do not touch the semicolons, they are doing important work π
# 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 π
okay so SQL::SplitStatement is this simple module that tries to split any SQL code β even the non-standard-extension girlies (details are over in the SUPPORTED DBMSs section below) β into the atomic statements itβs composed of. periodt.
The logic is way more sophisticated than doing a raw split on the ; (semicolon) character. First, it recognizes various different statement terminator tokens (the list is below, iykyk), and then this module can correctly handle those tokens showing up inside identifiers, values, comments, BEGIN ... END blocks (even nested ones), dollar-quoted strings, MySQL custom DELIMITERs, procedural code, etc. β as partially exemplified in the SYNOPSIS above. π
But bestie, do not mistake this for a validating parser. Technically speaking, itβs just a context-sensitive tokenizer. Think of it as an in-progress heuristic approach that will gradually improve as test cases get reported. That also means, except for the LIMITATIONS detailed below, there is no known SQL code (to the author) that the most current release of this module canβt correctly split. no cap.
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 what this module is capable of. slay. β¨
If your atomic statements are going to be fed to a DBMS, you are encouraged to use DBIx::MultiStatementDo instead. It uses this module and also (optionally) offers automatic transactions support, so that you get the all-or-nothing behavior you would probably want. π«Ά
β¨ METHODS β¨
πΈ new
SQL::SplitStatement->new( %options )SQL::SplitStatement->new( \%options )
This one creates a brand-new SQL::SplitStatement object and hands it right back to you, bestie. It's chill like that β pass your options either as a plain hash or as a hashref, whichever vibe you're on. π«Ά
new accepts the following Boolean options, and for documentation purposes we're grouping them into two little cliques: Formatting Options and DBMSs Specific Options.
π Formatting Options
keep_terminatorsβ a Boolean option that, when it's set to a false value (and false is literally the default, no cap), makes the trailing terminator token get yeeted from the returned atomic statements. Set it to a true value and the terminators stay. Periodt.
The possible terminators (which get treated as terminators depending on context) are:
;(the semicolon character);- any string defined by the MySQL
DELIMITERcommand; - a
;followed by a/(forward-slash character) on its own line; - a
;followed by a.(dot character) on its own line, followed by a/on its own line; - a
/on its own line regardless of the preceding characters (only if theslash_terminatesoption, explained below, is set).
Those multi-line terminators up there? They're always treated as a single token, so they get discarded (or returned) as a whole β no splitting them up, and this holds regardless of the slash_terminates option value. π
If your statements are headed to a DBMS, you are advised to keep this option at its default (false) value, since some drivers/DBMSs really don't want the terminator sitting at the end of the (single) statement. Rude of them but okay.
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 thekeep_terminatorsoption explained above. Heads up though: ifkeep_terminatorsandkeep_terminatorare both passed tonew, an exception is thrown. Not me picking favorites, it's just the rules. πkeep_extra_spacesβ a Boolean option that, when false (default, obviously), trims the spaces (\s) around the statements. Set it true and those spaces get to stay.
When keep_terminators is also false, the terminator is discarded first (regardless of the spaces around it) and then the trailing spaces get trimmed. This guarantees that when keep_extra_spaces is false, your returned statements will never have trailing (nor leading) spaces β no matter what keep_terminators is doing. Iconic behavior honestly.
keep_commentsβ a Boolean option that, when false (default, as always), discards the comments in the returned statements. When true, the comments stay with their statements. β¨
Both SQL and multi-line C-style comments are recognized.
When kept, each comment comes back in the same string as the atomic statement it belongs to. A comment belongs to a statement if, in the original SQL code, it appears before the end of that statement and after the terminator of the previous statement (if one exists) β like this pseudo-SQL snippet:
/* This comment
will be returned
together with statement1 */
<statement1>; -- This will go with statement2
-- (note the semicolon which closes statement1)
<statement2>
-- This with statement2 as well
keep_empty_statementsβ a Boolean option that, when false (default, we been knew), discards empty statements. Set it true and the empty statements come back instead. π¦
A statement counts as empty when it contains no characters other than the terminator and space characters (\s).
A statement made up solely of comments is not recognized as empty, so it may still be returned even when keep_empty_statements is false. To dodge that, just leave keep_comments false too β problem solved, bestie.
But note: an empty statement is recognized as empty regardless of the value of the keep_terminators and keep_extra_spaces options. It's giving consistent.
These options are basically meant to be kept at their default (false) values β especially if the atomic statements are getting handed to a DBMS.
They're mainly for cosmetic reasons, or if you want to count how many atomic statements (empty ones included) your original SQL code was composed of. Main character energy for stat nerds. π
Another situation where they're useful (and 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. Slay responsibly. π
π DBMSs Specific Options
The same syntactic structure can have different semantics across different SQL dialects, so sometimes you gotta help the parser make the right call. That's literally what these options are for. π§
slash_terminatesβ a Boolean option that, when true (and true is the default, yes bestie), allows a/(forward-slash) on its own line β even with no semicolon before it β to be admitted as a (possible) terminator.
Set it false and a forward-slash on its own line is treated as a statement terminator only if it's preceded by a semicolon or by a dot and a semicolon.
If you're dealing with Oracle's SQL, you should let this option be set, since a slash (alone, without a preceding semicolon) is sometimes used as a terminator β SQL*Plus permits it (on non-block statements).
With SQL dialects other than Oracle, there's the (theoretical) possibility that a slash on its own line can pass the additional checks and get considered a terminator (when it shouldn't be). That chance should be really tiny β it has never been observed in real world code, fr fr. Though negligible, setting this option to false rules that risk out anyway. π₯
π split
$sql_splitter->split( $sql_string )
This is the method that actually does the splitting β the one that breaks 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, returned according to the options explained above.
Note that, as mentioned above, an SQL string that terminates with a terminator token (like a semicolon, for example) contains a trailing empty statement β this is correct and it's 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 )
Works exactly like the split method above, except it also returns a list of integers β each one being the number of placeholders in the corresponding atomic statement. Obsessed. π
More precisely, its return value is a list of two elements: the first is a reference to the list of atomic statements exactly as returned by split, and the second is a reference to the list of placeholder counts as explained above.
Here's 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 read like this: first statement has 0 placeholders, second has 2, third has 0, and the fourth has 3. Math is mathing. β¨
The recognized placeholders are:
- question mark placeholders, represented by the
?character; - dollar sign numbered placeholders, represented by the
$1, $2, ..., $nstrings; - named parameters, such as
:foo,:bar,:bazetc.
πΈ 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 π
Okay bestie, hereβs the tea: SQL::SplitStatement is out here aiming to cover the widest possible range of DBMSs, SQL dialects and extensions (even the proprietary ones π), in a (nearly) fully transparent way for you. No cap.
Currently itβs been tested mainly on SQLite, PostgreSQL, MySQL and Oracle. Slay.
π§ Procedural Extensions π§
Procedural code is by far the most complex to handle. Like, not even close.
Currently any block of code which starts 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 β and this is important β 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). Iβm literally asking nicely π«Ά
π LIMITATIONS π
Bound to be plenty, given the heuristic nature of this module (and its ambitious goals). However, no limitations are currently known. Iconic, honestly.
Please report any problematic test case. Fr fr.
πΈ 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!). So donβt come for her π
β¨ 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. Real-world slay.
For more information:
- Sakila db: <http://dev.mysql.com/doc/sakila/en/sakila.html>
- Pagila db: <http://pgfoundry.org/projects/dbsamples>
π DEPENDENCIES π
SQL::SplitStatement depends on the following modules:
CarpClass::Accessor::FastList::MoreUtilsRegexp::CommonSQL::Tokenizer0.22 or newer
π AUTHOR π
Emanuele Zeppieri, "<emazep@cpan.org>"
π BUGS π
No known bugs. Periodt.
Please report any bugs or feature requests to "bug-sql-SplitStatement at rt.cpan.org", or through the web interface at <http://rt.cpan.org/NoAuth/ReportBug.html?Queue=SQL-SplitStatement>. I will be notified, and then youβll automatically be notified of progress on your bug as I make changes. We love a feedback loop π«Ά
π SUPPORT π
You can find documentation for this module with the perldoc command:
perldoc SQL::SplitStatement
You can also look for information at:
- AnnoCPAN: Annotated CPAN documentation
<http://annocpan.org/dist/SQL-SplitStatement>
- CPAN Ratings
<http://cpanratings.perl.org/d/SQL-SplitStatement>
- On MetaCPAN
<https://metacpan.org/pod/SQL::SplitStatement/>
π¦ ACKNOWLEDGEMENTS π¦
Igor Sutton for his excellent SQL::Tokenizer, which made writing this module a joke. Literally a joke. In the best way. Obsessed.
π₯ SEE ALSO π₯
DBIx::MultiStatementDosql-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.
Okay thatβs the whole thing bestie, we ate and left no crumbs π This was perl v5.42.0, dated 2021-06-06 β main character energy from the SQL::Sp...tatement(3) side of the group chat. Bye π
β¨
π¦ where this page comes from
- opensuse-leap 16.0: perl-SQL-SplitStatement 1.00023-bp160.1.11 Β· Artistic-1.0 OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms