File Coverage

blib/lib/DBIx/Class/Helper/Schema/LintContents.pm
Criterion Covered Total %
statement 44 44 100.0
branch 2 4 50.0
condition 2 3 66.6
subroutine 11 11 100.0
pod 6 6 100.0
total 65 68 95.5


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