App-Sqitch

 view release on metacpan or  search on metacpan

lib/App/Sqitch/Engine.pm  view on Meta::CPAN

    isa     => Maybe[Str],
    lazy    => 1,
    default => sub {
        my $self = shift;
        $self->target->username || $self->_def_user
    },
);

has password => (
    is      => 'ro',
    isa     => Maybe[Str],
    lazy    => 1,
    default => sub {
        my $self = shift;
        $self->target->password || $self->_def_pass
    },
);

sub _def_user { }
sub _def_pass { }

sub registry_destination { shift->destination }

has start_at => (
    is  => 'rw',
    isa => Str
);

has no_prompt => (
    is      => 'rw',
    isa     => Bool,
    trigger => sub {
        # Deprecation notice added Feb 2023
        warnings::warnif(
            "deprecated",
            "Engine::no_prompt is deprecated and will be removed in a future release\n"
            . "Use direct arguments to revert() instead",
        );
    }
);

has prompt_accept => (
    is      => 'rw',
    isa     => Bool,
    trigger => sub {
        # Deprecation notice added Feb 2023
        warnings::warnif(
            "deprecated",
            "Engine::prompt_accept is deprecated and will be removed in a future release\n"
            . "Use direct arguments to revert() instead",
        );
    }
);

has log_only => (
    is      => 'rw',
    isa     => Bool,
    default => 0,
);

has with_verify => (
    is      => 'rw',
    isa     => Bool,
    default => 0,
);

has max_name_length => (
    is      => 'rw',
    isa     => Int,
    default => 0,
    lazy    => 1,
    default => sub {
        my $plan = shift->plan;
        max map {
            length $_->format_name_with_tags
        } $plan->changes;
    },
);

has plan => (
    is       => 'rw',
    isa      => Plan,
    lazy     => 1,
    default  => sub { shift->target->plan }
);

has _variables => (
    is      => 'rw',
    isa     => HashRef[Str],
    default => sub { {} },
);

# Usually expressed as an integer, but made a number for the purposes of
# shorter test run times.
has lock_timeout => (
    is      => 'rw',
    isa     => Num,
    default => default_lock_timeout,
);

has _locked => (
    is      => 'rw',
    isa     => Bool,
    default => 0,
);

has _no_registry => (
    is      => 'rw',
    isa     => Bool,
    default => 0,
);

sub variables       { %{ shift->_variables }       }
sub set_variables   {    shift->_variables({ @_ }) }
sub clear_variables { %{ shift->_variables } = ()  }

sub default_registry { 'sqitch' }

sub load {
    my ( $class, $p ) = @_;

lib/App/Sqitch/Engine.pm  view on Meta::CPAN

            $sqitch->debug(__ 'Would revert the following changes:');
            map { $sqitch->debug($_) } @change_descriptions;
            hurl {
                ident   => 'revert:confirm',
                message => __ 'Nothing reverted',
                exitval => 1,
            } unless $sqitch->ask_yes_no(__x(
                'Revert changes to {change} from {destination}?',
                change      => $change->format_name_with_tags,
                destination => $self->destination,
            ), $prompt_default );
        }
    } else {
        # NB this is an array of unblessed references, not of
        # Sqitch::Plan::Change references.
        @changes = $self->deployed_changes or do {
            $sqitch->info(__ 'Nothing to revert (nothing deployed)');
            return $self;
        };
        my @change_descriptions =
            map { $self->_format_deployed_change_name_with_tags($_) } @changes;

        unless ($prompt) {
            $sqitch->info(__x(
                'Reverting all changes from {destination}',
                destination => $self->destination,
            ));
            $sqitch->debug(__ 'Will revert the following changes:');
            map { $sqitch->debug($_) } @change_descriptions;
        } else {
            $sqitch->debug(__ 'Would revert the following changes:');
            map { $sqitch->debug($_) } @change_descriptions;
            hurl {
                ident   => 'revert',
                message => __ 'Nothing reverted',
                exitval => 1,
            } unless $sqitch->ask_yes_no(__x(
                'Revert all changes from {destination}?',
                destination => $self->destination,
            ), $prompt_default );
        }
    }

    # Make change objects and check that all dependencies will be satisfied.
    @changes = reverse $self->_load_changes( @changes );
    $self->check_revert_dependencies(@changes);

    # Do we want to support modes, where failures would re-deploy to previous
    # tag or all the way back to the starting point? This would be very much
    # like deploy() mode. I'm thinking not, as a failure on a revert is not
    # something you generally want to recover from by deploying back to where
    # you started. But maybe I'm wrong?
    $self->max_name_length(
        max map { length $_->format_name_with_tags } @changes
    );
    $self->revert_change($_) for @changes;

    return $self;
}

