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