File Coverage

blib/lib/DBIx/Diff/Struct.pm
Criterion Covered Total %
statement 115 126 91.2
branch 61 112 54.4
condition 4 34 11.7
subroutine 14 14 100.0
pod 2 2 100.0
total 196 288 68.0


line stmt bran cond sub pod time code
1             package DBIx::Diff::Struct;
2              
3             our $DATE = '2015-01-03'; # DATE
4             our $VERSION = '0.03'; # VERSION
5              
6 1     1   234672 use 5.010001;
  1         4  
  1         31  
7 1     1   5 use strict;
  1         1  
  1         24  
8 1     1   3 use warnings;
  1         1  
  1         27  
9 1     1   475 use experimental 'smartmatch';
  1         676  
  1         4  
10 1     1   40517 use Log::Any '$log';
  1         2318  
  1         6  
11              
12 1     1   63 use List::Util qw(first);
  1         1  
  1         138  
13              
14 1     1   7 use Exporter;
  1         2  
  1         1563  
15             our @ISA = qw(Exporter);
16             our @EXPORT_OK = qw(
17             diff_db_struct
18             diff_table_struct
19             );
20              
21             our %SPEC;
22              
23             $SPEC{':package'} = {
24             v => 1.1,
25             summary => 'Compare structure of two DBI databases',
26             };
27              
28             my %common_args = (
29             dbh1 => {
30             schema => ['obj*'],
31             summary => 'DBI database handle for the first table',
32             req => 1,
33             pos => 0,
34             },
35             dbh2 => {
36             schema => ['obj*'],
37             summary => 'DBI database handle for the second table',
38             req => 1,
39             pos => 1,
40             },
41             );
42              
43             sub _list_tables {
44 8     8   13 my ($dbh) = @_;
45              
46 8         168 my $driver = $dbh->{Driver}{Name};
47              
48 8         23 my @res;
49 8         57 my $sth = $dbh->table_info(undef, undef, undef, undef);
50 8         3676 while (my $row = $sth->fetchrow_hashref) {
51 32 50       92 next if $row->{TABLE_TYPE} eq 'VIEW';
52 32 50       80 next if $row->{TABLE_SCHEM} =~ /^(information_schema)$/;
53              
54 32 50       106 if ($driver eq 'Pg') {
    50          
55 0 0       0 next if $row->{TABLE_SCHEM} =~ /^(pg_catalog)$/;
56             } elsif ($driver eq 'SQLite') {
57 32 100       1171 next if $row->{TABLE_SCHEM} =~ /^(temp)$/;
58 24 100       302 next if $row->{TABLE_NAME} =~ /^(sqlite_master|sqlite_temp_master)$/;
59             }
60              
61 16 50       487 push @res, join(
62             "",
63             $row->{TABLE_SCHEM},
64             length($row->{TABLE_SCHEM}) ? "." : "",
65             $row->{TABLE_NAME},
66             );
67             }
68 8         203 sort @res;
69             }
70              
71             sub _list_columns {
72 12     12   26 my ($dbh, $table) = @_;
73              
74 12         20 my @res;
75 12         46 my ($schema, $utable) = split /\./, $table;
76 12         106 my $sth = $dbh->column_info(undef, $schema, $utable, undef);
77 12         17583 while (my $row = $sth->fetchrow_hashref) {
78 44         1323 push @res, $row;
79             }
80 12         975 sort @res;
81             }
82              
83             sub _diff_column_struct {
84 20     20   29 my ($c1, $c2) = @_;
85              
86 20         31 my $res = {};
87             {
88 20 100       21 if ($c1->{TYPE_NAME} ne $c2->{TYPE_NAME}) {
  20         64  
89 2         9 $res->{old_type} = $c1->{TYPE_NAME};
90 2         7 $res->{new_type} = $c2->{TYPE_NAME};
91 2         5 last;
92             }
93 18 100 100     109 if ($c1->{NULLABLE} xor $c2->{NULLABLE}) {
94 2         7 $res->{old_nullable} = $c1->{NULLABLE};
95 2         7 $res->{new_nullable} = $c2->{NULLABLE};
96             }
97 18 50       45 if (defined $c1->{CHAR_OCTET_LENGTH}) {
98 0 0       0 if ($c1->{CHAR_OCTET_LENGTH} != $c2->{CHAR_OCTET_LENGTH}) {
99 0         0 $res->{old_length} = $c1->{CHAR_OCTET_LENGTH};
100 0         0 $res->{new_length} = $c2->{CHAR_OCTET_LENGTH};
101             }
102             }
103 18 100       48 if (defined $c1->{DECIMAL_DIGITS}) {
104 4 50       23 if ($c1->{DECIMAL_DIGITS} != $c2->{DECIMAL_DIGITS}) {
105 0         0 $res->{old_digits} = $c1->{DECIMAL_DIGITS};
106 0         0 $res->{new_digits} = $c2->{DECIMAL_DIGITS};
107             }
108             }
109             }
110 20         40 $res;
111             }
112              
113             $SPEC{diff_table_struct} = {
114             v => 1.1,
115             summary => 'Compare structure of two DBI tables',
116             description => <<'_',
117              
118             This function compares structures of two DBI tables. You supply two `DBI`
119             database handles along with table name and this function will return a hash:
120              
121             {
122             deleted_columns => [...],
123             added_columns => [...],
124             modified_columns => {
125             column1 => {
126             old_type => '...',
127             new_type => '...',
128             ...
129             },
130             },
131             }
132              
133             _
134             args => {
135             %common_args,
136             table => {
137             schema => 'str*',
138             name => 'str*',
139             summary => 'Table name',
140             req => 1,
141             pos => 2,
142             },
143             },
144             args_as => "array",
145             result_naked => 1,
146             "x.perinci.sub.wrapper.disable_validate_args" => 1,
147             };
148             sub diff_table_struct {
149 6 0 0 6 1 9 my $dbh1 = shift; require Scalar::Util;my $_sahv_dpath = []; my $arg_err; ((defined($dbh1)) ? 1 : (($arg_err //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((Scalar::Util::blessed($dbh1)) ? 1 : (($arg_err //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type object"),0)); if ($arg_err) { die "diff_table_struct(): " . "Invalid argument value for dbh1: $arg_err" } # VALIDATE_ARG
  6 50 0     58  
  6 0       11  
  6 50       11  
  6 50       55  
  6 50       19  
  0         0  
150 6 0 0     11 my $dbh2 = shift; ((defined($dbh2)) ? 1 : (($arg_err //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((Scalar::Util::blessed($dbh2)) ? 1 : (($arg_err //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type object"),0)); if ($arg_err) { die "diff_table_struct(): " . "Invalid argument value for dbh2: $arg_err" } # VALIDATE_ARG
  6 50 0     49  
  6 0       18  
  0 50       0  
    50          
    50          
151 6 0 0     10 my $table = shift; ((defined($table)) ? 1 : (($arg_err //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((!ref($table)) ? 1 : (($arg_err //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type text"),0)); if ($arg_err) { die "diff_table_struct(): " . "Invalid argument value for table: $arg_err" } # VALIDATE_ARG
  6 50 0     32  
  6 0       16  
  0 50       0  
    50          
    50          
152              
153             #$log->tracef("Comparing table %s ...", $table);
154              
155 6         17 my @columns1 = _list_columns($dbh1, $table);
156 6         23 my @columns2 = _list_columns($dbh2, $table);
157              
158             #$log->tracef("columns1: %s ...", \@columns1);
159             #$log->tracef("columns2: %s ...", \@columns2);
160              
161 6         14 my (@added, @deleted, %modified);
162 6         16 for my $c1 (@columns1) {
163 22         40 my $c1n = $c1->{COLUMN_NAME};
164 22     63   125 my $c2 = first {$c1n eq $_->{COLUMN_NAME}} @columns2;
  63         99  
165 22 100       77 if (defined $c2) {
166 20         44 my $tres = _diff_column_struct($c1, $c2);
167 20 100       90 $modified{$c1n} = $tres if keys %$tres;
168             } else {
169 2         7 push @deleted, $c1n;
170             }
171             }
172 6         13 for my $c2 (@columns2) {
173 22         32 my $c2n = $c2->{COLUMN_NAME};
174 22     69   100 my $c1 = first {$c2n eq $_->{COLUMN_NAME}} @columns1;
  69         99  
175 22 100       85 if (defined $c1) {
176             } else {
177 2         7 push @added, $c2n;
178             }
179             }
180              
181 6         13 my $res = {};
182 6 100       24 $res->{added_columns} = \@added if @added;
183 6 100       21 $res->{deleted_columns} = \@deleted if @deleted;
184 6 100       26 $res->{modified_columns} = \%modified if keys %modified;
185 6         108 $res;
186             }
187              
188             $SPEC{diff_db_struct} = {
189             v => 1.1,
190             summary => 'Compare structure of two DBI databases',
191             description => <<'_',
192              
193             This function compares structures of two DBI databases. You supply two `DBI`
194             database handles and this function will return a hash:
195              
196             {
197             # list of tables found in first db but missing in second
198             deleted_tables => ['table1', ...],
199              
200             # list of tables found only in the second db
201             added_tables => ['table2', ...],
202              
203             # list of modified tables, with details for each
204             modified_tables => {
205             table3 => {
206             deleted_columns => [...],
207             added_columns => [...],
208             modified_columns => {
209             column1 => {
210             old_type => '...',
211             new_type => '...',
212             ...
213             },
214             },
215             },
216             },
217             }
218              
219             _
220             args => {
221             %common_args,
222             },
223             args_as => "array",
224             result_naked => 1,
225             "x.perinci.sub.wrapper.disable_validate_args" => 1,
226             };
227             sub diff_db_struct {
228 4 0 0 4 1 1960377 my $dbh1 = shift; require Scalar::Util;my $_sahv_dpath = []; my $arg_err; ((defined($dbh1)) ? 1 : (($arg_err //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((Scalar::Util::blessed($dbh1)) ? 1 : (($arg_err //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type object"),0)); if ($arg_err) { die "diff_db_struct(): " . "Invalid argument value for dbh1: $arg_err" } # VALIDATE_ARG
  4 50 0     46  
  4 0       9  
  4 50       9  
  4 50       42  
  4 50       18  
  0         0  
229 4 0 0     6 my $dbh2 = shift; ((defined($dbh2)) ? 1 : (($arg_err //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Required but not specified"),0)) && ((Scalar::Util::blessed($dbh2)) ? 1 : (($arg_err //= (@$_sahv_dpath ? '@'.join("/",@$_sahv_dpath).": " : "") . "Not of type object"),0)); if ($arg_err) { die "diff_db_struct(): " . "Invalid argument value for dbh2: $arg_err" } # VALIDATE_ARG
  4 50 0     146  
  4 0       21  
  0 50       0  
    50          
    50          
230              
231 4         19 my @tables1 = _list_tables($dbh1);
232 4         14 my @tables2 = _list_tables($dbh2);
233              
234 4         35 $log->tracef("tables1: %s ...", \@tables1);
235 4         21 $log->tracef("tables2: %s ...", \@tables2);
236              
237 4         12 my (@added, @deleted, %modified);
238 4         10 for (@tables1) {
239 8 100       78 if ($_ ~~ @tables2) {
240             #$log->tracef("Comparing table %s ...", $_);
241 6         20 my $tres = diff_table_struct($dbh1, $dbh2, $_);
242 6 100       35 $modified{$_} = $tres if keys %$tres;
243             } else {
244 2         7 push @deleted, $_;
245             }
246             }
247 4         13 for (@tables2) {
248 8 100       32 if ($_ ~~ @tables1) {
249             } else {
250 2         6 push @added, $_;
251             }
252             }
253              
254 4         9 my $res = {};
255 4 100       17 $res->{added_tables} = \@added if @added;
256 4 100       15 $res->{deleted_tables} = \@deleted if @deleted;
257 4 100       16 $res->{modified_tables} = \%modified if keys %modified;
258 4         30 $res;
259             }
260              
261             1;
262             # ABSTRACT: Compare structure of two DBI databases
263              
264             __END__