File Coverage

blib/lib/LINQ/FieldSet/Assertion.pm
Criterion Covered Total %
statement 184 188 97.8
branch 50 58 86.2
condition 10 10 100.0
subroutine 51 53 96.2
pod 6 6 100.0
total 301 315 95.5


line stmt bran cond sub pod time code
1 1     1   19 use 5.006;
  1         4  
2 1     1   5 use strict;
  1         2  
  1         39  
3 1     1   6 use warnings;
  1         2  
  1         186  
4              
5              
6             my $_process_args = sub {
7             require Scalar::Util;
8             if ( Scalar::Util::blessed( $_[0] )
9             and Scalar::Util::blessed( $_[1] )
10             and @_ < 4 )
11             {
12             return $_[2] ? ( $_[1], $_[0] ) : ( $_[0], $_[1] );
13             }
14            
15             my ( $self, @other ) = @_;
16             my $other = __PACKAGE__->new( @other );
17             return ( $self, $other );
18             };
19              
20             our $AUTHORITY = 'cpan:TOBYINK';
21             our $VERSION = '0.003';
22              
23             use Class::Tiny;
24 1     1   7 use parent qw( LINQ::FieldSet );
  1         1  
  1         8  
25 1     1   82  
  1         2  
  1         6  
26             use LINQ::Util::Internal ();
27 1     1   44  
  1         2  
  1         48  
28             use overload (
29             'fallback' => !!1,
30             q[bool] => sub { !! 1 },
31 0     0   0 q[""] => 'to_string',
32 1         9 q[&{}] => 'coderef',
33             q[|] => 'or',
34             q[&] => 'and',
35             q[~] => 'not',
36             );
37 1     1   5  
  1         3  
