File Coverage

blib/lib/OData/QueryParams/DBIC.pm
Criterion Covered Total %
statement 141 141 100.0
branch 42 42 100.0
condition 20 23 86.9
subroutine 19 19 100.0
pod 1 1 100.0
total 223 226 98.6


line stmt bran cond sub pod time code
1             package OData::QueryParams::DBIC;
2              
3             # ABSTRACT: parse OData style query params and provide info for DBIC queries.
4              
5 15     15   1050479 use v5.20;
  15         217  
6              
7 15     15   82 use strict;
  15         30  
  15         375  
8 15     15   114 use warnings;
  15         59  
  15         549  
9              
10 15     15   94 use feature 'signatures';
  15         25  
  15         6543  
11 15     15   119 no warnings 'experimental::signatures';
  15         29  
  15         604  
12              
13 15     15   11964 use parent 'Exporter';
  15         4402  
  15         73  
14              
15 15     15   909 use Carp qw(croak);
  15         32  
  15         733  
16 15     15   6657 use Mojo::Parameters;
  15         3057193  
  15         108  
17 15     15   9433 use OData::QueryParams::DBIC::FilterUtils qw(parser);
  15         42  
  15         952  
18 15     15   113 use List::Util qw(any);
  15         31  
  15         987  
19 15     15   95 use Scalar::Util qw(blessed);
  15         34  
  15         20356  
