App-JESP
view release on metacpan or search on metacpan
lib/App/JESP.pm view on Meta::CPAN
}
sub install{
my ($self) = @_;
# First try to select from $self->patches_table_name
my $dbh = $self->dbix_simple->dbh();
my $patches = eval{
$self->_protect_select(
sub{ $self->dbix_simple()->query('SELECT '.$dbh->quote_identifier('id').' FROM '.$dbh->quote_identifier($self->patches_table_name)); },
"CANNOT_SELECT_FROM_META");
};
if( my $err = $@ ){
unless( $err =~ /^CANNOT_SELECT_FROM_META\n/ ){
$log->critical("Unexpected error from _protect_select. Run again in verbose mode.");
die $err;
}
$log->info("Innitiating meta tables");
$self->_apply_meta_patch( $self->meta_patches()->[0] );
}
$log->info("Uprading meta tables");
# Select all meta patches and make sure all of mine are applied.
my $applied_patches = { $self->dbix_simple()
->select( $self->patches_table_name() , [ 'id', 'applied_datetime' ] , { id => { -like => $self->prefix().'meta_%' } } )
->map_hashes('id')
};
foreach my $meta_patch ( @{ $self->meta_patches() } ){
if( $applied_patches->{$meta_patch->{id}} ){
$log->debug("Patch ".$meta_patch->{id}." already applied on ".$applied_patches->{$meta_patch->{id}}->{applied_datetime});
next;
}
$self->_apply_meta_patch( $meta_patch );
}
$log->info("Done upgrading meta tables");
return 1;
}
sub _applied_patches{
my ($self) = @_;
my $db = $self->dbix_simple();
my $applied_patches_result = $self->_protect_select(
sub{
$db->select( $self->patches_table_name() , [ 'id', 'applied_datetime' ] , {} , 'applied_datetime' );
}, "ERROR querying meta schema. Did you forget to run 'install'?");
return { $applied_patches_result->map_hashes('id') };
}
sub status{
my ($self) = @_;
my $plan_patches = $self->plan()->patches();
$log->debug("Got ".scalar(@$plan_patches)." patches to inspect");
my $applied_patches = $self->_applied_patches();
foreach my $plan_patch ( @$plan_patches ){
if( my $applied_patch = delete $applied_patches->{$plan_patch->id()} ){
$plan_patch->applied_datetime( $applied_patch->{applied_datetime} );
$log->info( $self->colorizer->colored('âï¸', "bold green")." ".sprintf('%-52s', "'".$plan_patch->id()."'" )." Applied on ".$plan_patch->applied_datetime() );
}else{
$log->info( $self->colorizer->colored('â ', "bold yellow")." ".sprintf('%-52s', "'".$plan_patch->id()."'" )." Not applied (yet?)" );
}
}
my $meta_prefix = $self->prefix().'meta';
my $plan_orphans = [ grep{ $_ !~ /^$meta_prefix/ } keys %$applied_patches ];
if( @$plan_orphans ){
$log->warn($self->colorizer()->colored('â ï¸', "bold red")." Got orphan patches (patches in meta table but not in plan): ".join(', ' , map{ "'$_'" } @$plan_orphans ) );
}
return {
plan_patches => $plan_patches,
plan_orphans => $plan_orphans,
};
}
sub deploy{
my ($self, $options) = @_;
defined($options) ||( $options = {} );
$log->info("DEPLOYING DB Patches");
my $db = $self->dbix_simple();
my $patches = $self->plan()->patches();
if( $options->{patches} ){
# Filter existing patches.
my @patch_ids = @{$options->{patches}};
$log->info("Applying only patches ".join(', ', map{ "'".$_."'" } @patch_ids)." in this order");
my $patches_by_id = { map{ $_->id() => $_ } @$patches };
my @new_patch_list = ();
foreach my $patch_id ( @patch_ids ){
my $patch = $patches_by_id->{$patch_id};
unless( $patch ){
die "Cannot find patch '$patch_id' in the plan ".$self->plan()->file().". Check the name\n";
}
push @new_patch_list , $patch;
}
$patches = \@new_patch_list;
}
my $applied_patches = $self->_applied_patches();
my $applied = 0;
foreach my $patch ( @{$patches} ){
if( my $applied_patch = $applied_patches->{$patch->id()}){
unless( $options->{force} ){
$log->debug("Patch '".$patch->id()."' has already been applied on ".$applied_patch->{applied_datetime}." skipping");
next;
}
$log->warn("Patch '".$patch->id()."' has already been applied on ".$applied_patch->{applied_datetime}." but forcing it.");
}else{
$log->info("Patch '".$patch->id()."' never applied");
}
eval{
$db->begin_work();
if( my $already_applied = $db->select( $self->patches_table_name(), '*',
{ id => $patch->id() } )->hash() ){
$db->update( $self->patches_table_name(),
{ applied_datetime => \'CURRENT_TIMESTAMP' },
{ id => $patch->id() } );
} else {
$db->insert( $self->patches_table_name() , { id => $patch->id() } );
}
unless( $options->{logonly} ){
$self->driver()->apply_patch( $patch );
}else{
$log->info("logonly mode. Patch not really applied");
}
$db->commit();
};
if( my $err = $@ ){
$log->error("Got error $err. ROLLING BACK");
$db->rollback();
die "ERROR APPLYING PATCH ".$patch->id().": $err. ".$self->colorizer()->colored("ABORTING", "bold red")."\n";
};
$log->info($self->colorizer()->colored("Patch '".$patch->id()."' applied successfully", "green"));
$applied++;
}
$log->info($self->colorizer()->colored("DONE Deploying DB Patches", "green"));
return $applied;
}
# Runs the code to return a DBIx::Simple::Result
# or die with the given error message (for humans)
#
# Mainly this is for testing that a table exists by attemtpting to select from
# it. Do NOT use that in any other cases.
sub _protect_select{
my ( $self, $code , $message) = @_;
my $result = eval{ $code->(); };
if( my $err = $@ || $result->isa('DBIx::Simple::Dummy') ){
$log->trace("Error doing select: ".( $err || $self->dbix_simple()->error() ) );
die $message."\n";
}
return $result;
}
sub _apply_meta_patch{
my ($self, $meta_patch) = @_;
$log->debug("Applying meta patch ".$meta_patch->{id});
my $sql = $meta_patch->{sql};
my $db = $self->dbix_simple();
$log->debug("Doing ".$sql);
eval{
$db->begin_work();
$db->dbh->do( $sql ) or die "Cannot do '$sql':".$db->dbh->errstr()."\n";
$db->insert( $self->patches_table_name() , { id => $meta_patch->{id} } );
$db->commit();
};
if( my $err = $@ ){
$log->error("Got error $err. ROLLING BACK");
$db->rollback();
die "ERROR APPLYING META PATCH ".$meta_patch->{id}.": $err. ABORTING\n";
};
}
__PACKAGE__->meta->make_immutable();
1;
__END__
=head1 NAME
App::JESP - Just Enough SQL Patches
=cut
=head1 SYNOPSIS
Use the command line utility:
jesp
( run in 0.410 second using v1.01-cache-2.11-cpan-d7f47b0818f )