38             my ( $self ) = ( shift );
39            
40 41     41   68 return (
41             $self->SUPER::_known_parameter_names,
42             'is' => 1,
43 41         114 'in' => 1,
44             'like' => 1,
45             'match' => 1,
46             'to' => 1,
47             'cmp' => 1,
48             'numeric' => 0,
49             'string' => 0,
50             'nix' => 0,
51             'nocase' => 0,
52             );
53             } #/ sub _known_parameter_names
54              
55             my ( $self ) = ( shift );
56             $self->{coderef} ||= $self->_build_coderef;
57             }
58 69     69 1 220  
59 69   100     221 my ( $self ) = ( shift );
60             if ( $self->seen_asterisk ) {
61             LINQ::Util::Internal::throw(
62             "CallerError",
63 41     41 1 770 message => "Field '*' does not make sense for assertions",
64 41 100       867 );
65 1         10 }
66             }
67              
68             my ( $self ) = ( shift );
69             my @checks = map $self->_make_check( $_ ), @{ $self->fields };
70             return sub {
71             for my $check ( @checks ) {
72             return !!0 unless $check->( $_ );
73 40     40   66 }
74 40         55 return !!1;
  40         749  
75             };
76 73     73   143 } #/ sub _build_coderef
77 75 100       1098  
78             {
79 34         111 my %makers = (
80 37         231 'is' => '_make_is_check',
81             'in' => '_make_in_check',
82             'to' => '_make_to_check',
83             'like' => '_make_like_check',
84             'match' => '_make_match_check',
85             );
86              
87             my ( $self, $field ) = ( shift, @_ );
88            
89             my @found;
90             for my $key ( sort keys %makers ) {
91             push @found, $key if exists $field->params->{$key};
92             }
93 41     41   259
94             if ( @found > 1 ) {
95 41         52 my $params = join q[, ], map "-$_", @found;
96 41         213 LINQ::Util::Internal::throw(
97 205 100       3569 "CallerError",
98             message => "Multiple conflicting assertions ($params) found for field '@{[ $field->name ]}'",
99             );
100 41 100       247 }
101 1         7
102 1         6 if ( @found == 0 ) {
103             LINQ::Util::Internal::throw(
104 1         18 "CallerError",
105             message => "No assertions found for field '@{[ $field->name ]}'",
106             );
107             }
108 40 100       73
109 1         3 my $method = $makers{ $found[0] };
110             return $self->$method( $field );
111 1         17 }
112             }
113              
114             {
115 39         75 my %templates = (
116 39         119 'numeric ==' => '%s == %s',
117             'numeric !=' => '%s != %s',
118             'numeric >' => '%s > %s',
119             'numeric >=' => '%s >= %s',
120             'numeric <' => '%s < %s',
121             'numeric <=' => '%s <= %s',
122             'string ==' => '%s eq %s',
123             'string !=' => '%s ne %s',
124             'string >' => '%s gt %s',
125             'string >=' => '%s ge %s',
126             'string <' => '%s lt %s',
127             'string <=' => '%s le %s',
128             'null ==' => '! defined( %s )',
129             );
130            
131             my ( $self, $field ) = ( shift, @_ );
132             my $getter = $field->getter;
133            
134             my $expected = $field->params->{is};
135             my $cmp = $field->params->{cmp} || "==";
136             my $type =
137             $field->params->{numeric} ? 'numeric'
138 22     22   52 : $field->params->{string} ? 'string'
139 22         56 : !defined( $expected ) ? 'null'
140             : $expected =~ /^[0-9]+(?:\.[0-9]+)$/ ? 'numeric'
141 22         354 : !ref( $expected ) ? 'string'
142 22   100     389 : 'numeric';
143             my $template = $templates{"$type $cmp"}
144             or LINQ::Util::Internal::throw(
145 22 50       434 "CallerError",
    100          
    100          
    50          
    50          
146             message => "Unexpected comparator '$cmp' for type '$type'",
147             );
148            
149             my $guts;
150 22 100       538 if ( $type eq 'null' ) {
151             $guts = sprintf( $template, '$getter->( $_ )' );
152             }
153             elsif ( $field->params->{nocase} ) {
154             my $fold = ( $] > 5.016 ) ? 'CORE::fc' : 'lc';
155             $guts = sprintf(
156 21         31 $template,
157 21 100       305 "$fold( \$getter->( \$_ ) )",
    100          
158 2         9 ref( $expected ) ? "$fold( \$expected )" : do {
159             require B;
160             "$fold( " . B::perlstring( $expected ) . ' )';
161 1 50       9 },
162             );
163             } #/ elsif ( $field->params->{...})
164             else {
165 1 50       5 $guts = sprintf(
166 1         54 $template,
167 1         10 '$getter->( $_ )',
168             ref( $expected ) ? '$expected' : do {
169             require B;
170             B::perlstring( $expected );
171             },
172             );
173             } #/ else [ if ( $type eq 'null' )]
174            
175 18 50       95 if ( $field->params->{nix} ) {
176 18         66 $guts = "!( $guts )";
177 18         106 }
178            
179             no warnings qw( uninitialized );
180             return eval "sub { $guts }";
181             }
182 21 100       376
183 1         10 my ( $self, $field ) = ( shift, @_ );
184             my $getter = $field->getter;
185            
186 1     1   958 my $other = 'LINQ::Field'->new( value => $field->params->{to} )->getter;
  1         2  
  1         301  
187 21         1751 my $cmp = $field->params->{cmp} || "==";
188             my $type = $field->params->{string} ? 'string' : 'numeric';
189             my $template = $templates{"$type $cmp"}
190             or LINQ::Util::Internal::throw(
191 7     7   16 "CallerError",
192 7         19 message => "Unexpected comparator '$cmp' for type '$type'",
193             );
194 7         114
195 7   100     24 my $guts;
196 7 100       315 if ( $field->params->{nocase} ) {
197 7 50       52 my $fold = ( $] > 5.016 ) ? 'CORE::fc' : 'lc';
198             $guts = sprintf(
199             $template,
200             "$fold( \$getter->( \$_ ) )",
201             "$fold( \$other->( \$_ ) )",
202             );
203 7         11 } #/ elsif ( $field->params->{...})
204 7 100       106 else {
205 2 50       13 $guts = sprintf(
206 2         13 $template,
207             '$getter->( $_ )',
208             '$other->( $_ )',
209             );
210             } #/ else [ if ( $type eq 'null' )]
211            
212             if ( $field->params->{nix} ) {
213 5         37 $guts = "!( $guts )";
214             }
215            
216             no warnings qw( uninitialized );
217             return eval "sub { $guts }";
218             }
219             }
220 7 100       109  
221 1         8 my ( $self, $field ) = ( shift, @_ );
222             my $getter = $field->getter;
223             my @expected = @{ $field->params->{in} };
224 1     1   7
  1         3  
  1         1215  
