File Coverage

blib/lib/App/Sqitch/Engine.pm
Criterion Covered Total %
statement 600 640 93.7
branch 179 204 87.7
condition 97 193 50.2
subroutine 118 123 95.9
pod 69 69 100.0
total 1063 1229 86.4


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