File Coverage

blib/lib/DBIx/Diff/Schema.pm
Criterion Covered Total %
statement 133 146 91.1
branch 61 90 67.7
condition 8 39 20.5
subroutine 21 21 100.0
pod 4 4 100.0
total 227 300 75.6


line stmt bran cond sub pod time code
1             package DBIx::Diff::Schema;
2              
3 2     2   451767 use 5.010001;
  2         9  
4 2     2   11 use strict 'subs', 'vars';
  2         5  
  2         96  
5 2     2   16 use warnings;
  2         3  
  2         126  
6 2     2   5795 use Log::ger;
  2         128  
  2         14  
7              
8 2     2   4818 use DBIx::Util::Schema qw(has_table list_tables list_columns);
  2         9539  
  2         202  
9 2     2   17 use List::Util qw(first);
  2         4  
  2         129  
10              
11 2     2   14 use Exporter qw(import);
  2         4  
  2         2141  
12              
13             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
14             our $DATE = '2024-07-17'; # DATE
15             our $DIST = 'DBIx-Diff-Schema'; # DIST
16             our $VERSION = '0.098'; # VERSION
17              
18             our @EXPORT_OK = qw(
19             diff_db_schema
20             diff_table_schema
21             db_schema_eq
22             table_schema_eq
23             );
24              
25             our %SPEC;
26              
27             $SPEC{':package'} = {
28             v => 1.1,
29             summary => 'Compare schema of two DBI databases',
30             };
31              
32             my %arg0_dbh = (
33             dbh => {
34             schema => ['obj*'],
35             summary => 'DBI database handle',
36             req => 1,
37             pos => 0,
38             },
39             );
40              
41             my %arg1_table = (
42             table => {
43             schema => ['str*'],
44             summary => 'Table name',
45             req => 1,
46             pos => 1,
47             },
48             );
49              
50             my %diff_db_args = (
51             dbh1 => {
52             schema => ['obj*'],
53             summary => 'DBI database handle for the first database',
54             req => 1,
55             pos => 0,
56             },
57             dbh2 => {
58             schema => ['obj*'],
59             summary => 'DBI database handle for the second database',
60             req => 1,
61             pos => 1,
62             },
63             );
64              
65             my %diff_table_args = (
66             %diff_db_args,
67             table1 => {
68             schema => 'str*',
69             summary => 'Table name',
70             req => 1,
71             pos => 2,
72             },
73             table2 => {
74             schema => 'str*',
75             summary => 'Second table name (assumed to be the same as first table name if unspecified)',
76             pos => 3,
77             },
78             );
79              
80             sub _diff_column_schema {
81 30     30   83 my ($c1, $c2) = @_;
82              
83 30         52 my $res = {};
84             {
85 30 100       46 if ($c1->{TYPE_NAME} ne $c2->{TYPE_NAME}) {
  30         93  
86 4         15 $res->{old_type} = $c1->{TYPE_NAME};
87 4         13 $res->{new_type} = $c2->{TYPE_NAME};
88 4         9 last;
89             }
90 26 100 100     123 if ($c1->{NULLABLE} xor $c2->{NULLABLE}) {
91 4         15 $res->{old_nullable} = $c1->{NULLABLE};
92 4         12 $res->{new_nullable} = $c2->{NULLABLE};
93             }
94 26 50       66 if (defined $c1->{CHAR_OCTET_LENGTH}) {
95 0 0       0 if ($c1->{CHAR_OCTET_LENGTH} != $c2->{CHAR_OCTET_LENGTH}) {
96 0         0 $res->{old_length} = $c1->{CHAR_OCTET_LENGTH};
97 0         0 $res->{new_length} = $c2->{CHAR_OCTET_LENGTH};
98             }
99             }
100 26 100       63 if (defined $c1->{DECIMAL_DIGITS}) {
101 6 50       22 if ($c1->{DECIMAL_DIGITS} != $c2->{DECIMAL_DIGITS}) {
102 0         0 $res->{old_digits} = $c1->{DECIMAL_DIGITS};
103 0         0 $res->{new_digits} = $c2->{DECIMAL_DIGITS};
104             }
105             }
106 26 50 50     144 if (($c1->{mysql_is_auto_increment} // 0) != ($c2->{mysql_is_auto_increment} // 0)) {
      50        
107 0   0     0 $res->{old_auto_increment} = $c1->{mysql_is_auto_increment} // 0;
108 0   0     0 $res->{new_auto_increment} = $c2->{mysql_is_auto_increment} // 0;
109             }
110             }
111 30         66 $res;
112             }
113              
114             sub _diff_table_schema {
115 11     11   37 my ($dbh1, $dbh2, $table1, $table2) = @_;
116              
117 11         40 my @columns1 = list_columns($dbh1, $table1);
118 11         20456 my @columns2 = list_columns($dbh2, $table2);
119              
120 11         16106 log_trace("columns1: %s ...", \@columns1);
121 11         85 log_trace("columns2: %s ...", \@columns2);
122              
123 11         39 my (@added, @deleted, %modified);
124 11         30 for my $c1 (@columns1) {
125 35         75 my $c1n = $c1->{COLUMN_NAME};
126 35     112   169 my $c2 = first {$c1n eq $_->{COLUMN_NAME}} @columns2;
  112         228  
127 35 100       121 if (defined $c2) {
128 30         78 my $tres = _diff_column_schema($c1, $c2);
129 30 100       102 $modified{$c1n} = $tres if %$tres;
130             } else {
131 5         19 push @deleted, $c1n;
132             }
133             }
134 11         40 for my $c2 (@columns2) {
135 39         106 my $c2n = $c2->{COLUMN_NAME};
136 39     112   127 my $c1 = first {$c2n eq $_->{COLUMN_NAME}} @columns1;
  112         189  
137 39 100       119 if (defined $c1) {
138             } else {
139 9         23 push @added, $c2n;
140             }
141             }
142              
143 11         21 my $res = {};
144 11 100       36 $res->{added_columns} = \@added if @added;
145 11 100       27 $res->{deleted_columns} = \@deleted if @deleted;
146 11 100       30 $res->{modified_columns} = \%modified if %modified;
147 11         192 $res;
148             }
149              
150             $SPEC{diff_table_schema} = {
151             v => 1.1,
152             summary => 'Compare schema of two DBI tables',
153             description => <<'_',
154              
155             This function compares schemas of two DBI tables. You supply two `DBI` database
156             handles along with table name and this function will return a hash:
157              
158             {
159             deleted_columns => [...],
160             added_columns => [...],
161             modified_columns => {
162             column1 => {
163             old_type => '...',
164             new_type => '...',
165             ...
166             },
167             },
168             }
169              
170             _
171             args => {
172             %diff_table_args,
173             },
174             args_as => "array",
175             result_naked => 1,
176             "x.perinci.sub.wrapper.disable_validate_args" => 1,
177             };
178             sub diff_table_schema {
179 2 50 0 2 1 26 my $dbh1 = shift; my $arg_err; { no warnings ('void');require Scalar::Util;((defined($dbh1)) ? 1 : (($arg_err //= "Required but not specified"),0)) && ((Scalar::Util::blessed($dbh1)) ? 1 : (($arg_err //= "Not of type object"),0)); if ($arg_err) { die "diff_table_schema(): " . "Invalid argument value for dbh1: $arg_err" } } # VALIDATE_ARG
  2 50 0 6   5  
  2 50       361  
  6 50       130  
  6         34  
  6         10  
  6         46  
  6         33  
  6         17  
  0         0  
180 2 50 0 2   17 my $dbh2 = shift; { no warnings ('void');((defined($dbh2)) ? 1 : (($arg_err //= "Required but not specified"),0)) && ((Scalar::Util::blessed($dbh2)) ? 1 : (($arg_err //= "Not of type object"),0)); if ($arg_err) { die "diff_table_schema(): " . "Invalid argument value for dbh2: $arg_err" } } # VALIDATE_ARG
  2 50 0     4  
  2 50       241  
  6 50       12  
  6         10  
  6         23  
  6         16  
  0         0  
181 2 50 0 2   13 my $table1 = shift; { no warnings ('void');((defined($table1)) ? 1 : (($arg_err //= "Required but not specified"),0)) && ((!ref($table1)) ? 1 : (($arg_err //= "Not of type text"),0)); if ($arg_err) { die "diff_table_schema(): " . "Invalid argument value for table1: $arg_err" } } # VALIDATE_ARG
  2 50 0     4  
  2 50       412  
  6 50       13  
  6         8  
  6         26  
  6         15  
  0         0  
182 2 50 66 2   56 my $table2 = shift // $table1; { no warnings ('void');((defined($table2)) ? 1 : (($arg_err //= "Required but not specified"),0)) && ((!ref($table2)) ? 1 : (($arg_err //= "Not of type text"),0)); if ($arg_err) { die "diff_table_schema(): " . "Invalid argument value for table2: $arg_err" } } # VALIDATE_ARG
  2 50 0     5  
  2 50 0     853  
  6 50       21  
  6         9  
  6         22  
  6         14  
  0         0  
183              
184             #$log->tracef("Comparing table %s vs %s ...", $table1, $table2);
185              
186 6 100       25 die "Table $table1 in first database does not exist"
187             unless has_table($dbh1, $table1);
188 5 100       3198 die "Table $table2 in second database does not exist"
189             unless has_table($dbh2, $table2);
190 4         2390 _diff_table_schema($dbh1, $dbh2, $table1, $table2);
191             }
192              
193             $SPEC{table_schema_eq} = {
194             v => 1.1,
195             summary => 'Return true if two DBI tables have the same schema',
196             description => <<'_',
197              
198             This is basically just a shortcut for:
199              
200             my $res = diff_table_schema(...);
201             !%res;
202              
203             _
204             args => {
205             %diff_table_args,
206             },
207             args_as => "array",
208             result_naked => 1,
209             "x.perinci.sub.wrapper.disable_validate_args" => 1,
210             };
211             sub table_schema_eq {
212 2     2 1 1995 my $res = diff_table_schema(@_);
213 2         17 !%$res;
214             }
215              
216             $SPEC{diff_db_schema} = {
217             v => 1.1,
218             summary => 'Compare schemas of two DBI databases',
219             description => <<'_',
220              
221             This function compares schemas of two DBI databases. You supply two `DBI`
222             database handles and this function will return a hash:
223              
224             {
225             # list of tables found in first db but missing in second
226             deleted_tables => ['table1', ...],
227              
228             # list of tables found only in the second db
229             added_tables => ['table2', ...],
230              
231             # list of modified tables, with details for each
232             modified_tables => {
233             table3 => {
234             deleted_columns => [...],
235             added_columns => [...],
236             modified_columns => {
237             column1 => {
238             old_type => '...',
239             new_type => '...',
240             ...
241             },
242             },
243             },
244             },
245             }
246              
247             _
248             args => {
249             %diff_db_args,
250             },
251             args_as => "array",
252             result_naked => 1,
253             "x.perinci.sub.wrapper.disable_validate_args" => 1,
254             };
255             sub diff_db_schema {
256 2 50 0 2 1 19 my $dbh1 = shift; my $arg_err; { no warnings ('void');require Scalar::Util;((defined($dbh1)) ? 1 : (($arg_err //= "Required but not specified"),0)) && ((Scalar::Util::blessed($dbh1)) ? 1 : (($arg_err //= "Not of type object"),0)); if ($arg_err) { die "diff_db_schema(): " . "Invalid argument value for dbh1: $arg_err" } } # VALIDATE_ARG
  2 50 0 5   5  
  2 50       390  
  5 50       617545  
  5         12  
  5         12  
  5         52  
  5         64  
  5         16  
  0         0  
257 2 50 0 2   17 my $dbh2 = shift; { no warnings ('void');((defined($dbh2)) ? 1 : (($arg_err //= "Required but not specified"),0)) && ((Scalar::Util::blessed($dbh2)) ? 1 : (($arg_err //= "Not of type object"),0)); if ($arg_err) { die "diff_db_schema(): " . "Invalid argument value for dbh2: $arg_err" } } # VALIDATE_ARG
  2 50 0     3  
  2 50       1195  
  5 50       9  
  5         11  
  5         18  
  5         15  
  0         0  
258              
259 5         24 my @tables1 = list_tables($dbh1);
260 5         3617 my @tables2 = list_tables($dbh2);
261              
262 5         3139 log_trace("tables1: %s ...", \@tables1);
263 5         26 log_trace("tables2: %s ...", \@tables2);
264              
265 5         17 my (@added, @deleted, %modified);
266 5         12 for my $t (@tables1) {
267 10 100       27 if (grep {$_ eq $t} @tables2) {
  20         58  
268             #$log->tracef("Comparing table %s ...", $_);
269 7         20 my $tres = _diff_table_schema($dbh1, $dbh2, $t, $t);
270 7 100       31 $modified{$t} = $tres if %$tres;
271             } else {
272 3         10 push @deleted, $t;
273             }
274             }
275 5         11 for my $t (@tables2) {
276 10 100       20 if (grep {$_ eq $t} @tables1) {
  20         54  
277             } else {
278 3         9 push @added, $t;
279             }
280             }
281              
282 5         11 my $res = {};
283 5 100       18 $res->{added_tables} = \@added if @added;
284 5 100       16 $res->{deleted_tables} = \@deleted if @deleted;
285 5 100       17 $res->{modified_tables} = \%modified if %modified;
286 5         34 $res;
287             }
288              
289             $SPEC{db_schema_eq} = {
290             v => 1.1,
291             summary => 'Return true if two DBI databases have the same schema',
292             description => <<'_',
293              
294             This is basically just a shortcut for:
295              
296             my $res = diff_db_schema(...);
297             !%$res;
298              
299             _
300             args => {
301             %diff_db_args,
302             },
303             args_as => "array",
304             result_naked => 1,
305             "x.perinci.sub.wrapper.disable_validate_args" => 1,
306             };
307             sub db_schema_eq {
308 2     2 1 3974 my $res = diff_db_schema(@_);
309 2         21 !%$res;
310             }
311              
312             1;
313             # ABSTRACT: Compare schema of two DBI databases
314              
315             __END__