File Coverage

blib/lib/SQL/DBx/Deploy.pm
Criterion Covered Total %
statement 156 181 86.1
branch 42 70 60.0
condition 11 21 52.3
subroutine 18 20 90.0
pod 7 7 100.0
total 234 299 78.2


line stmt bran cond sub pod time code
1             package SQL::DBx::Deploy;
2 2     2   3514 use strict;
  2         5  
  2         82  
3 2     2   10 use warnings;
  2         3  
  2         58  
4 2     2   1123 use Moo::Role;
  2         15032  
  2         10  
5 2     2   663 use Log::Any qw/$log/;
  2         3  
  2         17  
6 2     2   140 use Carp qw/croak carp confess/;
  2         4  
  2         153  
7 2     2   1271 use File::ShareDir qw/dist_dir/;
  2         11138  
  2         153  
8 2     2   1304 use File::Slurp qw/read_file/;
  2         19819  
  2         137  
9 2     2   17 use File::Temp;
  2         2  
  2         143  
10 2     2   22 use Path::Class;
  2         3  
  2         4434  
11              
12             our $VERSION = '0.971.2';
13              
14             sub last_deploy_id {
15 13     13 1 3703 my $self = shift;
16 13   100     90 my $app = shift || 'default';
17 13         111 my $dbh = $self->conn->dbh;
18              
19 13         1271 my $sth = $dbh->table_info( '%', '%', '_deploy' );
20 13 100       5768 return 0 unless ( @{ $sth->fetchall_arrayref } );
  13         360  
21              
22 11         124 return $dbh->selectrow_array(
23             'SELECT COALESCE(MAX(seq),0) FROM _deploy WHERE app=?',
24             undef, $app );
25             }
26              
27             sub _load_file {
28 24     24   43 my $file = shift;
29 24         76 my $type = lc $file;
30              
31 24         723 $log->debug( '_load_file(' . $file . ')' );
32 24 50       729 confess "fatal: missing extension/type: $file\n"
33             unless $type =~ s/.*\.(.+)$/$1/;
34              
35 24         114 my $input = read_file $file;
36 24         4524 my $end = '';
37 24         37 my $item = '';
38 24         32 my @items;
39              
40 24 100       67 if ( $type eq 'sql' ) {
    50          
41              
42 18         50 $input =~ s/^\s*--.*\n//gm;
43 18         42 $input =~ s!/\*.*?\*/!!gsm;
44              
45 18         142 while ( $input =~ s/(.*\n)// ) {
46 237         316 my $try = $1;
47              
48 237 100       647 if ($end) {
    100          
    50          
    100          
49 70 100       326 if ( $try =~ m/$end/ ) {
50 7         13 $item .= $try;
51              
52 7 50       24 if ( $try =~ m/;/ ) {
53 7         25 $item =~ s/(^[\s\n]+)|(\s\n]+$)//;
54 7         17 push( @items, { sql => $item } );
55 7         14 $item = '';
56             }
57              
58 7         24 $end = '';
59             }
60             else {
61 63         198 $item .= $try;
62             }
63              
64             }
65             elsif ( $try =~ m/;/ ) {
66 46         54 $item .= $try;
67 46         453 $item =~ s/(^[\s\n]+)|(\s\n]+$)//;
68 46         112 push( @items, { sql => $item } );
69 46         163 $item = '';
70             }
71             elsif ( $try =~ m/^\s*CREATE( OR REPLACE)? FUNCTION.*AS (\S*)/i ) {
72 0         0 $end = $2;
73 0         0 $end =~ s/\$/\\\$/g;
74 0         0 $item .= $try;
75             }
76             elsif ( $try =~ m/^\s*CREATE TRIGGER/i ) {
77 7         35 $end = qr/(EXECUTE PROCEDURE)|(^END)/i;
78 7         26 $item .= $try;
79             }
80             else {
81 114         405 $item .= $try;
82             }
83             }
84             }
85             elsif ( $type eq 'pl' ) {
86 6         15 push( @items, { $type => $input } );
87             }
88             else {
89 0         0 die "Cannot load file of type '$type': $file";
90             }
91              
92 24         113 $log->debug( scalar @items . ' statements' );
93 24         116 return @items;
94             }
95              
96             sub _run_cmds {
97 10     10   17 my $self = shift;
98 10         15 my $ref = shift;
99              
100 10         69 my $dbh = $self->conn->dbh;
101              
102 10         3383 $log->debug( 'running ' . scalar @$ref . ' statements' );
103 10         29 my $i = 1;
104              
105 10         27 foreach my $cmd (@$ref) {
106 38 50       118 if ( exists $cmd->{sql} ) {
    0          
107 38         250 $log->debug( "-- _run_cmd $i\n" . $cmd->{sql} );
108 38         102 eval { $dbh->do( $cmd->{sql} ) };
  38         236  
109 38 50       396364 die $cmd->{sql} . "\n" . $@ if $@;
110             }
111             elsif ( exists $cmd->{pl} ) {
112 0         0 $log->debug( "-- _run_cmd\n" . $cmd->{pl} );
113 0         0 my $tmp = File::Temp->new;
114 0         0 print $tmp $cmd->{pl};
115 0 0       0 system( $^X, $tmp->filename ) == 0 or die "system failed";
116             }
117             else {
118 0         0 confess "Missing 'sql' or 'pl' key";
119             }
120              
121 38         103 $i++;
122             }
123              
124 10         229 return scalar @$ref;
125             }
126              
127             sub run_file {
128 0     0 1 0 my $self = shift;
129 0         0 my $file = shift;
130              
131 0         0 $log->debug("run_file($file)");
132 0         0 $self->_run_cmds( _load_file($file) );
133             }
134              
135             sub run_dir {
136 10     10 1 429 my $self = shift;
137 10   33     38 my $dir = dir(shift) || confess 'deploy_dir($dir)';
138              
139 10 50       406 confess "directory not found: $dir" unless -d $dir;
140 10         302 $log->debug("run_dir($dir)");
141              
142 10         188 my @files;
143 10         51 while ( my $file = $dir->next ) {
144 30 100 66     7478 push( @files, $file )
145             if $file =~ m/.+\.((sql)|(pl))$/ and -f $file;
146             }
147              
148 10         40 my @items =
149 0         0 map { _load_file($_) }
150 10         955 sort { $a->stringify cmp $b->stringify } @files;
151              
152 10         38 $self->_run_cmds( \@items );
153             }
154              
155             sub _setup_deploy {
156 7     7   16 my $self = shift;
157              
158 7         21 $log->debug("_setup_deploy");
159              
160             # The lib ("prove -Ilib t/*") case:
161 7         48 my $dir1 =
162             file(__FILE__)
163             ->parent->parent->parent->parent->subdir( 'share', $self->dbd );
164              
165             # The blib ("make test") case
166 7         2882 my $dir2 =
167             file(__FILE__)
168             ->parent->parent->parent->parent->parent->subdir( 'share', $self->dbd );
169              
170 7 50       2732 if ( -d $dir1 ) {
    50          
171 0 0       0 $self->run_dir( $dir1->subdir('deploy') )
172             || die "Failed to run $dir1";
173             }
174             elsif ( -d $dir2 ) {
175 7 50       397 $self->run_dir( $dir2->subdir('deploy') )
176             || die "Failed to run $dir1";
177             }
178             else {
179             # The "installed" case
180 0         0 my $distdir = dir( dist_dir('SQL-DB'), $self->dbd, 'deploy' );
181 0 0       0 $self->run_dir($distdir) || die "Failed to run $distdir";
182             }
183             }
184              
185             sub deploy {
186 0     0 1 0 my $self = shift;
187 0         0 my $ref = shift;
188 0   0     0 my $app = shift || 'default';
189              
190 0         0 $log->debug("deploy($app)");
191 0         0 $self->_setup_deploy;
192 0         0 $self->_deploy( $ref, $app );
193             }
194              
195             sub _deploy {
196 7     7   12 my $self = shift;
197 7         9 my $ref = shift;
198 7   50     50 my $app = shift || 'default';
199              
200 7 50       32 confess 'deploy(ARRAYREF)' unless ref $ref eq 'ARRAY';
201              
202 7         52 my $dbh = $self->conn->dbh;
203 7         673 my @current =
204             $dbh->selectrow_array( 'SELECT COUNT(app) from _deploy WHERE app=?',
205             undef, $app );
206              
207 7 100       4323 unless ( $current[0] ) {
208 3         30 $dbh->do( '
209             INSERT INTO _deploy(app)
210             VALUES(?)
211             ', undef, $app );
212             }
213              
214 7         43403 my $latest_change_id = $self->last_deploy_id($app);
215 7         1186 $log->debug( 'Current Change ID:', $latest_change_id );
216 7         34 $log->debug( 'Requested Change ID:', scalar @$ref );
217              
218 7 50       33 die "Requested Change ID is in the past!" if @$ref < $latest_change_id;
219              
220 7         13 my $count = 0;
221 7         32 foreach my $cmd (@$ref) {
222 21         36 $count++;
223 21 100       99 next unless ( $count > $latest_change_id );
224              
225 11 50 66     101 exists $cmd->{sql}
226             || exists $cmd->{pl}
227             || confess "Missing 'sql' or 'pl' key for id " . $count;
228              
229 11 100       44 if ( exists $cmd->{sql} ) {
230 7         65 $log->debug( "-- change #$count\n" . $cmd->{sql} );
231 7         23 eval { $dbh->do( $cmd->{sql} ) };
  7         62  
232 7 50       134902 die $cmd->{sql} . "\n" . $@ if $@;
233 7         112 $dbh->do( "
234             UPDATE
235             _deploy
236             SET
237             type = ?,
238             data = ?
239             WHERE
240             app = ?
241             ",
242             undef, 'sql', $cmd->{sql}, $app );
243             }
244              
245 11 100       152134 if ( exists $cmd->{pl} ) {
246 4         52 $log->debug( "# change #$count\n" . $cmd->{pl} );
247 4         79 my $tmp = File::Temp->new;
248 4         2750 print $tmp $cmd->{pl};
249              
250             # TODO stop and restart the transaction (if any) around
251             # this
252 4 50       31 system( $^X, $tmp->filename ) == 0 or die "system failed";
253 4         39776 $dbh->do( "
254             UPDATE
255             _deploy
256             SET
257             type = ?,
258             data = ?
259             WHERE
260             app = ?
261             ",
262             undef, 'pl', $cmd->{pl}, $app );
263             }
264             }
265 7         76010 $log->debug( 'Deployed to Change ID:', $count );
266 7         244 return ( $latest_change_id, $count );
267             }
268              
269             sub deploy_file {
270 2     2 1 1905 my $self = shift;
271 2         7 my $file = shift;
272 2         3 my $app = shift;
273 2         16 $log->debug("deploy_file($file)");
274 2         115 $self->_setup_deploy;
275 2         20 $self->_deploy( [ _load_file($file) ], $app );
276             }
277              
278             sub deploy_dir {
279 5     5 1 2878 my $self = shift;
280 5   33     31 my $dir = dir(shift) || confess 'deploy_dir($dir)';
281 5         436 my $app = shift;
282              
283 5 50       22 confess "directory not found: $dir" unless -d $dir;
284 5         229 $log->debug("deploy_dir($dir)");
285 5         157 $self->_setup_deploy;
286              
287 5         44 my @files;
288 5         27 while ( my $file = $dir->next ) {
289 22 100 66     5085 push( @files, $file )
290             if $file =~ m/.+\.((sql)|(pl))$/ and -f $file;
291             }
292              
293 12         216 my @items =
294 8         118 map { _load_file($_) }
295 5         388 sort { $a->stringify cmp $b->stringify } @files;
296              
297 5         30 $self->_deploy( \@items, $app );
298             }
299              
300             sub deployed_table_info {
301 1     1 1 905 my $self = shift;
302 1         4 my $dbschema = shift;
303              
304 1 50       6 if ( !$dbschema ) {
305 1 50       12 if ( $self->dbd eq 'SQLite' ) {
    0          
306 1         3 $dbschema = 'main';
307             }
308             elsif ( $self->dbd eq 'Pg' ) {
309 0         0 $dbschema = 'public';
310             }
311             else {
312 0         0 $dbschema = '%';
313             }
314             }
315              
316 1         15 my $sth = $self->conn->dbh->table_info( '%', $dbschema, '%',
317             "'TABLE','VIEW','GLOBAL TEMPORARY','LOCAL TEMPORARY'" );
318              
319 1         679 my %tables;
320              
321 1         17 while ( my $table = $sth->fetchrow_arrayref ) {
322 4         347 my $sth2 = $self->conn->dbh->column_info( '%', '%', $table->[2], '%' );
323 4         5345 $tables{ $table->[2] } = $sth2->fetchall_arrayref;
324             }
325              
326 1         74 return \%tables;
327             }
328              
329             Moo::Role->apply_role_to_package( 'SQL::DB', __PACKAGE__ );
330              
331             1;