File Coverage

blib/lib/WebService/Solr/Query.pm
Criterion Covered Total %
statement 125 132 94.7
branch 8 8 100.0
condition 13 17 76.4
subroutine 24 26 92.3
pod 4 4 100.0
total 174 187 93.0


line stmt bran cond sub pod time code
1             package WebService::Solr::Query;
2              
3 2     2   13605 use Moo;
  2         8895  
  2         9  
4              
5 2     2   2394 use Types::Standard qw(ArrayRef);
  2         77696  
  2         18  
6              
7 2     2   1280 use overload q("") => 'stringify';
  2         5  
  2         10  
8              
9             my $escape_chars = quotemeta( '+-&|!(){}[]^"~*?:\\' );
10              
11             has 'query' => ( is => 'ro', isa => ArrayRef, default => sub { [] } );
12              
13 2     2   189 use constant D => 0;
  2         4  
  2         2644  
14              
15             sub BUILDARGS {
16 33     33 1 88713 my $class = shift;
17              
18 33 100 33     241 if ( @_ == 1 && ref $_[ 0 ] && ref $_[ 0 ] eq 'ARRAY' ) {
      66        
19 1         16 return { query => $_[ 0 ] };
20             }
21              
22 32         497 return { query => \@_ };
23             }
24              
25             sub stringify {
26 66     66 1 28215 my $self = shift;
27              
28 66         194 return $self->_dispatch_struct( $self->query );
29             }
30              
31             sub _dispatch_struct {
32 150     150   257 my ( $self, $struct ) = @_;
33              
34 150         292 my $method = '_struct_' . ref $struct;
35              
36 150         213 D && $self->___log( "Dispatching to ->$method " . __dumper( $struct ) );
37              
38 150         352 my $rv = $self->$method( $struct );
39              
40 150         200 D && $self->___log( "Returned: $rv" );
41              
42 150         391 return $rv;
43             }
44              
45             sub _struct_HASH {
46 84     84   132 my ( $self, $struct ) = @_;
47              
48 84         130 my @clauses;
49              
50 84         237 for my $k ( sort keys %$struct ) {
51 106         192 my $v = $struct->{ $k };
52              
53 106         133 D && $self->___log( "Key => $k, value => " . __dumper( $v ) );
54              
55 106 100       244 if ( $k =~ m{^-(.+)} ) {
56 8         21 my $method = "_op_$1";
57              
58 8         11 D && $self->___log( "Dispatch ->$method " . __dumper( $v ) );
59 8         20 push @clauses, $self->$method( $v );
60             }
61             else {
62 98         132 D
63             && $self->___log(
64             "Dispatch ->_dispatch_value $k, " . __dumper( $v ) );
65 98         195 push @clauses, $self->_dispatch_value( $k, $v );
66             }
67             }
68              
69 84         156 my $rv = join( ' AND ', @clauses );
70              
71 84         114 D && $self->___log( "Returning: $rv" );
72              
73 84         146 return $rv;
74             }
75              
76             sub _struct_ARRAY {
77 66     66   110 my ( $self, $struct ) = @_;
78              
79             my $rv
80             = '('
81 66         141 . join( " OR ", map { $self->_dispatch_struct( $_ ) } @$struct )
  68         136  
82             . ')';
83              
84 66         99 D && $self->___log( "Returning: $rv" );
85              
86 66         105 return $rv;
87             }
88              
89             sub _dispatch_value {
90 106     106   187 my ( $self, $k, $v ) = @_;
91              
92 106         140 my $rv;
93             ### it's an array ref, the first element MAY be an operator!
94             ### it would look something like this:
95             # [ '-and',
96             # { '-require' => 'star' },
97             # { '-require' => 'wars' }
98             # ];
99 106 100 100     672 if ( ref $v
      66        
      100        
100             and UNIVERSAL::isa( $v, 'ARRAY' )
101             and defined $v->[ 0 ]
102             and $v->[ 0 ] =~ /^ - ( AND|OR ) $/ix )
103             {
104             ### XXX we're assuming that all the next statements MUST
105             ### be hashrefs. is this correct?
106 8         21 $v = [ @$v ]; # Copy the array because we're going to be modifying it.
107 8         17 shift @$v;
108 8         22 my $op = uc $1;
109              
110 8         13 D
111             && $self->___log(
112             "Special operator detected: $op " . __dumper( $v ) );
113              
114 8         12 my @clauses;
115 8         15 for my $href ( @$v ) {
116 16         25 D
117             && $self->___log( "Dispatch ->_dispatch_struct({ $k, "
118             . __dumper( $href )
119             . '})' );
120              
121             ### the individual directive ($href) pertains to the key,
122             ### so we should send that along.
123 16         43 my $part = $self->_dispatch_struct( { $k => $href } );
124              
125 16         32 D && $self->___log( "Returned $part" );
126              
127 16         34 push @clauses, '(' . $part . ')';
128             }
129              
130 8         26 $rv = '(' . join( " $op ", @clauses ) . ')';
131              
132             ### nothing special about this combo, so do a usual dispatch
133             }
134             else {
135 98   100     285 my $method = '_value_' . ( ref $v || 'SCALAR' );
136              
137 98         135 D && $self->___log( "Dispatch ->$method $k, " . __dumper( $v ) );
138              
139 98         228 $rv = $self->$method( $k, $v );
140             }
141              
142 106         159 D && $self->___log( "Returning: $rv" );
143              
144 106         224 return $rv;
145             }
146              
147             sub _value_SCALAR {
148 72     72   124 my ( $self, $k, $v ) = @_;
149              
150 72 100       130 if ( ref $v ) {
151 2         5 $v = $$v;
152             }
153             else {
154 70         134 $v = '"' . $self->escape( $v ) . '"';
155             }
156              
157 72         146 my $r = qq($k:$v);
158 72         126 $r =~ s{^:}{};
159              
160 72         99 D && $self->___log( "Returning: $r" );
161              
162 72         171 return $r;
163             }
164              
165             sub _value_HASH {
166 52     52   93 my ( $self, $k, $v ) = @_;
167              
168 52         106 my @clauses;
169              
170 52         124 for my $op ( sort keys %$v ) {
171 52         86 my $struct = $v->{ $op };
172 52         304 $op =~ s{^-(.+)}{_op_$1};
173              
174 52         94 D && $self->___log( "Dispatch ->$op $k, " . __dumper( $v ) );
175              
176 52         141 push @clauses, $self->$op( $k, $struct );
177             }
178              
179 52         122 my $rv = join( ' AND ', @clauses );
180              
181 52         65 D && $self->___log( "Returning: $rv" );
182              
183 52         107 return $rv;
184             }
185              
186             sub _value_ARRAY {
187 28     28   52 my ( $self, $k, $v ) = @_;
188              
189             my $rv = '('
190 28         49 . join( ' OR ', map { $self->_value_SCALAR( $k, $_ ) } @$v ) . ')';
  54         108  
191              
192 28         55 D && $self->___log( "Returning: $rv" );
193              
194 28         61 return $rv;
195             }
196              
197             sub _op_default {
198 8     8   16 my ( $self, $v ) = @_;
199 8         18 return $self->_dispatch_value( '', $v );
200             }
201              
202             sub _op_range {
203 4     4   8 my ( $self, $k ) = ( shift, shift );
204 4         7 my @v = @{ shift() };
  4         9  
205 4         15 return "$k:[$v[ 0 ] TO $v[ 1 ]]";
206             }
207              
208             *_op_range_inc = \&_op_range;
209              
210             sub _op_range_exc {
211 8     8   18 my ( $self, $k ) = ( shift, shift );
212 8         11 my @v = @{ shift() };
  8         21  
213 8         28 return "$k:{$v[ 0 ] TO $v[ 1 ]}";
214             }
215              
216             sub _op_boost {
217 6     6   15 my ( $self, $k ) = ( shift, shift );
218 6         9 my ( $v, $boost ) = @{ shift() };
  6         13  
219 6         16 $v = $self->escape( $v );
220 6         21 return qq($k:"$v"^$boost);
221             }
222              
223             sub _op_fuzzy {
224 6     6   13 my ( $self, $k ) = ( shift, shift );
225 6         8 my ( $v, $distance ) = @{ shift() };
  6         13  
226 6         16 $v = $self->escape( $v );
227 6         20 return qq($k:$v~$distance);
228             }
229              
230             sub _op_proximity {
231 6     6   13 my ( $self, $k ) = ( shift, shift );
232 6         10 my ( $v, $distance ) = @{ shift() };
  6         14  
233 6         14 $v = $self->escape( $v );
234 6         23 return qq($k:"$v"~$distance);
235             }
236              
237             sub _op_require {
238 8     8   17 my ( $self, $k, $v ) = @_;
239 8         18 $v = $self->escape( $v );
240 8         27 return qq(+$k:"$v");
241             }
242              
243             sub _op_prohibit {
244 14     14   27 my ( $self, $k, $v ) = @_;
245 14         24 $v = $self->escape( $v );
246 14         39 return qq(-$k:"$v");
247             }
248              
249             sub escape {
250 111     111 1 873 my ( $self, $text ) = @_;
251 111         431 $text =~ s{([$escape_chars])}{\\$1}g;
252 111         275 return $text;
253             }
254              
255             sub unescape {
256 1     1 1 4 my ( $self, $text ) = @_;
257 1         27 $text =~ s{\\([$escape_chars])}{$1}g;
258 1         5 return $text;
259             }
260              
261             sub ___log {
262 0     0     my $self = shift;
263 0           my $msg = shift;
264              
265             ### subroutine the log call came from, and line number the log
266             ### call came from. that's 2 different caller frames :(
267 0           my $who = join ':', [ caller( 1 ) ]->[ 3 ], [ caller( 0 ) ]->[ 2 ];
268              
269             ### make sure we prefix every line with a #
270 0           $msg =~ s/\n/\n#/g;
271              
272 0           print "# $who: $msg\n";
273             }
274              
275             sub __dumper {
276 0     0     require Data::Dumper;
277              
278 0           return Data::Dumper::Dumper( @_ );
279             }
280              
281 2     2   15 no Moo;
  2         4  
  2         12  
282              
283             1;
284              
285             __END__