File Coverage

blib/lib/App/Sqitch/Engine.pm
Criterion Covered Total %
statement 610 659 92.5
branch 183 208 87.9
condition 97 193 50.2
subroutine 124 128 96.8
pod 69 69 100.0
total 1083 1257 86.1


line stmt bran cond sub pod time code
1             package App::Sqitch::Engine;
2              
3 30     30   42780 use 5.010;
  30         139  
4 30     30   209 use Moo;
  30         83  
  30         287  
5 30     30   15294 use strict;
  30         77  
  30         994  
6 30     30   230 use utf8;
  30         76  
  30         289  
7 30     30   1166 use Try::Tiny;
  30         196  
  30         3182  
8 30     30   205 use Locale::TextDomain qw(App-Sqitch);
  30         103  
  30         419  
9 30     30   8127 use Path::Class qw(file);
  30         106  
  30         2055  
10 30     30   220 use App::Sqitch::X qw(hurl);
  30         56  
  30         391  
11 30     30   12261 use List::Util qw(first max);
  30         102  
  30         2916  
12 30     30   2522 use URI::db 0.20;
  30         87970  
  30         1813  
13 30     30   263 use App::Sqitch::Types qw(Str Int Num Sqitch Plan Bool HashRef URI Maybe Target);
  30         143  
  30         462  
14 30     30   136030 use namespace::autoclean;
  30         87  
  30         474  
15 30     30   3257 use constant registry_release => '1.1';
  30         73  
  30         3213  
16 30     30   204 use constant default_lock_timeout => 60;
  30         196  
  30         379753  
17              
18             our $VERSION = 'v1.6.1'; # VERSION
19              
20             has sqitch => (
21             is => 'ro',
22             isa => Sqitch,
23             required => 1,
24             weak_ref => 1,
25             );
26              
27             has target => (
28             is => 'ro',
29             isa => Target,
30             required => 1,
31             weak_ref => 1,
32             handles => {
33             uri => 'uri',
34             client => 'client',
35             registry => 'registry',
36             destination => 'name',
37             }
38             );
39              
40             has username => (
41             is => 'ro',
42             isa => Maybe[Str],
43             lazy => 1,
44             default => sub {
45             my $self = shift;
46             $self->target->username || $self->_def_user
47             },
48             );
49              
50             has password => (
51             is => 'ro',
52             isa => Maybe[Str],
53             lazy => 1,
54             default => sub {
55             my $self = shift;
56             $self->target->password || $self->_def_pass
57             },
58             );
59              
60       0     sub _def_user { }
61       0     sub _def_pass { }
62              
63 48     48 1 74965 sub registry_destination { shift->destination }
64              
65             has start_at => (
66             is => 'rw',
67             isa => Str
68             );
69              
70             has no_prompt => (
71             is => 'rw',
72             isa => Bool,
73             trigger => sub {
74             # Deprecation notice added Feb 2023
75             warnings::warnif(
76             "deprecated",
77             "Engine::no_prompt is deprecated and will be removed in a future release\n"
78             . "Use direct arguments to revert() instead",
79             );
80             }
81             );
82              
83             has prompt_accept => (
84             is => 'rw',
85             isa => Bool,
86             trigger => sub {
87             # Deprecation notice added Feb 2023
88             warnings::warnif(
89             "deprecated",
90             "Engine::prompt_accept is deprecated and will be removed in a future release\n"
91             . "Use direct arguments to revert() instead",
92             );
93             }
94             );
95              
96             has log_only => (
97             is => 'rw',
98             isa => Bool,
99             default => 0,
100             );
101              
102             has with_verify => (
103             is => 'rw',
104             isa => Bool,
105             default => 0,
106             );
107              
108             has max_name_length => (
109             is => 'rw',
110             isa => Int,
111             default => 0,
112             lazy => 1,
113             default => sub {
114             my $plan = shift->plan;
115             max map {
116             length $_->format_name_with_tags
117             } $plan->changes;
118             },
119             );
120              
121             has plan => (
122             is => 'rw',
123             isa => Plan,
124             lazy => 1,
125             default => sub { shift->target->plan }
126             );
127              
128             has _variables => (
129             is => 'rw',
130             isa => HashRef[Str],
131             default => sub { {} },
132             );
133              
134             # Usually expressed as an integer, but made a number for the purposes of
135             # shorter test run times.
136             has lock_timeout => (
137             is => 'rw',
138             isa => Num,
139             default => default_lock_timeout,
140             );
141              
142             has _locked => (
143             is => 'rw',
144             isa => Bool,
145             default => 0,
146             );
147              
148             has _no_registry => (
149             is => 'rw',
150             isa => Bool,
151             default => 0,
152             );
153              
154 77     77 1 1186 sub variables { %{ shift->_variables } }
  77         2488  
155 12     12 1 8627 sub set_variables { shift->_variables({ @_ }) }
156 4     4 1 4436 sub clear_variables { %{ shift->_variables } = () }
  4         158  
157              
158 39     39 1 9617 sub default_registry { 'sqitch' }
159              
160             sub load {
161 144     144 1 19110 my ( $class, $p ) = @_;
162              
163             # We should have an engine param.
164 144 100       640 my $target = $p->{target} or hurl 'Missing "target" parameter to load()';
165              
166             # Load the engine class.
167 143 100       3395 my $ekey = $target->engine_key or hurl engine => __(
168             'No engine specified; specify via target or core.engine'
169             );
170              
171 142         10182 my $pkg = __PACKAGE__ . '::' . $target->engine_key;
172 142 100       18277 eval "require $pkg" or hurl engine => __x(
173             'Unknown engine: {engine}',
174             engine => $ekey,
175             );
176 140         3297 return $pkg->new( $p );
177             }
178              
179 1     1 1 5 sub driver { shift->key }
180              
181             sub key {
182 6   66 6 1 1060 my $class = ref $_[0] || shift;
183 6 100       30 hurl engine => __ 'No engine specified; specify via target or core.engine'
184             if $class eq __PACKAGE__;
185 5         12 my $pkg = quotemeta __PACKAGE__;
186 5         55 $class =~ s/^$pkg\:://;
187 5         34 return $class;
188             }
189              
190 3     3 1 1473 sub name { shift->key }
191              
192             sub config_vars {
193             return (
194 13     13 1 4315377 target => 'any',
195             registry => 'any',
196             client => 'any'
197             );
198             }
199              
200             sub use_driver {
201 14     14 1 7584 my $self = shift;
202 14         65 my $driver = $self->driver;
203 14     7   2440 eval "use $driver";
  7     2   1408  
  0     2   0  
  0     1   0  
  2     1   259  
  0     1   0  
  0         0  
  2         241  
  0         0  
  0         0  
  1         88  
  0         0  
  0         0  
  1         95  
  0         0  
  0         0  
  1         92  
  0            
  0            
204 14 50       128 hurl $self->key => __x(
205             '{driver} required to manage {engine}',
206             driver => $driver,
207             engine => $self->name,
208             ) if $@;
209 0         0 return $self;
210             }
211              
212             sub deploy {
213 13     13 1 52176 my ( $self, $to, $mode ) = @_;
214 13         67 $self->lock_destination;
215 13         202 my $sqitch = $self->sqitch;
216 13         70 my $plan = $self->_sync_plan;
217 13         62 my $to_index = $plan->count - 1;
218 13         318 my $position = $plan->position;
219              
220 13 100       118 hurl plan => __ 'Nothing to deploy (empty plan)' if $to_index < 0;
221              
222 12 100       47 if (defined $to) {
223 8   66     32 $to_index = $plan->index_of($to) // hurl plan => __x(
224             'Unknown change: "{change}"',
225             change => $to,
226             );
227              
228             # Just return if there is nothing to do.
229 7 100       25 if ($to_index == $position) {
230 1         7 $sqitch->info(__x(
231             'Nothing to deploy (already at "{change}")',
232             change => $to
233             ));
234 1         136 return $self;
235             }
236             }
237              
238 10 100       45 if ($position == $to_index) {
    100          
239             # We are up-to-date.
240 1         6 $sqitch->info( __ 'Nothing to deploy (up-to-date)' );
241 1         59 return $self;
242              
243             } elsif ($position == -1) {
244             # Initialize or upgrade the database, if necessary.
245 7 100       57 if ($self->initialized) {
246 3         29 $self->upgrade_registry;
247             } else {
248 4         55 $sqitch->info(__x(
249             'Adding registry tables to {destination}',
250             destination => $self->registry_destination,
251             ));
252 4         725 $self->initialize;
253             }
254 7         41 $self->register_project;
255              
256             } else {
257             # Make sure that $to_index is greater than the current point.
258 2 100       14 hurl deploy => __ 'Cannot deploy to an earlier change; use "revert" instead'
259             if $to_index < $position;
260             # Upgrade database if it needs it.
261 1         7 $self->upgrade_registry;
262             }
263              
264 8 100       167 $sqitch->info(
265             defined $to ? __x(
266             'Deploying changes through {change} to {destination}',
267             change => $plan->change_at($to_index)->format_name_with_tags,
268             destination => $self->destination,
269             ) : __x(
270             'Deploying changes to {destination}',
271             destination => $self->destination,
272             )
273             );
274              
275 8         1377 $sqitch->debug(__ "Will deploy the following changes:");
276 8         544 foreach my $will_deploy_position (($plan->position + 1) .. $to_index) {
277 18         599 $sqitch->debug($plan->change_at($will_deploy_position)->format_name_with_tags);
278             }
279              
280             # Check that all dependencies will be satisfied.
281 8         172 $self->check_deploy_dependencies($plan, $to_index);
282              
283             # Do it!
284 8   100     141 $mode ||= 'all';
285 8 100       45 my $meth = $mode eq 'change' ? '_deploy_by_change'
    100          
    100          