225 7         570 my $nix = ! $field->params->{nix};
226            
227             if ( $field->params->{nocase} ) {
228             return sub {
229             my $value = lc $getter->( $_ );
230 3     3   6 for my $expected ( @expected ) {
231 3         9 return !!$nix if $value eq lc $expected;
232 3         7 }
  3         50  
233             return !$nix;
234 3         56 };
235             }
236 3 100       53 else {
237             return sub {
238 2     2   6 my $value = $getter->( $_ );
239 2         4 for my $expected ( @expected ) {
240 3 100       15 return !!$nix if $value eq $expected;
241             }
242 1         5 return !$nix;
243 1         11 };
244             }
245             }
246              
247 4     4   8 {
248 4         9 my $_like_to_regexp = sub {
249 7 100       18 my ( $like, $ci ) = @_;
250             my $re = '';
251 3         12 my %anchors = (
252 2         22 start => substr( $like, 0, 1 ) ne '%',
253             end => substr( $like, -1, 1 ) ne '%',
254             );
255             my @parts = split qr{(\\*[.%])}, $like;
256             for my $p ( @parts ) {
257             next unless length $p;
258             my $backslash_count =()= $p =~ m{(\\)}g;
259             my $wild_count =()= $p =~ m{([%.])}g;
260             if ( $wild_count ) {
261             if ( $backslash_count && $backslash_count % 2 ) {
262             my $last = substr( $p, -2, 2, '' );
263             $p =~ s{\\\\}{\\};
264             $re .= quotemeta( $p . substr( $last, -1, 1 ) );
265             }
266             elsif ( $backslash_count ) {
267             my $last = substr( $p, -1, 1, '' );
268             $p =~ s{\\\\}{\\};
269             $re .= quotemeta( $p ) . ( $last eq '%' ? '.*' : '.' );
270             }
271             else {
272             $re .= $p eq '%' ? '.*' : '.';
273             }
274             } #/ if ( $wild_count )
275             else {
276             $p =~ s{\\(.)}{$1}g;
277             $re .= quotemeta( $p );
278             }
279             } #/ for my $p ( @parts )
280            
281             substr( $re, 0, 0, '\A' ) if $anchors{start};
282             $re .= '\z' if $anchors{end};
283            
284             $ci ? qr/$re/i : qr/$re/;
285             };
286              
287             my ( $self, $field ) = ( shift, @_ );
288             my $getter = $field->getter;
289            
290             my $match = $_like_to_regexp->(
291             $field->params->{like},
292             $field->params->{nocase},
293             );
294            
295             if ( $field->params->{nix} ) {
296             return sub {
297 5     5   11 my $value = $getter->( $_ );
298 5         15 $value !~ $match;
299             };
300             }
301             else {
302             return sub {
303 5         95 my $value = $getter->( $_ );
304             $value =~ $match;
305 5 100       102 };
306             }
307 2     2   5 }
308 2         10 }
309 1         12  
310             my ( $self, $field ) = ( shift, @_ );
311             my $getter = $field->getter;
312            
313 6     6   13 my $match = $field->params->{match};
314 6         45
315 4         46 require match::simple;
316            
317             if ( $field->params->{nix} ) {
318             return sub {
319             my $value = $getter->( $_ );
320             not match::simple::match( $value, $match );
321 2     2   6 };
322 2         7 }
323             else {
324 2         34 return sub {
325             my $value = $getter->( $_ );
326 2         663 match::simple::match( $value, $match );
327             };
328 2 100       2369 }
329             }
330 2     2   5  
331 2         9 my ( $self ) = ( shift );
332 1         11 return 'LINQ::FieldSet::Assertion::NOT'->new(
333             left => $self,
334             );
335             }
336 2     2   5  
337 2         11 my ( $self, $other ) = &$_process_args;
338 1         15 return 'LINQ::FieldSet::Assertion::AND'->new(
339             left => $self,
340             right => $other,
341             );
342             }
343 4     4 1 137  
344 4         21 my ( $self, $other ) = &$_process_args;
345             return 'LINQ::FieldSet::Assertion::OR'->new(
346             left => $self,
347             right => $other,
348             );
349             }
350 5     5 1 58  
351 5         26 my ( $self ) = ( shift );
352             sprintf 'check_fields(%s)', join q[, ], map $_->name, @{ $self->fields };
353             }
354              
355              
356             our $AUTHORITY = 'cpan:TOBYINK';
357             our $VERSION = '0.003';
358 1     1 1 15  
359 1         12 use Role::Tiny;
360             requires( qw/ left right _build_coderef / );
361              
362             my ( $self ) = ( shift );
363             $self->{coderef} ||= $self->_build_coderef;
364             }
365              
366 0     0 1 0 my ( $self ) = ( shift );
367 0         0 return 'LINQ::FieldSet::Assertion::NOT'->new(
  0         0  
368             left => $self,
369             );
370             }
371              
372             my ( $self, $other ) = &$_process_args;
373             return 'LINQ::FieldSet::Assertion::AND'->new(
374             left => $self,
375 1     1   16 right => $other,
  1         2  
  1         7  
376             );
377             }
378              
379 20     20   537 my ( $self, $other ) = &$_process_args;
380 20   100     112 return 'LINQ::FieldSet::Assertion::OR'->new(
381             left => $self,
382             right => $other,
383             );
384 1     1   27 }
385 1         7  
386              
387             our $AUTHORITY = 'cpan:TOBYINK';
388             our $VERSION = '0.003';
389              
390             use Class::Tiny qw( left );
391 1     1   28 use Role::Tiny::With ();
392 1         5 Role::Tiny::With::with( 'LINQ::FieldSet::Assertion::Combination' );
393              
394             use overload ();
395             'overload'->import(
396             q[&{}] => 'coderef',
397             q[|] => 'or',
398             q[&] => 'and',
399 2     2   56 q[~] => 'not',
400 2         12 );
401              
402             my ( $self ) = ( shift );
403             my $left = $self->left->coderef;
404             return sub { not $left->( $_ ) };
405             }
406              
407             my ( $self ) = ( shift );
408             return $self->left;
409             }
410              
411 1     1   368 LINQ::Util::Internal::throw(
  1         2  
  1         27  
412 1     1   231 "InternalError",
  1         2  
  1         24  
413             message => 'Unexpected second branch to NOT.',
414             );
415 1     1   4 }
  1         2  
  1         206  
