File Coverage

blib/lib/SQL/DBx/Deploy.pm
Criterion Covered Total %
statement 154 179 86.0
branch 41 68 60.2
condition 11 21 52.3
subroutine 18 20 90.0
pod 7 7 100.0
total 231 295 78.3


line stmt bran cond sub pod time code
1             package SQL::DBx::Deploy;
2 2     2   3860 use strict;
  2         4  
  2         81  
3 2     2   13 use warnings;
  2         3  
  2         58  
4 2     2   1911 use Moo::Role;
  2         18262  
  2         12  
5 2     2   818 use Log::Any qw/$log/;
  2         6  
  2         18  
6 2     2   151 use Carp qw/croak carp confess/;
  2         5  
  2         166  
7 2     2   2258 use File::ShareDir qw/dist_dir/;
  2         13383  
  2         171  
8 2     2   2747 use File::Slurp qw/read_file/;
  2         25648  
  2         180  
9 2     2   25 use File::Temp;
  2         5  
  2         206  
10 2     2   14 use Path::Class;
  2         5  
  2         5688  
11              
12             our $VERSION = '0.971.0';
13              
14             sub last_deploy_id {
15 13     13 1 5986 my $self = shift;
16 13   100     125 my $app = shift || 'default';
17 13         155 my $dbh = $self->conn->dbh;
18              
19 13         1647 my $sth = $dbh->table_info( '%', '%', '_deploy' );
20 13 100       8351 return 0 unless ( @{ $sth->fetchall_arrayref } );
  13         491  
21              
22 11         160 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   42 my $file = shift;
29 24         156 my $type = lc $file;
30              
31 24         1133 $log->debug( '_load_file(' . $file . ')' );
32 24 50       1038 confess "fatal: missing extension/type: $file\n"
33             unless $type =~ s/.*\.(.+)$/$1/;
34              
35 24         157 my $input = read_file $file;
36 24         9762 my $end = '';
37 24         60 my $item = '';
38 24         49 my @items;
39              
40 24 100       87 if ( $type eq 'sql' ) {
    50          
41              
42 18         71 $input =~ s/^\s*--.*\n//gm;
43 18         42 $input =~ s!/\*.*?\*/!!gsm;
44              
45 18         145 while ( $input =~ s/(.*\n)// ) {
46 234         507 my $try = $1;
47              
48 234 100       1197 if ($end) {
    100          
    50          
    100          
49 70 100       454 if ( $try =~ m/$end/ ) {
50 7         15 $item .= $try;
51              
52 7 50       36 if ( $try =~ m/;/ ) {
53 7         46 $item =~ s/(^[\s\n]+)|(\s\n]+$)//;
54 7         27 push( @items, { sql => $item } );
55 7         25 $item = '';
56             }
57              
58 7         55 $end = '';
59             }
60             else {
61 63         325 $item .= $try;
62             }
63              
64             }
65             elsif ( $try =~ m/;/ ) {
66 43         85 $item .= $try;
67 43         557 $item =~ s/(^[\s\n]+)|(\s\n]+$)//;
68 43         126 push( @items, { sql => $item } );
69 43         191 $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         58 $end = qr/(EXECUTE PROCEDURE)|(^END)/i;
78 7         47 $item .= $try;
79             }
80             else {
81 114         606 $item .= $try;
82             }
83             }
84             }
85             elsif ( $type eq 'pl' ) {
86 6         24 push( @items, { $type => $input } );
87             }
88             else {
89 0         0 die "Cannot load file of type '$type': $file";
90             }
91              
92 24         134 $log->debug( scalar @items . ' statements' );
93 24         163 return @items;
94             }
95              
96             sub _run_cmds {
97 10     10   23 my $self = shift;
98 10         57 my $ref = shift;
99              
100 10         107 my $dbh = $self->conn->dbh;
101              
102 10         4690 $log->debug( 'running ' . scalar @$ref . ' statements' );
103 10         47 my $i = 1;
104              
105 10         354 foreach my $cmd (@$ref) {
106 35 50       185 if ( exists $cmd->{sql} ) {
    0          
107 35         357 $log->debug( "-- _run_cmd $i\n" . $cmd->{sql} );
108 35         142 eval { $dbh->do( $cmd->{sql} ) };
  35         668  
109 35 50       12052845 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 35         150 $i++;
122             }
123              
124 10         344 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 639 my $self = shift;
137 10   33     48 my $dir = dir(shift) || confess 'deploy_dir($dir)';
138              
139 10 50       593 confess "directory not found: $dir" unless -d $dir;
140 10         448 $log->debug("run_dir($dir)");
141              
142 10         232 my @files;
143 10         77 while ( my $file = $dir->next ) {
144 30 100 66     11399 push( @files, $file )
145             if $file =~ m/.+\.((sql)|(pl))$/ and -f $file;
146             }
147              
148 10         65 my @items =
149 0         0 map { _load_file($_) }
150 10         1204 sort { $a->stringify cmp $b->stringify } @files;
151              
152 10         67 $self->_run_cmds( \@items );
153             }
154              
155             sub _setup_deploy {
156 7     7   16 my $self = shift;
157              
158 7         28 $log->debug("_setup_deploy");
159              
160             # The lib ("prove -Ilib t/*") case:
161 7         56 my $dir1 =
162             file(__FILE__)
163             ->parent->parent->parent->parent->subdir( 'share', $self->dbd );
164              
165             # The blib ("make test") case
166 7         4055 my $dir2 =
167             file(__FILE__)
168             ->parent->parent->parent->parent->parent->subdir( 'share', $self->dbd );
169              
170 7 50       4090 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       644 $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   17 my $self = shift;
197 7         15 my $ref = shift;
198 7   50     67 my $app = shift || 'default';
199              
200 7 50       32 confess 'deploy(ARRAYREF)' unless ref $ref eq 'ARRAY';
201              
202 7         109 my $dbh = $self->conn->dbh;
203 7         1264 my @current =
204             $dbh->selectrow_array( 'SELECT COUNT(app) from _deploy WHERE app=?',
205             undef, $app );
206              
207 7 100       2689 unless ( $current[0] ) {
208 3         34 $dbh->do( '
209             INSERT INTO _deploy(app)
210             VALUES(?)
211             ', undef, $app );
212             }
213              
214 7         480398 my $latest_change_id = $self->last_deploy_id($app);
215 7         1573 $log->debug( 'Latest Change ID:', $latest_change_id );
216              
217 7         31 my $count = 0;
218 7         40 foreach my $cmd (@$ref) {
219 21         46 $count++;
220 21 100       87 next unless ( $count > $latest_change_id );
221              
222 11 50 66     97 exists $cmd->{sql}
223             || exists $cmd->{pl}
224             || confess "Missing 'sql' or 'pl' key for id " . $count;
225              
226 11 100       56 if ( exists $cmd->{sql} ) {
227 7         88 $log->debug( "-- change #$count\n" . $cmd->{sql} );
228 7         32 eval { $dbh->do( $cmd->{sql} ) };
  7         153  
229 7 50       539045 die $cmd->{sql} . "\n" . $@ if $@;
230 7         113 $dbh->do( "
231             UPDATE
232             _deploy
233             SET
234             type = ?,
235             data = ?
236             WHERE
237             app = ?
238             ",
239             undef, 'sql', $cmd->{sql}, $app );
240             }
241              
242 11 100       446227 if ( exists $cmd->{pl} ) {
243 4         56 $log->debug( "# change #$count\n" . $cmd->{pl} );
244 4         120 my $tmp = File::Temp->new;
245 4         14265 print $tmp $cmd->{pl};
246              
247             # TODO stop and restart the transaction (if any) around
248             # this
249 4 50       27 system( $^X, $tmp->filename ) == 0 or die "system failed";
250 4         113621 $dbh->do( "
251             UPDATE
252             _deploy
253             SET
254             type = ?,
255             data = ?
256             WHERE
257             app = ?
258             ",
259             undef, 'pl', $cmd->{pl}, $app );
260             }
261             }
262 7         209145 $log->debug( 'Deployed to Change ID:', $count );
263 7         714 return ( $latest_change_id, $count );
264             }
265              
266             sub deploy_file {
267 2     2 1 2034 my $self = shift;
268 2         5 my $file = shift;
269 2         5 my $app = shift;
270 2         29 $log->debug("deploy_file($file)");
271 2         130 $self->_setup_deploy;
272 2         30 $self->_deploy( [ _load_file($file) ], $app );
273             }
274              
275             sub deploy_dir {
276 5     5 1 5111 my $self = shift;
277 5   33     54 my $dir = dir(shift) || confess 'deploy_dir($dir)';
278 5         663 my $app = shift;
279              
280 5 50       27 confess "directory not found: $dir" unless -d $dir;
281 5         344 $log->debug("deploy_dir($dir)");
282 5         163 $self->_setup_deploy;
283              
284 5         51 my @files;
285 5         36 while ( my $file = $dir->next ) {
286 22 100 66     6748 push( @files, $file )
287             if $file =~ m/.+\.((sql)|(pl))$/ and -f $file;
288             }
289              
290 12         380 my @items =
291 8         171 map { _load_file($_) }
292 5         598 sort { $a->stringify cmp $b->stringify } @files;
293              
294 5         74 $self->_deploy( \@items, $app );
295             }
296              
297             sub deployed_table_info {
298 1     1 1 1576 my $self = shift;
299 1         5 my $dbschema = shift;
300              
301 1 50       10 if ( !$dbschema ) {
302 1 50       18 if ( $self->dbd eq 'SQLite' ) {
    0          
303 1         6 $dbschema = 'main';
304             }
305             elsif ( $self->dbd eq 'Pg' ) {
306 0         0 $dbschema = 'public';
307             }
308             else {
309 0         0 $dbschema = '%';
310             }
311             }
312              
313 1         26 my $sth = $self->conn->dbh->table_info( '%', $dbschema, '%',
314             "'TABLE','VIEW','GLOBAL TEMPORARY','LOCAL TEMPORARY'" );
315              
316 1         1778 my %tables;
317              
318 1         32 while ( my $table = $sth->fetchrow_arrayref ) {
319 4         445 my $sth2 = $self->conn->dbh->column_info( '%', '%', $table->[2], '%' );
320 4         13055 $tables{ $table->[2] } = $sth2->fetchall_arrayref;
321             }
322              
323 1         212 return \%tables;
324             }
325              
326             Moo::Role->apply_role_to_package( 'SQL::DB', __PACKAGE__ );
327              
328             1;