File Coverage

blib/lib/Project/Easy/Helper/DB.pm
Criterion Covered Total %
statement 93 147 63.2
branch 34 72 47.2
condition 11 27 40.7
subroutine 3 7 42.8
pod 0 3 0.0
total 141 256 55.0


line stmt bran cond sub pod time code
1             package Project::Easy::Helper;
2              
3 2     2   10 use Class::Easy;
  2         4  
  2         16  
4              
5 2     2   7353 use Getopt::Long;
  2         34433  
  2         16  
6              
7             my $update_defaults = {
8             schema_variable => 'db_schema_version',
9             can_be_created => 'table|index|tablespace|trigger|routine|procedure|function',
10            
11             sql => {
12             ver_get => "select var_value from var where var_name = ?",
13             ver_upd => "update var set var_value = ? where var_name = ?",
14             ver_ins => "insert into var (var_value, var_name) values (?, ?)",
15             }
16             };
17              
18             sub updatedb {
19             # script
20 0     0 0 0 my ($pack, $libs) = &_script_wrapper();
21            
22 0         0 my $mode = 'update';
23 0         0 my $clean = 0;
24 0         0 my $schema_file;
25 0         0 my $datasource = 'default';
26            
27             GetOptions (
28 0     0   0 'h|help' => sub { &help },
29 0     0   0 'install' => sub {$mode = 'install'},
30 0         0 'clean' => \$clean,
31             'schema_file=s' => \$schema_file,
32             'datasource=s' => \$datasource
33             );
34            
35 0         0 update_schema (
36             mode => $mode,
37             clean => $clean,
38             schema_file => $schema_file,
39             datasource => $datasource
40             );
41            
42             }
43              
44             # TODO: move to DBI::Easy
45             sub update_schema {
46 5     5 0 20796 my $settings = {@_};
47            
48 5   100     39 my $mode = $settings->{mode} || 'update';
49 5   50     37 my $clean = $settings->{clean} || 0;
50 5   50     26 my $db = $settings->{datasource} || 'default';
51            
52 5         11 my $dbh = $settings->{dbh};
53 5         9 my $schema_file = $settings->{schema_file};
54              
55 5         13 my $update_sql = $update_defaults->{sql};
56              
57 5         14 my $ver_get = $update_sql->{'ver_get'};
58 5         14 my $ver_upd = $update_sql->{'ver_upd'};
59 5         10 my $ver_ins = $update_sql->{'ver_ins'};
60              
61 5         12 my $ver_fld = $update_defaults->{'schema_variable'};
62              
63 5         13 my $can_created = $update_defaults->{'can_be_created'};
64            
65 5 50 33     34 if ($schema_file and !$dbh) {
66             # try to create DBI connection from environment
67 0         0 try_to_use ('DBI');
68 0         0 my $dbh = DBI->connect;
69             }
70            
71 5 0 33     76 if (!$dbh and !$schema_file) { # using with Project::Easy
72            
73 0         0 my ($pack, $libs) = &Project::Easy::Helper::_script_wrapper ();
74              
75 0         0 $dbh = $pack->db ($db);
76              
77 0 0       0 die "can't initialize dbh via Project::Easy"
78             unless $dbh;
79              
80 0         0 my $pack_conf = $pack->config->{db}->{$db};
81 0         0 my $pack_sql = $pack_conf->{update_sql};
82              
83 0 0       0 warn "no update file for datasource '$db'", return
84             unless defined $pack_conf->{update};
85              
86 0         0 $schema_file = $pack->root->file_io ($pack_conf->{update});
87            
88 0 0       0 $ver_get = $pack_sql->{'ver_get'} if $pack_sql->{'ver_get'};
89 0 0       0 $ver_upd = $pack_sql->{'ver_upd'} if $pack_sql->{'ver_upd'};
90 0 0       0 $ver_ins = $pack_sql->{'ver_ins'} if $pack_sql->{'ver_ins'};
91              
92 0 0       0 $ver_fld = $pack_conf->{'schema_variable'}
93             if $pack_conf->{'schema_variable'};
94              
95 0 0       0 $can_created = $pack_conf->{'can_be_created'}
96             if $pack_conf->{'can_be_created'};
97             }
98            
99 5         7 my $schema_version;
100              
101 5 100       26 if ($mode eq 'update') {
    50          
102 1         50 eval {
103 1         46 debug "fetching $ver_get, ['$ver_fld']";
104 1         80 ($schema_version) = $dbh->selectrow_array ($ver_get, {}, $ver_fld);
105             };
106              
107 1 50       347 unless ($schema_version) {
108 0         0 die "can't fetch db_schema version, statement: $ver_get ['$ver_fld'].
109             if you want to init database, please use 'bin/updatedb --install'\n";
110             }
111              
112             } elsif ($mode eq 'install') {
113 4         8 $schema_version = 'NEW';
114             }
115              
116 5 50       23 critical "can't open schema file '$schema_file'"
117             unless open SCHEMA, $schema_file;
118            
119 5         223 my $found = 0;
120 5         10 my $harvest = 0;
121              
122 5 100       13 if ($mode eq 'install') {
123 4         7 $found = 1;
124 4         5 $harvest = 1;
125             }
126              
127 5         8 my $latest_version;
128 5         9 my $stages = {};
129              
130 5         13 my @cleaning = ();
131            
132 5         149 while () {
133            
134 50 100       193 if ($_ =~ /^-{2,}\s*(\d\d\d\d-\d\d-\d\d(?:\.\d+)?)/) {
    100          
135 13 100       47 if ($schema_version eq $1) {
136             # warn "we found latest declaration, start to find next declaration\n";
137 1         2 $found = 1;
138 1         3 next;
139             }
140 12 100       35 next unless $found;
141              
142 10         21 $latest_version = $1;
143 10         56 $harvest = 1;
144             } elsif ($harvest) {
145 29 100       83 die "first string of schema file must contains stage date in format: '--- YYYY-MM-DD'"
146             unless defined $latest_version;
147            
148 28         74 $stages->{$latest_version} .= $_;
149             }
150              
151 46 100 66     518 if (/\bcreate\s+($can_created)\s+['`"]*(\w+)['`"]*/i and ! /^\-\-/) {
152 17         276 push @cleaning, "drop $1 `$2`";
153             }
154             }
155              
156 4         65 close SCHEMA;
157            
158 4 50 33     31 if (! defined $latest_version or $latest_version eq '') {
159 0         0 $latest_version = $schema_version;
160             }
161            
162 4 50       18 if ($settings->{dry_run}) {
163 0         0 my $version = {db => $schema_version, schema => $latest_version};
164 0         0 return $version;
165             }
166            
167 4 50 66     27 if ($mode eq 'install' and $clean) {
168 0         0 print "\nWARNING!\n\nthese strings applied to database before installing new schema:\n",
169             join "\n", @cleaning,
170             "\n\ndo you really want to clean all data from database? ";
171 0         0 my $clean_check = getc;
172 0 0       0 critical "clean requested, but not approved! exiting…"
173             unless $clean_check =~ /^y$/i;
174             } else {
175 4         13 @cleaning = ();
176             }
177            
178 4 50       10 if ($schema_version eq $latest_version) {
179 0         0 print "no updates, db schema version: $schema_version\n";
180 0         0 return;
181             }
182              
183 4         995 print "current version: $schema_version\n";
184 4         799 print " new version: $latest_version\n";
185 4         192 print "\nupdating... ";
186              
187             # i don't want to check for errors here
188 0         0 map {
189 4 50       16 print "doing '$_'";
190 0         0 eval {$dbh->do ($_)};
  0         0  
191             } reverse @cleaning
192             if scalar @cleaning;
193              
194             # updating schema
195            
196 4         9 my $delimiter = ';';
197            
198 4         60 $dbh->{RaiseError} = 1;
199            
200 4         10 eval {
201            
202 4         35 foreach my $stage (sort keys %$stages) {
203            
204 7         82 debug "starting stage $stage";
205            
206 7         810 my @new_items = split /(?<=\;)\s+/, $stages->{$stage};
207            
208 7         72 $dbh->begin_work;
209              
210 7         142 my $statement;
211 7         18 my $wait_for_delimiter = $delimiter;
212              
213 7         23 foreach (@new_items) {
214            
215 11         3110 s/^\s+//s;
216 11         53 s/\s+$//s;
217            
218             # fix for stupid mysql delimiters
219 11 50       62 if (/(.*)^delimiter\s+([^\s]+)(?:\s+(.*))?/ms) {
    50          
220              
221 0 0 0     0 if ($wait_for_delimiter ne $delimiter and $2 eq $delimiter) {
    0 0        
222              
223 0         0 my $old_delimiter = $wait_for_delimiter;
224              
225             # routine or trigger body finished
226 0         0 $wait_for_delimiter = $2;
227              
228 0         0 $statement .= "\n" . $1;
229 0         0 debug ("delimiter changed to default");
230              
231 0         0 my @routines = split /\Q$old_delimiter\E/, $statement;
232              
233 0         0 foreach my $routine (@routines) {
234 0 0       0 next if $routine =~ /^\s+$/s;
235 0         0 debug ("doing \n$routine");
236 0         0 $dbh->do ($routine);
237             }
238              
239             } elsif ($wait_for_delimiter eq $delimiter and $2 ne $delimiter) {
240              
241             # we must change delimiter for routine or trigger body
242 0         0 debug ("delimiter changed from default to $2");
243 0         0 $wait_for_delimiter = $2;
244 0         0 $statement = $3;
245              
246             } else {
247 0         0 critical "something wrong with delimiter. default: '$delimiter', we want '$wait_for_delimiter', but receive '$1'";
248             }
249              
250 0         0 next;
251              
252             } elsif ($wait_for_delimiter ne $delimiter) {
253             # accumulating statement
254 0         0 $statement .= "\n" . $_;
255             } else {
256              
257 11         53 debug ("doing $_");
258 11         774 $dbh->do ($_);
259              
260             }
261             }
262              
263 5         2692 my $sth;
264 5 100       47 if ($schema_version eq 'NEW') {
265 2         10 debug "preparing $ver_ins";
266 2         117 $sth = $dbh->prepare ($ver_ins);
267 2         119 $schema_version = 'DIRTY_HACK';
268             } else {
269 3         16 debug "preparing $ver_upd";
270 3         235 $sth = $dbh->prepare ($ver_upd);
271             }
272              
273 5         210 debug "executing ['$stage', '$ver_fld']";
274              
275 5         624 my $status = $sth->execute ($stage, $ver_fld);
276 5 50       20 critical "can't setup schema version\n"
277             unless $status;
278            
279 5         169479 $dbh->commit;
280            
281             }
282             };
283              
284 4 100       689 if ($@){
285 2 50       637 print "eval errors: $@\n"
286             if $@ ne $dbh->errstr;
287              
288 2 50       38 print "dbh errors: " . $dbh->errstr . "\n"
289             unless $dbh->{RaiseError};
290              
291             # print "database error: $@\n";
292             # print "database error: " . $dbh->errstr . "\n";
293 2         6 eval {$dbh->rollback};
  2         13  
294 2         125 warn "can't apply new db schema, rollback\n";
295 2         34 return;
296             }
297              
298 2         1124 print "done\n";
299 2         93 return 1;
300            
301             }
302              
303             sub db {
304 0     0 0   my ($pack, $libs) = &_script_wrapper;
305            
306 0           my $root = $pack->root;
307            
308 0           my $config = $pack->config;
309            
310            
311             }
312              
313             1;