416              
417              
418             our $AUTHORITY = 'cpan:TOBYINK';
419             our $VERSION = '0.003';
420              
421             use Class::Tiny qw( left right );
422             use Role::Tiny::With ();
423             Role::Tiny::With::with( 'LINQ::FieldSet::Assertion::Combination' );
424 3     3   6  
425 3         59 use overload ();
426 3     6   35 'overload'->import(
  6         14  
427             q[&{}] => 'coderef',
428             q[|] => 'or',
429             q[&] => 'and',
430 1     1   30 q[~] => 'not',
431 1         37 );
432              
433             my ( $self ) = ( shift );
434             my $left = $self->left->coderef;
435 1     1   29 my $right = $self->right->coderef;
436             return sub { $left->( $_ ) and $right->( $_ ) };
437             }
438              
439              
440             our $AUTHORITY = 'cpan:TOBYINK';
441             our $VERSION = '0.003';
442              
443             use Class::Tiny qw( left right );
444             use Role::Tiny::With ();
445             Role::Tiny::With::with( 'LINQ::FieldSet::Assertion::Combination' );
446 1     1   9  
  1         2  
  1         4  
447 1     1   383 use overload ();
  1         2  
  1         23  
448             'overload'->import(
449             q[&{}] => 'coderef',
450 1     1   4 q[|] => 'or',
  1         2  
  1         154  
451             q[&] => 'and',
452             q[~] => 'not',
453             );
454              
455             my ( $self ) = ( shift );
456             my $left = $self->left->coderef;
457             my $right = $self->right->coderef;
458             return sub { $left->( $_ ) or $right->( $_ ) };
459 6     6   14 }
460 6         126  
461 6         132 1;
462 6 100   17   32  
  17         34  
