| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package App::JESP::Driver; | 
| 2 |  |  |  |  |  |  | $App::JESP::Driver::VERSION = '0.014'; | 
| 3 | 2 |  |  | 2 |  | 3308 | use Moose; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 12 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | =head1 NAME | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | App::JESP::Driver - DB Specific stuff superclass. | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | =cut | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 2 |  |  | 2 |  | 8181 | use Log::Any qw/$log/; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 13 |  | 
| 12 | 2 |  |  | 2 |  | 468 | use DBI; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 81 |  | 
| 13 | 2 |  |  | 2 |  | 1248 | use IPC::Run; | 
|  | 2 |  |  |  |  | 35561 |  | 
|  | 2 |  |  |  |  | 1270 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | has 'jesp' => ( is => 'ro' , isa => 'App::JESP', required => 1, weak_ref => 1); | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | =head2 apply_patch | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | Applies the given L<App::JESP::Patch> to the database. Dies in case of error. | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | You do NOT need to implement that in subclasses. | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | Usage: | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | $this->apply_patch( $patch ); | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | =cut | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | sub apply_patch{ | 
| 30 | 5 |  |  | 5 | 1 | 13 | my ($self, $patch) = @_; | 
| 31 | 5 |  |  |  |  | 104 | $log->info("Applying patch ".$patch->id()); | 
| 32 | 5 | 50 |  |  |  | 112 | if( my $sql = $patch->sql() ){ | 
| 33 | 5 |  |  |  |  | 20 | $log->trace("Patch is SQL='$sql'"); | 
| 34 | 5 |  |  |  |  | 24 | return $self->apply_sql( $sql ); | 
| 35 |  |  |  |  |  |  | } | 
| 36 | 0 | 0 |  |  |  |  | if( my $script_file = $patch->script_file() ){ | 
| 37 | 0 |  |  |  |  |  | $log->trace("Patch is SCRIPT='".$script_file."'"); | 
| 38 | 0 |  |  |  |  |  | return $self->apply_script( $script_file ); | 
| 39 |  |  |  |  |  |  | } | 
| 40 |  |  |  |  |  |  | } | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | =head2 apply_script | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | Runs the given 'script' file, with the given environment: | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | JESP_DSN : The full Perl DSN string | 
| 47 |  |  |  |  |  |  | JESP_USER: DB User | 
| 48 |  |  |  |  |  |  | JESP_PASSWORD: DB Password | 
| 49 |  |  |  |  |  |  | JESP_SCHEME: 'dbi' | 
| 50 |  |  |  |  |  |  | JESP_DRIVER: The name of the DBI driver in use | 
| 51 |  |  |  |  |  |  | JESP_DRIVER_DSN: the part of the DSN after the driver | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | Then the JESP_DRIVER_DSN is parsed and split into its components to generate environment variables. | 
| 54 |  |  |  |  |  |  | The most common is: | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | JESP_DATABASE: Name of the database to connect to | 
| 57 |  |  |  |  |  |  | JESP_PORT: The port to connect to. | 
| 58 |  |  |  |  |  |  | ... | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | =cut | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | sub apply_script{ | 
| 63 | 0 |  |  | 0 | 1 |  | my ($self, $script) = @_; | 
| 64 |  |  |  |  |  |  |  | 
| 65 | 0 |  |  |  |  |  | my @cmd = ( $script ); | 
| 66 |  |  |  |  |  |  |  | 
| 67 | 0 |  |  |  |  |  | my $input = ''; | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | my $on_stdout = sub{ | 
| 70 | 0 |  |  | 0 |  |  | $log->info( @_ ); | 
| 71 | 0 |  |  |  |  |  | }; | 
| 72 | 0 |  |  |  |  |  | my @stderr; | 
| 73 |  |  |  |  |  |  | my $on_stderr = sub{ | 
| 74 | 0 |  |  | 0 |  |  | $log->warn( @_ ); | 
| 75 | 0 |  |  |  |  |  | push @stderr , @_; | 
| 76 | 0 |  |  |  |  |  | }; | 
| 77 |  |  |  |  |  |  |  | 
| 78 | 0 |  |  |  |  |  | my $properties = {}; | 
| 79 | 0 |  |  |  |  |  | my ($scheme, $driver, $attr_string, $attr_hash, $driver_dsn) = DBI->parse_dsn( $self->jesp()->dsn() ); | 
| 80 | 0 |  |  |  |  |  | ref($self)->_OdbcParse( $driver_dsn , $properties , [] ); | 
| 81 | 0 |  | 0 |  |  |  | $properties->{user} ||= $self->jesp()->username(); | 
| 82 | 0 |  | 0 |  |  |  | $properties->{password} ||= $self->jesp()->password(); | 
| 83 |  |  |  |  |  |  | $properties = { | 
| 84 |  |  |  |  |  |  | %$properties, | 
| 85 | 0 | 0 |  |  |  |  | %{ defined( $attr_hash ) ? $attr_hash : {} }, | 
|  | 0 |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | dsn => $self->jesp()->dsn(), | 
| 87 |  |  |  |  |  |  | scheme => $scheme, | 
| 88 |  |  |  |  |  |  | driver => $driver, | 
| 89 |  |  |  |  |  |  | driver_dsn => $driver_dsn, | 
| 90 |  |  |  |  |  |  | attr_string => $attr_string, | 
| 91 |  |  |  |  |  |  | }; | 
| 92 | 0 |  |  |  |  |  | my %EXTRA_ENV = (); | 
| 93 |  |  |  |  |  |  | # Outside testing, be verbose. | 
| 94 | 0 | 0 | 0 |  |  |  | $EXTRA_ENV{IPCRUNDEBUG} = 'basic' unless( $ENV{AUTOMATED_TESTING} || $ENV{HARNESS_ACTIVE} ); | 
| 95 |  |  |  |  |  |  | # Transfer all the DB properties | 
| 96 | 0 |  |  |  |  |  | foreach my $key ( keys %{$properties} ){ | 
|  | 0 |  |  |  |  |  |  | 
| 97 | 0 | 0 |  |  |  |  | if( $properties->{$key} ){ | 
| 98 | 0 |  |  |  |  |  | $EXTRA_ENV{'JESP_'.uc($key)} = $properties->{$key}; | 
| 99 |  |  |  |  |  |  | } | 
| 100 |  |  |  |  |  |  | } | 
| 101 |  |  |  |  |  |  |  | 
| 102 | 0 |  |  |  |  |  | local %ENV = ( %ENV , %EXTRA_ENV ); | 
| 103 | 0 | 0 |  |  |  |  | IPC::Run::run( \@cmd , \$input , $on_stdout , $on_stderr ) or die join(' ', @cmd).": $? : ".join("\n", @stderr )."\n"; | 
| 104 |  |  |  |  |  |  | } | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | =head2 apply_sql | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | Databases and their drivers vary a lot when it comes | 
| 110 |  |  |  |  |  |  | to apply SQL patches. Some of them are just fine with sending | 
| 111 |  |  |  |  |  |  | a blog of SQL to the driver, even when it contains multiple | 
| 112 |  |  |  |  |  |  | statements and trigger or procedure, function definitions. | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | Some of them require a specific implementation. | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | This is the default implementation that just use the underlying DB | 
| 117 |  |  |  |  |  |  | connection to send the patch SQL content. | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | =cut | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | sub apply_sql{ | 
| 122 | 0 |  |  | 0 | 1 |  | my ($self, $sql) = @_; | 
| 123 | 0 |  |  |  |  |  | my $dbh = $self->jesp()->get_dbh()->(); | 
| 124 | 0 |  |  |  |  |  | my $ret = $dbh->do( $sql ); | 
| 125 | 0 | 0 |  |  |  |  | return  defined($ret) ? $ret : confess( $dbh->errstr() ); | 
| 126 |  |  |  |  |  |  | } | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | # Shamelessly copied from DBD-mysql-4.043/lib/DBD/mysql.pm | 
| 130 |  |  |  |  |  |  | sub _OdbcParse { | 
| 131 | 0 |  |  | 0 |  |  | my($class, $dsn, $hash, $args) = @_; | 
| 132 | 0 |  |  |  |  |  | my($var, $val); | 
| 133 | 0 | 0 |  |  |  |  | if (!defined($dsn)) { | 
| 134 | 0 |  |  |  |  |  | return; | 
| 135 |  |  |  |  |  |  | } | 
| 136 | 0 |  |  |  |  |  | while (length($dsn)) { | 
| 137 | 0 | 0 |  |  |  |  | if ($dsn =~ /([^:;]*\[.*]|[^:;]*)[:;](.*)/) { | 
| 138 | 0 |  |  |  |  |  | $val = $1; | 
| 139 | 0 |  |  |  |  |  | $dsn = $2; | 
| 140 | 0 |  |  |  |  |  | $val =~ s/\[|]//g; # Remove [] if present, the rest of the code prefers plain IPv6 addresses | 
| 141 |  |  |  |  |  |  | } else { | 
| 142 | 0 |  |  |  |  |  | $val = $dsn; | 
| 143 | 0 |  |  |  |  |  | $dsn = ''; | 
| 144 |  |  |  |  |  |  | } | 
| 145 | 0 | 0 |  |  |  |  | if ($val =~ /([^=]*)=(.*)/) { | 
| 146 | 0 |  |  |  |  |  | $var = $1; | 
| 147 | 0 |  |  |  |  |  | $val = $2; | 
| 148 | 0 | 0 | 0 |  |  |  | if ($var eq 'hostname'  ||  $var eq 'host') { | 
|  |  | 0 | 0 |  |  |  |  | 
| 149 | 0 |  |  |  |  |  | $hash->{'host'} = $val; | 
| 150 |  |  |  |  |  |  | } elsif ($var eq 'db'  ||  $var eq 'dbname') { | 
| 151 | 0 |  |  |  |  |  | $hash->{'database'} = $val; | 
| 152 |  |  |  |  |  |  | } else { | 
| 153 | 0 |  |  |  |  |  | $hash->{$var} = $val; | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  | } else { | 
| 156 | 0 |  |  |  |  |  | foreach $var (@$args) { | 
| 157 | 0 | 0 |  |  |  |  | if (!defined($hash->{$var})) { | 
| 158 | 0 |  |  |  |  |  | $hash->{$var} = $val; | 
| 159 | 0 |  |  |  |  |  | last; | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  | } | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | __PACKAGE__->meta()->make_immutable(); |