File Coverage

blib/lib/DBIx/ThinSQL/Deploy.pm
Criterion Covered Total %
statement 154 181 85.0
branch 37 62 59.6
condition 9 21 42.8
subroutine 17 19 89.4
pod 10 10 100.0
total 227 293 77.4


line stmt bran cond sub pod time code
1             package DBIx::ThinSQL::Deploy;
2 2     2   386 use strict;
  2         3  
  2         44  
3 2     2   5 use warnings;
  2         2  
  2         44  
4 2     2   4 use Log::Any qw/$log/;
  2         3  
  2         11  
5 2     2   259 use Carp qw/croak carp confess/;
  2         2  
  2         92  
6 2     2   653 use Path::Tiny;
  2         7478  
  2         2943  
7              
8             our $VERSION = '0.0.45_2';
9              
10             sub _split_sql {
11 41     41   6945 my $input = shift;
12 41         70 my $end = '';
13 41         44 my $item = '';
14 41         39 my @items;
15              
16 41         122 $input =~ s/^\s*--.*\n//gm;
17 41         91 $input =~ s!/\*.*?\*/!!gsm;
18              
19 41         291 while ( $input =~ s/(.*\n)// ) {
20 325         457 my $try = $1;
21              
22 325 100       769 if ($end) {
    100          
    50          
    100          
23 60 100       230 if ( $try =~ m/$end/ ) {
24 6         12 $item .= $try;
25              
26 6 50       25 if ( $try =~ m/;/ ) {
27 6         32 $item =~ s/(^[\s\n]+)|(\s\n]+$)//;
28 6         12 push( @items, { sql => $item } );
29 6         11 $item = '';
30             }
31              
32 6         21 $end = '';
33             }
34             else {
35 54         170 $item .= $try;
36             }
37              
38             }
39             elsif ( $try =~ m/;/ ) {
40 107         99 $item .= $try;
41 107         657 $item =~ s/(^[\s\n]+)|(\s\n]+$)//;
42 107         205 push( @items, { sql => $item } );
43 107         363 $item = '';
44             }
45             elsif ( $try =~ m/^\s*CREATE( OR REPLACE)? FUNCTION.*AS (\S*)/i ) {
46 0         0 $end = $2;
47 0         0 $end =~ s/\$/\\\$/g;
48 0         0 $item .= $try;
49             }
50             elsif ( $try =~ m/^\s*CREATE TRIGGER/i ) {
51 6         30 $end = qr/(EXECUTE PROCEDURE)|(^END)/i;
52 6         25 $item .= $try;
53             }
54             else {
55 152         597 $item .= $try;
56             }
57             }
58              
59 41         77 foreach my $item (@items) {
60 113         382 $item->{sql} =~ s/;[\s\n]*$//;
61             }
62 41         149 return \@items;
63             }
64              
65             sub _load_file {
66 46     46   90 my $file = path(shift);
67 46         483 my $type = lc $file;
68              
69 46         208 $log->debug( '_load_file(' . $file . ')' );
70              
71 46 50       903 confess "fatal: missing extension/type: $file\n"
72             unless $type =~ s/.*\.(.+)$/$1/;
73              
74 46 100       139 if ( $type eq 'sql' ) {
    50          
75              
76             # TODO add file name to hashrefs
77 41         154 return _split_sql( $file->slurp_utf8 );
78             }
79             elsif ( $type eq 'pl' ) {
80 5         14 return [ { $type => $file->slurp_utf8 } ];
81             }
82              
83 0         0 die "Cannot load file of type '$type': $file";
84             }
85              
86             sub run_arrayref {
87 34     34 1 41 my $self = shift;
88 34         24 my $ref = shift;
89              
90 34         312 local $self->{ShowErrorStatement} = 1;
91 34         491 local $self->{RaiseError} = 1;
92              
93 34         345 $log->debug( 'running ' . scalar @$ref . ' statements' );
94 34         407 my $i = 1;
95              
96 34         57 foreach my $cmd (@$ref) {
97 100 50       249 if ( exists $cmd->{sql} ) {
    0          
98 100         559 $self->do( $cmd->{sql} );
99             }
100             elsif ( exists $cmd->{pl} ) {
101 0         0 $log->debug( "-- _run_cmd\n" . $cmd->{pl} );
102 0         0 my $tmp = Path::Tiny->tempfile;
103 0         0 print $tmp $cmd->{pl};
104 0 0       0 system( $^X, $tmp->filename ) == 0 or die "system failed";
105             }
106             else {
107 0         0 confess "Missing 'sql' or 'pl' key";
108             }
109              
110 100         573247 $i++;
111             }
112              
113 34         484 return scalar @$ref;
114             }
115              
116             sub run_sql {
117 0     0 1 0 my $self = shift;
118 0         0 my $sql = shift;
119              
120 0         0 $log->debug("run_sql");
121 0         0 $self->run_arrayref( _split_sql($sql) );
122             }
123              
124             sub run_file {
125 34     34 1 700 my $self = shift;
126 34         34 my $file = shift;
127              
128 34         86 $log->debug("run_file($file)");
129 34         654 my $result = eval { $self->run_arrayref( _load_file($file) ) };
  34         88  
130 34 50       195 if ($@) {
131 0         0 die "$file\n" . $@;
132             }
133 34         290 return $result;
134             }
135              
136             sub run_dir {
137 6     6 1 748 my $self = shift;
138 6   33     14 my $dir = path(shift) || confess 'deploy_dir($dir)';
139              
140 6 50       73 confess "directory not found: $dir" unless -d $dir;
141 6         140 $log->debug("run_dir($dir)");
142              
143 6         85 my @files;
144 6         24 my $iter = $dir->iterator;
145 6         142 while ( my $file = $iter->() ) {
146 6 50 33     601 push( @files, $file )
147             if $file =~ m/.+\.((sql)|(pl))$/ and -f $file;
148             }
149              
150 6         313 $self->run_file($_) for sort { $a->stringify cmp $b->stringify } @files;
  0         0  
151             }
152              
153             sub _setup_deploy {
154 6     6   9 my $self = shift;
155 6         73 my $driver = $self->{Driver}->{Name};
156              
157 6         22 $log->debug("_setup_deploy");
158              
159 6 50       70 if ( defined &static::find ) {
160 0         0 my $src = 'auto/share/dist/DBIx-ThinSQL/Deploy/' . $driver . '.sql';
161 0 0       0 my $sql = static::find($src)
162             or croak 'Driver not supported for deploy: ' . $driver;
163 0         0 return $self->run_sql($sql);
164             }
165              
166 6         26 return $self->run_dir( $self->share_dir->child( 'Deploy', $driver ) );
167             }
168              
169             sub last_deploy_id {
170 12     12 1 3011 my $self = shift;
171 12   100     55 my $app = shift || 'default';
172              
173 12         94 my $sth = $self->table_info( '%', '%', '_deploy' );
174 12 100       3822 return 0 unless ( @{ $sth->fetchall_arrayref } );
  12         270  
175              
176 10         89 return $self->selectrow_array(
177             'SELECT COALESCE(MAX(seq),0) FROM _deploy WHERE app=?',
178             undef, $app );
179             }
180              
181             sub deploy_arrayref {
182 6     6 1 9 my $self = shift;
183 6         7 my $ref = shift;
184 6   50     41 my $app = shift || 'default';
185              
186 6 50       20 confess 'deploy(ARRAYREF)' unless ref $ref eq 'ARRAY';
187 6         58 local $self->{ShowErrorStatement} = 1;
188 6         114 local $self->{RaiseError} = 1;
189              
190 6         98 my @current =
191             $self->selectrow_array( 'SELECT COUNT(app) from _deploy WHERE app=?',
192             undef, $app );
193              
194 6 100       755 unless ( $current[0] ) {
195 2         15 $self->do( '
196             INSERT INTO _deploy(app)
197             VALUES(?)
198             ', undef, $app );
199             }
200              
201 6         6155 my $latest_change_id = $self->last_deploy_id($app);
202 6         776 $log->debug( 'Current Change ID:', $latest_change_id );
203 6         123 $log->debug( 'Requested Change ID:', scalar @$ref );
204              
205 6 50       88 die "Requested Change ID("
206             . ( scalar @$ref )
207             . ") is less than current: $latest_change_id"
208             if @$ref < $latest_change_id;
209              
210 6         9 my $count = 0;
211 6         17 foreach my $cmd (@$ref) {
212 18         42 $count++;
213 18 100       44 next unless ( $count > $latest_change_id );
214              
215             exists $cmd->{sql}
216             || exists $cmd->{pl}
217 8 50 66     69 || confess "Missing 'sql' or 'pl' key for id " . $count;
218              
219 8 100       20 if ( exists $cmd->{sql} ) {
220 5         32 $log->debug("-- change #$count\n");
221 5         109 $self->do( $cmd->{sql} );
222             $self->do( "
223             UPDATE
224             _deploy
225             SET
226             type = ?,
227             data = ?
228             WHERE
229             app = ?
230             ",
231 5         22398 undef, 'sql', $cmd->{sql}, $app );
232             }
233              
234 8 100       14545 if ( exists $cmd->{pl} ) {
235 3         20 $log->debug( "# change #$count\n" . $cmd->{pl} );
236 3         76 my $tmp = Path::Tiny->tempfile;
237 3         1732 $tmp->spew_utf8( $cmd->{pl} );
238              
239             # TODO stop and restart the transaction (if any) around
240             # this
241 3 50       23995 system( $^X, $tmp ) == 0 or die "system failed";
242             $self->do( "
243             UPDATE
244             _deploy
245             SET
246             type = ?,
247             data = ?
248             WHERE
249             app = ?
250             ",
251 3         113 undef, 'pl', $cmd->{pl}, $app );
252             }
253             }
254 6         11640 $log->debug( 'Deployed to Change ID:', $count );
255 6         336 return ( $latest_change_id, $count );
256             }
257              
258             sub deploy_sql {
259 0     0 1 0 my $self = shift;
260 0         0 my $sql = shift;
261 0   0     0 my $app = shift || 'default';
262              
263 0         0 $log->debug("deploy_sql($app)");
264 0         0 $self->_setup_deploy;
265 0         0 $self->deploy_arrayref( _split_sql($sql), $app );
266             }
267              
268             sub deploy_file {
269 2     2 1 1859 my $self = shift;
270 2         4 my $file = shift;
271 2         3 my $app = shift;
272 2         11 $log->debug("deploy_file($file)");
273 2         40 $self->_setup_deploy;
274 2         9 $self->deploy_arrayref( _load_file($file), $app );
275             }
276              
277             sub deploy_dir {
278 4     4 1 2696 my $self = shift;
279 4   33     18 my $dir = path(shift) || confess 'deploy_dir($dir)';
280 4         75 my $app = shift;
281              
282 4 50       13 confess "directory not found: $dir" unless -d $dir;
283 4         104 $log->debug("deploy_dir($dir)");
284 4         80 $self->_setup_deploy;
285              
286 4         9 my @files;
287 4         22 my $iter = $dir->iterator;
288 4         85 while ( my $file = $iter->() ) {
289 10 50 33     659 if ( $file =~ m/.+\.((sql)|(pl))$/ and -f $file ) {
290 10         298 push( @files, $file );
291             }
292             else {
293 0         0 warn "Cannot deploy file: $file";
294             }
295             }
296              
297 10         116 my @items = map { @{ _load_file($_) } }
  10         19  
298 4         106 sort { $a->stringify cmp $b->stringify } @files;
  7         31  
299              
300 4         427 $self->deploy_arrayref( \@items, $app );
301             }
302              
303             sub deployed_table_info {
304 1     1 1 867 my $self = shift;
305 1         4 my $dbschema = shift;
306 1         14 my $driver = $self->{Driver}->{Name};
307              
308 1 50       17 if ( !$dbschema ) {
309 1 50       5 if ( $driver eq 'SQLite' ) {
    0          
310 1         3 $dbschema = 'main';
311             }
312             elsif ( $driver eq 'Pg' ) {
313 0         0 $dbschema = 'public';
314             }
315             else {
316 0         0 $dbschema = '%';
317             }
318             }
319              
320 1         12 my $sth = $self->table_info( '%', $dbschema, '%',
321             "'TABLE','VIEW','GLOBAL TEMPORARY','LOCAL TEMPORARY'" );
322              
323 1         771 my %tables;
324              
325 1         40 while ( my $table = $sth->fetchrow_arrayref ) {
326 4         312 my $sth2 = $self->column_info( '%', '%', $table->[2], '%' );
327 4         4429 $tables{ $table->[2] } = $sth2->fetchall_arrayref;
328             }
329              
330 1         66 return \%tables;
331             }
332              
333             {
334 2     2   10 no strict 'refs';
  2         2  
  2         232  
335             *{'DBIx::ThinSQL::db::last_deploy_id'} = \&last_deploy_id;
336             *{'DBIx::ThinSQL::db::_split_sql'} = \&_split_sql;
337             *{'DBIx::ThinSQL::db::_load_file'} = \&_load_file;
338             *{'DBIx::ThinSQL::db::run_sql'} = \&run_sql;
339             *{'DBIx::ThinSQL::db::run_arrayref'} = \&run_arrayref;
340             *{'DBIx::ThinSQL::db::run_file'} = \&run_file;
341             *{'DBIx::ThinSQL::db::run_dir'} = \&run_dir;
342             *{'DBIx::ThinSQL::db::_setup_deploy'} = \&_setup_deploy;
343             *{'DBIx::ThinSQL::db::deploy_arrayref'} = \&deploy_arrayref;
344             *{'DBIx::ThinSQL::db::deploy_sql'} = \&deploy_sql;
345             *{'DBIx::ThinSQL::db::deploy_file'} = \&deploy_file;
346             *{'DBIx::ThinSQL::db::deploy_dir'} = \&deploy_dir;
347             *{'DBIx::ThinSQL::db::deployed_table_info'} = \&deployed_table_info;
348             }
349              
350             1;