File Coverage

blib/lib/GitDDL.pm
Criterion Covered Total %
statement 36 119 30.2
branch 0 42 0.0
condition 0 3 0.0
subroutine 12 26 46.1
pod 6 6 100.0
total 54 196 27.5


line stmt bran cond sub pod time code
1             package GitDDL;
2 1     1   81531 use strict;
  1         2  
  1         31  
3 1     1   5 use warnings;
  1         2  
  1         26  
4              
5 1     1   1245 use Mouse;
  1         32900  
  1         6  
6              
7             our $VERSION = '0.03';
8              
9 1     1   339 use Carp;
  1         2  
  1         67  
10 1     1   2675 use DBI;
  1         18147  
  1         69  
11 1     1   12 use File::Spec;
  1         3  
  1         25  
12 1     1   6 use File::Temp;
  1         2  
  1         87  
13 1     1   6 use Git::Repository;
  1         2  
  1         11  
14 1     1   1227 use SQL::Translator;
  1         338665  
  1         37  
15 1     1   1737 use SQL::Translator::Diff;
  1         7377  
  1         38  
16 1     1   8 use Try::Tiny;
  1         2  
  1         143  
17              
18             has work_tree => (
19             is => 'ro',
20             required => 1,
21             );
22              
23             has ddl_file => (
24             is => 'ro',
25             required => 1,
26             );
27              
28             has dsn => (
29             is => 'ro',
30             required => 1,
31             );
32              
33             has sql_filter => (
34             is => 'rw',
35             lazy => 1,
36             builder => '_build_sql_filter',
37             );
38              
39             has version_table => (
40             is => 'rw',
41             default => 'git_ddl_version',
42             );
43              
44             has _dbh => (
45             is => 'rw',
46             lazy => 1,
47             builder => '_build_dbh',
48             );
49              
50             has _git => (
51             is => 'rw',,
52             lazy => 1,
53             builder => '_build_git',
54             );
55              
56 1     1   6 no Mouse;
  1         2  
  1         9  