sub verify {
    my ( $self, $from, $to ) = @_;
    # $from = verify changes after and including this one, or if undefined starting from the first change.
    # $to = verify changes up to but not including this one, or if undefined up to all changes.

    $self->_check_registry;
    my $sqitch   = $self->sqitch;
    my $plan     = $self->plan;
    my @changes  = $self->_load_changes( $self->deployed_changes );

    $sqitch->info(__x(
        'Verifying {destination}',
        destination => $self->destination,
    ));

    if (!@changes) {
        my $msg = $plan->count
            ? __ 'No changes deployed'
            : __ 'Nothing to verify (no planned or deployed changes)';
        $sqitch->info($msg);
        return $self;
    }

    if ($plan->count == 0) {
        # Oy, there are deployed changes, but not planned!
        hurl verify => __ 'There are deployed changes, but none planned!';
    }

    # Figure out where to start and end relative to the plan.
    my $from_idx = $self->_from_idx('verify', $from, \@changes);
    my $to_idx = $self->_to_idx('verify', $to, \@changes);

    # Run the verify tests.
    if ( my $count = $self->_verify_changes($from_idx, $to_idx, !$to, @changes) ) {
        # Emit a quick report.
        # XXX Consider coloring red.
        my $num_changes = 1 + $to_idx - $from_idx;
        $num_changes = @changes if @changes > $num_changes;
        my $msg = __ 'Verify Summary Report';
        $sqitch->emit("\n", $msg);
        $sqitch->emit('-' x length $msg);
        $sqitch->emit(__x 'Changes: {number}', number => $num_changes );
        $sqitch->emit(__x 'Errors:  {number}', number => $count );
        hurl verify => __ 'Verify failed';
    }

    # Success!
    # XXX Consider coloring green.
    $sqitch->emit(__ 'Verify successful');

    return $self;
}

sub _from_idx {
    my ( $self, $ident, $from, $changes) = @_;
    return 0 unless defined $from;
    return $self->_trim_to($ident, $from, $changes)
}

sub _to_idx {
    my ( $self, $ident, $to, $changes) = @_;
    my $plan = $self->plan;
    return $self->_trim_to($ident, $to, $changes, 1) if defined $to;
    if (my $id = $self->latest_change_id) {
        return $plan->index_of( $id ) // $plan->count - 1;
    }
    return $plan->count - 1;
}

sub _trim_to {
    my ( $self, $ident, $key, $changes, $pop ) = @_;
    my $sqitch = $self->sqitch;
    my $plan   = $self->plan;

    # Find the to change in the database.
    my $to_id = $self->change_id_for_key( $key ) || hurl $ident => (
        $plan->contains( $key ) ? __x(
            'Change "{change}" has not been deployed',
            change => $key,
        ) : __x(
            'Cannot find "{change}" in the database or the plan',
            change => $key,
        )
    );

    # Find the change in the plan.
    my $to_idx = $plan->index_of( $to_id ) // hurl $ident => __x(
        'Change "{change}" is deployed, but not planned',
        change => $key,
    );

    # Pop or shift changes till we find the change we want.
    if ($pop) {
        pop @{ $changes }   while $changes->[-1]->id ne $to_id;
    } else {
        shift @{ $changes } while $changes->[0]->id  ne $to_id;
    }

    # We good.
    return $to_idx;
}

