File Coverage

lib/DBIx/Schema/Changelog.pm
Criterion Covered Total %
statement 79 81 97.5
branch 7 16 43.7
condition 2 6 33.3
subroutine 22 22 100.0
pod 2 2 100.0
total 112 127 88.1


line stmt bran cond sub pod time code
1             package DBIx::Schema::Changelog;
2              
3             =head1 NAME
4              
5             DBIx::Schema::Changelog - Continuous Database Migration
6              
7             =head1 VERSION
8              
9             Version 0.8.0
10              
11             =cut
12              
13             our $VERSION = '0.8.0';
14              
15             =head1 DESCRIPTION
16              
17             C<DBIx::Schema::Changelog> is a pure Perl module.
18              
19             Continuous Database Migration
20             A package which allows a continuous development with an application that hold the appropriate database system synchronously.
21              
22             =cut
23              
24 4     4   223268 use utf8;
  4         52  
  4         23  
25 4     4   195 use strict;
  4         11  
  4         204  
26 4     4   37 use warnings;
  4         9  
  4         197  
27 4     4   11261 use DBI;
  4         78484  
  4         513  
28 4     4   42 use File::Spec;
  4         8  
  4         135  
29 4     4   3024 use DBIx::Schema::Changelog::Changeset;
  4         14  
  4         312  
30 4     4   3507 use DBIx::Schema::Changelog::Action::Table;
  4         18  
  4         240  
31 4     4   3387 use DBIx::Schema::Changelog::Action::View;
  4         17  
  4         200  
32 4     4   2753 use DBIx::Schema::Changelog::Action::Index;
  4         16  
  4         249  
33 4     4   47 use DBIx::Schema::Changelog::Action::Constraint;
  4         9  
  4         147  
34 4     4   72 use Data::Dumper;
  4         12  
  4         353  
35 4     4   29 use Moose;
  4         11  
  4         34  
36 4     4   27934 use MooseX::HasDefaults::RO;
  4         10  
  4         389  
37 4     4   24312 use MooseX::Types::Moose qw(ArrayRef Str Defined);
  4         11  
  4         55  
38 4     4   28815 use MooseX::Types::LoadableClass qw(LoadableClass);
  4         243327  
  4         37  
39 4     4   7384 use Method::Signatures::Simple;
  4         9  
  4         54  
40              
41 4     4   5748 use Hash::MD5 qw(sum_hash);
  4         4528  
  4         631  
42              
43             has db_changelog_table => ( isa => Str, default => 'databasechangelog' );
44             has db_driver => ( isa => Str, default => 'SQLite' );
45             has file_type => ( isa => Str, default => 'Yaml' );
46             has dbh => ( isa => 'DBI::db', required => 1, );
47              
48             has table_action => (
49             lazy => 1,
50             does => 'DBIx::Schema::Changelog::Action',
51             default => method {
52             DBIx::Schema::Changelog::Action::Table->new(
53             driver => $self->driver(),
54             dbh => $self->dbh(),
55             )
56             },
57             );
58              
59             has changeset => (
60             lazy => 1,
61             isa => 'DBIx::Schema::Changelog::Changeset',
62             default => method {
63             DBIx::Schema::Changelog::Changeset->new(
64             driver => $self->driver(),
65             dbh => $self->dbh(),
66             table_action => $self->table_action(),
67             )
68             },
69             );
70              
71             has insert_dblog => (
72             isa => 'DBI::st',
73             lazy => 1,
74             default => method {
75             $self->dbh()
76             ->prepare( "INSERT INTO "
77             . $self->db_changelog_table()
78             . "(id, author, filename, md5sum, changelog) VALUES (?,?,?,?,?)"
79             )
80             },
81             );
82              
83             has loader_class => (
84             isa => LoadableClass,
85             lazy => 1,
86             default => sub {
87             'DBIx::Schema::Changelog::File::' . shift->file_type();
88             }
89             );
90              
91             has loader => (
92             does => 'DBIx::Schema::Changelog::File',
93             lazy => 1,
94             default => sub { shift->loader_class()->new(); }
95             );
96              
97             has driver_class => (
98             isa => LoadableClass,
99             lazy => 1,
100             default => sub {
101             'DBIx::Schema::Changelog::Driver::' . shift->db_driver();
102             }
103 2     2   5 );
104 2         4  
  2         78  
