File Coverage

blib/lib/OData/QueryParams/DBIC/FilterUtils.pm
Criterion Covered Total %
statement 68 68 100.0
branch 33 34 97.0
condition 5 5 100.0
subroutine 11 11 100.0
pod 3 3 100.0
total 120 121 99.1


line stmt bran cond sub pod time code
1             package OData::QueryParams::DBIC::FilterUtils;
2              
3             # ABSTRACT: parse filter param
4              
5 17     17   122430 use v5.20;
  17         78  
6              
7 17     17   94 use strict;
  17         39  
  17         355  
8 17     17   83 use warnings;
  17         36  
  17         684  
9              
10 17     17   103 use feature 'signatures';
  17         101  
  17         1416  
11 17     17   100 no warnings 'experimental::signatures';
  17         37  
  17         710  
12              
13 17     17   969 use parent 'Exporter';
  17         609  
  17         191  
14              
15             our @EXPORT_OK = qw(parser);
16             our $VERSION = '0.08';
17              
18 17         19273 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 17     17   1833 };
  17         41  
30              
31 229     229 1 433 sub predicate ($config) {
  229         333  
  229         326  
32              
33 229   100     485 $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 229 100       1021 };
42              
43 229 100 100     1395 if ( $this->{value} && $this->{value} =~ m{\A[0-9]+(?:\.[0-9]+)?\z} ) {
44 136         333 $this->{val_type} = 'numeric';
45             }
46              
47 229         481 return $this;
48             }
49              
50             sub parser {
51 173     173 1 14103 state $order = [qw/parenthesis andor math op startsWith endsWith contains substringof/];
52 173         461 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 317     317 1 500 sub parse_fragment ($filter) {
  317         479  
  317         463  
72 317         522 my $found;
73             my $obj;
74              
75             KEY:
76 317         495 for my $key ( @{$order} ) {
  317         662  
77 1733 100       3139 last KEY if $found;
78              
79 1494         2545 my $regex = $REGEX->{$key};
80              
81 1494         13600 my @match = $filter =~ $regex;
82              
83 1494 100       3894 if ( @match ) {
84 248 100       834 if ( $key eq 'parenthesis' ) {
    100          
    100          
    100          
85 21 100       94 if( index( $match[1], ')' ) < index( $match[1], '(' ) ) {
86 5         28 next KEY;
87             }
88              
89 16         70 $obj = parse_fragment($match[1]);
90             }
91             elsif ( $key eq 'math' ) {
92 24         90 $obj = parse_fragment( $match[2] . ' ' . $match[3] );
93 24         118 $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         62 $obj->{sub_type} = undef;
102             }
103             elsif ( $key eq 'andor' ) {
104 15         73 $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 174         856 $obj = predicate({
112             subject => $match[0],
113             sub_type => 'field',
114             operator => $match[1],
115             value => $match[2],
116             });
117              
118 174 100       826 if ( $match[0] =~ m{\(.*?\)} ) {
119 75         197 $obj->{subject} = parse_fragment( $match[0] );
120 75         158 $obj->{sub_type} = undef;
121             }
122              
123 174 100       969 if ( $match[2] =~ m{\A'.*?'\z} ) {
    100          
    50          
124             #$obj->{value} = $1;
125 37         86 $obj->{val_type} = 'string';
126             }
127             elsif ( $match[2] eq 'true' | $match[2] eq 'false' ) {
128 12         79 $obj->{val_type} = 'bool';
129             }
130             elsif ( $match[2] =~ m{\A[0-9]+(?:\.[0-9]+)?\z} ) {
131 125         265 $obj->{val_type} = 'numeric';
132             }
133              
134             #if(typeof obj.value === 'string') {
135             # var quoted = obj.value.match(/^'(.*)'$/);
136             # var m = obj.value.match(/^datetimeoffset'(.*)'$/);
137             # if(quoted && quoted.length > 1) {
138             # obj.value = quoted[1];
139             # } else if(m && m.length > 1) {
140             # obj.value = new Date(m[1]);
141             # }
142             #}
143             }
144             # ( $key eq 'startsWith' || $key eq 'endsWith' || $key eq 'contains' || $key eq 'substringof' ) {
145             else {
146 14 100       111 $obj = predicate({
    100          
147             subject => $match[0],
148             sub_type => ( $key eq 'substringof' ? 'string' : 'field' ),
149             operator => $key,
150             value => $match[1],
151             val_type => ( $key eq 'substringof' ? 'field' : 'string' ),
152             });
153             }
154              
155 243         617 $found++;
156             }
157             }
158              
159 317         934 return $obj;
160             }
161              
162             return
163 175     175   251 sub ($filter_string) {
  175         287  
  175         310  
164              
165 175 100       402 return if !defined $filter_string;
166              
167 174         556 $filter_string =~ s{\A\s+}{};
168 174         701 $filter_string =~ s{\s+\z}{};
169              
170 174 100       408 return if $filter_string eq '';
171 172         357 return parse_fragment($filter_string);
172 173         747 };
173             }
174              
175             1;
176              
177             __END__