sub _verify_changes {
    my $self     = shift;
    my $from_idx = shift;
    my $to_idx   = shift;
    my $pending  = shift;
    my $sqitch   = $self->sqitch;
    my $plan     = $self->plan;
    my $errcount = 0;
    my $i        = -1;
    my @seen;

    my $max_name_len = max map {
        length $_->format_name_with_tags
    } @_, map { $plan->change_at($_) } $from_idx..$to_idx;

    for my $change (@_) {
        $i++;
        my $errs     = 0;
        my $reworked = 0;
        my $name     = $change->format_name_with_tags;
        $sqitch->emit_literal(
            "  * $name ..",
            '.' x ($max_name_len - length $name), ' '
        );

        my $plan_index = $plan->index_of( $change->id );
        if (defined $plan_index) {
            push @seen => $plan_index;
            if ( $plan_index != ($from_idx + $i) ) {
                $sqitch->comment(__ 'Out of order');
                $errs++;
            }
            # Is it reworked?
            $reworked = $plan->change_at($plan_index)->is_reworked;
        } else {
            $sqitch->comment(__ 'Not present in the plan');
            $errs++;
        }

        # Run the verify script.
        try { $self->verify_change( $change ) } catch {
            $sqitch->comment(eval { $_->message } // $_);
            $errs++;
        } unless $reworked;

        # Emit pass/fail and add to the total error count.
        $sqitch->emit( $errs ? __ 'not ok' : __ 'ok' );
        $errcount += $errs;
    }

    # List any undeployed changes.
    for my $idx ($from_idx..$to_idx) {
        next if defined first { $_ == $idx } @seen;
        my $change = $plan->change_at( $idx );
        my $name   = $change->format_name_with_tags;
        $sqitch->emit_literal(
            "  * $name ..",
            '.' x ($max_name_len - length $name), ' ',
            __ 'not ok', ' '
        );
        $sqitch->comment(__ 'Not deployed');
        $errcount++;
    }

    # List any pending changes.
    if ($pending && $to_idx < ($plan->count - 1)) {
        if (my @pending = map {
            $plan->change_at($_)
        } ($to_idx + 1)..($plan->count - 1) ) {
            $sqitch->emit(__n(
                'Undeployed change:',
                'Undeployed changes:',
                @pending,
            ));

            $sqitch->emit( '  * ', $_->format_name_with_tags ) for @pending;
        }
    }

    return $errcount;
}

sub verify_change {
    my ( $self, $change ) = @_;
    my $file = $change->verify_file;
    if (-e $file) {
        return try { $self->run_verify($file) }
        catch {
            hurl {
                ident => 'verify',
                previous_exception => $_,
                message => __x(
                    'Verify script "{script}" failed.',
                    script => $file,
                ),
            };
        };
    }

    # The file does not exist. Complain, but don't die.
    $self->sqitch->vent(__x(
        'Verify script {file} does not exist',
        file => $file,
    ));

    return $self;
}

sub run_deploy  { shift->run_file(@_) }
sub run_revert  { shift->run_file(@_) }
sub run_verify  { shift->run_file(@_) }
sub run_upgrade { shift->run_file(@_) }

sub check_deploy_dependencies {
    my ( $self, $plan, $to_index ) = @_;
    my $from_index = $plan->position + 1;
    $to_index    //= $plan->count - 1;
    my @changes = map { $plan->change_at($_) } $from_index..$to_index;
    my (%seen, @conflicts, @required);

    for my $change (@changes) {
        # Check for conflicts.
        push @conflicts => grep {
            $seen{ $_->id // '' } || $self->change_id_for_depend($_)
        } $change->conflicts;

        # Check for prerequisites.
        push @required => grep { !$_->resolved_id(do {
            if ( my $req = $seen{ $_->id // '' } ) {
                $req->id;
            } else {
                $self->change_id_for_depend($_);
            }
        }) } $change->requires;
        $seen{ $change->id } = $change;
    }

    if (@conflicts or @required) {
        require List::MoreUtils;
        my $listof = sub { List::MoreUtils::uniq(map { $_->as_string } @_) };
        # Dependencies not satisfied. Put together the error messages.
        my @msg;
        push @msg, __nx(
            'Conflicts with previously deployed change: {changes}',
            'Conflicts with previously deployed changes: {changes}',
            scalar @conflicts,
            changes => join ' ', @conflicts,
        ) if @conflicts = $listof->(@conflicts);

        push @msg, __nx(
            'Missing required change: {changes}',
            'Missing required changes: {changes}',
            scalar @required,
            changes => join ' ', @required,
        ) if @required = $listof->(@required);

        hurl deploy => join "\n" => @msg;
    }

    # Make sure nothing isn't already deployed.
    if ( my @ids = $self->are_deployed_changes(@changes) ) {
        hurl deploy => __nx(
            'Change "{changes}" has already been deployed',
            'Changes have already been deployed: {changes}',
            scalar @ids,
            changes => join ', ', map { $seen{$_}->format_name_with_tags . " ($_)" } @ids
        );
    }

    return $self;
}

lib/App/Sqitch/Engine.pm  view on Meta::CPAN

    } elsif (my $state = $self->current_state) {
        my $idx = $plan->index_of($state->{change_id}) // hurl plan => __x(
            'Cannot find change {id} ({change}) in {file}',
            id     => $state->{change_id},
            change => join(' ', $state->{change}, @{ $state->{tags} || [] }),
            file   => $plan->file,
        );

        # Upgrade the registry if there is no script_hash column.
        unless ( exists $state->{script_hash} ) {
            $self->upgrade_registry;
            $state->{script_hash} = $state->{change_id};
        }

        # Update the script hashes if they're the same as the change ID.
        # DEPRECATTION: Added in v0.998 (Jan 2015, c86cba61c); consider removing
        # in the future when all databases are likely to be updated already.
        $self->_update_script_hashes if $state->{script_hash}
            && $state->{script_hash} eq $state->{change_id};

        $plan->position($idx);
        my $change = $plan->change_at($idx);
        if (my @tags = $change->tags) {
            $self->log_new_tags($change);
            $self->start_at( $change->format_name . $tags[-1]->format_name );
        } else {
            $self->start_at( $change->format_name );
        }

    } else {
        $plan->reset;
    }
    return $plan;
}

