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