File Coverage

blib/lib/DBIx/SchemaChecksum.pm
Criterion Covered Total %
statement 125 146 85.6
branch 33 54 61.1
condition 3 8 37.5
subroutine 19 19 100.0
pod 2 3 66.6
total 182 230 79.1


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