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.104'; # VERSION
5              
6 2     2   352134 use 5.010;
  2         25  
7              
8 2     2   985 use MooseX::App::Command;
  2         2020978  
  2         11  
9             extends qw(DBIx::SchemaChecksum::App);
10 2     2   1942237 use IO::Prompt::Tiny qw(prompt);
  2         1238  
  2         163  
11 2     2   18 use Try::Tiny;
  2         6  
  2         2094  
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   2681 my $self = shift;
19              
20 1         6 $self->apply_sql_snippets($self->checksum);
21             }
22              
23              
24             sub apply_sql_snippets {
25 6     6   2829 my ($self, $this_checksum ) = @_;
26 6         224 my $update_path = $self->_update_path;
27              
28             my $update = $update_path->{$this_checksum}
29 6 50       27 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       34 if ( $update->[0] eq 'SAME_CHECKSUM' ) {
51 2 50       98 return unless $update->[1];
52 2         22 my ( $file, $expected_post_checksum ) = splice( @$update, 1, 2 );
53              
54 2         18 $self->apply_file( $file, $expected_post_checksum );
55             }
56             else {
57 4         259 $self->apply_file( @$update );
58             }
59             }
60              
61             sub apply_file {
62 6     6   19 my ( $self, $file, $expected_post_checksum ) = @_;
63 6         257 my $filename = $file->relative($self->sqlsnippetdir);
64              
65 6 100       1236 my $no_checksum_change = $self->checksum eq $expected_post_checksum ? 1 : 0;
66              
67 6         17 my $answer;
68 6 100       18 if ($no_checksum_change) {
69 1         6 $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       697 if ($answer eq 'y') {
    0          
76 6 50       259 say "Starting to apply $filename" if $self->verbose;
77              
78 6         37 my $content = $file->slurp;
79              
80 6         2482 my $dbh = $self->dbh;
81 6         88 $dbh->begin_work;
82              
83 6         181 my $split_regex = qr/(?!:[\\]);/;
84              
85 6 50       34 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         53 foreach my $command ( split( $split_regex , $content ) ) {
92 11         52 $command =~ s/\A\s+//;
93 11         39 $command =~ s/\s+\Z//;
94              
95 11 100       38 next unless $command;
96 5 50       179 say "Executing SQL statement: $command" if $self->verbose;
97             my $success = try {
98 5     5   607 $dbh->do($command);
99 5         2198 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         67 };
107 5 50       138 return unless $success; # abort all further changes
108 5 50       224 say "Successful!" if $self->verbose;
109             }
110              
111 6 50       307 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         256 $self->reset_checksum;
119 6         32 my $post_checksum = $self->checksum;
120              
121 6 50       27 if ( $post_checksum eq $expected_post_checksum ) {
122 6         145 say "post checksum OK";
123 6         78643 $dbh->commit;
124 6 100       515 if ($self->_update_path->{$post_checksum}) {
125 4         42 return $self->apply_sql_snippets($post_checksum);
126             }
127             else {
128 2         82 say 'No more changes';
129 2         137 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.104
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