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 )