File Coverage

blib/lib/OData/QueryParams/DBIC/FilterUtils.pm
Criterion Covered Total %
statement 69 69 100.0
branch 34 34 100.0
condition 5 5 100.0
subroutine 11 11 100.0
pod 3 3 100.0
total 122 122 100.0


line stmt bran cond sub pod time code
1             package OData::QueryParams::DBIC::FilterUtils;
2              
3             # ABSTRACT: parse filter param
4              
5 18     18   132242 use v5.20;
  18         82  
6              
7 18     18   120 use strict;
  18         38  
  18         413  
8 18     18   92 use warnings;
  18         50  
  18         522  
9              
10 18     18   100 use feature 'signatures';
  18         42  
  18         1460  
11 18     18   103 no warnings 'experimental::signatures';
  18         38  
  18         714  
12              
13 18     18   952 use parent 'Exporter';
  18         640  
  18         126  
14              
15             our @EXPORT_OK = qw(parser);
16             our $VERSION = '0.09';
17              
18 18         19848 use constant Operators => {
19             EQUALS => 'eq',
20             AND => 'and',
21             OR => 'or',
22             GREATER_THAN => 'gt',
23             GREATER_THAN_EQUAL => 'ge',
24             LESS_THAN => 'lt',
25             LESS_THAN_EQUAL => 'le',
26             LIKE => 'like',
27             IS_NULL => 'is null',
28             NOT_EQUAL => 'ne',
29 18     18   1964 };
  18         42  
30              
31 231     231 1 463 sub predicate ($config) {
  231         392  
  231         319  
32              
33 231   100     520 $config ||= {};
34              
35             my $this = {
36             subject => $config->{subject},
37             sub_type => $config->{sub_type},
38             value => $config->{value},
39             val_type => $config->{val_type},
40             operator => ($config->{operator}) ? $config->{operator} : Operators->{EQUALS},
41 231 100       1176 };
42              
43 231 100 100     1419 if ( $this->{value} && $this->{value} =~ m{\A[0-9]+(?:\.[0-9]+)?\z} ) {
44 137         343 $this->{val_type} = 'numeric';
45             }
46              
47 231         544 return $this;
48             }
49              
50             sub parser {
51 175     175 1 15166 state $order = [qw/parenthesis andor math op startsWith endsWith contains substringof/];
52 175         471 state $REGEX = {
53             parenthesis => qr/^([(](.*)[)])$/x,
54             andor => qr/^(.*?) \s+ (or|and) \s+ (.*)$/x,
55             math => qr/\(? ([A-Za-z0-9\/\._]*) \s+ (mod|div|add|sub|mul) \s+ ([0-9]+(?:\.[0-9]+)? ) \)? \s+ (.*) /x,
56             op => qr/
57             ((?:(?:\b[A-Za-z]+\(.*?\))
58             | [A-Za-z0-9\/\._]
59             | '.*?')*)
60             \s+
61             (eq|gt|lt|ge|le|ne)
62             \s+
63             (true|false|datetimeoffset'(.*)'|('.*')|(?:[0-9]+(?:\.[0-9]+)?)*)
64             /x,
65             startsWith => qr/^startswith[(](.*), \s* '(.*)'[)]/x,
66             endsWith => qr/^endswith[(](.*), \s* '(.*)'[)]/x,
67             contains => qr/^contains[(](.*), \s* '(.*)'[)]/x,
68             substringof => qr/^substringof[(]'(.*?)', \s* (.*)[)]/x,
69             };
70              
71 319     319 1 473 sub parse_fragment ($filter) {
  319         510  
  319         459  
72 319         555 my $found;
73             my $obj;
74              
75             KEY:
76 319         540 for my $key ( @{$order} ) {
  319         680  
77 1743 100       3207 last KEY if $found;
78              
79 1502         2561 my $regex = $REGEX->{$key};
80              
81 1502         13690 my @match = $filter =~ $regex;
82              
83 1502 100       4108 if ( @match ) {
84 250 100       865 if ( $key eq 'parenthesis' ) {
    100          
    100          
    100          
85 21 100       99 if( index( $match[1], ')' ) < index( $match[1], '(' ) ) {
86 5         21 next KEY;
87             }
88              
89 16         71 $obj = parse_fragment($match[1]);
90             }
91             elsif ( $key eq 'math' ) {
92 24         88 $obj = parse_fragment( $match[2] . ' ' . $match[3] );
93 24         123 $obj->{subject} = predicate({
94             subject => $match[0],
95             sub_type => 'field',
96             operator => $match[1],
97             value => $match[2],
98             val_type => 'string',
99             });
100              
101 24         73 $obj->{sub_type} = undef;
102             }
103             elsif ( $key eq 'andor' ) {
104 15         51 $obj = predicate({
105             subject => parse_fragment( $match[0] ),
106             operator => $match[1],
107             value => parse_fragment( $match[2] ),
108             });
109             }
110             elsif ( $key eq 'op' ) {
111 176         885 $obj = predicate({
112             subject => $match[0],
113             sub_type => 'field',
114             operator => $match[1],
115             value => $match[2],
116             });
117              
118 176 100       836 if ( $match[0] =~ m{\(.*?\)} ) {
119 75         230 $obj->{subject} = parse_fragment( $match[0] );
120 75         155 $obj->{sub_type} = undef;
121             }
122              
123 176 100       1078 if ( $match[2] =~ m{\A'.*?'\z} ) {
    100          
    100          
124             #$obj->{value} = $1;
125 37         89 $obj->{val_type} = 'string';
126             }
127             elsif ( $match[2] eq 'true' | $match[2] eq 'false' ) {
128 12         81 $obj->{val_type} = 'bool';
129             }
130             elsif ( $match[2] =~ m{\A[0-9]+(?:\.[0-9]+)?\z} ) {
131 126         282 $obj->{val_type} = 'numeric';
132             }
133             else { # datetimeoffset
134 1         2 $obj->{val_type} = 'function';
135             }
136              
137             #if(typeof obj.value === 'string') {
138             # var quoted = obj.value.match(/^'(.*)'$/);
139             # var m = obj.value.match(/^datetimeoffset'(.*)'$/);
140             # if(quoted && quoted.length > 1) {
141             # obj.value = quoted[1];
142             # } else if(m && m.length > 1) {
143             # obj.value = new Date(m[1]);
144             # }
145             #}
146             }
147             # ( $key eq 'startsWith' || $key eq 'endsWith' || $key eq 'contains' || $key eq 'substringof' ) {
148             else {
149 14 100       112 $obj = predicate({
    100          
150             subject => $match[0],
151             sub_type => ( $key eq 'substringof' ? 'string' : 'field' ),
152             operator => $key,
153             value => $match[1],
154             val_type => ( $key eq 'substringof' ? 'field' : 'string' ),
155             });
156             }
157              
158 245         662 $found++;
159             }
160             }
161              
162 319         864 return $obj;
163             }
164              
165             return
166 177     177   284 sub ($filter_string) {
  177         297  
  177         359  
167              
168 177 100       433 return if !defined $filter_string;
169              
170 176         608 $filter_string =~ s{\A\s+}{};
171 176         730 $filter_string =~ s{\s+\z}{};
172              
173 176 100       444 return if $filter_string eq '';
174 174         435 return parse_fragment($filter_string);
175 175         800 };
176             }
177              
178             1;
179              
180             __END__