line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package DBIx::Class::Helper::Schema::LintContents; |
2
|
|
|
|
|
|
|
$DBIx::Class::Helper::Schema::LintContents::VERSION = '2.034002'; |
3
|
|
|
|
|
|
|
# ABSTRACT: suite of methods to find violated "constraints" |
4
|
|
|
|
|
|
|
|
5
|
55
|
|
|
55
|
|
303497
|
use strict; |
|
55
|
|
|
|
|
126
|
|
|
55
|
|
|
|
|
1435
|
|
6
|
55
|
|
|
55
|
|
254
|
use warnings; |
|
55
|
|
|
|
|
141
|
|
|
55
|
|
|
|
|
1327
|
|
7
|
|
|
|
|
|
|
|
8
|
55
|
|
|
55
|
|
253
|
use parent 'DBIx::Class::Schema'; |
|
55
|
|
|
|
|
109
|
|
|
55
|
|
|
|
|
267
|
|
9
|
|
|
|
|
|
|
|
10
|
55
|
|
|
55
|
|
2947
|
use Scalar::Util 'blessed'; |
|
55
|
|
|
|
|
116
|
|
|
55
|
|
|
|
|
36514
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
sub null_check_source { |
13
|
11
|
|
|
11
|
1
|
23
|
my ($self, $source_name, $non_nullable_columns) = @_; |
14
|
|
|
|
|
|
|
|
15
|
11
|
|
|
|
|
27
|
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
|
26023
|
my ($self, $source_name) = @_; |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
my %ci = %{ |
26
|
11
|
|
|
|
|
17
|
$self->source($source_name)->columns_info |
|
11
|
|
|
|
|
34
|
|
27
|
|
|
|
|
|
|
}; |
28
|
11
|
|
|
|
|
831
|
$self->null_check_source($source_name, [grep { !$ci{$_}->{is_nullable} } keys %ci]); |
|
29
|
|
|
|
|
86
|
|
29
|
|
|
|
|
|
|
} |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
sub dup_check_source { |
32
|
11
|
|
|
11
|
1
|
22
|
my ($self, $source, $unique_columns) = @_; |
33
|
|
|
|
|
|
|
|
34
|
11
|
|
|
|
|
30
|
$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
|
16808
|
my ($self, $source) = @_; |
43
|
|
|
|
|
|
|
|
44
|
10
|
|
|
|
|
32
|
my %uc = $self->source($source)->unique_constraints; |
45
|
|
|
|
|
|
|
return { |
46
|
|
|
|
|
|
|
map { |
47
|
10
|
|
|
|
|
586
|
$_ => scalar $self->dup_check_source($source, $uc{$_}) |
|
11
|
|
|
|
|
90
|
|
48
|
|
|
|
|
|
|
} keys %uc |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
sub _fk_cond_fixer { |
53
|
6
|
|
|
6
|
|
490
|
my ($self, $cond) = @_; |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
return { |
56
|
|
|
|
|
|
|
map { |
57
|
6
|
|
|
|
|
20
|
my $k = $_; |
|
6
|
|
|
|
|
13
|
|
58
|
6
|
|
|
|
|
12
|
my $v = $cond->{$_}; |
59
|
6
|
|
|
|
|
41
|
$_ =~ s/^(self|foreign)\.// for $k, $v; |
60
|
|
|
|
|
|
|
|
61
|
6
|
|
|
|
|
33
|
($v => $k) |
62
|
|
|
|
|
|
|
} keys %$cond |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub fk_check_source_auto { |
67
|
10
|
|
|
10
|
1
|
8422
|
my ($self, $from_moniker) = @_; |
68
|
|
|
|
|
|
|
|
69
|
10
|
|
|
|
|
27
|
my $from_source = $self->source($from_moniker); |
70
|
|
|
|
|
|
|
my %rels = map { |
71
|
10
|
|
|
|
|
480
|
$_ => $from_source->relationship_info($_) |
|
12
|
|
|
|
|
62
|
|
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
|
|
|
|
|
569
|
) |
81
|
|
|
|
|
|
|
} grep { |
82
|
10
|
|
|
|
|
63
|
my %r = %{$rels{$_}}; |
|
12
|
|
|
|
|
18
|
|
|
12
|
|
|
|
|
59
|
|
83
|
|
|
|
|
|
|
ref $r{cond} eq 'HASH' && ($r{attrs}{is_foreign_rel} || $r{attrs}{is_foreign_key_constraint}) |
84
|
12
|
50
|
66
|
|
|
104
|
} keys %rels |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
sub fk_check_source { |
89
|
6
|
|
|
6
|
1
|
14
|
my ($self, $source_from, $source_to, $columns) = @_; |
90
|
|
|
|
|
|
|
|
91
|
6
|
50
|
|
|
|
38
|
my $to_rs = blessed $source_to |
92
|
|
|
|
|
|
|
? $source_to->resultset |
93
|
|
|
|
|
|
|
: $self->resultset($source_to) |
94
|
|
|
|
|
|
|
; |
95
|
6
|
|
|
|
|
842
|
my $me = $self->resultset($source_from)->current_source_alias; |
96
|
6
|
|
|
|
|
1429
|
$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) 2019 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 |