57              
58             sub check_version {
59 0     0 1   my ($self) = @_;
60 0           $self->database_version eq $self->ddl_version;
61             }
62              
63             sub database_version {
64 0     0 1   my ($self) = @_;
65              
66 0 0         croak sprintf 'invalid version_table: %s', $self->version_table
67             unless $self->version_table =~ /^[a-zA-Z_]+$/;
68              
69 0           my ($version) =
70             $self->_dbh->selectrow_array('SELECT version FROM ' . $self->version_table);
71              
72 0 0         if (defined $version) {
73 0           return $version;
74             }
75             else {
76 0           croak "Failed to get database version, please deploy first";
77             }
78             }
79              
80             sub ddl_version {
81 0     0 1   my ($self) = @_;
82 0           $self->_git->run('log', '-n', '1', '--pretty=format:%H', '--', $self->ddl_file);
83             }
84              
85             sub deploy {
86 0     0 1   my ($self) = @_;
87              
88             my $version = try {
89 0     0     open my $fh, '>', \my $stderr;
90 0           local *STDERR = $fh;
91 0           $self->database_version;
92 0           close $fh;
93 0           };
94              
95 0 0         if ($version) {
96 0           croak "database already deployed, use upgrade_database instead";
97             }
98              
99 0 0         croak sprintf 'invalid version_table: %s', $self->version_table
100             unless $self->version_table =~ /^[a-zA-Z_]+$/;
101              
102 0           $self->_do_sql($self->_slurp(File::Spec->catfile($self->work_tree, $self->ddl_file)));
103              
104 0           $self->_do_sql(<<"__SQL__");
105 0           CREATE TABLE @{[ $self->version_table ]} (
106             version VARCHAR(40) NOT NULL
107             );
108             __SQL__
109              
110 0 0         $self->_dbh->do(
111 0           "INSERT INTO @{[ $self->version_table ]} (version) VALUES (?)", {}, $self->ddl_version
112             ) or croak $self->_dbh->errstr;
113             }
114              
115             sub diff {
116 0     0 1   my ($self) = @_;
117              
118 0 0         if ($self->check_version) {
119 0           croak 'ddl_version == database_version, should no differences';
120             }
121              
122 0           my $dsn0 = $self->dsn->[0];
123             my $db
124             = $dsn0 =~ /:mysql:/ ? 'MySQL'
125             : $dsn0 =~ /:Pg:/ ? 'PostgreSQL'
126 0 0         : do { my ($d) = $dsn0 =~ /dbi:(.*?):/; $d };
  0 0          
  0            
127              
128 0           my $tmp_fh = File::Temp->new;
129 0           $self->_dump_sql_for_specified_commit($self->database_version, $tmp_fh->filename);
130              
131 0           my $source_sql = $self->sql_filter->($self->_slurp($tmp_fh->filename));
132 0           my $source = SQL::Translator->new;
133 0 0         $source->parser($db) or croak $source->error;
134 0 0         $source->translate(\$source_sql) or croak $source->error;
135              
136 0           my $target_sql = $self->sql_filter->(
137             $self->_slurp(File::Spec->catfile($self->work_tree, $self->ddl_file))
138             );
139 0           my $target = SQL::Translator->new;
140 0 0         $target->parser($db) or croak $target->error;
141 0 0         $target->translate(\$target_sql) or croak $target->error;
142              
143 0           my $diff = SQL::Translator::Diff->new({
144             output_db => $db,
145             source_schema => $source->schema,
146             target_schema => $target->schema,
147             })->compute_differences->produce_diff_sql;
148              
149             # ignore first line
150 0           $diff =~ s/.*?\n//;
151              
152 0           $diff
153             }
154              
155             sub upgrade_database {
156 0     0 1   my ($self) = @_;
157              
158 0           $self->_do_sql($self->diff);
159              
160 0 0         $self->_dbh->do(
161 0           "UPDATE @{[ $self->version_table ]} SET version = ?", {}, $self->ddl_version
162             ) or croak $self->_dbh->errstr;
163             }
164              
165             sub _build_dbh {
166 0     0     my ($self) = @_;
167              
168             # support on_connect_do
169 0           my $on_connect_do;
170 0 0         if (ref $self->dsn->[-1] eq 'HASH') {
171 0           $on_connect_do = delete $self->dsn->[-1]{on_connect_do};
172             }
173              
174 0 0         my $dbh = DBI->connect(@{ $self->dsn })
  0            
175             or croak $DBI::errstr;
176              
177 0 0         if ($on_connect_do) {
178 0 0         if (ref $on_connect_do eq 'ARRAY') {
179             $dbh->do($_) || croak $dbh->errstr
180 0   0       for @$on_connect_do;
181             }
182             else {
183 0 0         $dbh->do($on_connect_do) or croak $dbh->errstr;
184             }
185             }
186              
187 0           $dbh;
188             }
189              
190             sub _build_git {
191 0     0     my ($self) = @_;
192 0           Git::Repository->new( work_tree => $self->work_tree );
193             }
194              
195             sub _build_sql_filter {
196 0     0     my ($self) = @_;
197 0     0     sub { shift };
  0            
198             }
199              
200             sub _do_sql {
201 0     0     my ($self, $sql) = @_;
202              
203 0           my @statements = map { "$_;" } grep { /\S+/ } split ';', $sql;
  0            
  0            
204 0           for my $statement (@statements) {
205 0 0         $self->_dbh->do($statement)
206             or croak $self->_dbh->errstr;
207             }
208             }
209              
210             sub _slurp {
211 0     0     my ($self, $file) = @_;
212              
213 0 0         open my $fh, '<', $file or croak sprintf 'Cannot open file: %s, %s', $file, $!;
214 0           my $data = do { local $/; <$fh> };
  0            
  0            
215 0           close $fh;
216              
217 0           $data;
218             }
219              
220             sub _dump_sql_for_specified_commit {
221 0     0     my ($self, $commit_hash, $outfile) = @_;
222              
223 0           my ($mode, $type, $blob_hash) = split /\s+/, scalar $self->_git->run(
224             'ls-tree', $commit_hash, '--', $self->ddl_file,
225             );
226              
227 0           my $sql = $self->_git->run('cat-file', 'blob', $blob_hash);
228              
229 0 0         open my $fh, '>', $outfile or croak $!;
230 0           print $fh $sql;
231 0           close $fh;
232             }
233              
234             __PACKAGE__->meta->make_immutable;
235              
236             __END__