File Coverage

inc/SQL/SplitStatement.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #line 1
2             package SQL::SplitStatement;
3 7     7   21276  
  0            
  0            
4             use Moose;
5              
6             our $VERSION = '0.05003';
7             $VERSION = eval $VERSION;
8              
9             use SQL::Tokenizer qw(tokenize_sql);
10             use List::MoreUtils qw(firstval each_array);
11              
12             use constant {
13             SEMICOLON => ';',
14             FORWARD_SLASH => '/',
15             PLACEHOLDER => '?'
16             };
17              
18             my $transaction_re = qr[^(?:
19             ;
20             |/
21             |WORK
22             |TRAN
23             |TRANSACTION
24             |ISOLATION
25             |READ
26             )$]xi;
27             my $procedural_END_re = qr/^(?:IF|LOOP)$/i;
28             my $terminator_re = qr[;|/|;\s+/];
29             my $begin_comment_re = qr/^(?:--|\/\*)/;
30             my $DECLARE_re = qr/^(?:DECLARE|PROCEDURE|FUNCTION)$/i;
31             my $PACKAGE_re = qr/^PACKAGE$/i;
32             my $BEGIN_re = qr/^BEGIN$/i;
33             my $END_re = qr/^END$/i;
34              
35             my $CREATE_ALTER_re = qr/^(?:CREATE|ALTER)$/i;
36             my $OR_REPLACE_re = qr/^(?:OR|REPLACE)$/i;
37             my $OR_REPLACE_PACKAGE_BODY_re = qr/^(?:OR|REPLACE|PACKAGE|BODY)$/i;
38              
39             my $BODY_re = qr/^BODY$/i;
40              
41             has [ qw(
42             keep_terminator
43             keep_extra_spaces
44             keep_empty_statements
45             keep_comments
46             )] => (
47             is => 'rw',
48             isa => 'Bool',
49             default => undef
50             );
51              
52             # TODO: DEPRECATED, to remove!
53             has 'keep_semicolon' => (
54             is => 'rw',
55             isa => 'Bool',
56             default => undef,
57             trigger => \&_set_keep_terminator
58             );
59              
60             sub _set_keep_terminator {
61             my ($self, $value) = @_;
62             $self->keep_terminator($value)
63             }
64              
65             sub split {
66             my ($self, $code) = @_;
67             my ( $statements, undef ) = $self->split_with_placeholders($code);
68             return @$statements
69             }
70              
71             sub split_with_placeholders {
72             my ($self, $code) = @_;
73            
74             my $statement = '';
75             my @statements = ();
76             my $inside_block = 0;
77             my $inside_create_alter = 0;
78             my $inside_declare = 0;
79             my $inside_package = 0;
80             my $package_name = '';
81             my $statement_placeholders = 0;
82             my @placeholders = ();
83            
84             my @tokens = tokenize_sql($code);
85            
86             while ( defined( my $token = shift @tokens ) ) {
87             $statement .= $token
88             unless $self->_is_comment($token) && ! $self->keep_comments;
89            
90             if ( $self->_is_BEGIN_of_block($token, \@tokens) ) {
91             $inside_block++;
92             $inside_declare = 0
93             }
94             elsif ( $token =~ $CREATE_ALTER_re ) {
95             $inside_create_alter = 1;
96            
97             my $next_token
98             = $self->_get_next_significant_token(\@tokens, $OR_REPLACE_re);
99            
100             if ( $next_token =~ $PACKAGE_re ) {
101             $inside_package = 1;
102             $package_name = $self->_get_package_name(\@tokens)
103             }
104            
105             }
106             elsif ( $token =~ $DECLARE_re ) {
107             $inside_declare = 1
108             }
109             elsif ( my $name = $self->_is_END_of_block($token, \@tokens) ) {
110             $inside_block-- if $inside_block;
111             if ($name eq $package_name) {
112             $inside_package = 0;
113             $package_name = ''
114             }
115             }
116             elsif ( $token eq PLACEHOLDER ) {
117             $statement_placeholders++
118             }
119             elsif ( $self->_is_terminator($token, \@tokens) ) {
120             $inside_create_alter = 0
121             }
122            
123             next if ! $self->_is_terminator($token, \@tokens)
124             || $inside_block || $inside_declare || $inside_package;
125            
126             push @statements, $statement;
127             push @placeholders, $statement_placeholders;
128             $statement = '';
129             $statement_placeholders = 0;
130             }
131             push @statements, $statement;
132             push @placeholders, $statement_placeholders;
133            
134             my ( @filtered_statements, @filtered_placeholders );
135            
136             if ( $self->keep_empty_statements ) {
137             @filtered_statements = @statements;
138             @filtered_placeholders = @placeholders
139             } else {
140             my $sp = each_array( @statements, @placeholders );
141             while ( my ($statement, $placeholder_num ) = $sp->() ) {
142             unless ( $statement =~ /^\s*$terminator_re?\s*$/ ) {
143             push @filtered_statements , $statement;
144             push @filtered_placeholders, $placeholder_num
145             }
146             }
147             }
148            
149             unless ( $self->keep_terminator ) {
150             s/$terminator_re$// foreach @filtered_statements
151             }
152            
153             unless ( $self->keep_extra_spaces ) {
154             s/^\s+|\s+$//g foreach @filtered_statements
155             }
156            
157             return ( \@filtered_statements, \@filtered_placeholders )
158             }
159              
160             sub _is_comment {
161             my ($self, $token) = @_;
162             return $token =~ $begin_comment_re
163             }
164              
165             sub _is_BEGIN_of_block {
166             my ($self, $token, $tokens) = @_;
167             return
168             $token =~ $BEGIN_re
169             && $self->_get_next_significant_token($tokens) !~ $transaction_re
170             }
171              
172             sub _is_END_of_block {
173             my ($self, $token, $tokens) = @_;
174             my $next_token = $self->_get_next_significant_token($tokens);
175            
176             # Return possible package name
177             return $next_token || 1
178             if $token =~ $END_re && (
179             ! defined($next_token)
180             || $next_token !~ $procedural_END_re
181             );
182            
183             return
184             }
185              
186             sub _get_package_name {
187             my ($self, $tokens) = @_;
188             return $self->_get_next_significant_token(
189             $tokens, $OR_REPLACE_PACKAGE_BODY_re
190             )
191             }
192              
193             sub _is_terminator {
194             my ($self, $token, $tokens) = @_;
195            
196             return if $token ne FORWARD_SLASH && $token ne SEMICOLON;
197             return 1 if $token eq FORWARD_SLASH;
198            
199             # $token eq SEMICOLON
200             my $next_token = $self->_get_next_significant_token($tokens);
201             return 1 if ! defined($next_token) || $next_token ne FORWARD_SLASH;
202             # $next_token eq FORWARD_SLASH
203             return
204             }
205              
206             sub _get_next_significant_token {
207             my ($self, $tokens, $skiptoken_re) = @_;
208             return $skiptoken_re
209             ? firstval {
210             /\S/ && ! $self->_is_comment($_) && ! /$skiptoken_re/
211             } @$tokens
212             : firstval {
213             /\S/ && ! $self->_is_comment($_)
214             } @$tokens
215             }
216              
217             no Moose;
218             __PACKAGE__->meta->make_immutable;
219              
220             1;
221              
222             __END__