105 14 50       1678 has driver => (
106 14         118 lazy => 1,
107 14 50       4405 does => 'DBIx::Schema::Changelog::Driver',
108 14         7390 default => sub { shift->driver_class()->new(); }
109 14         60 );
110 14 50       1119  
111             sub _parse_log {
112 14 50       1179 my ( $self, $file ) = @_;
113             foreach ( @{ $self->loader()->load($file) } ) {
114             die "No id for changeset found" unless $_->{id};
115 14         6167 my $hash = sum_hash($_);
116             next if ( $self->_check_key( $_->{id}, $hash ) );
117             print STDOUT __PACKAGE__, " Handle changeset: $_->{id}\n";
118             my $handle_time = time();
119             $self->changeset()->handle( $_->{entries} )
120             if ( defined $_->{entries} );
121             $self->insert_dblog()
122 14     14   39 ->execute( $_->{id}, $_->{author}, $file, $hash, $VERSION )
123             or die $self->dbh()->errstr;
124 14 50       1215 print STDOUT __PACKAGE__,
125             " Changeset: $_->{id} author: $_->{author} executed. "
126             . ( time() - $handle_time ) . " \n";
127             }
128             }
129 0 0       0  
130             sub _check_key {
131 0         0 my ( $self, $id, $value ) = @_;
132             my @resp
133             = $self->dbh()
134             ->selectrow_array( "select md5sum, changelog from "
135             . $self->db_changelog_table()
136             . " where id = '$id'" )
137             or return 0;
138             die "MD5 hash changed for changeset: $id expect $value got $resp[0]"
139             if ( $resp[0] ne $value );
140             return ( @resp >= 1 );
141             }
142              
143             =head1 SUBROUTINES/METHODS
144              
145 2     2 1 5 =head2 BUILD
146 2         115  
147 2         789 Run to check driver version with installed db driver.
148              
149 2         168 Creates changelog table if it's not existing.
150              
151 2         419 =cut
152              
153             sub BUILD {
154             my $self = shift;
155             my $vendor = uc $self->dbh()->get_info(17);
156             print STDOUT __PACKAGE__, ". Db vendor $vendor. \n";
157              
158             $self->driver()->check_version( $self->dbh()->get_info(18) );
159              
160             $self->table_action()->add(
161             $self->driver()->create_changelog_table(
162             $self->dbh(), $self->db_changelog_table()
163             )
164             );
165 2     2 1 999 }
166              
167 2         107 =head2 read
168              
169             Read main changelog file and sub changelog files
170              
171 2         1156 =cut
172 2 50 33     74  
173             sub read {
174             my ( $self, $folder ) = @_;
175              
176             my $main = $self->loader()
177 2 50 33     76 ->load( File::Spec->catfile( $folder, 'changelog' ) );
178              
179             #first load templates
180             $self->table_action()->load_templates( $main->{templates} );
181             $self->table_action()->prefix(
182             ( defined $main->{prefix} && $main->{prefix} ne '' )
183             ? $main->{prefix} . '_'
184 2         45 : ''
185 2         6 );
186             $self->table_action()->postfix(
187             ( defined $main->{postfix} && $main->{postfix} ne '' )
188 4     4   8151 ? '_' . $main->{postfix}
  4         12  
  4         44  
189             : ''
190             );
191              
192             # now load changelogs
193             $self->_parse_log( File::Spec->catfile( $folder, "changelog-$_" ) )
194             foreach @{ $main->{changelogs} };
195             }
196              
197             no Moose;
198             __PACKAGE__->meta->make_immutable;
199              
200             1;
201              
202             __END__
203              
204             =head1 Synopsis
205              
206             use DBI;
207             use DBIx::Schema::Changelog;
208              
209             my $dbh = DBI->connect( "dbi:SQLite:database=league.sqlite" );
210             DBIx::Schema::Changelog->new( dbh => $dbh )->read( $FindBin::Bin . '/../changelog' );
211              
212             ...
213            
214             my $dbh = DBI->connect( "dbi:Pg:dbname=database;host=127.0.0.1", "user", "password" );
215             DBIx::Schema::Changelog->new( dbh => $dbh, db_driver => 'Pg' )->read( $FindBin::Bin . '/../changelog' );
216              
217             =head1 Motivation
218              
219             When working with several people on a large project that is bound to a database.
220             If you there and back the databases have different levels of development.
221              
222             You can keep in sync with SQL statements, but these are then incompatible with other database systems.
223              
224             =head1 Constructor and initialization
225              
226             new(...) returns an object of type C<DBIx::Schema::Changelog>.
227              
228             This is the class's constructor.
229              
230             Usage: DBIx::Schema::Changelog -> new().
231              
232             This method takes a set of parameters. Only the dbh parameter is mandatory.
233              
234             For each parameter you wish to use, call new as new(param_1 => value_1, ...).
235              
236             =over 4
237              
238             =item dbh
239              
240             This is a database handle, returned from DBI's connect() call.
241              
242             This parameter is mandatory.
243              
244             There is no default.
245              
246             =item verbose
247              
248             =back
249              
250             =head1 Method: read()
251              
252             =over 4
253              
254             =item path to changelog folder
255              
256              
257             =back
258              
259             =head1 SEE ALSO
260              
261             =head2 L<DBIx::Admin::CreateTable>
262              
263             =over 4
264              
265             The package from which the idea originated.
266              
267             =back
268              
269             =head1 AUTHOR
270              
271             Mario Zieschang, C<< <mario.zieschang at combase.de> >>
272              
273             =head1 LICENSE AND COPYRIGHT
274              
275             Copyright 2015 Mario Zieschang.
276              
277             This program is free software; you can redistribute it and/or modify it
278             under the terms of the the Artistic License (2.0). You may obtain a
279             copy of the full license at:
280              
281             L<http://www.perlfoundation.org/artistic_license_2_0>
282              
283             Any use, modification, and distribution of the Standard or Modified
284             Versions is governed by this Artistic License. By using, modifying or
285             distributing the Package, you accept this license. Do not use, modify,
286             or distribute the Package, if you do not accept this license.
287              
288             If your Modified Version has been derived from a Modified Version made
289             by someone other than you, you are nevertheless required to ensure that
290             your Modified Version complies with the requirements of this license.
291              
292             This license does not grant you the right to use any trademark, service
293             mark, trade name, or logo of the Copyright Holder.
294              
295             This license includes the non-exclusive, worldwide, free-of-charge
296             patent license to make, have made, use, offer to sell, sell, import and
297             otherwise transfer the Package with respect to any patent claims
298             licensable by the Copyright Holder that are necessarily infringed by the
299             Package. If you institute patent litigation (including a cross-claim or
300             counterclaim) against any party alleging that the Package constitutes
301             direct or contributory patent infringement, then this Artistic License
302             to you shall terminate on the date that such litigation is filed.
303              
304             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
305             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
306             THE IMPLIED WARRANTIES OF MERCHANT ABILITY, FITNESS FOR A PARTICULAR
307             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
308             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
309             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
310             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
311             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
312              
313              
314             =cut
315