File Coverage

blib/lib/DBIx/SchemaChecksum.pm
Criterion Covered Total %
statement 122 143 85.3
branch 33 54 61.1
condition 3 8 37.5
subroutine 18 18 100.0
pod 2 3 66.6
total 178 226 78.7


line stmt bran cond sub pod time code
1             package DBIx::SchemaChecksum;
2              
3             # ABSTRACT: Manage your datebase schema via checksums
4             our $VERSION = '1.103'; # VERSION
5              
6 11     11   1290887 use 5.010;
  11         140  
7 11     11   5507 use Moose;
  11         4564787  
  11         91  
8              
9 11     11   101906 use DBI;
  11         150886  
  11         780  
10 11     11   6594 use Digest::SHA1;
  11         7778  
  11         520  
11 11     11   91 use Data::Dumper;
  11         22  
  11         626  
12 11     11   5298 use Path::Class;
  11         363670  
  11         807  
13 11     11   110 use Carp;
  11         28  
  11         606  
14 11     11   6381 use File::Find::Rule;
  11         94913  
  11         102  
15              
16             has 'dbh' => (
17             is => 'ro',
18             required => 1
19             );
20              
21             has 'catalog' => (
22             is => 'ro',
23             isa => 'Str',
24             default => '%',
25             documentation => q[might be required by some DBI drivers]
26             );
27              
28             has 'schemata' => (
29             is => 'ro',
30             isa => 'ArrayRef[Str]',
31             default => sub { ['%'] },
32             documentation => q[List of schematas to include in checksum]
33             );
34              
35             has 'sqlsnippetdir' => (
36             isa => 'Str',
37             is => 'ro',
38             documentation => q[Directory containing sql update files],
39             );
40              
41             has 'driveropts' => (
42             isa => 'HashRef',
43             is => 'ro',
44             default => sub {{}},
45             documentation => q[Driver specific options],
46             );
47              
48             has 'verbose' => (
49             is => 'rw',
50             isa => 'Bool',
51             default => 0
52             );
53              
54             has '_update_path' => (
55             is => 'rw',
56             isa => 'Maybe[HashRef]',
57             lazy_build => 1,
58             builder => '_build_update_path',
59             );
60              
61             has '_schemadump' => (
62             isa=>'Str',
63             is=>'rw',
64             lazy_build=>1,
65             clearer=>'reset_checksum',
66             builder => '_build_schemadump',
67             );
68              
69             sub BUILD {
70 15     15 0 48 my ($self) = @_;
71              
72             # Apply driver role to instance
73 15         759 my $driver = $self->dbh->{Driver}{Name};
74 15         79 my $class = __PACKAGE__.'::Driver::'.$driver;
75 15 50       109 if (Class::Load::try_load_class($class)) {
76 15         518 $class->meta->apply($self);
77             }
78 15         169018 return $self;
79             }
80              
81              
82              
83             sub checksum {
84 22     22 1 5583 my $self = shift;
85 22         768 return Digest::SHA1::sha1_hex($self->_schemadump);
86             }
87              
88              
89             sub _build_schemadump {
90 13     13   36 my $self = shift;
91              
92 13         47 my %relevants = ();
93              
94 13         37 foreach my $schema ( @{ $self->schemata } ) {
  13         489  
95 13         75 my $schema_relevants = $self->_build_schemadump_schema($schema);
96 13         37 while (my ($type,$type_value) = each %{$schema_relevants}) {
  26         128  
97 13         41 my $ref = ref($type_value);
98 13 50       73 if ($ref eq 'ARRAY') {
    50          
99 0   0     0 $relevants{$type} ||= [];
100 0         0 foreach my $value (@{$type_value}) {
  0         0  
101 0         0 push(@{$relevants{$type}}, $value);
  0         0  
102             }
103             }
104             elsif ($ref eq 'HASH') {
105 13         28 while (my ($key,$value) = each %{$type_value}) {
  33         121  
106 20         59 $relevants{$type}{$key} = $value;
107             }
108             }
109             }
110             }
111              
112 13         191 my $dumper = Data::Dumper->new( [ \%relevants ] );
113 13         656 $dumper->Sortkeys(1);
114 13         181 $dumper->Indent(1);
115 13         223 my $dump = $dumper->Dump;
116              
117 13         2670 return $dump;
118             }
119              
120              
121             sub _build_schemadump_schema {
122 13     13   45 my ($self,$schema) = @_;
123              
124 13         31 my %relevants = ();
125 13         83 $relevants{tables} = $self->_build_schemadump_tables($schema);
126              
127 13         38 return \%relevants;
128             }
129              
130              
131             sub _build_schemadump_tables {
132 13     13   52 my ($self,$schema) = @_;
133              
134 13         514 my $dbh = $self->dbh;
135              
136 13         39 my %relevants;
137 13         458 foreach my $table ( $dbh->tables( $self->catalog, $schema, '%' ) ) {
138             next
139 59 50       25606 unless $table =~ m/^"?(?<schema>[^"]+)"?\."?(?<table>[^"]+)"?$/;
140 11     11   14254 my $this_schema = $+{schema};
  11         5068  
  11         13287  
  59         395  
141 59         286 my $table = $+{table};
142              
143 59         272 my $table_data = $self->_build_schemadump_table($this_schema,$table);
144             next
145 59 100       200 unless $table_data;
146 20         107 $relevants{$this_schema.'.'.$table} = $table_data;
147             }
148              
149 13         68 return \%relevants;
150             }
151              
152              
153             sub _build_schemadump_table {
154 20     20   60 my ($self,$schema,$table) = @_;
155              
156 20         58 my %relevants = ();
157              
158 20         775 my $dbh = $self->dbh;
159              
160             # Primary key
161 20         608 my @primary_keys = $dbh->primary_key( $self->catalog, $schema, $table );
162             $relevants{primary_keys} = \@primary_keys
163 20 100       34628 if scalar @primary_keys;
164              
165             # Columns
166 20         841 my $sth_col = $dbh->column_info( $self->catalog, $schema, $table, '%' );
167 20         17760 my $column_info = $sth_col->fetchall_hashref('COLUMN_NAME');
168 20         3777 while ( my ( $column, $data ) = each %$column_info ) {
169 49         202 my $column_data = $self->_build_schemadump_column($schema,$table,$column,$data);
170 49 50       257 $relevants{columns}->{$column} = $column_data
171             if $column_data;
172             }
173              
174             # Foreign keys (only use a few selected meta-fields)
175 20         793 my $sth_fk = $dbh->foreign_key_info( undef, undef, undef, $self->catalog, $schema, $table );
176 20 50       15310 if ($sth_fk) {
177 20         51 my $fk={};
178 20         218 while (my $data = $sth_fk->fetchrow_hashref) {
179 0         0 my %useful = map { $_ => $data->{$_}} qw(UK_COLUMN_NAME UK_TABLE_NAME UK_TABLE_SCHEM);
  0         0  
180 0         0 $fk->{$data->{FK_COLUMN_NAME}} = \%useful;
181             }
182 20 50       491 $relevants{foreign_keys} = $fk if keys %$fk;
183             }
184              
185 20         1007 return \%relevants;
186             }
187              
188              
189             sub _build_schemadump_column {
190 49     49   135 my ($self,$schema,$table,$column,$data) = @_;
191              
192 49         101 my $relevants = { map { $_ => $data->{$_} } qw(COLUMN_NAME COLUMN_SIZE NULLABLE TYPE_NAME COLUMN_DEF) };
  245         588  
193              
194             # some cleanup
195 49 50       164 if (my $default = $relevants->{COLUMN_DEF}) {
196 0 0       0 if ( $default =~ /nextval/ ) {
197 0         0 $default =~ m{'([\w\.\-_]+)'};
198 0 0       0 if ($1) {
199 0         0 my $new = $1;
200 0         0 $new =~ s/^\w+\.//;
201 0         0 $default = 'nextval:' . $new;
202             }
203             }
204 0         0 $default=~s/["'\(\)\[\]\{\}]//g;
205 0         0 $relevants->{COLUMN_DEF}=$default;
206             }
207              
208 49         314 $relevants->{TYPE_NAME} =~ s/^(?:.+\.)?(.+)$/$1/g;
209              
210 49         125 return $relevants;
211             }
212              
213              
214             sub _build_update_path {
215 9     9   28 my $self = shift;
216 9         348 my $dir = $self->sqlsnippetdir;
217 9 50       42 croak("Please specify sqlsnippetdir") unless $dir;
218 9 100       368 croak("Cannot find sqlsnippetdir: $dir") unless -d $dir;
219              
220 7 50       311 say "Checking directory $dir for checksum_files" if $self->verbose;
221              
222 7         21 my %update_info;
223 7         228 my @files = File::Find::Rule->file->name('*.sql')->in($dir);
224              
225 7         9572 foreach my $file ( sort @files ) {
226 18         2118 my ( $pre, $post ) = $self->get_checksums_from_snippet($file);
227              
228 18 0 33     57 if ( !$pre && !$post ) {
229 0 0       0 say "skipping $file (has no checksums)" if $self->verbose;
230 0         0 next;
231             }
232              
233 18 100       53 if ( $pre eq $post ) {
234 3 50       17 if ( $update_info{$pre} ) {
235 3         13 my @new = ('SAME_CHECKSUM');
236 3         7 foreach my $item ( @{ $update_info{$pre} } ) {
  3         12  
237 6 50       186 push( @new, $item ) unless $item eq 'SAME_CHECKSUM';
238             }
239 3         13 $update_info{$pre} = \@new;
240             }
241             else {
242 0         0 $update_info{$pre} = ['SAME_CHECKSUM'];
243             }
244             }
245              
246 18 100 66     87 if ( $update_info{$pre}
247             && $update_info{$pre}->[0] eq 'SAME_CHECKSUM' )
248             {
249 3 50       19 if ( $post eq $pre ) {
250 3         9 splice( @{ $update_info{$pre} },
  3         24  
251             1, 0, Path::Class::File->new($file), $post );
252             }
253             else {
254 0         0 push( @{ $update_info{$pre} },
  0         0  
255             Path::Class::File->new($file), $post );
256             }
257             }
258             else {
259 15         136 $update_info{$pre} = [ Path::Class::File->new($file), $post ];
260             }
261             }
262              
263 7 100       786 return $self->_update_path( \%update_info ) if %update_info;
264 1         40 return;
265             }
266              
267              
268             sub get_checksums_from_snippet {
269 22     22 1 2064 my ($self, $filename) = @_;
270 22 100       80 die "need a filename" unless $filename;
271              
272 21         112 my %checksums;
273              
274 21 100       1084 open( my $fh, "<", $filename ) || croak "Cannot read $filename: $!";
275 20         389 while (<$fh>) {
276 114 100       616 if (m/^--\s+(pre|post)SHA1sum:?\s+([0-9A-Fa-f]{40,})\s+$/) {
277 39         215 $checksums{$1} = $2;
278             }
279             }
280 20         227 close $fh;
281 20 100       125 return map { $checksums{$_} || '' } qw(pre post);
  40         253  
282             }
283              
284             __PACKAGE__->meta->make_immutable();
285              
286             __END__
287              
288             =pod
289              
290             =encoding UTF-8
291              
292             =head1 NAME
293              
294             DBIx::SchemaChecksum - Manage your datebase schema via checksums
295              
296             =head1 VERSION
297              
298             version 1.103
299              
300             =head1 SYNOPSIS
301              
302             my $sc = DBIx::SchemaChecksum->new( dbh => $dbh );
303             print $sc->checksum;
304              
305             =head1 DESCRIPTION
306              
307             When you're dealing with several instances of the same database (eg.
308             developer, testing, stage, production), it is crucial to make sure
309             that all databases use the same schema. This can be quite an
310             hair-pulling experience, and this module should help you keep your
311             hair (if you're already bald, it won't make your hair grow back,
312             sorry...)
313              
314             C<DBIx::SchemaChecksum> gets schema information (tables, columns,
315             primary keys, foreign keys and some more depending on your DB) and
316             generates a SHA1 digest. This digest can then be used to easily verify
317             schema consistency across different databases, and to build an update
318             graph of changes. Therefor, C<DBIx::SchemaChecksum> does not requires
319             you to add a meta-table to your database to keep track of which
320             changes have already been deployed.
321              
322             B<Caveat:> The same schema might produce different checksums on
323             different database versions.
324              
325             B<Caveat:> C<DBIx::SchemaChecksum> only works with database engines
326             that support changes to the schema inside a transaction. We know this
327             works with PostgreSQL and SQLite. We know it does not work with MySQL
328             and Oracle. We don't know how other database engines behave, but would
329             be happy to hear about your experiences.
330              
331             =head2 RUNNING DBIx::SchemaChecksum
332              
333             Please take a look at the L<dbchecksum|bin/dbchecksum> script included
334             in this distribution. It provides a nice and powerful commandline
335             interface to make working with your schema a breeze.
336              
337             =head2 EXAMPLE WORKFLOW
338              
339             So you have this genious idea for a new startup that will make you
340             incredibly rich and famous...
341              
342             =head3 Collect underpants
343              
344             Usually such ideas involve a database. So you grab your L<favourite database engine|http://postgresql.org/> and start a new database:
345              
346             ~/Gnomes$ createdb gnomes # createdb is a postgres tool
347              
348             Of course this new DB is rather empty:
349              
350             gnomes=# \d
351             No relations found.
352              
353             So you think long and hard about your database schema and write it down
354              
355             ~/Gnomes$ cat sql/handcrafted_schema.sql
356             create table underpants (
357             id serial primary key,
358             type text,
359             size text,
360             color text
361             );
362              
363             But instead of going down the rabbit hole of manually keeping the
364             dev-DB on your laptop, the one on the workstation in the office, the
365             staging and the production one in sync (and don't forget all the
366             databases running on the laptops of the countless coding monkeys
367             you're going to hire after all the VC money starts flowing), you grab
368             a (free!) copy of C<DBIx::SchemaChecksum>
369              
370             ~/Gnomes$ cpanm DBIx::SchemaChecksum
371             .. wait a bit while the giant, on which shoulders we are standing, is being assembled
372             Successfully installed DBIx-SchemaChecksum
373             42 distribution installed
374              
375             Now you can create a new C<changes file>:
376              
377             ~/Gnomes$ dbchecksum new_changes_file --sqlsnippetdir sql --dsn dbi:Pg:dbname=gnomes --change_name "initial schema"
378             New change-file ready at sql/inital_schema.sql
379              
380             Let's take a look:
381              
382             ~/Gnomes$ cat sql/inital_schema.sql
383             -- preSHA1sum: 54aa14e7b7e54cce8ae07c441f6bda316aa8458c
384             -- postSHA1sum: xxx-New-Checksum-xxx
385             -- inital schema
386              
387             Each C<changes file> contains two very import "header" lines masked as a SQL comment:
388              
389             C<preSHA1sum> is the checksum of the DB schema before the changes in
390             this file have been applied. C<postSHA1sum> is (you probably guessed
391             it) the checksum we expect after the changes have been applied.
392             Currently the C<postSHA1sum> is "xxx-New-Checksum-xxx" because we have
393             neither defined nor run the changes yet.
394              
395             So let's append the handcrafted schema from earlier to the change file:
396              
397             ~/Gnomes$ cat sql/handcrafted_schema.sql >> sql/inital_schema.sql
398              
399             The C<changes file> now looks like this:
400              
401             ~/Gnomes$ cat sql/inital_schema.sql
402             -- preSHA1sum: 54aa14e7b7e54cce8ae07c441f6bda316aa8458c
403             -- postSHA1sum: xxx-New-Checksum-xxx
404             -- inital schema
405              
406             create table underpants (
407             id serial primary key,
408             type text,
409             size text,
410             color text
411             );
412              
413             Let's apply this schema change, so we can finally start coding (you
414             just can't wait to get rich, can you?)
415              
416             ~/Gnomes$ dbchecksum apply_changes --sqlsnippetdir sql --dsn dbi:Pg:dbname=gnomes
417             Apply inital_schema.sql? [y/n] [y]
418             post checksum mismatch!
419             expected
420             got 611481f7599cc286fa539dbeb7ea27f049744dc7
421             ABORTING!
422              
423             Woops! What happend here? Why couldn't the change be applied? Well, we
424             haven't yet defined the C<postSHA1sum>, so we cannot be sure that the
425             database is in the state we expect it to be.
426              
427             When you author a sql change, you will always have to first apply the
428             change to figure out the new C<postSHA1sum>. As soon as
429             C<DBIx::SchemaChecksum> tells you the checksum the DB will have after
430             the change is applied, you have to add the new checksum to your
431             C<changes file>:
432              
433             ~/Gnomes$ vim sql/inital_schema.sql
434             # replace xxx-New-Checksum-xxx with 611481f7599cc286fa539dbeb7ea27f049744dc7
435              
436             ~/Gnomes$ head -2 sql/inital_schema.sql
437             -- preSHA1sum: 54aa14e7b7e54cce8ae07c441f6bda316aa8458c
438             -- postSHA1sum: 611481f7599cc286fa539dbeb7ea27f049744dc7
439              
440             Now we can try again:
441              
442             ~/Gnomes$ dbchecksum apply_changes --sqlsnippetdir sql --dsn dbi:Pg:dbname=gnomes
443             Apply inital_schema.sql? [y/n] [y]
444             post checksum OK
445             No more changes
446              
447             Yay, this looks much better!
448              
449             Now you can finally start to collect underpants!
450              
451             =head3 Teamwork
452              
453             Some weeks later (you have now convinced a friend to join you in your quest for fortune) a C<git pull> drops a new file into your C<sql> directory. It seems that your colleague needs some tweaks to the database:
454              
455             ~/Gnomes$ cat sql/underpants_need_washing.sql
456             -- preSHA1sum: 611481f7599cc286fa539dbeb7ea27f049744dc7
457             -- postSHA1sum: 094ef4321e60b50c1d34529c312ecc2fcbbdfb51
458             -- underpants need washing
459            
460             ALTER TABLE underpants ADD COLUMN needs_washing BOOLEAN NOT NULL DEFAULT false;
461              
462             Seems reasonable, so you apply it:
463              
464             ~/Gnomes$ dbchecksum apply_changes --sqlsnippetdir sql --dsn dbi:Pg:dbname=gnomes
465             Apply underpants_need_washing.sql? [y/n] [y]
466             post checksum OK
467             No more changes
468              
469             Now that was easy!
470              
471             =head3 Making things even easier: Config file
472              
473             C<DBIx::SchemaChecksum> uses L<MooseX::App> to power the commandline
474             interface. We use the C<Config> and C<ConfigHome> plugins, so you can
475             pack some of the flags into a config file, for even less typing (and typos):
476              
477             ~/Gnomes$ cat dbchecksum.yml
478             global:
479             sqlsnippetdir: sql
480             dsn: dbi:Pg:dbname=gnomes
481              
482             Now run:
483              
484             ~/Gnomes$ dbchecksum apply_changes --config dbchecksum.yml
485             db checksum 094ef4321e60b50c1d34529c312ecc2fcbbdfb51 matching sql/underpants_need_washing.sql
486              
487             Or you can store the config file into your F<~/.dbchecksum/config.yml>:
488              
489             ~/Gnomes$ cat ~/.dbchecksum/config.yml
490             global:
491             sqlsnippetdir: sql
492             dsn: dbi:Pg:dbname=gnomes
493              
494             And it magically works:
495              
496             ~/Gnomes$ dbchecksum apply_changes
497             db checksum 094ef4321e60b50c1d34529c312ecc2fcbbdfb51 matching sql/underpants_need_washing.sql
498              
499             =head3 Profit!
500              
501             This section is left empty as an exercise for the reader!
502              
503             =head2 Anatomy of a changes-file
504              
505             C<sqlsnippetdir> points to a directory containing so-called C<changes
506             files>. For a file to be picked up by C<dbchecksum> it needs to use
507             the extension F<.sql>.
508              
509             The file itself has to contain a header formated as sql comments, i.e.
510             starting with C<-->. The header has to contain the C<preSHA1sum> and
511             should include the C<postSHA1sum>.
512              
513             If the C<postSHA1sum> is missing, we assume that you don't know it yet and try to apply the change. As the new checksum will not match the empty C<postSHA1sum> the change will fail. But we will report the new checksum, which you can now insert into the changes file.
514              
515             After the header, the changes file should list all sql commands you
516             want to apply to change the schema, seperated by a semicolon C<;>,
517             just as you would type them into your sql prompt.
518              
519             -- preSHA1sum: b1387d808800a5969f0aa9bcae2d89a0d0b4620b
520             -- postSHA1sum: 55df89fd956a03d637b52d13281bc252896f602f
521            
522             CREATE TABLE nochntest (foo TEXT);
523              
524             Not all commands need to actually alter the schema, you can also
525             include sql that just updates some data. In fact, some schmema changes
526             even require that: for example, if you want to add a C<NOT NULL>
527             constraint to a column, you first have to make sure that the column in
528             fact does not contain a C<NULL>.
529              
530             -- preSHA1sum: c50519c54300ec2670618371a06f9140fa552965
531             -- postSHA1sum: 48dd6b3710a716fb85b005077dc534a8f9c11cba
532            
533             UPDATE foo SET some_field = 42 WHERE some_field IS NULL;
534             ALTER TABLE foo ALTER some_filed SET NOT NULL;
535              
536             =head3 Creating functions / stored procedures
537              
538             Functions usually contain semicolons inside the function definition,
539             so we cannot split the file on semicolon. Luckily, you can specifiy a different splitter using C<-- split-at>. We usually use C<----> (again, the SQL comment marker) so the changes file is still valid SQL.
540              
541             -- preSHA1sum c50519c54300ec2670618371a06f9140fa552965
542             -- postSHA1sum 48dd6b3710a716fb85b005077dc534a8f9c11cba
543             -- split-at ------
544              
545             ALTER TABLE underpants
546             ADD COLUMN modified timestamp with time zone DEFAULT now() NOT NULL;
547             ------
548             CREATE FUNCTION update_modified() RETURNS trigger
549             LANGUAGE plpgsql
550             AS $$
551             BEGIN
552             if NEW <> OLD THEN
553             NEW.modified = now();
554             END IF;
555             RETURN NEW;
556             END;
557             $$;
558             ------
559             CREATE TRIGGER underpants_modified
560             BEFORE UPDATE ON underpants
561             FOR EACH ROW EXECUTE PROCEDURE update_modified();
562              
563             =head2 TIPS & TRICKS
564              
565             We have been using C<DBIx::SchemaChecksum> since 2008 and encountered
566             a few issues. Here are our solutions:
567              
568             =head3 Using 'checksum --show_dump' to find inconsistencies between databases
569              
570             Sometimes two databases will produce different checksums. This can be
571             caused by a number of things. A good method to figure out what's
572             causing the problem is running C<<dbchecksum checksum --show_dump > some_name>>
573             on the databases causing the problem. Then you can use
574             C<diff> or C<vim -d> to inspect the raw dump.
575              
576             Some problems we have encountered, and how to fix them:
577              
578             =over
579              
580             =item * Manual changes
581              
582             Somebody did a manual change to a database (maybe an experiment on a
583             local DB, or some quick-fix on a live DB).
584              
585             B<Fix:> Revert the change. Maybe make a proper change file if the
586             change makes sense for the project.
587              
588             =item * Bad search-path
589              
590             The C<search_paths> of the DBs differ. This can cause subtile
591             diferences in the way keys and references are reported, thus causing a
592             different checksum.
593              
594             B<Fix:> Make sure all DBs use the same C<search_path>.
595              
596             =item * Other schema-related troubles
597              
598             Maybe the two instances use different values for C<--schemata>?
599              
600             B<Fix:> Use the same C<--schemata> everywhere. Put them in a
601             config-file or write a wrapper script.
602              
603             =item * Just weird diffs
604              
605             Maybe the systems are using different version of the database server,
606             client, C<DBI> or C<DBD::*>. While we try hard to filter out
607             version-specific differences, this might still cause problems.
608              
609             B<Fix:> Use the same versions on all machines.
610              
611             =back
612              
613             =head3 Use show_update_path if DBIx::SchemaChecksum cannot run on the database server
614              
615             Sometimes it's impossible to get C<DBIx::SchemaChecksum> installed on
616             the database server (or on some other machine, I have horrible
617             recollections about a colleague using Windows..). And the sysadmin
618             won't let you access the database over the network...
619              
620             B<Fix:> Prepare all changes on your local machine, and run them manually on the target machine.
621              
622             ~/Gnomes$ dbchecksum show_update_path --from_checksum 54aa14e7b7e54cce8ae07c441f6bda316aa8458c
623             inital_schema.sql (611481f7599cc286fa539dbeb7ea27f049744dc7)
624             underpants_need_washing.sql (094ef4321e60b50c1d34529c312ecc2fcbbdfb51)
625             No update found that's based on 094ef4321e60b50c1d34529c312ecc2fcbbdfb51.
626              
627             Now you could import the changes manually on the server. But it's even
628             easier using the C<--output> flag:
629              
630             ~/Gnomes$ dbchecksum show_update_path --output psql --dbname gnomes --from_checksum 54aa14e7b7e54cce8ae07c441f6bda316aa8458c
631             psql gnomes -1 -f inital_schema.sql
632             psql gnomes -1 -f underpants_need_washing.sql
633             # No update found that's based on 094ef4321e60b50c1d34529c312ecc2fcbbdfb51.
634              
635             You could pipe this into F<changes.sh> and then run that.
636              
637             Or use C<--output concat>:
638              
639             ~/Gnomes$ dbchecksum show_update_path --output concat --from_checksum 54aa14e7b7e54cce8ae07c441f6bda316aa8458c > changes.sql
640             ~/Gnomes$ cat changes.sql
641             -- file: inital_schema.sql
642             -- preSHA1sum: 54aa14e7b7e54cce8ae07c441f6bda316aa8458c
643             -- postSHA1sum: 611481f7599cc286fa539dbeb7ea27f049744dc7
644             -- inital schema
645            
646             create table underpants (
647             id serial primary key,
648             type text,
649             size text,
650             color text
651             );
652            
653             -- file: underpants_need_washing.sql
654             -- preSHA1sum: 611481f7599cc286fa539dbeb7ea27f049744dc7
655             -- postSHA1sum: 094ef4321e60b50c1d34529c312ecc2fcbbdfb51
656             -- underpants need washing
657            
658             ALTER TABLE underpants ADD COLUMN needs_washing BOOLEAN NOT NULL DEFAULT false;
659            
660             -- No update found that's based on 094ef4321e60b50c1d34529c312ecc2fcbbdfb51.
661              
662             Happyness!
663              
664             =head1 METHODS
665              
666             You will only need those methods if you want to use the library itself instead of using the C<dbchecksum> wrapper script.
667              
668             =head2 checksum
669              
670             my $sha1_hex = $self->checksum();
671              
672             Gets the schemadump and runs it through Digest::SHA1, returning the current checksum.
673              
674             =head2 schemadump
675              
676             my $schemadump = $self->schemadump;
677              
678             Returns a string representation of the whole schema (as a Data::Dumper Dump).
679              
680             Lazy Moose attribute.
681              
682             =head2 _build_schemadump_schema
683              
684             my $hashref = $self->_build_schemadump_schema( $schema );
685              
686             This is the main entry point for checksum calculations per schema.
687             Method-modifiy it if you need to alter the complete schema data
688             structure before/after checksumming.
689              
690             Returns a HashRef like:
691              
692             {
693             tables => $hash_ref
694             }
695              
696             =head2 _build_schemadump_tables
697              
698             my $hashref = $self->_build_schemadump_tables( $schema );
699              
700             Iterate through all tables in a schema, calling
701             L<_build_schemadump_table> for each table and collecting the results
702             in a HashRef
703              
704             =head2 _build_schemadump_table
705              
706             my $hashref = $self->_build_schemadump_table( $schema, $table );
707              
708             Get metadata on a table (columns, primary keys & foreign keys) via DBI
709             introspection.
710              
711             This is a good place to method-modify if you need some special processing for your database
712              
713             Returns a hashref like
714              
715             {
716             columns => $data,
717             primary_keys => $data,
718             foreign_keys => $data,
719             }
720              
721             =head2 _build_schemadump_column
722              
723             my $hashref = $self->_build_schemadump_column( $schema, $table, $column, $raw_dbi_data );
724              
725             Does some cleanup on the data returned by DBI.
726              
727             =head2 update_path
728              
729             my $update_info = $self->update_path
730              
731             Lazy Moose attribute that returns the data structure needed by L<apply_sql_update>.
732              
733             =head2 _build_update_path
734              
735             C<_build_update_path> reads in all files ending in ".sql" in C<< $self->sqlsnippetdir >>.
736             It builds something like a linked list of files, which are chained by their
737             C<preSHA1sum> and C<postSHA1sum>.
738              
739             =head2 get_checksums_from_snippet
740              
741             my ($pre, $post) = $self->get_checksums_from_snippet( $filename );
742              
743             Returns a list of the preSHA1sum and postSHA1sum for the given file in C< sqlnippetdir>.
744              
745             The file has to contain this info in SQL comments, eg:
746              
747             -- preSHA1sum: 89049e457886a86886a4fdf1f905b69250a8236c
748             -- postSHA1sum: d9a02517255045167053ea92dace728e1389f8ca
749              
750             alter table foo add column bar;
751              
752             =head2 dbh
753              
754             Database handle (DBH::db). Moose attribute
755              
756             =head2 catalog
757              
758             The database catalog searched for data. Not implemented by all DBs. See C<DBI::table_info>
759              
760             Default C<%>.
761              
762             Moose attribute
763              
764             =head2 schemata
765              
766             An Arrayref containing names of schematas to include in checksum calculation. See C<DBI::table_info>
767              
768             Default C<%>.
769              
770             Moose attribute
771              
772             =head2 sqlsnippetdir
773              
774             Path to the directory where the sql change files are stored.
775              
776             Moose attribute
777              
778             =head2 verbose
779              
780             Be verbose or not. Default: 0
781              
782             =head2 driveropts
783              
784             Additional options for the specific database driver.
785              
786             =head1 GLOBAL OPTIONS
787              
788             =head2 Connecting to the database
789              
790             These options define how to connect to your database.
791              
792             =head3 dsn
793              
794             B<Required>. The C<Data Source Name (DSN)> as used by L<DBI> to connect to your database.
795              
796             Some examples: C<dbi:SQLite:dbname=sqlite.db>,
797             C<dbi:Pg:dbname=my_project;host=db.example.com;port=5433>,
798             C<dbi:Pg:service=my_project_dbadmin>
799              
800             =head3 user
801              
802             Username to use to connect to your database.
803              
804             =head3 password
805              
806             Password to use to connect to your database.
807              
808             =head2 Defining the schema dump
809              
810             These options define which parts of the schema are relevant to the checksum
811              
812             =head3 catalog
813              
814             Default: C<%>
815              
816             Needed during L<DBI> introspection. C<Pg> does not need it.
817              
818             =head3 schemata
819              
820             Default: C<%> (all schemata)
821              
822             If you have several schemata in your database, but only want to consider some for the checksum, use C<--schemata> to list the ones you care about. Can be specified more than once to list several schemata:
823              
824             dbchecksum apply --schemata foo --schemata bar
825              
826             =head3 driveropts
827              
828             Some database drivers might implement further options only relevant
829             for the specific driver. As of now, this only applies to
830             L<DBIx::SchemaChecksum::Driver::Pg>, which defines the driveropts
831             C<triggers>, C<sequences> and C<functions>
832              
833             =head1 SEE ALSO
834              
835             L<bin/dbchecksum> for a command line frontend powered by L<MooseX::App>
836              
837             There are quite a lot of other database schema management tools out
838             there, but nearly all of them need to store meta-info in some magic
839             table in your database.
840              
841             =head2 Talks
842              
843             You can find more information on the rational, usage & implementation
844             in the slides for my talk at the Austrian Perl Workshop 2012,
845             available here: L<http://domm.plix.at/talks/dbix_schemachecksum.html>
846              
847             =head1 ACKNOWLEDGMENTS
848              
849             Thanks to
850              
851             =over
852              
853             =item * Klaus Ita and Armin Schreger for writing the initial core code. I
854             just glued it together and improved it a bit over the years.
855              
856             =item * revdev, a nice little software company run by Koki, domm
857             (L<http://search.cpan.org/~domm/>) and Maroš (L<http://search.cpan.org/~maros/>) from 2008 to 2011. We initially wrote C<DBIx::SchemaChecksum> for our work at revdev.
858              
859             =item * L<validad.com|https://www.validad.com/> which grew out of
860             revdev and still uses (and supports) C<DBIx::SchemaChecksum> every
861             day.
862              
863             =item * L<Farhad|https://twitter.com/Grauwolf> from L<Spherical
864             Elephant|https://www.sphericalelephant.com> for nagging me into
865             writing proper docs.
866              
867             =item
868              
869             =back
870              
871             =head1 AUTHORS
872              
873             =over 4
874              
875             =item *
876              
877             Thomas Klausner <domm@plix.at>
878              
879             =item *
880              
881             Maroš Kollár <maros@cpan.org>
882              
883             =item *
884              
885             Klaus Ita <koki@worstofall.com>
886              
887             =back
888              
889             =head1 COPYRIGHT AND LICENSE
890              
891             This software is copyright (c) 2012 - 2021 by Thomas Klausner, Maroš Kollár, Klaus Ita.
892              
893             This is free software; you can redistribute it and/or modify it under
894             the same terms as the Perl 5 programming language system itself.
895              
896             =cut