| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package DBIx::Class::Helper::Schema::LintContents; |
|
2
|
|
|
|
|
|
|
$DBIx::Class::Helper::Schema::LintContents::VERSION = '2.036000'; |
|
3
|
|
|
|
|
|
|
# ABSTRACT: suite of methods to find violated "constraints" |
|
4
|
|
|
|
|
|
|
|
|
5
|
56
|
|
|
56
|
|
345089
|
use strict; |
|
|
56
|
|
|
|
|
141
|
|
|
|
56
|
|
|
|
|
1660
|
|
|
6
|
56
|
|
|
56
|
|
311
|
use warnings; |
|
|
56
|
|
|
|
|
129
|
|
|
|
56
|
|
|
|
|
1528
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
56
|
|
|
56
|
|
289
|
use parent 'DBIx::Class::Schema'; |
|
|
56
|
|
|
|
|
112
|
|
|
|
56
|
|
|
|
|
296
|
|
|
9
|
|
|
|
|
|
|
|
|
10
|
56
|
|
|
56
|
|
3484
|
use Scalar::Util 'blessed'; |
|
|
56
|
|
|
|
|
142
|
|
|
|
56
|
|
|
|
|
42820
|
|
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
sub null_check_source { |
|
13
|
11
|
|
|
11
|
1
|
25
|
my ($self, $source_name, $non_nullable_columns) = @_; |
|
14
|
|
|
|
|
|
|
|
|
15
|
11
|
|
|
|
|
35
|
return $self->resultset($source_name)->search({ |
|
16
|
|
|
|
|
|
|
-or => [ |
|
17
|
|
|
|
|
|
|
map +{ $_ => undef }, @$non_nullable_columns, |
|
18
|
|
|
|
|
|
|
], |
|
19
|
|
|
|
|
|
|
}) |
|
20
|
|
|
|
|
|
|
} |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub null_check_source_auto { |
|
23
|
11
|
|
|
11
|
1
|
27477
|
my ($self, $source_name) = @_; |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
my %ci = %{ |
|
26
|
11
|
|
|
|
|
19
|
$self->source($source_name)->columns_info |
|
|
11
|
|
|
|
|
39
|
|
|
27
|
|
|
|
|
|
|
}; |
|
28
|
11
|
|
|
|
|
867
|
$self->null_check_source($source_name, [grep { !$ci{$_}->{is_nullable} } keys %ci]); |
|
|
29
|
|
|
|
|
81
|
|
|
29
|
|
|
|
|
|
|
} |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
sub dup_check_source { |
|
32
|
11
|
|
|
11
|
1
|
27
|
my ($self, $source, $unique_columns) = @_; |
|
33
|
|
|
|
|
|
|
|
|
34
|
11
|
|
|
|
|
33
|
$self->resultset($source)->search(undef, { |
|
35
|
|
|
|
|
|
|
columns => $unique_columns, |
|
36
|
|
|
|
|
|
|
group_by => $unique_columns, |
|
37
|
|
|
|
|
|
|
having => \'count(*) > 1', |
|
38
|
|
|
|
|
|
|
}) |
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
sub dup_check_source_auto { |
|
42
|
10
|
|
|
10
|
1
|
18323
|
my ($self, $source) = @_; |
|
43
|
|
|
|
|
|
|
|
|
44
|
10
|
|
|
|
|
46
|
my %uc = $self->source($source)->unique_constraints; |
|
45
|
|
|
|
|
|
|
return { |
|
46
|
|
|
|
|
|
|
map { |
|
47
|
10
|
|
|
|
|
671
|
$_ => scalar $self->dup_check_source($source, $uc{$_}) |
|
|
11
|
|
|
|
|
98
|
|
|
48
|
|
|
|
|
|
|
} keys %uc |
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
} |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
sub _fk_cond_fixer { |
|
53
|
6
|
|
|
6
|
|
505
|
my ($self, $cond) = @_; |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
return { |
|
56
|
|
|
|
|
|
|
map { |
|
57
|
6
|
|
|
|
|
23
|
my $k = $_; |
|
|
6
|
|
|
|
|
12
|
|
|
58
|
6
|
|
|
|
|
16
|
my $v = $cond->{$_}; |
|
59
|
6
|
|
|
|
|
54
|
$_ =~ s/^(self|foreign)\.// for $k, $v; |
|
60
|
|
|
|
|
|
|
|
|
61
|
6
|
|
|
|
|
43
|
($v => $k) |
|
62
|
|
|
|
|
|
|
} keys %$cond |
|
63
|
|
|
|
|
|
|
} |
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub fk_check_source_auto { |
|
67
|
10
|
|
|
10
|
1
|
10099
|
my ($self, $from_moniker) = @_; |
|
68
|
|
|
|
|
|
|
|
|
69
|
10
|
|
|
|
|
42
|
my $from_source = $self->source($from_moniker); |
|
70
|
|
|
|
|
|
|
my %rels = map { |
|
71
|
10
|
|
|
|
|
552
|
$_ => $from_source->relationship_info($_) |
|
|
12
|
|
|
|
|
98
|
|
|
72
|
|
|
|
|
|
|
} $from_source->relationships; |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
return { |
|
75
|
|
|
|
|
|
|
map { |
|
76
|
|
|
|
|
|
|
$_ => scalar $self->fk_check_source( |
|
77
|
|
|
|
|
|
|
$from_moniker, |
|
78
|
|
|
|
|
|
|
$from_source->related_source($_), |
|
79
|
|
|
|
|
|
|
$self->_fk_cond_fixer($rels{$_}->{cond}) |
|
80
|
6
|
|
|
|
|
603
|
) |
|
81
|
|
|
|
|
|
|
} grep { |
|
82
|
10
|
|
|
|
|
77
|
my %r = %{$rels{$_}}; |
|
|
12
|
|
|
|
|
25
|
|
|
|
12
|
|
|
|
|
69
|
|
|
83
|
|
|
|
|
|
|
ref $r{cond} eq 'HASH' && ($r{attrs}{is_foreign_rel} || $r{attrs}{is_foreign_key_constraint}) |
|
84
|
12
|
50
|
66
|
|
|
120
|
} keys %rels |
|
85
|
|
|
|
|
|
|
} |
|
86
|
|
|
|
|
|
|
} |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
sub fk_check_source { |
|
89
|
6
|
|
|
6
|
1
|
23
|
my ($self, $source_from, $source_to, $columns) = @_; |
|
90
|
|
|
|
|
|
|
|
|
91
|
6
|
50
|
|
|
|
41
|
my $to_rs = blessed $source_to |
|
92
|
|
|
|
|
|
|
? $source_to->resultset |
|
93
|
|
|
|
|
|
|
: $self->resultset($source_to) |
|
94
|
|
|
|
|
|
|
; |
|
95
|
6
|
|
|
|
|
982
|
my $me = $self->resultset($source_from)->current_source_alias; |
|
96
|
6
|
|
|
|
|
1517
|
$self->resultset($source_from)->search({ |
|
97
|
|
|
|
|
|
|
-not_exists => $to_rs |
|
98
|
|
|
|
|
|
|
->search({ |
|
99
|
|
|
|
|
|
|
map +( "self.$_" => { -ident => "other.$columns->{$_}" } ), keys %$columns |
|
100
|
|
|
|
|
|
|
}, { |
|
101
|
|
|
|
|
|
|
alias => 'other', |
|
102
|
|
|
|
|
|
|
})->as_query, |
|
103
|
|
|
|
|
|
|
}, { |
|
104
|
|
|
|
|
|
|
alias => 'self', |
|
105
|
|
|
|
|
|
|
}) |
|
106
|
|
|
|
|
|
|
} |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
1; |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
__END__ |
|
111
|
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=pod |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head1 NAME |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
DBIx::Class::Helper::Schema::LintContents - suite of methods to find violated "constraints" |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
package MyApp::Schema; |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
use parent 'DBIx::Class::Schema'; |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
__PACKAGE__->load_components('Helper::Schema::LintContents'); |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
1; |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
And later, somewhere else: |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
say "Incorrectly Null Users:"; |
|
131
|
|
|
|
|
|
|
for ($schema->null_check_source_auto('User')->all) { |
|
132
|
|
|
|
|
|
|
say '* ' . $_->id |
|
133
|
|
|
|
|
|
|
} |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
say "Duplicate Users:"; |
|
136
|
|
|
|
|
|
|
my $duplicates = $schema->dup_check_source_auto('User'); |
|
137
|
|
|
|
|
|
|
for (keys %$duplicates) { |
|
138
|
|
|
|
|
|
|
say "Constraint: $_"; |
|
139
|
|
|
|
|
|
|
for ($duplicates->{$_}->all) { |
|
140
|
|
|
|
|
|
|
say '* ' . $_->id |
|
141
|
|
|
|
|
|
|
} |
|
142
|
|
|
|
|
|
|
} |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
say "Users with invalid FK's:"; |
|
145
|
|
|
|
|
|
|
my $invalid_fks = $schema->fk_check_source_auto('User'); |
|
146
|
|
|
|
|
|
|
for (keys %$invalid_fks) { |
|
147
|
|
|
|
|
|
|
say "Rel: $_"; |
|
148
|
|
|
|
|
|
|
for ($invalid_fks->{$_}->all) { |
|
149
|
|
|
|
|
|
|
say '* ' . $_->id |
|
150
|
|
|
|
|
|
|
} |
|
151
|
|
|
|
|
|
|
} |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Some people think that constraints make their databases slower. As silly as |
|
156
|
|
|
|
|
|
|
that is, I have been in a similar situation! I'm here to help you, dear |
|
157
|
|
|
|
|
|
|
developers! Basically this is a suite of methods that allow you to find |
|
158
|
|
|
|
|
|
|
violated "constraints." To be clear, the constraints I mean are the ones you |
|
159
|
|
|
|
|
|
|
tell L<DBIx::Class> about, real constraints are fairly sure to be followed. |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head1 METHODS |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=head2 fk_check_source |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
my $busted = $schema->fk_check_source( |
|
166
|
|
|
|
|
|
|
'User', |
|
167
|
|
|
|
|
|
|
'Group', |
|
168
|
|
|
|
|
|
|
{ group_id => 'id' }, |
|
169
|
|
|
|
|
|
|
); |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
C<fk_check_source> takes three arguments, the first is the B<from> source |
|
172
|
|
|
|
|
|
|
moniker of a relationship. The second is the B<to> source or source moniker of a relationship. |
|
173
|
|
|
|
|
|
|
The final argument is a hash reference representing the columns of the |
|
174
|
|
|
|
|
|
|
relationship. The return value is a resultset of the B<from> source that do |
|
175
|
|
|
|
|
|
|
not have a corresponding B<to> row. To be clear, the example given above would |
|
176
|
|
|
|
|
|
|
return a resultset of C<User> rows that have a C<group_id> that points to a |
|
177
|
|
|
|
|
|
|
C<Group> that does not exist. |
|
178
|
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=head2 fk_check_source_auto |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
my $broken = $schema->fk_check_source_auto('User'); |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
C<fk_check_source_auto> takes a single argument: the source to check. It will |
|
184
|
|
|
|
|
|
|
check all the foreign key (that is, C<belongs_to>) relationships for missing... |
|
185
|
|
|
|
|
|
|
C<foreign> rows. The return value will be a hashref where the keys are the |
|
186
|
|
|
|
|
|
|
relationship name and the values are resultsets of the respective violated |
|
187
|
|
|
|
|
|
|
relationship. |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head2 dup_check_source |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
my $smashed = $schema->fk_check_source( 'Group', ['id'] ); |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
C<dup_check_source> takes two arguments, the first is the source moniker to be |
|
194
|
|
|
|
|
|
|
checked. The second is an arrayref of columns that "should be" unique. |
|
195
|
|
|
|
|
|
|
The return value is a resultset of the source that duplicate the passed |
|
196
|
|
|
|
|
|
|
columns. So with the example above the resultset would return all groups that |
|
197
|
|
|
|
|
|
|
are "duplicates" of other groups based on C<id>. |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=head2 dup_check_source_auto |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
my $ruined = $schema->dup_check_source_auto('Group'); |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
C<dup_check_source_auto> takes a single argument, which is the name of the |
|
204
|
|
|
|
|
|
|
resultsource in which to check for duplicates. It will return a hashref where |
|
205
|
|
|
|
|
|
|
they keys are the names of the unique constraints to be checked. The values |
|
206
|
|
|
|
|
|
|
will be resultsets of the respective duplicate rows. |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head2 null_check_source |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
my $blarg = $schema->null_check_source('Group', ['id']); |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
C<null_check_source> tales two arguments, the first is the name of the source |
|
213
|
|
|
|
|
|
|
to check. The second is an arrayref of columns that should contain no nulls. |
|
214
|
|
|
|
|
|
|
The return value is simply a resultset of rows that contain nulls where they |
|
215
|
|
|
|
|
|
|
shouldn't be. |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=head2 null_check_source_auto |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
my $wrecked = $schema->null_check_source_auto('Group'); |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
C<null_check_source_auto> takes a single argument, which is the name of the |
|
222
|
|
|
|
|
|
|
resultsource in which to check for nulls. The return value is simply a |
|
223
|
|
|
|
|
|
|
resultset of rows that contain nulls where they shouldn't be. This method |
|
224
|
|
|
|
|
|
|
automatically uses the configured columns that have C<is_nullable> set to |
|
225
|
|
|
|
|
|
|
false. |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
=head1 AUTHOR |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
Arthur Axel "fREW" Schmidt <frioux+cpan@gmail.com> |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
This software is copyright (c) 2020 by Arthur Axel "fREW" Schmidt. |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
|
236
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
|
237
|
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=cut |