463              
464             =pod
465              
466             =encoding utf-8
467              
468             =head1 NAME
469              
470 1     1   7 LINQ::FieldSet::Assertion - represents an SQL-WHERE-like assertion/check
  1         2  
  1         3  
471 1     1   354  
  1         2  
  1         22  
472             =head1 DESCRIPTION
473              
474 1     1   5 LINQ::FieldSet::Assertion is a subclass of L<LINQ::FieldSet>.
  1         1  
  1         130  
475              
476             This is used internally by LINQ and you probably don't need to know about it
477             unless you're writing very specific extensions for LINQ. The end user
478             interface is the C<check_fields> function in L<LINQ::Util>.
479              
480             =head1 CONSTRUCTOR
481              
482             =over
483 3     3   7  
484 3         57 =item C<< new( ARGSLIST ) >>
485 3         60  
486 3 100   10   18 Constructs a fieldset from a list of fields like:
  10         16  
487              
488             'LINQ::FieldSet::Assertion'->new(
489             'field1', -param1 => 'value1', -param2,
490             'field2', -param1 => 'value2',
491             );
492              
493             Allowed parameters are:
494             C<< -is >> (followed by a value),
495             C<< -to >> (followed by a value),
496             C<< -in >> (followed by a value),
497             C<< -like >> (followed by a value),
498             C<< -match >> (followed by a value),
499             C<< -cmp >> (followed by a value),
500             C<< -numeric >> (no value),
501             C<< -string >> (no value),
502             C<< -not >> (no value), and
503             C<< -nocase >> (no value).
504              
505             =back
506              
507             =begin trustme
508              
509             =item BUILD
510              
511             =end trustme
512              
513             =head1 METHODS
514              
515             =over
516              
517             =item C<< and( OTHER ) >>
518              
519             Return a LINQ::FieldSet::Assertion::AND object which is a conjunction of this
520             assertion and another assertion.
521              
522             =item C<< or( OTHER ) >>
523              
524             Return a LINQ::FieldSet::Assertion::OR object which is an inclusive disjunction
525             of this assertion and another assertion.
526              
527             =item C<not>
528              
529             Return a LINQ::FieldSet::Assertion::NOT object which is the negation of this
530             assertion.
531              
532             =item C<coderef>
533              
534             Gets a coderef for this assertion; the coderef operates on C<< $_ >>.
535              
536             =item C<to_string>
537              
538             Basic string representation of the fieldset.
539              
540             =back
541              
542             The LINQ::FieldSet::Assertion::{AND,OR,NOT} classes are lightweight classes
543             which also implement the C<and>, C<or>, C<not>, and C<coderef> methods, and
544             have the same overloading as LINQ::FieldSet::Assertion, but do not inherit
545             from it.
546              
547             =head1 OVERLOADING
548              
549             This class overloads
550             C<< "" >> to call the C<< to_string >> method;
551             C<< & >> to call the C<< and >> method;
552             C<< | >> to call the C<< or >> method;
553             C<< ~ >> to call the C<< not >> method; and
554             C<< &{} >> to call the C<< coderef >> method.
555              
556             =head1 BUGS
557              
558             Please report any bugs to
559             L<http://rt.cpan.org/Dist/Display.html?Queue=LINQ>.
560              
561             =head1 SEE ALSO
562              
563             L<LINQ::FieldSet>, L<LINQ::Util>.
564              
565             =head1 AUTHOR
566              
567             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
568              
569             =head1 COPYRIGHT AND LICENCE
570              
571             This software is copyright (c) 2021 by Toby Inkster.
572              
573             This is free software; you can redistribute it and/or modify it under
574             the same terms as the Perl 5 programming language system itself.
575              
576             =head1 DISCLAIMER OF WARRANTIES
577              
578             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
579             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
580             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.