File Coverage

blib/lib/DBIx/SchemaChecksum/App/ApplyChanges.pm
Criterion Covered Total %
statement 57 89 64.0
branch 21 40 52.5
condition n/a
subroutine 8 9 88.8
pod n/a
total 86 138 62.3


line stmt bran cond sub pod time code
1             package DBIx::SchemaChecksum::App::ApplyChanges;
2              
3             # ABSTRACT: Apply changes based on current checksum
4             our $VERSION = '1.103'; # VERSION
5              
6 2     2   355571 use 5.010;
  2         28  
7              
8 2     2   996 use MooseX::App::Command;
  2         2017301  
  2         10  
9             extends qw(DBIx::SchemaChecksum::App);
10 2     2   1942886 use IO::Prompt::Tiny qw(prompt);
  2         1318  
  2         162  
11 2     2   17 use Try::Tiny;
  2         5  
  2         2062  
12              
13             option '+sqlsnippetdir' => ( required => 1);
14             option 'dry_run' => ( is => 'rw', isa => 'Bool', default => 0, documentation=>'Only list changes, do not apply' );
15             has 'no_prompt' => ( is => 'rw', isa => 'Bool', default => 0, documentation=>'Do not prompt, just use defaults');
16              
17             sub run {
18 1     1   2770 my $self = shift;
19              
20 1         6 $self->apply_sql_snippets($self->checksum);
21             }
22              
23              
24             sub apply_sql_snippets {
25 6     6   2879 my ($self, $this_checksum ) = @_;
26 6         220 my $update_path = $self->_update_path;
27              
28             my $update = $update_path->{$this_checksum}
29 6 50       31 if ( exists $update_path->{$this_checksum} );
30              
31 6 50       22 unless ($update) {
32 0         0 foreach my $update_entry (values %{$update_path}) {
  0         0  
33 0         0 my $post_checksum_index = 0;
34 0         0 while (@{$update_entry} > $post_checksum_index) {
  0         0  
35 0 0       0 if ($update_entry->[$post_checksum_index] eq 'SAME_CHECKSUM') {
36 0         0 $post_checksum_index++;
37 0         0 next;
38             }
39 0 0       0 if ($update_entry->[$post_checksum_index+1] eq $this_checksum) {
40 0         0 say "db checksum $this_checksum matching ".$update_entry->[$post_checksum_index]->relative;
41 0         0 return;
42             }
43 0         0 $post_checksum_index += 2;
44             }
45             }
46 0         0 say "No update found that's based on $this_checksum.";
47 0         0 return;
48             }
49              
50 6 100       46 if ( $update->[0] eq 'SAME_CHECKSUM' ) {
51 2 50       21 return unless $update->[1];
52 2         42 my ( $file, $expected_post_checksum ) = splice( @$update, 1, 2 );
53              
54 2         22 $self->apply_file( $file, $expected_post_checksum );
55             }
56             else {
57 4         255 $self->apply_file( @$update );
58             }
59             }
60              
61             sub apply_file {
62 6     6   24 my ( $self, $file, $expected_post_checksum ) = @_;
63 6         257 my $filename = $file->relative($self->sqlsnippetdir);
64              
65 6 100       1208 my $no_checksum_change = $self->checksum eq $expected_post_checksum ? 1 : 0;
66              
67 6         19 my $answer;
68 6 100       23 if ($no_checksum_change) {
69 1         73 $answer = prompt("Apply $filename (won't change checksum)? [y/n/s]",'y');
70             }
71             else {
72 5         28 $answer = prompt("Apply $filename? [y/n]",'y');
73             }
74              
75 6 50       705 if ($answer eq 'y') {
    0          
76 6 50       235 say "Starting to apply $filename" if $self->verbose;
77              
78 6         38 my $content = $file->slurp;
79              
80 6         2074 my $dbh = $self->dbh;
81 6         91 $dbh->begin_work;
82              
83 6         192 my $split_regex = qr/(?!:[\\]);/;
84              
85 6 50       29 if ($content =~ m/--\s*split-at:\s*(\S+)\n/s) {
86 0         0 say "Splitting $filename commands at >$1<";
87 0         0 $split_regex = qr/$1/;
88             }
89              
90 6         57 $content =~ s/^\s*--.+$//gm;
91 6         54 foreach my $command ( split( $split_regex , $content ) ) {
92 11         48 $command =~ s/\A\s+//;
93 11         42 $command =~ s/\s+\Z//;
94              
95 11 100       38 next unless $command;
96 5 50       159 say "Executing SQL statement: $command" if $self->verbose;
97             my $success = try {
98 5     5   580 $dbh->do($command);
99 5         2259 return 1;
100             }
101             catch {
102 0     0   0 $dbh->rollback;
103 0 0       0 say "SQL error: $_" unless $dbh->{PrintError};
104 0         0 say "ABORTING!";
105 0         0 return undef;
106 5         78 };
107 5 50       135 return unless $success; # abort all further changes
108 5 50       205 say "Successful!" if $self->verbose;
109             }
110              
111 6 50       238 if ( $self->dry_run ) {
112 0         0 $dbh->rollback;
113 0         0 say "dry run, so checksums cannot match. We proceed anyway...";
114 0         0 return $self->apply_sql_snippets($expected_post_checksum);
115             }
116              
117             # new checksum
118 6         236 $self->reset_checksum;
119 6         32 my $post_checksum = $self->checksum;
120              
121 6 50       33 if ( $post_checksum eq $expected_post_checksum ) {
122 6         240 say "post checksum OK";
123 6         62979 $dbh->commit;
124 6 100       503 if ($self->_update_path->{$post_checksum}) {
125 4         50 return $self->apply_sql_snippets($post_checksum);
126             }
127             else {
128 2         49 say 'No more changes';
129 2         111 return;
130             }
131             }
132             else {
133 0           say "post checksum mismatch!";
134 0           say " expected $expected_post_checksum";
135 0           say " got $post_checksum";
136 0           $dbh->rollback;
137 0           say "ABORTING!";
138 0           return;
139             }
140             }
141             elsif ($answer eq 's') {
142 0           return $self->apply_sql_snippets($expected_post_checksum);
143             }
144             else {
145 0           say "Not applying $filename, so we stop.";
146 0           return;
147             }
148             }
149              
150             __PACKAGE__->meta->make_immutable();
151             1;
152              
153             __END__
154              
155             =pod
156              
157             =encoding UTF-8
158              
159             =head1 NAME
160              
161             DBIx::SchemaChecksum::App::ApplyChanges - Apply changes based on current checksum
162              
163             =head1 VERSION
164              
165             version 1.103
166              
167             =head1 DESCRIPTION
168              
169             Apply all changes found in C<sqlsnippetdir> based on the current
170             checksum. For each file there will be a prompt asking if you want to
171             apply the file. Use C<--no_prompt> to always accept each change
172             (useful for deployment scripts etc). Use C<--dry_run> to run all
173             changes, but do not apply them.
174              
175             =head1 METHODS
176              
177             =head2 apply_sql_snippets
178              
179             $self->apply_sql_snippets( $starting_checksum );
180              
181             Applies SQL snippets in the correct order to the DB. Checks if the
182             checksum after applying the snippets is correct. If it isn't correct
183             rolls back the last change (if your DB supports transactions...)
184              
185             =head1 AUTHORS
186              
187             =over 4
188              
189             =item *
190              
191             Thomas Klausner <domm@plix.at>
192              
193             =item *
194              
195             Maroš Kollár <maros@cpan.org>
196              
197             =item *
198              
199             Klaus Ita <koki@worstofall.com>
200              
201             =back
202              
203             =head1 COPYRIGHT AND LICENSE
204              
205             This software is copyright (c) 2012 - 2021 by Thomas Klausner, Maroš Kollár, Klaus Ita.
206              
207             This is free software; you can redistribute it and/or modify it under
208             the same terms as the Perl 5 programming language system itself.
209              
210             =cut