sub is_deployed {
    my ($self, $thing) = @_;
    return $thing->isa('App::Sqitch::Plan::Tag')
        ? $self->is_deployed_tag($thing)
        : $self->is_deployed_change($thing);
}

sub deploy_change {
    my ( $self, $change ) = @_;
    my $sqitch = $self->sqitch;
    my $name = $change->format_name_with_tags;
    $sqitch->info_literal(
        "  + $name ..",
        '.' x ($self->max_name_length - length $name), ' '
    );
    $self->begin_work($change);

    return try {
        my $file = $change->deploy_file;
        hurl deploy => __x(
            'Deploy script {file} does not exist',
            file => $file,
        ) unless -e $file;
        $self->run_deploy($file) unless $self->log_only;
        try {
            $self->verify_change( $change ) if $self->with_verify;
            $self->log_deploy_change($change);
            $sqitch->info(__ 'ok');
        } catch {
            # Oy, logging or verify failed. Rollback.
            $sqitch->vent(eval { $_->message } // $_);
            $self->rollback_work($change);

            # Begin work and run the revert.
            try {
                # Don't bother displaying the reverting change name.
                # $self->sqitch->info('  - ', $change->format_name_with_tags);
                $self->begin_work($change);
                $self->run_revert($change->revert_file) unless $self->log_only;
            } catch {
                # Oy, the revert failed. Just emit the error.
                $sqitch->vent(eval { $_->message } // $_);
            };
            hurl private => __ 'Deploy failed';
        };
    } finally {
        $self->finish_work($change);
    } catch {
        $self->log_fail_change($change);
        $sqitch->info(__ 'not ok');
        die $_;
    };
}

sub revert_change {
    my ( $self, $change ) = @_;
    my $sqitch = $self->sqitch;
    my $name   = $change->format_name_with_tags;
    $sqitch->info_literal(
        "  - $name ..",
        '.' x ($self->max_name_length - length $name), ' '
    );

    $self->begin_work($change);

    try {
        my $file = $change->revert_file;
        hurl revert => __x(
            'Revert script {file} does not exist',
            file => $file,
        ) unless -e $file;
        $self->run_revert($file) unless $self->log_only;
        try {
            $self->log_revert_change($change);
            $sqitch->info(__ 'ok');
        } catch {
            # Oy, our logging died. Rollback and revert this change.
            $self->sqitch->vent(eval { $_->message } // $_);
            $self->rollback_work($change);
            hurl revert => 'Revert failed';
        };
    } finally {
        $self->finish_work($change);
    } catch {
        $sqitch->info(__ 'not ok');
        die $_;
    };
}

sub lock_destination {

lib/App/Sqitch/Engine.pm  view on Meta::CPAN

=head2 Constructors

=head3 C<load>

  my $cmd = App::Sqitch::Engine->load(%params);

A factory method for instantiating Sqitch engines. It loads the subclass for
the specified engine and calls C<new>, passing the Sqitch object. Supported
parameters are:

=over

=item C<sqitch>

The App::Sqitch object driving the whole thing.

=back

=head3 C<new>

  my $engine = App::Sqitch::Engine->new(%params);

Instantiates and returns a App::Sqitch::Engine object.

=head2 Instance Accessors

=head3 C<sqitch>

The current Sqitch object.

=head3 C<target>

An L<App::Sqitch::Target> object identifying the database target, usually
derived from the name of target specified on the command-line, or the default.

=head3 C<uri>

A L<URI::db> object representing the target database. Defaults to a URI
constructed from the L<App::Sqitch> C<db_*> attributes.

=head3 C<destination>

A string identifying the target database. Usually the same as the C<target>,
unless it's a URI with the password included, in which case it returns the
value of C<uri> with the password removed.

=head3 C<registry>

The name of the registry schema or database.

=head3 C<start_at>

The point in the plan from which to start deploying changes.

=head3 C<log_only>

Boolean indicating whether or not to log changes I<without running deploy or
revert scripts>. This is useful for an existing database schema that needs to
be converted to Sqitch. False by default.

=head3 C<with_verify>

Boolean indicating whether or not to run the verification script after each
deploy script. False by default.

=head3 C<variables>

A hash of engine client variables to be set. May be set and retrieved as a
list.

=head2 Instance Methods

=head3 C<username>

  my $username = $engine->username;

The username to use to connect to the database, for engines that require
authentication. The username is looked up in the following places, returning
the first to have a value:

=head3 C<lock_timeout>

Number of seconds to C<lock_destination()> to wait to acquire a lock before
timing out. Defaults to 60.

=over

=item 1.

The C<$SQITCH_USERNAME> environment variable.

=item 2.

The username from the target URI.

=item 3.

An engine-specific default password, which may be derived from an environment
variable, engine configuration file, the system user, or none at all.

=back

See L<sqitch-authentication> for details and best practices for Sqitch engine
authentication.

=head3 C<password>

  my $password = $engine->password;

The password to use to connect to the database, for engines that require
authentication. The password is looked up in the following places, returning
the first to have a value:

=over

=item 1.

The C<$SQITCH_PASSWORD> environment variable.

=item 2.

lib/App/Sqitch/Engine.pm  view on Meta::CPAN


Get, set, and clear engine variables. Variables are defined as key/value pairs
to be passed to the engine client in calls to C<deploy> and C<revert>, if the
client supports variables. For example, the
L<PostgreSQL|App::Sqitch::Engine::pg> and
L<Vertica|App::Sqitch::Engine::vertica> engines pass all the variables to
their C<psql> and C<vsql> clients via the C<--set> option, while the
L<MySQL engine|App::Sqitch::Engine::mysql> engine sets them via the C<SET>
command and the L<Oracle engine|App::Sqitch::Engine::oracle> engine sets them
via the SQL*Plus C<DEFINE> command.

=head3 C<deploy>

  $engine->deploy($to_change);
  $engine->deploy($to_change, $mode);
  $engine->deploy($to_change, $mode);

Deploys changes to the target database, starting with the current deployment
state, and continuing to C<$to_change>. C<$to_change> must be a valid change
specification as passable to the C<index_of()> method of L<App::Sqitch::Plan>.
If C<$to_change> is not specified, all changes will be applied.

The second argument specifies the reversion mode in the case of deployment
failure. The allowed values are:

=over

=item C<all>

In the event of failure, revert all deployed changes, back to the point at
which deployment started. This is the default.

=item C<tag>

In the event of failure, revert all deployed changes to the last
successfully-applied tag. If no tags were applied during this deployment, all
changes will be reverted to the pint at which deployment began.

=item C<change>

In the event of failure, no changes will be reverted. This is on the
assumption that a change failure is total, and the change may be applied again.

=back

Note that, in the event of failure, if a reversion fails, the target database
B<may be left in a corrupted state>. Write your revert scripts carefully!

=head3 C<revert>

  $engine->revert;
  $engine->revert($tag);
  $engine->revert($tag);

Reverts the L<App::Sqitch::Plan::Tag> from the database, including all of its
associated changes.

Note that this method does not respect the C<$cmd.strict> configuration
variables, which therefore must be checked by the caller.

=head3 C<verify>

  $engine->verify;
  $engine->verify( $from );
  $engine->verify( $from, $to );
  $engine->verify( undef, $to );

Verifies the database against the plan. Pass in change identifiers, as
described in L<sqitchchanges>, to limit the changes to verify. For each
change, information will be emitted if:

=over

=item *

It does not appear in the plan.

=item *

It has not been deployed to the database.

=item *

It has been deployed out-of-order relative to the plan.

=item *

Its verify script fails.

=back

Changes without verify scripts will emit a warning, but not constitute a
failure. If there are any failures, an exception will be thrown once all
verifications have completed.

=head3 C<check>

  $engine->check;
  $engine->check( $from );
  $engine->check( $from, $to );
  $engine->check( undef, $to );

Compares the state of the working directory and the database by comparing the
SHA1 hashes of the deploy scripts. Fails and reports divergence for all
changes with non-matching hashes, indicating that the project deploy scripts
differ from the scripts that were used to deploy to the database.

Pass in change identifiers, as described in L<sqitchchanges>, to limit the
changes to check. For each change, information will be emitted if the SHA1
digest of the current deploy script does not match its SHA1 digest at the
time of deployment.

=head3 C<check_deploy_dependencies>

  $engine->check_deploy_dependencies;
  $engine->check_deploy_dependencies($to_index);

Validates that all dependencies will be met for all changes to be deployed,
starting with the currently-deployed change up to the specified index, or to
the last change in the plan if no index is passed. If any of the changes to be
deployed would conflict with previously-deployed changes or are missing any
required changes, an exception will be thrown. Used internally by C<deploy()>
to ensure that dependencies will be satisfied before deploying any changes.

=head3 C<check_revert_dependencies>

  $engine->check_revert_dependencies(@changes);

Validates that the list of changes to be reverted, which should be passed in
the order in which they will be reverted, are not depended upon by other
changes. If any are depended upon by other changes, an exception will be
thrown listing the changes that cannot be reverted and what changes depend on
them. Used internally by C<revert()> to ensure no dependencies will be
violated before revering any changes.

=head3 C<deploy_change>

  $engine->deploy_change($change);
  $engine->deploy_change($change);

Used internally by C<deploy()> to deploy an individual change.

=head3 C<revert_change>

  $engine->revert_change($change);
  $engine->revert_change($change);

Used internally by C<revert()> (and, by C<deploy()> when a deploy fails) to
revert an individual change.

=head3 C<verify_change>

  $engine->verify_change($change);

Used internally by C<deploy_change()> to verify a just-deployed change if
C<with_verify> is true.

=head3 C<is_deployed>

  say "Tag deployed"  if $engine->is_deployed($tag);
  say "Change deployed" if $engine->is_deployed($change);

Convenience method that dispatches to C<is_deployed_tag()> or
C<is_deployed_change()> as appropriate to its argument.

=head3 C<earliest_change>

  my $change = $engine->earliest_change;
  my $change = $engine->earliest_change($offset);

Returns the L<App::Sqitch::Plan::Change> object representing the earliest
applied change. With the optional C<$offset> argument, the returned change
will be the offset number of changes following the earliest change.


=head3 C<latest_change>

  my $change = $engine->latest_change;
  my $change = $engine->latest_change($offset);

Returns the L<App::Sqitch::Plan::Change> object representing the latest
applied change. With the optional C<$offset> argument, the returned change
will be the offset number of changes before the latest change.

=head3 C<change_for_key>

  my $change = if $engine->change_for_key($key);

Searches the deployed changes for a change corresponding to the specified key,
which should be in a format as described in L<sqitchchanges>. Throws an
exception if the key matches more than one changes. Returns C<undef> if it
matches no changes.

=head3 C<change_id_for_key>

  my $change_id = if $engine->change_id_for_key($key);

Searches the deployed changes for a change corresponding to the specified key,
which should be in a format as described in L<sqitchchanges>, and returns the
change's ID. Throws an exception if the key matches more than one change.
Returns C<undef> if it matches no changes.

=head3 C<change_for_key>

  my $change = if $engine->change_for_key($key);

Searches the list of deployed changes for a change corresponding to the
specified key, which should be in a format as described in L<sqitchchanges>.
Throws an exception if the key matches multiple changes.

=head3 C<change_id_for_depend>

  say 'Dependency satisfied' if $engine->change_id_for_depend($depend);

Returns the change ID for a L<dependency|App::Sqitch::Plan::Depend>, if the
dependency resolves to a change currently deployed to the database. Returns

lib/App/Sqitch/Engine.pm  view on Meta::CPAN


A change name.

=item C<tag>

A tag name.

=item C<project>

A project name. Defaults to the current project.

=item C<offset>

The number of changes offset from the change found by the other parameters
should actually be returned. May be positive or negative.

=back

The order of precedence for the search is:

=over

=item 1.

Search by change ID, if passed.

=item 2.

Search by change name as of tag, if both are passed.

=item 3.

Search by change name or tag.

=back

The offset, if passed, will be applied relative to whatever change is found by
the above algorithm.

=head3 C<find_change_id>

  my $change_id = $engine->find_change_id(%params);

Like C<find_change()>, taking the same parameters, but returning an ID instead
of a change.

=head3 C<run_deploy>

  $engine->run_deploy($deploy_file);

Runs a deploy script. The implementation is just an alias for C<run_file()>;
subclasses may override as appropriate.

=head3 C<run_revert>

  $engine->run_revert($revert_file);

Runs a revert script. The implementation is just an alias for C<run_file()>;
subclasses may override as appropriate.

=head3 C<run_verify>

  $engine->run_verify($verify_file);

Runs a verify script. The implementation is just an alias for C<run_file()>;
subclasses may override as appropriate.

=head3 C<run_upgrade>

  $engine->run_upgrade($upgrade_file);

Runs an upgrade script. The implementation is just an alias for C<run_file()>;
subclasses may override as appropriate.

=head3 C<needs_upgrade>

  if ($engine->needs_upgrade) {
      $engine->upgrade_registry;
  }

Determines if the target's registry needs upgrading and returns true if it
does.

=head3 C<upgrade_registry>

  $engine->upgrade_registry;

Upgrades the target's registry, if it needs upgrading. Used by the
L<C<upgrade>|App::Sqitch::Command::upgrade> command.

=head3 C<lock_destination>

  $engine->lock_destination;

This method is called before deploying or reverting changes. It attempts
to acquire a lock in the destination database to ensure that no other
instances of Sqitch can act on the database at the same time. If it fails
to acquire the lock, it emits a message to that effect, then tries again
and waits. If it acquires the lock, it continues its work. If the attempt
times out after C<lock_timeout> seconds, it exits with an error.

The default implementation is effectively a no-op; consult the documentation
for specific engines to determine whether they have implemented support for
destination locking (by overriding C<try_lock()> and C<wait_lock()>).

=head3 C<initialized>

  $engine->initialize unless $engine->initialized;

Returns true if the database has been initialized for Sqitch, and false if it
has not.

=head3 C<initialize>

  $engine->initialize;

Initializes the target database for Sqitch by installing the Sqitch registry
schema and/or tables. Should be overridden by subclasses. This implementation
throws an exception

=head2 Abstract Instance Methods

These methods must be overridden in subclasses.

=head3 C<try_lock>



( run in 1.972 second using v1.01-cache-2.11-cpan-5a3173703d6 )