File Coverage

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


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 16     16   1047387 use v5.20;
  16         231  
6              
7 16     16   87 use strict;
  16         29  
  16         421  
8 16     16   118 use warnings;
  16         51  
  16         594  
9              
10 16     16   92 use feature 'signatures';
  16         31  
  16         2649  
11 16     16   113 no warnings 'experimental::signatures';
  16         34  
  16         569  
12              
13 16     16   6948 use parent 'Exporter';
  16         4663  
  16         83  
14              
15 16     16   1005 use Carp qw(croak);
  16         38  
  16         737  
16 16     16   7076 use Mojo::Parameters;
  16         2953058  
  16         109  
17 16     16   9250 use OData::QueryParams::DBIC::FilterUtils qw(parser);
  16         51  
  16         1019  
18 16     16   124 use List::Util qw(any);
  16         29  
  16         1061  
19 16     16   101 use Scalar::Util qw(blessed);
  16         32  
  16         20784  
20              
21             our @EXPORT = qw(params_to_dbic);
22              
23             our $VERSION = '0.09';
24              
25 229     229 1 227176 sub params_to_dbic ( $query_string, %opts ) {
  229         433  
  229         408  
  229         326  
26 229         327 my $query;
27              
28 229 100       780 if ( blessed $query_string ) {
29 25 100       84 if ( $query_string->isa('Mojo::Parameters') ) {
30 24         39 $query = $query_string
31             }
32             else {
33 1         18 croak 'Invalid object';
34             }
35             }
36             else {
37 204         749 $query = Mojo::Parameters->new( $query_string );
38             }
39              
40 228         4353 my $params = $query->to_hash;
41              
42 228 100       27174 my $filter_key = $opts{strict} ? '$filter' : 'filter';
43 228         838 my %filter = _parse_filter( delete $params->{$filter_key}, %opts );
44              
45 126         215 my %dbic_opts;
46              
47             PARAM_KEY:
48 126         194 for my $param_key ( keys %{ $params } ) {
  126         386  
49 94         257 my $method = $param_key =~ s{\A\$}{}r;
50              
51 94 100 100     266 next PARAM_KEY if $opts{strict} && $param_key !~ m{\A\$}xms;
52              
53 93         583 my $sub = __PACKAGE__->can( '_parse_' . $method );
54 93 100       256 if ( $sub ) {
55 90         240 my %key_opts = $sub->( $params->{$param_key}, %opts );
56 90         345 %dbic_opts = (%dbic_opts, %key_opts);
57             }
58             }
59              
60 126         666 return \%filter, \%dbic_opts;
61             }
62              
63 11     11   18 sub _parse_top ( $top_data, %opt ) {
  11         17  
  11         16  
  11         14  
64 11 100       50 return if $top_data !~ m{\A[0-9]+\z};
65 10         31 return ( rows => $top_data );
66             }
67              
68 17     17   37 sub _parse_skip ( $skip_data, %opt ) {
  17         28  
  17         25  
  17         22  
69 17 100       84 return if $skip_data !~ m{\A[0-9]+\z};
70 14         55 return ( page => $skip_data + 1 );
71             }
72              
73 228     228   361 sub _parse_filter ( $filter_data, %opt ) {
  228         353  
  228         359  
  228         342  
74 228 100       620 return if !defined $filter_data;
75 155 100       363 return if $filter_data eq '';
76              
77 152         500 my $obj = parser->( $filter_data );
78 152         867 my %filter = _flatten_filter( $obj, %opt );
79              
80 50         218 return %filter;
81             }
82              
83 34     34   46 sub _parse_orderby ( $orderby_data, %opt ) {
  34         49  
  34         43  
  34         38  
84 34         178 my @order_bys = split /\s*,\s*/, $orderby_data;
85              
86 34         50 my @dbic_order_by;
87              
88 34         62 for my $order_by ( @order_bys ) {
89 62         71 my $direction;
90 62 100 100     293 $order_by =~ s{\s+(.*?)\z}{$1 && (lc $1 eq 'desc' || lc $1 eq 'asc') && ( $direction = lc $1 ); ''}e;
  40   100     244  
  40         124  
91              
92 62 100 100     211 if ( $opt{me} && $order_by !~ m{/} ) {
93 27         51 $order_by = 'me.' . $order_by;
94             }
95              
96 62         112 $order_by =~ s{/}{.};
97              
98 62   100     156 $direction //= 'asc';
99              
100 62         205 push @dbic_order_by, { -$direction => $order_by };
101             }
102              
103 34         116 return order_by => \@dbic_order_by;
104             }
105              
106 28     28   45 sub _parse_select ( $select_data, %opt ) {
  28         47  
  28         43  
  28         83  
107 28 100       89 return if !length $select_data;
108              
109 23         115 my @columns = split /\s*,\s*/, $select_data;
110 23 100       70 if ( $opt{me} ) {
111 5 100       9 @columns = map{ m{/} ? $_ : 'me.' . $_ }@columns;
  9         31  
112             }
113              
114 23         53 my @full_names = map{ s{/}{.}r }@columns;
  36         142  
115 23         86 return columns => \@full_names;
116             }
117              
118 179     179   2274 sub _flatten_filter ($obj, %opt) {
  179         271  
  179         273  
  179         258  
119 179         1099 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 179         381 my $op = $obj->{operator};
131              
132 179 100       731 croak 'Unknown op' if !defined $op;
133              
134 169         249 my %filter;
135              
136 169 100       450 if ( !exists $map{$op} ) {
137 2         451 croak 'Unsupported op: ' . $op;
138             }
139             else {
140 167         299 my $rule = $map{$op};
141 167         266 my $subject = $obj->{subject};
142 167         314 my $value = $obj->{value};
143              
144 167 100       432 if ( !defined $subject ) {
    100          
145 63         604 croak 'Unsupported expression';
146             }
147             elsif ( ref $rule ) {
148 12         61 my ($filter_key, $filter_value) = $rule->($obj, %opt);
149 12         44 $filter{$filter_key} = $filter_value;
150             }
151             else {
152 92 100       213 if ( ref $subject ) {
153 27         295 croak 'Complex expressions on the left side are not supported (yet)';
154             }
155              
156 65 100       219 if ( $value =~ m{\A'(.*)'\z} ) {
157 13         41 $value = $1;
158             }
159              
160 65   100     257 my $is_field = $obj->{sub_type} && $obj->{sub_type} eq 'field';
161 65         165 my $is_foreign_field = $subject =~ m{\A\w+\/};
162              
163 65 100 100     205 if ( $opt{me} && $is_field && !$is_foreign_field ) {
      100        
164 11         27 $subject = 'me.' . $subject;
165             }
166              
167 65         164 $subject =~ s{\A\w+\K/}{.};
168              
169 65         243 $filter{ $subject } = {
170             $rule => $value,
171             };
172             }
173             }
174              
175 77         415 return %filter;
176             }
177              
178 12     12   30 sub _build_bool ($obj, %opt) {
  12         20  
  12         21  
  12         20  
179 12         25 my $op = $obj->{operator};
180 12         23 my $subject = $obj->{subject};
181 12         22 my $value = $obj->{value};
182              
183 12         71 return "-$op" => [
184             { _flatten_filter( $subject, %opt ) },
185             { _flatten_filter( $value, %opt ) },
186             ];
187             }
188              
189             1;
190              
191             __END__