286             : $mode eq 'tag' ? '_deploy_by_tag'
287             : $mode eq 'all' ? '_deploy_all'
288             : hurl deploy => __x 'Unknown deployment mode: "{mode}"', mode => $mode;
289             ;
290              
291             $self->max_name_length(
292             max map {
293 7         48 length $_->format_name_with_tags
  15         113  
294             } ($plan->changes)[$position + 1..$to_index]
295             );
296              
297 7         381 $self->$meth( $plan, $to_index );
298             }
299              
300             # Do a thing similar to Sqitch::Plan::Change::format_name_with_tags,
301             # but for an output from $self->deployed_changes or
302             # $self->deployed_changes_since.
303             sub _format_deployed_change_name_with_tags($) {
304 21     21   55 my ( $self, $change ) = @_;
305              
306 21         52 return join ' ', $change->{name}, map { '@' . $_ } @{$change->{tags}};
  10         55  
  21         103  
307             }
308              
309             sub revert {
310             # $to = revert up to (but not including) this change. May be undefined.
311             # $prompt = If true, we ask for confirmation; if false, we don't.
312             # $prompt_default = Default if the user just hits enter at the prompt.
313 15     15 1 168753 my ( $self, $to, $prompt, $prompt_default ) = @_;
314              
315 15 100       127 if (defined $prompt) {
316 14 50       60 hurl revert => __('Missing required parameter $prompt_default')
317             unless defined $prompt_default;
318             } else {
319 1         53 warnings::warnif(deprecated => join ("\n",
320             "Engine::revert() requires the `prompt` and `prompt_default` arguments.",
321             'Omitting them will become fatal in a future release.',
322             ));
323              
324 1   50     1215 $prompt = !($self->no_prompt // 0);
325 1   50     44 $prompt_default = $self->prompt_accept // 1;
326             }
327              
328             # Check the registry and, once we know it's there, lock the destination.
329 15         89 $self->_check_registry;
330 14         86 $self->lock_destination;
331              
332 14         183 my $sqitch = $self->sqitch;
333 14         546 my $plan = $self->plan;
334 14         150 my @changes;
335              
336 14 100       52 if (defined $to) {
337             my ($change) = $self->_load_changes(
338             $self->change_for_key($to)
339 7 100       45 ) or do {
340             # Not deployed. Is it in the plan?
341 3 100       18 if ( $plan->find($to) ) {
342             # Known but not deployed.
343 1         6 hurl revert => __x(
344             'Change not deployed: "{change}"',
345             change => $to
346             );
347             }
348             # Never heard of it.
349 2         10 hurl revert => __x(
350             'Unknown change: "{change}"',
351             change => $to,
352             );
353             };
354              
355             # NB this is an array of unblessed references, not of
356             # Sqitch::Plan::Change references.
357             @changes = $self->deployed_changes_since(
358             $self->_load_changes($change)
359 4 100       25 ) or do {
360 1         14 $sqitch->info(__x(
361             'No changes deployed since: "{change}"',
362             change => $to,
363             ));
364 1         100 return $self;
365             };
366             my @change_descriptions =
367 3         39 map { $self->_format_deployed_change_name_with_tags($_) } @changes;
  5         22  
368              
369 3 100       13 unless ($prompt) {
370 1         8 $sqitch->info(__x(
371             'Reverting changes to {change} from {destination}',
372             change => $change->format_name_with_tags,
373             destination => $self->destination,
374             ));
375 1         520 $sqitch->debug(__ 'Will revert the following changes:');
376 1         46 map { $sqitch->debug($_) } @change_descriptions;
  1         4  
377             } else {
378 2         15 $sqitch->debug(__ 'Would revert the following changes:');
379 2         137 map { $sqitch->debug($_) } @change_descriptions;
  4         23  
380 2 100       24 hurl {
381             ident => 'revert:confirm',
382             message => __ 'Nothing reverted',
383             exitval => 1,
384             } unless $sqitch->ask_yes_no(__x(
385             'Revert changes to {change} from {destination}?',
386             change => $change->format_name_with_tags,
387             destination => $self->destination,
388             ), $prompt_default );
389             }
390             } else {
391             # NB this is an array of unblessed references, not of
392             # Sqitch::Plan::Change references.
393 7 100       32 @changes = $self->deployed_changes or do {
394 3         35 $sqitch->info(__ 'Nothing to revert (nothing deployed)');
395 3         225 return $self;
396             };
397             my @change_descriptions =
398 4         45 map { $self->_format_deployed_change_name_with_tags($_) } @changes;
  16         48  
399              
400 4 100       14 unless ($prompt) {
401 1         34 $sqitch->info(__x(
402             'Reverting all changes from {destination}',
403             destination => $self->destination,
404             ));
405 1         133 $sqitch->debug(__ 'Will revert the following changes:');
406 1         73 map { $sqitch->debug($_) } @change_descriptions;
  4         22  
407             } else {
408 3         17 $sqitch->debug(__ 'Would revert the following changes:');
409 3         174 map { $sqitch->debug($_) } @change_descriptions;
  12         70  
410 3 100       122 hurl {
411             ident => 'revert',
412             message => __ 'Nothing reverted',
413             exitval => 1,
414             } unless $sqitch->ask_yes_no(__x(
415             'Revert all changes from {destination}?',
416             destination => $self->destination,
417             ), $prompt_default );
418             }
419             }
420              
421             # Make change objects and check that all dependencies will be satisfied.
422 5         654 @changes = reverse $self->_load_changes( @changes );
423 5         49 $self->check_revert_dependencies(@changes);
424              
425             # Do we want to support modes, where failures would re-deploy to previous
426             # tag or all the way back to the starting point? This would be very much
427             # like deploy() mode. I'm thinking not, as a failure on a revert is not
428             # something you generally want to recover from by deploying back to where
429             # you started. But maybe I'm wrong?
430             $self->max_name_length(
431 5         60 max map { length $_->format_name_with_tags } @changes
  15         193  
432             );
433 5         405 $self->revert_change($_) for @changes;
434              
435 5         146 return $self;
436             }
437              
438             sub verify {
439 9     9 1 49657 my ( $self, $from, $to ) = @_;
440             # $from = verify changes after and including this one, or if undefined starting from the first change.
441             # $to = verify changes up to but not including this one, or if undefined up to all changes.
442              
443 9         85 $self->_check_registry;
444 8         48 my $sqitch = $self->sqitch;
445 8         337 my $plan = $self->plan;
446 8         114 my @changes = $self->_load_changes( $self->deployed_changes );
447              
448 8         287 $sqitch->info(__x(
449             'Verifying {destination}',
450             destination => $self->destination,
451             ));
452              
453 8 100       1265 if (!@changes) {
454 2 100       16 my $msg = $plan->count
455             ? __ 'No changes deployed'
456             : __ 'Nothing to verify (no planned or deployed changes)';
457 2         88 $sqitch->info($msg);
458 2         28 return $self;
459             }
460              
461 6 100       32 if ($plan->count == 0) {
462             # Oy, there are deployed changes, but not planned!
463 1         10 hurl verify => __ 'There are deployed changes, but none planned!';
464             }
465              
466             # Figure out where to start and end relative to the plan.
467 5         53 my $from_idx = $self->_from_idx('verify', $from, \@changes);
468 5         22 my $to_idx = $self->_to_idx('verify', $to, \@changes);
469              
470             # Run the verify tests.
471 5 100       37 if ( my $count = $self->_verify_changes($from_idx, $to_idx, !$to, @changes) ) {
472             # Emit a quick report.
473             # XXX Consider coloring red.
474 1         4 my $num_changes = 1 + $to_idx - $from_idx;
475 1 50       4 $num_changes = @changes if @changes > $num_changes;
476 1         5 my $msg = __ 'Verify Summary Report';
477 1         41 $sqitch->emit("\n", $msg);
478 1         14 $sqitch->emit('-' x length $msg);
479 1         10 $sqitch->emit(__x 'Changes: {number}', number => $num_changes );
480 1         133 $sqitch->emit(__x 'Errors: {number}', number => $count );
481 1         104 hurl verify => __ 'Verify failed';
482             }
483              
484             # Success!
485             # XXX Consider coloring green.
486 4         15 $sqitch->emit(__ 'Verify successful');
487              
488 4         135 return $self;
489             }
490              
491             sub _from_idx {
492 11     11   44 my ( $self, $ident, $from, $changes) = @_;
493 11 100       45 return 0 unless defined $from;
494 3         20 return $self->_trim_to($ident, $from, $changes)
495             }
496              
497             sub _to_idx {
498 12     12   8095 my ( $self, $ident, $to, $changes) = @_;
499 12         334 my $plan = $self->plan;
500 12 100       128 return $self->_trim_to($ident, $to, $changes, 1) if defined $to;
501 9 100       42 if (my $id = $self->latest_change_id) {
502 1   33     14 return $plan->index_of( $id ) // $plan->count - 1;
503             }
504 8         69 return $plan->count - 1;
505             }
506              
507             sub _trim_to {
508 19     19   53126 my ( $self, $ident, $key, $changes, $pop ) = @_;
509 19         82 my $sqitch = $self->sqitch;
510 19         546 my $plan = $self->plan;
511              
512             # Find the to change in the database.
513 19   66     201 my $to_id = $self->change_id_for_key( $key ) || hurl $ident => (
514             $plan->contains( $key ) ? __x(
515             'Change "{change}" has not been deployed',
516             change => $key,
517             ) : __x(
518             'Cannot find "{change}" in the database or the plan',
519             change => $key,
520             )
521             );
522              
523             # Find the change in the plan.
524 17   66     251 my $to_idx = $plan->index_of( $to_id ) // hurl $ident => __x(
525             'Change "{change}" is deployed, but not planned',
526             change => $key,
527             );
528              
529             # Pop or shift changes till we find the change we want.
530 16 100       54 if ($pop) {
531 9         238 pop @{ $changes } while $changes->[-1]->id ne $to_id;
  23         691  
532             } else {
533 7         205 shift @{ $changes } while $changes->[0]->id ne $to_id;
  9         263  
534             }
535              
536             # We good.
537 16         243 return $to_idx;
538             }
539              
540             sub _verify_changes {
541 14     14   12674 my $self = shift;
542 14         30 my $from_idx = shift;
543 14         21 my $to_idx = shift;
544 14         32 my $pending = shift;
545 14         49 my $sqitch = $self->sqitch;
546 14         361 my $plan = $self->plan;
547 14         126 my $errcount = 0;
548 14         25 my $i = -1;
549 14         24 my @seen;
550              
551             my $max_name_len = max map {
552 37         214 length $_->format_name_with_tags
553 14         55 } @_, map { $plan->change_at($_) } $from_idx..$to_idx;
  16         79  
554              
555 14         113 for my $change (@_) {
556 21         41 $i++;
557 21         36 my $errs = 0;
558 21         43 my $reworked = 0;
559 21         63 my $name = $change->format_name_with_tags;
560 21         240 $sqitch->emit_literal(
561             " * $name ..",
562             '.' x ($max_name_len - length $name), ' '
563             );
564              
565 21         590 my $plan_index = $plan->index_of( $change->id );
566 21 100       115 if (defined $plan_index) {
567 20         37 push @seen => $plan_index;
568 20 100       89 if ( $plan_index != ($from_idx + $i) ) {
569 4         14 $sqitch->comment(__ 'Out of order');
570 4         179 $errs++;
571             }
572             # Is it reworked?
573 20         60 $reworked = $plan->change_at($plan_index)->is_reworked;
574             } else {
575 1         8 $sqitch->comment(__ 'Not present in the plan');
576 1         59 $errs++;
577             }
578              
579             # Run the verify script.
580 19     19   953 try { $self->verify_change( $change ) } catch {
581 5   33 5   12815 $sqitch->comment(eval { $_->message } // $_);
  5         61  
582 5         178 $errs++;
583 21 100       397 } unless $reworked;
584              
585             # Emit pass/fail and add to the total error count.
586 21 100       509 $sqitch->emit( $errs ? __ 'not ok' : __ 'ok' );
587 21         1023 $errcount += $errs;
588             }
589              
590             # List any undeployed changes.
591 14         49 for my $idx ($from_idx..$to_idx) {
592 16 100   20   89 next if defined first { $_ == $idx } @seen;
  20         77  
593 2         11 my $change = $plan->change_at( $idx );
594 2         10 my $name = $change->format_name_with_tags;
595 2         32 $sqitch->emit_literal(
596             " * $name ..",
597             '.' x ($max_name_len - length $name), ' ',
598             __ 'not ok', ' '
599             );
600 2         88 $sqitch->comment(__ 'Not deployed');
601 2         60 $errcount++;
602             }
603              
604             # List any pending changes.
605 14 100 100     65 if ($pending && $to_idx < ($plan->count - 1)) {
606 1 50       7 if (my @pending = map {
607 7         27 $plan->change_at($_)
608             } ($to_idx + 1)..($plan->count - 1) ) {
609 1         6 $sqitch->emit(__n(
610             'Undeployed change:',
611             'Undeployed changes:',
612             @pending,
613             ));
614              
615 1         70 $sqitch->emit( ' * ', $_->format_name_with_tags ) for @pending;
616             }
617             }
618              
619 14         110 return $errcount;
620             }
621              
622             sub verify_change {
623 20     20 1 2364 my ( $self, $change ) = @_;
624 20         88 my $file = $change->verify_file;
625 20 100       2347 if (-e $file) {
626 11     11   528 return try { $self->run_verify($file) }
627             catch {
628 1     1   26 hurl {
629             ident => 'verify',
630             previous_exception => $_,
631             message => __x(
632             'Verify script "{script}" failed.',
633             script => $file,
634             ),
635             };
636 11         969 };
637             }
638              
639             # The file does not exist. Complain, but don't die.
640 9         640 $self->sqitch->vent(__x(
641             'Verify script {file} does not exist',
642             file => $file,
643             ));
644              
645 9         1270 return $self;
646             }
647              
648 59     59 1 5637 sub run_deploy { shift->run_file(@_) }
649 36     36 1 2909 sub run_revert { shift->run_file(@_) }
650 11     11 1 1827 sub run_verify { shift->run_file(@_) }
651 1     1 1 1722 sub run_upgrade { shift->run_file(@_) }
652              
653             sub check_deploy_dependencies {
654 8     8 1 1046 my ( $self, $plan, $to_index ) = @_;
655 8         196 my $from_index = $plan->position + 1;
656 8   66     92 $to_index //= $plan->count - 1;
657 8         28 my @changes = map { $plan->change_at($_) } $from_index..$to_index;
  47         104  
658 8         21 my (%seen, @conflicts, @required);
659              
660 8         17 for my $change (@changes) {
661             # Check for conflicts.
662             push @conflicts => grep {
663 47 100 100     325 $seen{ $_->id // '' } || $self->change_id_for_depend($_)
  9         238  
664             } $change->conflicts;
665              
666             # Check for prerequisites.
667 47         279 push @required => grep { !$_->resolved_id(do {
  10         292  
668 10 100 100     91 if ( my $req = $seen{ $_->id // '' } ) {
669 2         67 $req->id;
670             } else {
671 8         62 $self->change_id_for_depend($_);
672             }
673             }) } $change->requires;
674 47         1036 $seen{ $change->id } = $change;
675             }
676              
677 8 100 100     77 if (@conflicts or @required) {
678 5         64 require List::MoreUtils;
679 5     10   29 my $listof = sub { List::MoreUtils::uniq(map { $_->as_string } @_) };
  10         39  
  14         36  
680             # Dependencies not satisfied. Put together the error messages.
681 5         9 my @msg;
682 5 100       18 push @msg, __nx(
683             'Conflicts with previously deployed change: {changes}',
684             'Conflicts with previously deployed changes: {changes}',
685             scalar @conflicts,
686             changes => join ' ', @conflicts,
687             ) if @conflicts = $listof->(@conflicts);
688              
689 5 100       384 push @msg, __nx(
690             'Missing required change: {changes}',
691             'Missing required changes: {changes}',
692             scalar @required,
693             changes => join ' ', @required,
694             ) if @required = $listof->(@required);
695              
696 5         195 hurl deploy => join "\n" => @msg;
697             }
698              
699             # Make sure nothing isn't already deployed.
700 3 100       17 if ( my @ids = $self->are_deployed_changes(@changes) ) {
701             hurl deploy => __nx(
702             'Change "{changes}" has already been deployed',
703             'Changes have already been deployed: {changes}',
704             scalar @ids,
705 1         13 changes => join ', ', map { $seen{$_}->format_name_with_tags . " ($_)" } @ids
  2         20  
706             );
707             }
708              
709 2         28 return $self;
710             }
711              
712             sub check_revert_dependencies {
713 4     4 1 6433 my $self = shift;
714 4         134 my $proj = $self->plan->project;
715 4         183 my (%seen, @msg);
716              
717 4         14 for my $change (@_) {
718 5         214 $seen{ $change->id } = 1;
719             my @requiring = grep {
720 5 100       67 !$seen{ $_->{change_id} }
721 6         61 } $self->changes_requiring_change($change) or next;
722              
723             # XXX Include change_id in the output?
724             push @msg => __nx(
725             'Change "{change}" required by currently deployed change: {changes}',
726             'Change "{change}" required by currently deployed changes: {changes}',
727             scalar @requiring,
728             change => $change->format_name_with_tags,
729             changes => join ' ', map {
730 4         29 ($_->{project} eq $proj ? '' : "$_->{project}:" )
731             . $_->{change}
732 6 100 100     212 . ($_->{asof_tag} // '')
733             } @requiring
734             );
735             }
736              
737 4 100       473 hurl revert => join "\n", @msg if @msg;
738              
739             # XXX Should we make sure that they are all deployed before trying to
740             # revert them?
741              
742 1         7 return $self;
743             }
744              
745             sub change_id_for_depend {
746 18     18 1 2271 my ( $self, $dep ) = @_;
747 18 100 66     61 hurl engine => __x(
      66        
748             'Invalid dependency: {dependency}',
749             dependency => $dep->as_string,
750             ) unless defined $dep->id
751             || defined $dep->change
752             || defined $dep->tag;
753              
754             # Return the first one.
755 16         120 return $self->change_id_for(
756             change_id => $dep->id,
757             change => $dep->change,
758             tag => $dep->tag,
759             project => $dep->project,
760             first => 1,
761             );
762             }
763              
764             sub _params_for_key {
765 26     26   80 my ( $self, $key ) = @_;
766 26         128 my $offset = App::Sqitch::Plan::ChangeList::_offset $key;
767 26         127 my ( $cname, $tag ) = split /@/ => $key, 2;
768              
769 26         82 my @off = ( offset => $offset );
770 26 100       116 return ( @off, change => $cname, tag => $tag ) if $tag;
771 19 100       69 return ( @off, change_id => $cname ) if $cname =~ /^[0-9a-f]{40}$/;
772 17 100 100     106 return ( @off, tag => $cname ) if $cname eq 'HEAD' || $cname eq 'ROOT';
773 15         89 return ( @off, change => $cname );
774             }
775              
776             sub change_id_for_key {
777 19     19 1 37 my $self = shift;
778 19         71 return $self->find_change_id( $self->_params_for_key(shift) );
779             }
780              
781             sub find_change_id {
782 21     21 1 3734 my ( $self, %p ) = @_;
783              
784             # Find the change ID or return undef.
785             my $change_id = $self->change_id_for(
786             change_id => $p{change_id},
787             change => $p{change},
788             tag => $p{tag},
789 21   66     682 project => $p{project} || $self->plan->project,
      100        
790             ) // return;
791              
792             # Return relative to the offset.
793 19         987 return $self->change_id_offset_from_id($change_id, $p{offset});
794             }
795              
796             sub change_for_key {
797 7     7 1 17 my $self = shift;
798 7         41 return $self->find_change( $self->_params_for_key(shift) );
799             }
800              
801             sub find_change {
802 9     9 1 3881 my ( $self, %p ) = @_;
803              
804             # Find the change ID or return undef.
805             my $change_id = $self->change_id_for(
806             change_id => $p{change_id},
807             change => $p{change},
808             tag => $p{tag},
809 9   66     311 project => $p{project} || $self->plan->project,
      100        
810             ) // return;
811              
812             # Return relative to the offset.
813 6         363 return $self->change_offset_from_id($change_id, $p{offset});
814             }
815              
816             sub _load_changes {
817 29     29   58667 my $self = shift;
818 29         1177 my $plan = $self->plan;
819 29         360 my (@changes, %seen);
820 29         0 my %rework_tags_for;
821 29         165 for my $params (@_) {
822 45 100       149 next unless $params;
823 44   100     182 my $tags = $params->{tags} || [];
824 44         89 my $c = App::Sqitch::Plan::Change->new(%{ $params }, plan => $plan );
  44         1248  
825              
826             # Add tags.
827             $c->add_tag(
828             App::Sqitch::Plan::Tag->new(name => $_, plan => $plan, change => $c )
829 44         10603 ) for map { s/^@//; $_ } @{ $tags };
  25         64  
  25         491  
  44         159  
830              
831 44 100       509 if ( defined ( my $prev_idx = $seen{ $params->{name} } ) ) {
832             # It's reworked; grab all subsequent tags up to but not including
833             # the reworking change to the reworked change.
834 5   100     35 my $ctags = $rework_tags_for{ $prev_idx } ||= [];
835 5         7 my $i;
836 5         18 for my $x ($prev_idx..$#changes) {
837 6   100     28 my $rtags = $ctags->[$i++] ||= [];
838 6         11 my %s = map { $_->name => 1 } @{ $rtags };
  1         9  
  6         11  
839 6         12 push @{ $rtags } => grep { !$s{$_->name} } $changes[$x]->tags;
  6         24  
  10         77  
840             }
841             }
842              
843 44 100       77 if ( defined ( my $reworked_idx = eval {
844 44         268 $plan->first_index_of( @{ $params }{qw(name id)} )
  44         251  
845             } ) ) {
846             # The plan has it reworked later; grab all tags from this change
847             # up to but not including the reworked change.
848 2   50     15 my $ctags = $rework_tags_for{ $#changes + 1 } ||= [];
849 2         11 my $idx = $plan->index_of($params->{id});
850 2         3 my $i;
851 2         11 for my $x ($idx..$reworked_idx - 1) {
852 6         936 my $c = $plan->change_at($x);
853 6   50     24 my $rtags = $ctags->[$i++] ||= [];
854 6         7 push @{ $rtags } => $plan->change_at($x)->tags;
  6         12  
855             }
856             }
857              
858 44         4126 push @changes => $c;
859 44         190 $seen{ $params->{name} } = $#changes;
860             }
861              
862             # Associate all rework tags in reverse order. Tags fetched from the plan
863             # have priority over tags fetched from the database.
864 29         153 while (my ($idx, $tags) = each %rework_tags_for) {
865 6         28 my %seen;
866             $changes[$idx]->add_rework_tags(
867 15         159 grep { !$seen{$_->name}++ }
868 6         16 map { @{ $_ } } reverse @{ $tags }
  11         31  
  11         20  
  6         15  
869             );
870             }
871              
872 29         300 return @changes;
873             }
874              
875             sub _handle_lookup_index {
876 1     1   1198 my ( $self, $change, $ids ) = @_;
877              
878             # Return if 0 or 1 ID.
879 1 50       3 return $ids->[0] if @{ $ids } <= 1;
  1         6  
880              
881             # Too many found! Let the user know.
882 1         7 my $sqitch = $self->sqitch;
883 1         6 $sqitch->vent(__x(
884             'Change "{change}" is ambiguous. Please specify a tag-qualified change:',
885             change => $change,
886             ));
887              
888             # Lookup, emit reverse-chron list of tag-qualified changes, and die.
889 1         175 my $plan = $self->plan;
890 1         16 for my $id ( reverse @{ $ids } ) {
  1         4  
891             # Look in the plan, first.
892 2 50       26 if ( my $change = $plan->find($id) ) {
893 0         0 $self->sqitch->vent( ' * ', $change->format_tag_qualified_name )
894             } else {
895             # Look it up in the database.
896 2   50     20 $self->sqitch->vent( ' * ', $self->name_for_change_id($id) // '' )
897             }
898             }
899 1         21 hurl engine => __ 'Change Lookup Failed';
900             }
901              
902             sub _deploy_by_change {
903 5     5   5532 my ( $self, $plan, $to_index ) = @_;
904              
905             # Just deploy each change. If any fails, we just stop.
906 5         161 while ($plan->position < $to_index) {
907 11         449 $self->deploy_change($plan->next);
908             }
909              
910 4         278 return $self;
911             }
912              
913             sub _rollback {
914 10     10   35 my ($self, $tagged) = (shift, shift);
915 10         31 my $sqitch = $self->sqitch;
916              
917 10 50       54 if (my @run = reverse @_) {
918 10 100       296 $tagged = $tagged ? $tagged->format_name_with_tags : $self->start_at;
919 10 100       131 $sqitch->vent(
920             $tagged ? __x('Reverting to {change}', change => $tagged)
921             : __ 'Reverting all changes'
922             );
923              
924             try {
925 10     10   617 $self->revert_change($_) for @run;
926             } catch {
927             # Sucks when this happens.
928 1   33 1   40 $sqitch->vent(eval { $_->message } // $_);
  1         12  
929 1         11 $sqitch->vent(__ 'The schema will need to be manually repaired');
930 10         839 };
931             }
932              
933 10         429 hurl deploy => __ 'Deploy failed';
934             }
935              
936             sub _deploy_by_tag {
937 9     9   22439 my ( $self, $plan, $to_index ) = @_;
938              
939 9         28 my ($last_tagged, @run);
940             try {
941 9     9   935 while ($plan->position < $to_index) {
942 27         788 my $change = $plan->next;
943 27         163 $self->deploy_change($change);
944 22         465 push @run => $change;
945 22 100       118 if ($change->tags) {
946 8         96 @run = ();
947 8         254 $last_tagged = $change;
948             }
949             }
950             } catch {
951 5 100   5   264 if (my $ident = eval { $_->ident }) {
  5         90  
952 4 100       35 $self->sqitch->vent($_->message) unless $ident eq 'private'
953             } else {
954 1         13 $self->sqitch->vent($_);
955             }
956 5         52 $self->_rollback($last_tagged, @run);
957 9         155 };
958              
959 4         177 return $self;
960             }
961              
962             sub _deploy_all {
963 10     10   6496 my ( $self, $plan, $to_index ) = @_;
964              
965 10         22 my @run;
966             try {
967 10     10   970 while ($plan->position < $to_index) {
968 24         423 my $change = $plan->next;
969 24         122 $self->deploy_change($change);
970 19         956 push @run => $change;
971             }
972             } catch {
973 5 100   5   248 if (my $ident = try { $_->ident }) {
  5         226  
974 4 100       85 $self->sqitch->vent($_->message) unless $ident eq 'private'
975             } else {
976 1         26 $self->sqitch->vent($_);
977             }
978 5         79 $self->_rollback(undef, @run);
979 10         129 };
980              
981 5         235 return $self;
982             }
983              
984             sub _sync_plan {
985 21     21   11974 my $self = shift;
986 21         820 my $plan = $self->plan;
987              
988 21 100       976 if ($self->_no_registry) {
    100          
989             # No registry found on connection, so no records in the database.
990 1         11 $plan->reset;
991             } elsif (my $state = $self->current_state) {
992             my $idx = $plan->index_of($state->{change_id}) // hurl plan => __x(
993             'Cannot find change {id} ({change}) in {file}',
994             id => $state->{change_id},
995 8 0 33     185 change => join(' ', $state->{change}, @{ $state->{tags} || [] }),
  0         0  
996             file => $plan->file,
997             );
998              
999             # Upgrade the registry if there is no script_hash column.
1000 8 100       29 unless ( exists $state->{script_hash} ) {
1001 1         8 $self->upgrade_registry;
1002 1         5 $state->{script_hash} = $state->{change_id};
1003             }
1004              
1005             # Update the script hashes if they're the same as the change ID.
1006             # DEPRECATTION: Added in v0.998 (Jan 2015, c86cba61c); consider removing
1007             # in the future when all databases are likely to be updated already.
1008             $self->_update_script_hashes if $state->{script_hash}
1009 8 100 100     50 && $state->{script_hash} eq $state->{change_id};
1010              
1011 8         233 $plan->position($idx);
1012 8         405 my $change = $plan->change_at($idx);
1013 8 100       41 if (my @tags = $change->tags) {
1014 7         129 $self->log_new_tags($change);
1015 7         86 $self->start_at( $change->format_name . $tags[-1]->format_name );
1016             } else {
1017 1         18 $self->start_at( $change->format_name );
1018             }
1019              
1020             } else {
1021 12         271 $plan->reset;
1022             }
1023 21         404 return $plan;
1024             }
1025              
1026             sub is_deployed {
1027 2     2 1 1878 my ($self, $thing) = @_;
1028 2 100       25 return $thing->isa('App::Sqitch::Plan::Tag')
1029             ? $self->is_deployed_tag($thing)
1030             : $self->is_deployed_change($thing);
1031             }
1032              
1033             sub deploy_change {
1034 75     75 1 33748 my ( $self, $change ) = @_;
1035 75         355 my $sqitch = $self->sqitch;
1036 75         397 my $name = $change->format_name_with_tags;
1037 75         2612 $sqitch->info_literal(
1038             " + $name ..",
1039             '.' x ($self->max_name_length - length $name), ' '
1040             );
1041 75         1924 $self->begin_work($change);
1042              
1043             return try {
1044 75     75   6083 my $file = $change->deploy_file;
1045 75 100       10952 hurl deploy => __x(
1046             'Deploy script {file} does not exist',
1047             file => $file,
1048             ) unless -e $file;
1049 74 100       8078 $self->run_deploy($file) unless $self->log_only;
1050             try {
1051 66 100       5184 $self->verify_change( $change ) if $self->with_verify;
1052 64         852 $self->log_deploy_change($change);
1053 57         559 $sqitch->info(__ 'ok');
1054             } catch {
1055             # Oy, logging or verify failed. Rollback.
1056 9   33     28340 $sqitch->vent(eval { $_->message } // $_);
  9         113  
1057 9         123 $self->rollback_work($change);
1058              
1059             # Begin work and run the revert.
1060             try {
1061             # Don't bother displaying the reverting change name.
1062             # $self->sqitch->info(' - ', $change->format_name_with_tags);
1063 9         460 $self->begin_work($change);
1064 9 100       329 $self->run_revert($change->revert_file) unless $self->log_only;
1065             } catch {
1066             # Oy, the revert failed. Just emit the error.
1067 1   33     3257 $sqitch->vent(eval { $_->message } // $_);
  1         12  
1068 9         94 };
1069 9         311 hurl private => __ 'Deploy failed';
1070 66         1429 };
1071             } finally {
1072 75     75   6280 $self->finish_work($change);
1073             } catch {
1074 18     18   47849 $self->log_fail_change($change);
1075 18         181 $sqitch->info(__ 'not ok');
1076 18         1099 die $_;
1077 75         1297 };
1078             }
1079              
1080             sub revert_change {
1081 41     41 1 16351 my ( $self, $change ) = @_;
1082 41         191 my $sqitch = $self->sqitch;
1083 41         227 my $name = $change->format_name_with_tags;
1084 41         1437 $sqitch->info_literal(
1085             " - $name ..",
1086             '.' x ($self->max_name_length - length $name), ' '
1087             );
1088              
1089 41         1720 $self->begin_work($change);
1090              
1091             try {
1092 41     41   4730 my $file = $change->revert_file;
1093 41 100       6058 hurl revert => __x(
1094             'Revert script {file} does not exist',
1095             file => $file,
1096             ) unless -e $file;
1097 40 100       4489 $self->run_revert($file) unless $self->log_only;
1098             try {
1099 39         2308 $self->log_revert_change($change);
1100 38         358 $sqitch->info(__ 'ok');
1101             } catch {
1102             # Oy, our logging died. Rollback and revert this change.
1103 1   33     3862 $self->sqitch->vent(eval { $_->message } // $_);
  1         18  
1104 1         50 $self->rollback_work($change);
1105 1         6 hurl revert => 'Revert failed';
1106 39         694 };
1107             } finally {
1108 41     41   3834 $self->finish_work($change);
1109             } catch {
1110 3     3   10364 $sqitch->info(__ 'not ok');
1111 3         232 die $_;
1112 41         698 };
1113             }
1114              
1115             sub lock_destination {
1116 4     4 1 13567 my $self = shift;
1117              
1118             # Try to acquire the lock without waiting.
1119 4 100       118 return $self if $self->_locked;
1120 3 100       36 return $self->_locked(1) if $self->try_lock;
1121              
1122             # Lock not acquired. Tell the user what's happening.
1123 2         74 my $wait = $self->lock_timeout;
1124 2         76 $self->sqitch->info(__x(
1125             'Blocked by another instance of Sqitch working on {dest}; waiting {secs} seconds...',
1126             dest => $self->destination,
1127             secs => $wait,
1128             ));
1129              
1130             # Try waiting for the lock.
1131 2 100       360 return $self->_locked(1) if $self->wait_lock;
1132              
1133             # Timed out, so bail.
1134 1         40 hurl engine => __x(
1135             'Timed out waiting {secs} seconds for another instance of Sqitch to finish work on {dest}',
1136             dest => $self->destination,
1137             secs => $wait,
1138             );
1139             }
1140              
1141             sub _timeout {
1142 0     0   0 my ($self, $code) = @_;
1143 0         0 require Algorithm::Backoff::Exponential;
1144 0         0 my $ab = Algorithm::Backoff::Exponential->new(
1145             max_actual_duration => $self->lock_timeout,
1146             initial_delay => 0.01, # 10 ms
1147             max_delay => 10, # 10 s
1148             );
1149              
1150 0         0 while (1) {
1151 0 0       0 if (my $ret = $code->()) {
1152 0         0 return 1;
1153             }
1154 0         0 my $secs = $ab->failure;
1155 0 0       0 return 0 if $secs < 0;
1156 0         0 sleep $secs;
1157             }
1158             }
1159              
1160 1     1 1 7 sub try_lock { 1 }
1161             sub wait_lock {
1162 1   33 1 1 8249 my $class = ref $_[0] || $_[0];
1163 1         9 hurl "$class has not implemented wait_lock()";
1164             }
1165              
1166 1     1 1 6 sub begin_work { shift }
1167 1     1 1 7 sub finish_work { shift }
1168 10     10 1 24 sub rollback_work { shift }
1169              
1170             sub earliest_change {
1171 2     2 1 2303 my $self = shift;
1172 2   50     10 my $change_id = $self->earliest_change_id(@_) // return undef;
1173 2         76 return $self->plan->get( $change_id );
1174             }
1175              
1176             sub latest_change {
1177 2     2 1 1254 my $self = shift;
1178 2   50     13 my $change_id = $self->latest_change_id(@_) // return undef;
1179 2         89 return $self->plan->get( $change_id );
1180             }
1181              
1182             sub needs_upgrade {
1183 5     5 1 8 my $self = shift;
1184 5         19 $self->registry_version != $self->registry_release;
1185             }
1186              
1187             sub _check_registry {
1188 36     36   7549 my $self = shift;
1189 36         187 my $newver = $self->registry_release;
1190 36         167 my $oldver = $self->registry_version;
1191 36 100       458 return $self if $newver == $oldver;
1192              
1193 5 100 66     27 hurl engine => __x(
1194             'No registry found in {destination}. Have you ever deployed?',
1195             destination => $self->registry_destination,
1196             ) if $oldver == 0 && !$self->initialized;
1197              
1198 2 100       11 hurl engine => __x(
1199             'Registry version is {old} but {new} is the latest known. Please upgrade Sqitch',
1200             old => $oldver,
1201             new => $newver,
1202             ) if $newver < $oldver;
1203              
1204 1 50       10 hurl engine => __x(
1205             'Registry is at version {old} but latest is {new}. Please run the "upgrade" command',
1206             old => $oldver,
1207             new => $newver,
1208             ) if $newver > $oldver;
1209             }
1210              
1211             sub upgrade_registry {
1212 0     0 1 0 my $self = shift;
1213 0 0       0 return $self unless $self->needs_upgrade;
1214              
1215 0         0 my $sqitch = $self->sqitch;
1216 0         0 my $newver = $self->registry_release;
1217 0         0 my $oldver = $self->registry_version;
1218              
1219 0 0       0 hurl __x(
1220             'Registry version is {old} but {new} is the latest known. Please upgrade Sqitch.',
1221             old => $oldver,
1222             new => $newver,
1223             ) if $newver < $oldver;
1224              
1225 0         0 my $key = $self->key;
1226 0         0 my $dir = file(__FILE__)->dir->subdir(qw(Engine Upgrade));
1227              
1228 0         0 my @scripts = sort { $a->[0] <=> $b->[0] } grep { $_->[0] > $oldver } map {
  0         0  
1229 0         0 $_->basename =~ /\A\Q$key\E-(\d(?:[.]\d*)?)/;
  0         0  
1230 0   0     0 [ $1 || 0, $_ ];
1231             } $dir->children;
1232              
1233             # Make sure we're upgrading to where we want to be.
1234 0 0 0     0 hurl engine => __x(
1235             'Cannot upgrade to {version}: Cannot find upgrade script "{file}"',
1236             version => $newver,
1237             file => $dir->file("$key-$newver.*"),
1238             ) unless @scripts && $scripts[-1]->[0] == $newver;
1239              
1240             # Run the upgrades.
1241 0         0 $sqitch->info(__x(
1242             'Upgrading the Sqitch registry from {old} to {new}',
1243             old => $oldver,
1244             new => $newver,
1245             ));
1246 0         0 for my $script (@scripts) {
1247 0         0 my ($version, $file) = @{ $script };
  0         0  
1248 0         0 $sqitch->info(' * ' . __x(
1249             'From {old} to {new}',
1250             old => $oldver,
1251             new => $version,
1252             ));
1253 0         0 $self->run_upgrade($file);
1254 0         0 $self->_register_release($version);
1255 0         0 $oldver = $version;
1256             }
1257              
1258 0         0 return $self;
1259             }
1260              
1261             sub _find_planned_deployed_divergence_idx {
1262 7     7   21 my ($self, $from_idx, @deployed_changes) = @_;
1263 7         16 my $i = -1;
1264 7         178 my $plan = $self->plan;
1265              
1266 7         61 foreach my $change (@deployed_changes) {
1267 9         40 $i++;
1268 9 100 100     34 return $i if $i + $from_idx >= $plan->count
1269             || $change->script_hash ne $plan->change_at($i + $from_idx)->script_hash;
1270             }
1271              
1272 2         33 return -1;
1273             }
1274              
1275             sub planned_deployed_common_ancestor_id {
1276 1     1 1 6112 my ( $self ) = @_;
1277 1         8 my @deployed_changes = $self->_load_changes( $self->deployed_changes );
1278 1         23 my $divergent_idx = $self->_find_planned_deployed_divergence_idx(0, @deployed_changes);
1279              
1280 1 50       13 return $deployed_changes[-1]->id if $divergent_idx == -1;
1281 1 50       6 return undef if $divergent_idx == 0;
1282 1         32 return $deployed_changes[$divergent_idx - 1]->id;
1283             }
1284              
1285             sub check {
1286 8     8 1 33452 my ( $self, $from, $to ) = @_;
1287 8         38 $self->_check_registry;
1288 8         35 my $sqitch = $self->sqitch;
1289 8         276 my $plan = $self->plan;
1290 8         88 my @deployed_changes = $self->_load_changes( $self->deployed_changes );
1291 8         84 my $num_failed = 0;
1292              
1293 8         200 $sqitch->info(__x(
1294             'Checking {destination}',
1295             destination => $self->destination,
1296             ));
1297              
1298 8 100       1019 if (!@deployed_changes) {
1299 2 100       10 my $msg = $plan->count
1300             ? __ 'No changes deployed'
1301             : __ 'Nothing to check (no planned or deployed changes)';
1302 2         66 $sqitch->info($msg);
1303 2         20 return $self;
1304             }
1305              
1306             # Figure out where to start and end relative to the plan.
1307 6         32 my $from_idx = $self->_from_idx('check', $from, \@deployed_changes);
1308 6         30 $self->_to_idx('check', $to, \@deployed_changes);
1309              
1310 6         39 my $divergent_change_idx = $self->_find_planned_deployed_divergence_idx($from_idx, @deployed_changes);
1311 6 100       151 if ($divergent_change_idx != -1) {
1312 4         7 $num_failed++;
1313 4         26 $sqitch->emit(__x(
1314             'Script signatures diverge at change {change}',
1315             change => $deployed_changes[$divergent_change_idx]->format_name_with_tags,
1316             ));
1317             }
1318              
1319             hurl {
1320 6 100       486 ident => 'check',
1321             message => __nx(
1322             'Failed one check',
1323             'Failed {count} checks',
1324             $num_failed,
1325             count => $num_failed,
1326             ),
1327             exitval => $num_failed,
1328             } if $num_failed;
1329              
1330 2         9 $sqitch->emit(__ 'Check successful');
1331              
1332 2         130 return $self;
1333             }
1334              
1335             sub initialized {
1336 6 50   6 1 14593 return 0 if $_[0]->_no_registry;
1337 6         206 return $_[0]->_initialized;
1338             }
1339              
1340             sub _initialized {
1341 1   33 1   4 my $class = ref $_[0] || $_[0];
1342 1         7 hurl "$class has not implemented initialized()";
1343             }
1344              
1345             sub initialize {
1346 2     2 1 1915 my $self = shift;
1347 2 0       12 $self->_initialize || return;
1348 0         0 $self->_no_registry(0);
1349 0         0 return $self;
1350             }
1351              
1352             sub _initialize {
1353 1   33 1   7 my $class = ref $_[0] || $_[0];
1354 1         8 hurl "$class has not implemented initialize()";
1355             }
1356              
1357             sub register_project {
1358 1   33 1 1 927 my $class = ref $_[0] || $_[0];
1359 1         7 hurl "$class has not implemented register_project()";
1360             }
1361              
1362             sub run_file {
1363 1   33 1 1 2115 my $class = ref $_[0] || $_[0];
1364 1         7 hurl "$class has not implemented run_file()";
1365             }
1366              
1367             sub run_handle {
1368 1   33 1 1 2782 my $class = ref $_[0] || $_[0];
1369 1         8 hurl "$class has not implemented run_handle()";
1370             }
1371              
1372             sub log_deploy_change {
1373 1   33 1 1 2250 my $class = ref $_[0] || $_[0];
1374 1         7 hurl "$class has not implemented log_deploy_change()";
1375             }
1376              
1377             sub log_fail_change {
1378 1   33 1 1 926 my $class = ref $_[0] || $_[0];
1379 1         6 hurl "$class has not implemented log_fail_change()";
1380             }
1381              
1382             sub log_revert_change {
1383 1   33 1 1 891 my $class = ref $_[0] || $_[0];
1384 1         8 hurl "$class has not implemented log_revert_change()";
1385             }
1386              
1387             sub log_new_tags {
1388 1   33 1 1 968 my $class = ref $_[0] || $_[0];
1389 1         8 hurl "$class has not implemented log_new_tags()";
1390             }
1391              
1392             sub is_deployed_tag {
1393 1   33 1 1 869 my $class = ref $_[0] || $_[0];
1394 1         7 hurl "$class has not implemented is_deployed_tag()";
1395             }
1396              
1397             sub is_deployed_change {
1398 1   33 1 1 892 my $class = ref $_[0] || $_[0];
1399 1         8 hurl "$class has not implemented is_deployed_change()";
1400             }
1401              
1402             sub are_deployed_changes {
1403 1   33 1 1 982 my $class = ref $_[0] || $_[0];
1404 1         6 hurl "$class has not implemented are_deployed_changes()";
1405             }
1406              
1407             sub change_id_for {
1408 1   33 1 1 879 my $class = ref $_[0] || $_[0];
1409 1         7 hurl "$class has not implemented change_id_for()";
1410             }
1411              
1412             sub earliest_change_id {
1413 1   33 1 1 1065 my $class = ref $_[0] || $_[0];
1414 1         7 hurl "$class has not implemented earliest_change_id()";
1415             }
1416              
1417             sub latest_change_id {
1418 1   33 1 1 881 my $class = ref $_[0] || $_[0];
1419 1         7 hurl "$class has not implemented latest_change_id()";
1420             }
1421              
1422             sub deployed_changes {
1423 1   33 1 1 907 my $class = ref $_[0] || $_[0];
1424 1         9 hurl "$class has not implemented deployed_changes()";
1425             }
1426              
1427             sub deployed_changes_since {
1428 1   33 1 1 925 my $class = ref $_[0] || $_[0];
1429 1         7 hurl "$class has not implemented deployed_changes_since()";
1430             }
1431              
1432             sub load_change {
1433 1   33 1 1 1045 my $class = ref $_[0] || $_[0];
1434 1         7 hurl "$class has not implemented load_change()";
1435             }
1436              
1437             sub changes_requiring_change {
1438 1   33 1 1 897 my $class = ref $_[0] || $_[0];
1439 1         7 hurl "$class has not implemented changes_requiring_change()";
1440             }
1441              
1442             sub name_for_change_id {
1443 1   33 1 1 914 my $class = ref $_[0] || $_[0];
1444 1         7 hurl "$class has not implemented name_for_change_id()";
1445             }
1446              
1447             sub change_offset_from_id {
1448 1   33 1 1 830 my $class = ref $_[0] || $_[0];
1449 1         7 hurl "$class has not implemented change_offset_from_id()";
1450             }
1451              
1452             sub change_id_offset_from_id {
1453 1   33 1 1 2842 my $class = ref $_[0] || $_[0];
1454 1         7 hurl "$class has not implemented change_id_offset_from_id()";
1455             }
1456              
1457             sub registered_projects {
1458 1   33 1 1 868 my $class = ref $_[0] || $_[0];
1459 1         7 hurl "$class has not implemented registered_projects()";
1460             }
1461              
1462             sub current_state {
1463 1   33 1 1 899 my $class = ref $_[0] || $_[0];
1464 1         7 hurl "$class has not implemented current_state()";
1465             }
1466              
1467             sub current_changes {
1468 1   33 1 1 870 my $class = ref $_[0] || $_[0];
1469 1         7 hurl "$class has not implemented current_changes()";
1470             }
1471              
1472             sub current_tags {
1473 1   33 1 1 889 my $class = ref $_[0] || $_[0];
1474 1         79 hurl "$class has not implemented current_tags()";
1475             }
1476              
1477             sub search_events {
1478 1   33 1 1 822 my $class = ref $_[0] || $_[0];
1479 1         7 hurl "$class has not implemented search_events()";
1480             }
1481              
1482             sub registry_version {
1483 1   33 1 1 969 my $class = ref $_[0] || $_[0];
1484 1         6 hurl "$class has not implemented registry_version()";
1485             }
1486              
1487             sub _update_script_hashes {
1488 1   33 1   1377 my $class = ref $_[0] || $_[0];
1489 1         8 hurl "$class has not implemented _update_script_hashes()";
1490             }
1491              
1492             1;
1493              
1494             __END__
1495              
1496             =head1 Name
1497              
1498             App::Sqitch::Engine - Sqitch Deployment Engine
1499              
1500             =head1 Synopsis
1501              
1502             my $engine = App::Sqitch::Engine->new( sqitch => $sqitch );
1503              
1504             =head1 Description
1505              
1506             App::Sqitch::Engine provides the base class for all Sqitch storage engines.
1507             Most likely this will not be of much interest to you unless you are hacking on
1508             the engine code.
1509              
1510             =head1 Interface
1511              
1512             =head2 Class Methods
1513              
1514             =head3 C<key>
1515              
1516             my $name = App::Sqitch::Engine->key;
1517              
1518             The key name of the engine. Should be the last part of the package name.
1519              
1520             =head3 C<name>
1521              
1522             my $name = App::Sqitch::Engine->name;
1523              
1524             The name of the engine. Returns the same value as C<key> by default, but
1525             should probably be overridden to return a display name for the engine.
1526              
1527             =head3 C<default_registry>
1528              
1529             my $reg = App::Sqitch::Engine->default_registry;
1530              
1531             Returns the name of the default registry for the engine. Most engines just
1532             inherit the default value, C<sqitch>, but some must do more munging, such as
1533             specifying a file name, to determine the default registry name.
1534              
1535             =head3 C<default_client>
1536              
1537             my $cli = App::Sqitch::Engine->default_client;
1538              
1539             Returns the name of the default client for the engine. Must be implemented by
1540             each engine.
1541              
1542             =head3 C<driver>
1543              
1544             my $driver = App::Sqitch::Engine->driver;
1545              
1546             The name and version of the database driver to use with the engine, returned
1547             as a string suitable for passing to C<use>. Used internally by C<use_driver()>
1548             to C<use> the driver and, if it dies, to display an appropriate error message.
1549             Must be overridden by subclasses.
1550              
1551             =head3 C<use_driver>
1552              
1553             App::Sqitch::Engine->use_driver;
1554              
1555             Uses the driver and version returned by C<driver>. Returns an error on failure
1556             and returns true on success.
1557              
1558             =head3 C<config_vars>
1559              
1560             my %vars = App::Sqitch::Engine->config_vars;
1561              
1562             Returns a hash of names and types to use for configuration variables for the
1563             engine. These can be set under the C<engine.$engine_name> section in any
1564             configuration file.
1565              
1566             The keys in the returned hash are the names of the variables. The values are
1567             the data types. Valid data types include:
1568              
1569             =over
1570              
1571             =item C<any>
1572              
1573             =item C<int>
1574              
1575             =item C<num>
1576              
1577             =item C<bool>
1578              
1579             =item C<bool-or-int>
1580              
1581             =back
1582              
1583             Values ending in C<+> (a plus sign) may be specified multiple times. Example:
1584              
1585             (
1586             client => 'any',
1587             host => 'any',
1588             port => 'int',
1589             set => 'any+',
1590             )
1591              
1592             In this example, the C<port> variable will be stored and retrieved as an
1593             integer. The C<set> variable may be of any type and may be included multiple
1594             times. All the other variables may be of any type.
1595              
1596             By default, App::Sqitch::Engine returns:
1597              
1598             (
1599             target => 'any',
1600             registry => 'any',
1601             client => 'any',
1602             )
1603              
1604             Subclasses for supported engines will return more.
1605              
1606             =head3 C<registry_release>
1607              
1608             Returns the version of the registry understood by this release of Sqitch. The
1609             C<needs_upgrade()> method compares this value to that returned by
1610             C<registry_version()> to determine whether the target's registry needs
1611             upgrading.
1612              
1613             =head3 C<default_lock_timeout>
1614              
1615             Returns C<60>, the default value for the C<lock_timeout> attribute.
1616              
1617             =head2 Constructors
1618              
1619             =head3 C<load>
1620              
1621             my $cmd = App::Sqitch::Engine->load(%params);
1622              
1623             A factory method for instantiating Sqitch engines. It loads the subclass for
1624             the specified engine and calls C<new>, passing the Sqitch object. Supported
1625             parameters are:
1626              
1627             =over
1628              
1629             =item C<sqitch>
1630              
1631             The App::Sqitch object driving the whole thing.
1632              
1633             =back
1634              
1635             =head3 C<new>
1636              
1637             my $engine = App::Sqitch::Engine->new(%params);
1638              
1639             Instantiates and returns a App::Sqitch::Engine object.
1640              
1641             =head2 Instance Accessors
1642              
1643             =head3 C<sqitch>
1644              
1645             The current Sqitch object.
1646              
1647             =head3 C<target>
1648              
1649             An L<App::Sqitch::Target> object identifying the database target, usually
1650             derived from the name of target specified on the command-line, or the default.
1651              
1652             =head3 C<uri>
1653              
1654             A L<URI::db> object representing the target database. Defaults to a URI
1655             constructed from the L<App::Sqitch> C<db_*> attributes.
1656              
1657             =head3 C<destination>
1658              
1659             A string identifying the target database. Usually the same as the C<target>,
1660             unless it's a URI with the password included, in which case it returns the
1661             value of C<uri> with the password removed.
1662              
1663             =head3 C<registry>
1664              
1665             The name of the registry schema or database.
1666              
1667             =head3 C<start_at>
1668              
1669             The point in the plan from which to start deploying changes.
1670              
1671             =head3 C<log_only>
1672              
1673             Boolean indicating whether or not to log changes I<without running deploy or
1674             revert scripts>. This is useful for an existing database schema that needs to
1675             be converted to Sqitch. False by default.
1676              
1677             =head3 C<with_verify>
1678              
1679             Boolean indicating whether or not to run the verification script after each
1680             deploy script. False by default.
1681              
1682             =head3 C<variables>
1683              
1684             A hash of engine client variables to be set. May be set and retrieved as a
1685             list.
1686              
1687             =head2 Instance Methods
1688              
1689             =head3 C<username>
1690              
1691             my $username = $engine->username;
1692              
1693             The username to use to connect to the database, for engines that require
1694             authentication. The username is looked up in the following places, returning
1695             the first to have a value:
1696              
1697             =head3 C<lock_timeout>
1698              
1699             Number of seconds to C<lock_destination()> to wait to acquire a lock before
1700             timing out. Defaults to 60.
1701              
1702             =over
1703              
1704             =item 1.
1705              
1706             The C<$SQITCH_USERNAME> environment variable.
1707              
1708             =item 2.
1709              
1710             The username from the target URI.
1711              
1712             =item 3.
1713              
1714             An engine-specific default password, which may be derived from an environment
1715             variable, engine configuration file, the system user, or none at all.
1716              
1717             =back
1718              
1719             See L<sqitch-authentication> for details and best practices for Sqitch engine
1720             authentication.
1721              
1722             =head3 C<password>
1723              
1724             my $password = $engine->password;
1725              
1726             The password to use to connect to the database, for engines that require
1727             authentication. The password is looked up in the following places, returning
1728             the first to have a value:
1729              
1730             =over
1731              
1732             =item 1.
1733              
1734             The C<$SQITCH_PASSWORD> environment variable.
1735              
1736             =item 2.
1737              
1738             The password from the target URI.
1739              
1740             =item 3.
1741              
1742             An engine-specific default password, which may be derived from an environment
1743             variable, engine configuration file, or none at all.
1744              
1745             =back
1746              
1747             See L<sqitch-authentication> for details and best practices for Sqitch engine
1748             authentication.
1749              
1750             =head3 C<registry_destination>
1751              
1752             my $registry_destination = $engine->registry_destination;
1753              
1754             Returns the name of the registry database. In other words, the database in
1755             which Sqitch's own data is stored. It will usually be the same as C<target()>,
1756             but some engines, such as L<SQLite|App::Sqitch::Engine::sqlite>, may use a
1757             separate database. Used internally to name the target when the registration
1758             tables are created.
1759              
1760             =head3 C<variables>
1761              
1762             =head3 C<set_variables>
1763              
1764             =head3 C<clear_variables>
1765              
1766             my %vars = $engine->variables;
1767             $engine->set_variables(foo => 'bar', baz => 'hi there');
1768             $engine->clear_variables;
1769              
1770             Get, set, and clear engine variables. Variables are defined as key/value pairs
1771             to be passed to the engine client in calls to C<deploy> and C<revert>, if the
1772             client supports variables. For example, the
1773             L<PostgreSQL|App::Sqitch::Engine::pg> and
1774             L<Vertica|App::Sqitch::Engine::vertica> engines pass all the variables to
1775             their C<psql> and C<vsql> clients via the C<--set> option, while the
1776             L<MySQL engine|App::Sqitch::Engine::mysql> engine sets them via the C<SET>
1777             command and the L<Oracle engine|App::Sqitch::Engine::oracle> engine sets them
1778             via the SQL*Plus C<DEFINE> command.
1779              
1780             =head3 C<deploy>
1781              
1782             $engine->deploy($to_change);
1783             $engine->deploy($to_change, $mode);
1784             $engine->deploy($to_change, $mode);
1785              
1786             Deploys changes to the target database, starting with the current deployment
1787             state, and continuing to C<$to_change>. C<$to_change> must be a valid change
1788             specification as passable to the C<index_of()> method of L<App::Sqitch::Plan>.
1789             If C<$to_change> is not specified, all changes will be applied.
1790              
1791             The second argument specifies the reversion mode in the case of deployment
1792             failure. The allowed values are:
1793              
1794             =over
1795              
1796             =item C<all>
1797              
1798             In the event of failure, revert all deployed changes, back to the point at
1799             which deployment started. This is the default.
1800              
1801             =item C<tag>
1802              
1803             In the event of failure, revert all deployed changes to the last
1804             successfully-applied tag. If no tags were applied during this deployment, all
1805             changes will be reverted to the pint at which deployment began.
1806              
1807             =item C<change>
1808              
1809             In the event of failure, no changes will be reverted. This is on the
1810             assumption that a change failure is total, and the change may be applied again.
1811              
1812             =back
1813              
1814             Note that, in the event of failure, if a reversion fails, the target database
1815             B<may be left in a corrupted state>. Write your revert scripts carefully!
1816              
1817             =head3 C<revert>
1818              
1819             $engine->revert;
1820             $engine->revert($tag);
1821             $engine->revert($tag);
1822              
1823             Reverts the L<App::Sqitch::Plan::Tag> from the database, including all of its
1824             associated changes.
1825              
1826             Note that this method does not respect the C<$cmd.strict> configuration
1827             variables, which therefore must be checked by the caller.
1828              
1829             =head3 C<verify>
1830              
1831             $engine->verify;
1832             $engine->verify( $from );
1833             $engine->verify( $from, $to );
1834             $engine->verify( undef, $to );
1835              
1836             Verifies the database against the plan. Pass in change identifiers, as
1837             described in L<sqitchchanges>, to limit the changes to verify. For each
1838             change, information will be emitted if:
1839              
1840             =over
1841              
1842             =item *
1843              
1844             It does not appear in the plan.
1845              
1846             =item *
1847              
1848             It has not been deployed to the database.
1849              
1850             =item *
1851              
1852             It has been deployed out-of-order relative to the plan.
1853              
1854             =item *
1855              
1856             Its verify script fails.
1857              
1858             =back
1859              
1860             Changes without verify scripts will emit a warning, but not constitute a
1861             failure. If there are any failures, an exception will be thrown once all
1862             verifications have completed.
1863              
1864             =head3 C<check>
1865              
1866             $engine->check;
1867             $engine->check( $from );
1868             $engine->check( $from, $to );
1869             $engine->check( undef, $to );
1870              
1871             Compares the state of the working directory and the database by comparing the
1872             SHA1 hashes of the deploy scripts. Fails and reports divergence for all
1873             changes with non-matching hashes, indicating that the project deploy scripts
1874             differ from the scripts that were used to deploy to the database.
1875              
1876             Pass in change identifiers, as described in L<sqitchchanges>, to limit the
1877             changes to check. For each change, information will be emitted if the SHA1
1878             digest of the current deploy script does not match its SHA1 digest at the
1879             time of deployment.
1880              
1881             =head3 C<check_deploy_dependencies>
1882              
1883             $engine->check_deploy_dependencies;
1884             $engine->check_deploy_dependencies($to_index);
1885              
1886             Validates that all dependencies will be met for all changes to be deployed,
1887             starting with the currently-deployed change up to the specified index, or to
1888             the last change in the plan if no index is passed. If any of the changes to be
1889             deployed would conflict with previously-deployed changes or are missing any
1890             required changes, an exception will be thrown. Used internally by C<deploy()>
1891             to ensure that dependencies will be satisfied before deploying any changes.
1892              
1893             =head3 C<check_revert_dependencies>
1894              
1895             $engine->check_revert_dependencies(@changes);
1896              
1897             Validates that the list of changes to be reverted, which should be passed in
1898             the order in which they will be reverted, are not depended upon by other
1899             changes. If any are depended upon by other changes, an exception will be
1900             thrown listing the changes that cannot be reverted and what changes depend on
1901             them. Used internally by C<revert()> to ensure no dependencies will be
1902             violated before revering any changes.
1903              
1904             =head3 C<deploy_change>
1905              
1906             $engine->deploy_change($change);
1907             $engine->deploy_change($change);
1908              
1909             Used internally by C<deploy()> to deploy an individual change.
1910              
1911             =head3 C<revert_change>
1912              
1913             $engine->revert_change($change);
1914             $engine->revert_change($change);
1915              
1916             Used internally by C<revert()> (and, by C<deploy()> when a deploy fails) to
1917             revert an individual change.
1918              
1919             =head3 C<verify_change>
1920              
1921             $engine->verify_change($change);
1922              
1923             Used internally by C<deploy_change()> to verify a just-deployed change if
1924             C<with_verify> is true.
1925              
1926             =head3 C<is_deployed>
1927              
1928             say "Tag deployed" if $engine->is_deployed($tag);
1929             say "Change deployed" if $engine->is_deployed($change);
1930              
1931             Convenience method that dispatches to C<is_deployed_tag()> or
1932             C<is_deployed_change()> as appropriate to its argument.
1933              
1934             =head3 C<earliest_change>
1935              
1936             my $change = $engine->earliest_change;
1937             my $change = $engine->earliest_change($offset);
1938              
1939             Returns the L<App::Sqitch::Plan::Change> object representing the earliest
1940             applied change. With the optional C<$offset> argument, the returned change
1941             will be the offset number of changes following the earliest change.
1942              
1943              
1944             =head3 C<latest_change>
1945              
1946             my $change = $engine->latest_change;
1947             my $change = $engine->latest_change($offset);
1948              
1949             Returns the L<App::Sqitch::Plan::Change> object representing the latest
1950             applied change. With the optional C<$offset> argument, the returned change
1951             will be the offset number of changes before the latest change.
1952              
1953             =head3 C<change_for_key>
1954              
1955             my $change = if $engine->change_for_key($key);
1956              
1957             Searches the deployed changes for a change corresponding to the specified key,
1958             which should be in a format as described in L<sqitchchanges>. Throws an
1959             exception if the key matches more than one changes. Returns C<undef> if it
1960             matches no changes.
1961              
1962             =head3 C<change_id_for_key>
1963              
1964             my $change_id = if $engine->change_id_for_key($key);
1965              
1966             Searches the deployed changes for a change corresponding to the specified key,
1967             which should be in a format as described in L<sqitchchanges>, and returns the
1968             change's ID. Throws an exception if the key matches more than one change.
1969             Returns C<undef> if it matches no changes.
1970              
1971             =head3 C<change_for_key>
1972              
1973             my $change = if $engine->change_for_key($key);
1974              
1975             Searches the list of deployed changes for a change corresponding to the
1976             specified key, which should be in a format as described in L<sqitchchanges>.
1977             Throws an exception if the key matches multiple changes.
1978              
1979             =head3 C<change_id_for_depend>
1980              
1981             say 'Dependency satisfied' if $engine->change_id_for_depend($depend);
1982              
1983             Returns the change ID for a L<dependency|App::Sqitch::Plan::Depend>, if the
1984             dependency resolves to a change currently deployed to the database. Returns
1985             C<undef> if the dependency resolves to no currently-deployed change.
1986              
1987             =head3 C<find_change>
1988              
1989             my $change = $engine->find_change(%params);
1990              
1991             Finds and returns a deployed change, or C<undef> if the change has not been
1992             deployed. The supported parameters are:
1993              
1994             =over
1995              
1996             =item C<change_id>
1997              
1998             The change ID.
1999              
2000             =item C<change>
2001              
2002             A change name.
2003              
2004             =item C<tag>
2005              
2006             A tag name.
2007              
2008             =item C<project>
2009              
2010             A project name. Defaults to the current project.
2011              
2012             =item C<offset>
2013              
2014             The number of changes offset from the change found by the other parameters
2015             should actually be returned. May be positive or negative.
2016              
2017             =back
2018              
2019             The order of precedence for the search is:
2020              
2021             =over
2022              
2023             =item 1.
2024              
2025             Search by change ID, if passed.
2026              
2027             =item 2.
2028              
2029             Search by change name as of tag, if both are passed.
2030              
2031             =item 3.
2032              
2033             Search by change name or tag.
2034              
2035             =back
2036              
2037             The offset, if passed, will be applied relative to whatever change is found by
2038             the above algorithm.
2039              
2040             =head3 C<find_change_id>
2041              
2042             my $change_id = $engine->find_change_id(%params);
2043              
2044             Like C<find_change()>, taking the same parameters, but returning an ID instead
2045             of a change.
2046              
2047             =head3 C<run_deploy>
2048              
2049             $engine->run_deploy($deploy_file);
2050              
2051             Runs a deploy script. The implementation is just an alias for C<run_file()>;
2052             subclasses may override as appropriate.
2053              
2054             =head3 C<run_revert>
2055              
2056             $engine->run_revert($revert_file);
2057              
2058             Runs a revert script. The implementation is just an alias for C<run_file()>;
2059             subclasses may override as appropriate.
2060              
2061             =head3 C<run_verify>
2062              
2063             $engine->run_verify($verify_file);
2064              
2065             Runs a verify script. The implementation is just an alias for C<run_file()>;
2066             subclasses may override as appropriate.
2067              
2068             =head3 C<run_upgrade>
2069              
2070             $engine->run_upgrade($upgrade_file);
2071              
2072             Runs an upgrade script. The implementation is just an alias for C<run_file()>;
2073             subclasses may override as appropriate.
2074              
2075             =head3 C<needs_upgrade>
2076              
2077             if ($engine->needs_upgrade) {
2078             $engine->upgrade_registry;
2079             }
2080              
2081             Determines if the target's registry needs upgrading and returns true if it
2082             does.
2083              
2084             =head3 C<upgrade_registry>
2085              
2086             $engine->upgrade_registry;
2087              
2088             Upgrades the target's registry, if it needs upgrading. Used by the
2089             L<C<upgrade>|App::Sqitch::Command::upgrade> command.
2090              
2091             =head3 C<lock_destination>
2092              
2093             $engine->lock_destination;
2094              
2095             This method is called before deploying or reverting changes. It attempts
2096             to acquire a lock in the destination database to ensure that no other
2097             instances of Sqitch can act on the database at the same time. If it fails
2098             to acquire the lock, it emits a message to that effect, then tries again
2099             and waits. If it acquires the lock, it continues its work. If the attempt
2100             times out after C<lock_timeout> seconds, it exits with an error.
2101              
2102             The default implementation is effectively a no-op; consult the documentation
2103             for specific engines to determine whether they have implemented support for
2104             destination locking (by overriding C<try_lock()> and C<wait_lock()>).
2105              
2106             =head3 C<initialized>
2107              
2108             $engine->initialize unless $engine->initialized;
2109              
2110             Returns true if the database has been initialized for Sqitch, and false if it
2111             has not.
2112              
2113             =head3 C<initialize>
2114              
2115             $engine->initialize;
2116              
2117             Initializes the target database for Sqitch by installing the Sqitch registry
2118             schema and/or tables. Should be overridden by subclasses. This implementation
2119             throws an exception
2120              
2121             =head2 Abstract Instance Methods
2122              
2123             These methods must be overridden in subclasses.
2124              
2125             =head3 C<try_lock>
2126              
2127             $engine->try_lock;
2128              
2129             This method is called by C<lock_destination>, and this default implementation
2130             simply returns true. May be overridden in subclasses to acquire a database
2131             lock that would prevent any other instance of Sqitch from making changes at
2132             the same time. If it cannot acquire the lock, it should immediately return
2133             false without waiting.
2134              
2135             =head3 C<wait_lock>
2136              
2137             $engine->wait_lock;
2138              
2139             This method is called by C<lock_destination> when C<try_lock> returns false.
2140             It must be implemented if C<try_lock> is overridden; otherwise it throws
2141             an error when C<try_lock> returns false. It should attempt to acquire the
2142             same lock as C<try_lock>, but wait for it and time out after C<lock_timeout>
2143             seconds.
2144              
2145             =head3 C<begin_work>
2146              
2147             $engine->begin_work($change);
2148              
2149             This method is called just before a change is deployed or reverted. It should
2150             create a lock to prevent any other processes from making changes to the
2151             database, to be freed in C<finish_work> or C<rollback_work>. Unlike
2152             C<lock_destination>, this method generally starts a transaction for the
2153             duration of the deployment or reversion of a single change.
2154              
2155             =head3 C<finish_work>
2156              
2157             $engine->finish_work($change);
2158              
2159             This method is called after a change has been deployed or reverted. It should
2160             unlock the lock created by C<begin_work>.
2161              
2162             =head3 C<rollback_work>
2163              
2164             $engine->rollback_work($change);
2165              
2166             This method is called after a change has been deployed or reverted and the
2167             logging of that change has failed. It should rollback changes started by
2168             C<begin_work>.
2169              
2170             =head3 C<_initialized>
2171              
2172             Returns true if the database has been initialized for Sqitch, and false if it
2173             has not.
2174              
2175             =head3 C<_initialize>
2176              
2177             Initializes the target database for Sqitch by installing the Sqitch registry
2178             schema and/or tables. Should be overridden by subclasses. This implementation
2179             throws an exception.
2180              
2181             =head3 C<register_project>
2182              
2183             $engine->register_project;
2184              
2185             Registers the current project plan in the registry database. The
2186             implementation should insert the project name and URI if they have not already
2187             been inserted. If a project already exists with the same name but different
2188             URI, or a different name and the same URI, an exception should be thrown.
2189              
2190             =head3 C<is_deployed_tag>
2191              
2192             say 'Tag deployed' if $engine->is_deployed_tag($tag);
2193              
2194             Should return true if the L<tag|App::Sqitch::Plan::Tag> has been applied to
2195             the database, and false if it has not.
2196              
2197             =head3 C<is_deployed_change>
2198              
2199             say 'Change deployed' if $engine->is_deployed_change($change);
2200              
2201             Should return true if the L<change|App::Sqitch::Plan::Change> has been
2202             deployed to the database, and false if it has not.
2203              
2204             =head3 C<are_deployed_changes>
2205              
2206             say "Change $_ is deployed" for $engine->are_deployed_change(@changes);
2207              
2208             Should return the IDs of any of the changes passed in that are currently
2209             deployed. Used by C<deploy> to ensure that no changes already deployed are
2210             re-deployed.
2211              
2212             =head3 C<change_id_for>
2213              
2214             say $engine->change_id_for(
2215             change => $change_name,
2216             tag => $tag_name,
2217             project => $project,
2218             );
2219              
2220             Searches the database for the change with the specified name, tag, project,
2221             or ID. Returns C<undef> if it matches no changes. If it matches more than one
2222             change, it returns the earliest deployed change if the C<first> parameter is
2223             passed; otherwise it throws an exception The parameters are as follows:
2224              
2225             =over
2226              
2227             =item C<change>
2228              
2229             The name of a change. Required unless C<tag> or C<change_id> is passed.
2230              
2231             =item C<change_id>
2232              
2233             The ID of a change. Required unless C<tag> or C<change> is passed. Useful
2234             to determine whether an ID in a plan has been deployed to the database.
2235              
2236             =item C<tag>
2237              
2238             The name of a tag. Required unless C<change> is passed.
2239              
2240             =item C<project>
2241              
2242             The name of the project to search. Defaults to the current project.
2243              
2244             =item C<first>
2245              
2246             Return the earliest deployed change ID if the search matches more than one
2247             change. If false or not passed and more than one change is found, an
2248             exception will be thrown.
2249              
2250             =back
2251              
2252             If both C<change> and C<tag> are passed, C<find_change_id> will search for the
2253             last instance of the named change deployed I<before> the tag.
2254              
2255             =head3 C<changes_requiring_change>
2256              
2257             my @requiring = $engine->changes_requiring_change($change);
2258              
2259             Returns a list of hash references representing currently deployed changes that
2260             require the passed change. When this method returns one or more hash
2261             references, the change should not be reverted. Each hash reference should
2262             contain the following keys:
2263              
2264             =over
2265              
2266             =item C<change_id>
2267              
2268             The requiring change ID.
2269              
2270             =item C<change>
2271              
2272             The requiring change name.
2273              
2274             =item C<project>
2275              
2276             The project the requiring change is from.
2277              
2278             =item C<asof_tag>
2279              
2280             Name of the first tag to be applied after the requiring change was deployed,
2281             if any.
2282              
2283             =back
2284              
2285             =head3 C<log_deploy_change>
2286              
2287             $engine->log_deploy_change($change);
2288              
2289             Should write the records to the registry necessary to indicate that the change
2290             has been deployed.
2291              
2292             =head3 C<log_fail_change>
2293              
2294             $engine->log_fail_change($change);
2295              
2296             Should write to the database event history a record reflecting that deployment
2297             of the change failed.
2298              
2299             =head3 C<log_revert_change>
2300              
2301             $engine->log_revert_change($change);
2302              
2303             Should write to and/or remove from the registry the records necessary to
2304             indicate that the change has been reverted.
2305              
2306             =head3 C<log_new_tags>
2307              
2308             $engine->log_new_tags($change);
2309              
2310             Given a change, if it has any tags that are not currently logged in the
2311             database, they should be logged. This is assuming, of course, that the change
2312             itself has previously been logged.
2313              
2314             =head3 C<earliest_change_id>
2315              
2316             my $change_id = $engine->earliest_change_id($offset);
2317              
2318             Returns the ID of the earliest applied change from the current project. With
2319             the optional C<$offset> argument, the ID of the change the offset number of
2320             changes following the earliest change will be returned.
2321              
2322             =head3 C<latest_change_id>
2323              
2324             my $change_id = $engine->latest_change_id;
2325             my $change_id = $engine->latest_change_id($offset);
2326              
2327             Returns the ID of the latest applied change from the current project.
2328             With the optional C<$offset> argument, the ID of the change the offset
2329             number of changes before the latest change will be returned.
2330              
2331             =head3 C<deployed_changes>
2332              
2333             my @change_hashes = $engine->deployed_changes;
2334              
2335             Returns a list of hash references, each representing a change from the current
2336             project in the order in which they were deployed. The keys in each hash
2337             reference must be:
2338              
2339             =over
2340              
2341             =item C<id>
2342              
2343             The change ID.
2344              
2345             =item C<name>
2346              
2347             The change name.
2348              
2349             =item C<project>
2350              
2351             The name of the project with which the change is associated.
2352              
2353             =item C<note>
2354              
2355             The note attached to the change.
2356              
2357             =item C<planner_name>
2358              
2359             The name of the user who planned the change.
2360              
2361             =item C<planner_email>
2362              
2363             The email address of the user who planned the change.
2364              
2365             =item C<timestamp>
2366              
2367             An L<App::Sqitch::DateTime> object representing the time the change was planned.
2368              
2369             =item C<tags>
2370              
2371             An array reference of the tag names associated with the change.
2372              
2373             =back
2374              
2375             =head3 C<deployed_changes_since>
2376              
2377             my @change_hashes = $engine->deployed_changes_since($change);
2378              
2379             Returns a list of hash references, each representing a change from the current
2380             project deployed after the specified change. The keys in the hash references
2381             should be the same as for those returned by C<deployed_changes()>.
2382              
2383             =head3 C<name_for_change_id>
2384              
2385             my $change_name = $engine->name_for_change_id($change_id);
2386              
2387             Returns the tag-qualified name of the change identified by the ID. If a tag
2388             was applied to a change after that change, the name will be returned with the
2389             tag qualification, e.g., C<app_user@beta>. Otherwise, it will include the
2390             symbolic tag C<@HEAD>. e.g., C<widgets@HEAD>. This value should be suitable
2391             for uniquely identifying the change, and passing to the C<get> or C<index_of>
2392             methods of L<App::Sqitch::Plan>.
2393              
2394             =head3 C<registered_projects>
2395              
2396             my @projects = $engine->registered_projects;
2397              
2398             Returns a list of the names of Sqitch projects registered in the database.
2399              
2400             =head3 C<current_state>
2401              
2402             my $state = $engine->current_state;
2403             my $state = $engine->current_state($project);
2404              
2405             Returns a hash reference representing the current project deployment state of
2406             the database, or C<undef> if the database has no changes deployed. If a
2407             project name is passed, the state will be returned for that project. Otherwise,
2408             the state will be returned for the local project.
2409              
2410             The hash contains information about the last successfully deployed change, as
2411             well as any associated tags. The keys to the hash should include:
2412              
2413             =over
2414              
2415             =item C<project>
2416              
2417             The name of the project for which the state is reported.
2418              
2419             =item C<change_id>
2420              
2421             The current change ID.
2422              
2423             =item C<script_hash>
2424              
2425             The deploy script SHA-1 hash.
2426              
2427             =item C<change>
2428              
2429             The current change name.
2430              
2431             =item C<note>
2432              
2433             A brief description of the change.
2434              
2435             =item C<tags>
2436              
2437             An array reference of the names of associated tags.
2438              
2439             =item C<committed_at>
2440              
2441             An L<App::Sqitch::DateTime> object representing the date and time at which the
2442             change was deployed.
2443              
2444             =item C<committer_name>
2445              
2446             Name of the user who deployed the change.
2447              
2448             =item C<committer_email>
2449              
2450             Email address of the user who deployed the change.
2451              
2452             =item C<planned_at>
2453              
2454             An L<App::Sqitch::DateTime> object representing the date and time at which the
2455             change was added to the plan.
2456              
2457             =item C<planner_name>
2458              
2459             Name of the user who added the change to the plan.
2460              
2461             =item C<planner_email>
2462              
2463             Email address of the user who added the change to the plan.
2464              
2465             =back
2466              
2467             =head3 C<current_changes>
2468              
2469             my $iter = $engine->current_changes;
2470             my $iter = $engine->current_changes($project);
2471             while (my $change = $iter->()) {
2472             say '* ', $change->{change};
2473             }
2474              
2475             Returns a code reference that iterates over a list of the currently deployed
2476             changes in reverse chronological order. If a project name is not passed, the
2477             current project will be assumed. Each change is represented by a hash
2478             reference containing the following keys:
2479              
2480             =over
2481              
2482             =item C<change_id>
2483              
2484             The current change ID.
2485              
2486             =item C<script_hash>
2487              
2488             The deploy script SHA-1 hash.
2489              
2490             =item C<change>
2491              
2492             The current change name.
2493              
2494             =item C<committed_at>
2495              
2496             An L<App::Sqitch::DateTime> object representing the date and time at which the
2497             change was deployed.
2498              
2499             =item C<committer_name>
2500              
2501             Name of the user who deployed the change.
2502              
2503             =item C<committer_email>
2504              
2505             Email address of the user who deployed the change.
2506              
2507             =item C<planned_at>
2508              
2509             An L<App::Sqitch::DateTime> object representing the date and time at which the
2510             change was added to the plan.
2511              
2512             =item C<planner_name>
2513              
2514             Name of the user who added the change to the plan.
2515              
2516             =item C<planner_email>
2517              
2518             Email address of the user who added the change to the plan.
2519              
2520             =back
2521              
2522             =head3 C<current_tags>
2523              
2524             my $iter = $engine->current_tags;
2525             my $iter = $engine->current_tags($project);
2526             while (my $tag = $iter->()) {
2527             say '* ', $tag->{tag};
2528             }
2529              
2530             Returns a code reference that iterates over a list of the currently deployed
2531             tags in reverse chronological order. If a project name is not passed, the
2532             current project will be assumed. Each tag is represented by a hash reference
2533             containing the following keys:
2534              
2535             =over
2536              
2537             =item C<tag_id>
2538              
2539             The tag ID.
2540              
2541             =item C<tag>
2542              
2543             The name of the tag.
2544              
2545             =item C<committed_at>
2546              
2547             An L<App::Sqitch::DateTime> object representing the date and time at which the
2548             tag was applied.
2549              
2550             =item C<committer_name>
2551              
2552             Name of the user who applied the tag.
2553              
2554             =item C<committer_email>
2555              
2556             Email address of the user who applied the tag.
2557              
2558             =item C<planned_at>
2559              
2560             An L<App::Sqitch::DateTime> object representing the date and time at which the
2561             tag was added to the plan.
2562              
2563             =item C<planner_name>
2564              
2565             Name of the user who added the tag to the plan.
2566              
2567             =item C<planner_email>
2568              
2569             Email address of the user who added the tag to the plan.
2570              
2571             =back
2572              
2573             =head3 C<search_events>
2574              
2575             my $iter = $engine->search_events( %params );
2576             while (my $change = $iter->()) {
2577             say '* $change->{event}ed $change->{change}";
2578             }
2579              
2580             Searches the deployment event log and returns an iterator code reference with
2581             the results. If no parameters are provided, a list of all events will be
2582             returned from the iterator reverse chronological order. The supported parameters
2583             are:
2584              
2585             =over
2586              
2587             =item C<event>
2588              
2589             An array of the type of event to search for. Allowed values are "deploy",
2590             "revert", and "fail".
2591              
2592             =item C<project>
2593              
2594             Limit the events to those with project names matching the specified regular
2595             expression.
2596              
2597             =item C<change>
2598              
2599             Limit the events to those with changes matching the specified regular
2600             expression.
2601              
2602             =item C<committer>
2603              
2604             Limit the events to those logged for the actions of the committers with names
2605             matching the specified regular expression.
2606              
2607             =item C<planner>
2608              
2609             Limit the events to those with changes who's planner's name matches the
2610             specified regular expression.
2611              
2612             =item C<limit>
2613              
2614             Limit the number of events to the specified number.
2615              
2616             =item C<offset>
2617              
2618             Skip the specified number of events.
2619              
2620             =item C<direction>
2621              
2622             Return the results in the specified order, which must be a value matching
2623             C</^(:?a|de)sc/i> for "ascending" or "descending".
2624              
2625             =back
2626              
2627             Each event is represented by a hash reference containing the following keys:
2628              
2629             =over
2630              
2631             =item C<event>
2632              
2633             The type of event, which is one of:
2634              
2635             =over
2636              
2637             =item C<deploy>
2638              
2639             =item C<revert>
2640              
2641             =item C<fail>
2642              
2643             =back
2644              
2645             =item C<project>
2646              
2647             The name of the project with which the change is associated.
2648              
2649             =item C<change_id>
2650              
2651             The change ID.
2652              
2653             =item C<change>
2654              
2655             The name of the change.
2656              
2657             =item C<note>
2658              
2659             A brief description of the change.
2660              
2661             =item C<tags>
2662              
2663             An array reference of the names of associated tags.
2664              
2665             =item C<requires>
2666              
2667             An array reference of the names of any changes required by the change.
2668              
2669             =item C<conflicts>
2670              
2671             An array reference of the names of any changes that conflict with the change.
2672              
2673             =item C<committed_at>
2674              
2675             An L<App::Sqitch::DateTime> object representing the date and time at which the
2676             event was logged.
2677              
2678             =item C<committer_name>
2679              
2680             Name of the user who deployed the change.
2681              
2682             =item C<committer_email>
2683              
2684             Email address of the user who deployed the change.
2685              
2686             =item C<planned_at>
2687              
2688             An L<App::Sqitch::DateTime> object representing the date and time at which the
2689             change was added to the plan.
2690              
2691             =item C<planner_name>
2692              
2693             Name of the user who added the change to the plan.
2694              
2695             =item C<planner_email>
2696              
2697             Email address of the user who added the change to the plan.
2698              
2699             =back
2700              
2701             =head3 C<run_file>
2702              
2703             $engine->run_file($file);
2704              
2705             Should execute the commands in the specified file. This will generally be an
2706             SQL file to run through the engine's native client.
2707              
2708             =head3 C<run_handle>
2709              
2710             $engine->run_handle($file_handle);
2711              
2712             Should execute the commands in the specified file handle. The file handle's
2713             contents should be piped to the engine's native client.
2714              
2715             =head3 C<load_change>
2716              
2717             my $change = $engine->load_change($change_id);
2718              
2719             Given a deployed change ID, loads an returns a hash reference representing the
2720             change in the database. The keys should be the same as those in the hash
2721             references returned by C<deployed_changes()>. Returns C<undef> if the change
2722             has not been deployed.
2723              
2724             =head3 C<change_offset_from_id>
2725              
2726             my $change = $engine->change_offset_from_id( $change_id, $offset );
2727              
2728             Given a change ID and an offset, returns a hash reference of the data for a
2729             deployed change (with the same keys as defined for C<deployed_changes()>) in
2730             the current project that was deployed C<$offset> steps before the change
2731             identified by C<$change_id>. If C<$offset> is C<0> or C<undef>, the change
2732             represented by C<$change_id> should be returned (just like C<load_change()>).
2733             Otherwise, the change returned should be C<$offset> steps from that change ID,
2734             where C<$offset> may be positive (later step) or negative (earlier step).
2735             Returns C<undef> if the change was not found or if the offset is more than the
2736             number of changes before or after the change, as appropriate.
2737              
2738             =head3 C<change_id_offset_from_id>
2739              
2740             my $id = $engine->change_id_offset_from_id( $change_id, $offset );
2741              
2742             Like C<change_offset_from_id()> but returns the change ID rather than the
2743             change object.
2744              
2745             =head3 C<planned_deployed_common_ancestor_id>
2746              
2747             my $change_id = $engine->planned_deployed_common_ancestor_id;
2748              
2749             Compares the SHA1 hashes of the deploy scripts to their values at the time of
2750             deployment to the database and returns the latest change ID prior to any
2751             changes for which the values diverge. Used for the C<--modified> option to
2752             the C<revert> and C<rebase> commands.
2753              
2754             =head3 C<registry_version>
2755              
2756             Should return the current version of the target's registry.
2757              
2758             =head1 See Also
2759              
2760             =over
2761              
2762             =item L<sqitch>
2763              
2764             The Sqitch command-line client.
2765              
2766             =back
2767              
2768             =head1 Author
2769              
2770             David E. Wheeler <david@justatheory.com>
2771              
2772             =head1 License
2773              
2774             Copyright (c) 2012-2026 David E. Wheeler, 2012-2021 iovation Inc.
2775              
2776             Permission is hereby granted, free of charge, to any person obtaining a copy
2777             of this software and associated documentation files (the "Software"), to deal
2778             in the Software without restriction, including without limitation the rights
2779             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2780             copies of the Software, and to permit persons to whom the Software is
2781             furnished to do so, subject to the following conditions:
2782              
2783             The above copyright notice and this permission notice shall be included in all
2784             copies or substantial portions of the Software.
2785              
2786             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2787             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2788             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2789             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2790             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2791             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2792             SOFTWARE.
2793              
2794             =cut