File Coverage

lib/DBIx/Schema/Changelog.pm
Criterion Covered Total %
statement 76 78 97.4
branch 7 16 43.7
condition 2 6 33.3
subroutine 21 21 100.0
pod 2 2 100.0
total 108 123 87.8


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