20              
21             our @EXPORT = qw(params_to_dbic);
22              
23             our $VERSION = '0.08';
24              
25 229     229 1 199950 sub params_to_dbic ( $query_string, %opts ) {
  229         433  
  229         397  
  229         308  
26 229         360 my $query;
27              
28 229 100       709 if ( blessed $query_string ) {
29 25 100       74 if ( $query_string->isa('Mojo::Parameters') ) {
30 24         30 $query = $query_string
31             }
32             else {
33 1         18 croak 'Invalid object';
34             }
35             }
36             else {
37 204         770 $query = Mojo::Parameters->new( $query_string );
38             }
39              
40 228         4384 my $params = $query->to_hash;
41              
42 228 100       26635 my $filter_key = $opts{strict} ? '$filter' : 'filter';
43 228         813 my %filter = _parse_filter( delete $params->{$filter_key}, %opts );
44              
45 126         218 my %dbic_opts;
46              
47             PARAM_KEY:
48 126         185 for my $param_key ( keys %{ $params } ) {
  126         392  
49 94         265 my $method = $param_key =~ s{\A\$}{}r;
50              
51 94 100 100     260 next PARAM_KEY if $opts{strict} && $param_key !~ m{\A\$}xms;
52              
53 93         644 my $sub = __PACKAGE__->can( '_parse_' . $method );
54 93 100       260 if ( $sub ) {
55 90         270 my %key_opts = $sub->( $params->{$param_key}, %opts );
56 90         336 %dbic_opts = (%dbic_opts, %key_opts);
57             }
58             }
59              
60 126         675 return \%filter, \%dbic_opts;
61             }
62              
63 11     11   18 sub _parse_top ( $top_data, %opt ) {
  11         14  
  11         21  
  11         12  
64 11 100       47 return if $top_data !~ m{\A[0-9]+\z};
65 10         28 return ( rows => $top_data );
66             }
67              
68 17     17   26 sub _parse_skip ( $skip_data, %opt ) {
  17         28  
  17         24  
  17         22  
69 17 100       78 return if $skip_data !~ m{\A[0-9]+\z};
70 14         55 return ( page => $skip_data + 1 );
71             }
72              
73 228     228   351 sub _parse_filter ( $filter_data, %opt ) {
  228         361  
  228         348  
  228         303  
74 228 100       611 return if !defined $filter_data;
75 155 100       350 return if $filter_data eq '';
76              
77 152         455 my $obj = parser->( $filter_data );
78 152         820 my %filter = _flatten_filter( $obj, %opt );
79              
80 50         222 return %filter;
81             }
82              
83 34     34   55 sub _parse_orderby ( $orderby_data, %opt ) {
  34         53  
  34         50  
  34         47  
84 34         207 my @order_bys = split /\s*,\s*/, $orderby_data;
85              
86 34         60 my @dbic_order_by;
87              
88 34         67 for my $order_by ( @order_bys ) {
89 62         89 my $direction;
90 62 100 100     316 $order_by =~ s{\s+(.*?)\z}{$1 && (lc $1 eq 'desc' || lc $1 eq 'asc') && ( $direction = lc $1 ); ''}e;
  40   100     289  
  40         136  
91              
92 62 100 100     215 if ( $opt{me} && $order_by !~ m{/} ) {
93 27         63 $order_by = 'me.' . $order_by;
94             }
95              
96 62         133 $order_by =~ s{/}{.};
97              
98 62   100     177 $direction //= 'asc';
99              
100 62         250 push @dbic_order_by, { -$direction => $order_by };
101             }
102              
103 34         137 return order_by => \@dbic_order_by;
104             }
105              
106 28     28   38 sub _parse_select ( $select_data, %opt ) {
  28         44  
  28         45  
  28         71  
107 28 100       86 return if !length $select_data;
108              
109 23         118 my @columns = split /\s*,\s*/, $select_data;
110 23 100       87 if ( $opt{me} ) {
111 5 100       9 @columns = map{ m{/} ? $_ : 'me.' . $_ }@columns;
  9         33  
112             }
113              
114 23         51 my @full_names = map{ s{/}{.}r }@columns;
  36         195  
115 23         85 return columns => \@full_names;
116             }
117              
118 176     176   286 sub _flatten_filter ($obj, %opt) {
  176         267  
  176         267  
  176         247  
119 176         1052 my %map = (
120             'lt' => '<',
121             'le' => '<=',
122             'gt' => '>',
123             'ge' => '>=',
124             'eq' => '==',
125             'ne' => '!=',
126             'and' => \&_build_bool,
127             'or' => \&_build_bool,
128             );
129              
130 176         440 my $op = $obj->{operator};
131              
132 176 100       740 croak 'Unknown op' if !defined $op;
133              
134 166         249 my %filter;
135              
136 166 100       421 if ( !exists $map{$op} ) {
137 2         311 croak 'Unsupported op: ' . $op;
138             }
139             else {
140 164         296 my $rule = $map{$op};
141 164         274 my $subject = $obj->{subject};
142 164         254 my $value = $obj->{value};
143              
144 164 100       364 if ( !defined $subject ) {
    100          
145 63         615 croak 'Unsupported expression';
146             }
147             elsif ( ref $rule ) {
148 12         48 my ($filter_key, $filter_value) = $rule->($obj, %opt);
149 12         39 $filter{$filter_key} = $filter_value;
150             }
151             else {
152 89 100       205 if ( ref $subject ) {
153 27         291 croak 'Complex expressions on the left side are not supported (yet)';
154             }
155              
156 62 100       182 if ( $value =~ m{\A'(.*)'\z} ) {
157 13         44 $value = $1;
158             }
159              
160 62   33     235 my $is_field = $obj->{sub_type} && $obj->{sub_type} eq 'field';
161 62         132 my $is_foreign_field = $subject =~ m{\A\w+\/};
162              
163 62 100 66     197 if ( $opt{me} && $is_field && !$is_foreign_field ) {
      100        
164 11         29 $subject = 'me.' . $subject;
165             }
166              
167 62         149 $subject =~ s{\A\w+\K/}{.};
168              
169 62         227 $filter{ $subject } = {
170             $rule => $value,
171             };
172             }
173             }
174              
175 74         373 return %filter;
176             }
177              
178 12     12   27 sub _build_bool ($obj, %opt) {
  12         23  
  12         19  
  12         17  
179 12         23 my $op = $obj->{operator};
180 12         23 my $subject = $obj->{subject};
181 12         22 my $value = $obj->{value};
182              
183 12         54 return "-$op" => [
184             { _flatten_filter( $subject, %opt ) },
185             { _flatten_filter( $value, %opt ) },
186             ];
187             }
188              
189             1;
190              
191             __END__