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   57349 use Moo;
  2         9335  
  2         10  
4              
5 2     2   1931 use Types::Standard qw(ArrayRef);
  2         62210  
  2         20  
6              
7 2     2   1247 use overload q("") => 'stringify';
  2         4  
  2         11  
8              
9             my $escape_chars = quotemeta( '+-&|!(){}[]^"~*?:\\' );
10              
11             has 'query' => ( is => 'ro', isa => ArrayRef, default => sub { [] } );
12              
13 2     2   209 use constant D => 0;
  2         3  
  2         2959  
14              
15             sub BUILDARGS {
16 33     33 1 140061 my $class = shift;
17              
18 33 100 33     235 if ( @_ == 1 && ref $_[ 0 ] && ref $_[ 0 ] eq 'ARRAY' ) {
      66        
19 1         17 return { query => $_[ 0 ] };
20             }
21              
22 32         515 return { query => \@_ };
23             }
24              
25             sub stringify {
26 66     66 1 32262 my $self = shift;
27              
28 66         220 return $self->_dispatch_struct( $self->query );
29             }
30              
31             sub _dispatch_struct {
32 150     150   219 my ( $self, $struct ) = @_;
33              
34 150         245 my $method = '_struct_' . ref $struct;
35              
36 150         159 D && $self->___log( "Dispatching to ->$method " . __dumper( $struct ) );
37              
38 150         307 my $rv = $self->$method( $struct );
39              
40 150         152 D && $self->___log( "Returned: $rv" );
41              
42 150         350 return $rv;
43             }
44              
45             sub _struct_HASH {
46 84     84   108 my ( $self, $struct ) = @_;
47              
48 84         92 my @clauses;
49              
50 84         254 for my $k ( sort keys %$struct ) {
51 106         145 my $v = $struct->{ $k };
52              
53 106         107 D && $self->___log( "Key => $k, value => " . __dumper( $v ) );
54              
55 106 100       239 if ( $k =~ m{^-(.+)} ) {
56 8         21 my $method = "_op_$1";
57              
58 8         10 D && $self->___log( "Dispatch ->$method " . __dumper( $v ) );
59 8         21 push @clauses, $self->$method( $v );
60             }
61             else {
62 98         104 D
63             && $self->___log(
64             "Dispatch ->_dispatch_value $k, " . __dumper( $v ) );
65 98         200 push @clauses, $self->_dispatch_value( $k, $v );
66             }
67             }
68              
69 84         153 my $rv = join( ' AND ', @clauses );
70              
71 84         90 D && $self->___log( "Returning: $rv" );
72              
73 84         133 return $rv;
74             }
75              
76             sub _struct_ARRAY {
77 66     66   96 my ( $self, $struct ) = @_;
78              
79             my $rv
80             = '('
81 66         106 . join( " OR ", map { $self->_dispatch_struct( $_ ) } @$struct )
  68         115  
82             . ')';
83              
84 66         84 D && $self->___log( "Returning: $rv" );
85              
86 66         90 return $rv;
87             }
88              
89             sub _dispatch_value {
90 106     106   176 my ( $self, $k, $v ) = @_;
91              
92 106         117 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     544 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         20 $v = [ @$v ]; # Copy the array because we're going to be modifying it.
107 8         14 shift @$v;
108 8         21 my $op = uc $1;
109              
110 8         10 D
111             && $self->___log(
112             "Special operator detected: $op " . __dumper( $v ) );
113              
114 8         11 my @clauses;
115 8         13 for my $href ( @$v ) {
116 16         18 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         38 my $part = $self->_dispatch_struct( { $k => $href } );
124              
125 16         28 D && $self->___log( "Returned $part" );
126              
127 16         36 push @clauses, '(' . $part . ')';
128             }
129              
130 8         20 $rv = '(' . join( " $op ", @clauses ) . ')';
131              
132             ### nothing special about this combo, so do a usual dispatch
133             }
134             else {
135 98   100     233 my $method = '_value_' . ( ref $v || 'SCALAR' );
136              
137 98         105 D && $self->___log( "Dispatch ->$method $k, " . __dumper( $v ) );
138              
139 98         209 $rv = $self->$method( $k, $v );
140             }
141              
142 106         118 D && $self->___log( "Returning: $rv" );
143              
144 106         197 return $rv;
145             }
146              
147             sub _value_SCALAR {
148 72     72   105 my ( $self, $k, $v ) = @_;
149              
150 72 100       119 if ( ref $v ) {
151 2         3 $v = $$v;
152             }
153             else {
154 70         103 $v = '"' . $self->escape( $v ) . '"';
155             }
156              
157 72         131 my $r = qq($k:$v);
158 72         118 $r =~ s{^:}{};
159              
160 72         86 D && $self->___log( "Returning: $r" );
161              
162 72         173 return $r;
163             }
164              
165             sub _value_HASH {
166 52     52   89 my ( $self, $k, $v ) = @_;
167              
168 52         61 my @clauses;
169              
170 52         100 for my $op ( sort keys %$v ) {
171 52         71 my $struct = $v->{ $op };
172 52         318 $op =~ s{^-(.+)}{_op_$1};
173              
174 52         83 D && $self->___log( "Dispatch ->$op $k, " . __dumper( $v ) );
175              
176 52         128 push @clauses, $self->$op( $k, $struct );
177             }
178              
179 52         114 my $rv = join( ' AND ', @clauses );
180              
181 52         58 D && $self->___log( "Returning: $rv" );
182              
183 52         91 return $rv;
184             }
185              
186             sub _value_ARRAY {
187 28     28   46 my ( $self, $k, $v ) = @_;
188              
189             my $rv = '('
190 28         52 . join( ' OR ', map { $self->_value_SCALAR( $k, $_ ) } @$v ) . ')';
  54         101  
191              
192 28         40 D && $self->___log( "Returning: $rv" );
193              
194 28         44 return $rv;
195             }
196              
197             sub _op_default {
198 8     8   13 my ( $self, $v ) = @_;
199 8         19 return $self->_dispatch_value( '', $v );
200             }
201              
202             sub _op_range {
203 4     4   8 my ( $self, $k ) = ( shift, shift );
204 4         6 my @v = @{ shift() };
  4         9  
205 4         16 return "$k:[$v[ 0 ] TO $v[ 1 ]]";
206             }
207              
208             *_op_range_inc = \&_op_range;
209              
210             sub _op_range_exc {
211 8     8   17 my ( $self, $k ) = ( shift, shift );
212 8         11 my @v = @{ shift() };
  8         17  
213 8         28 return "$k:{$v[ 0 ] TO $v[ 1 ]}";
214             }
215              
216             sub _op_boost {
217 6     6   13 my ( $self, $k ) = ( shift, shift );
218 6         8 my ( $v, $boost ) = @{ shift() };
  6         13  
219 6         14 $v = $self->escape( $v );
220 6         20 return qq($k:"$v"^$boost);
221             }
222              
223             sub _op_fuzzy {
224 6     6   11 my ( $self, $k ) = ( shift, shift );
225 6         8 my ( $v, $distance ) = @{ shift() };
  6         10  
226 6         13 $v = $self->escape( $v );
227 6         21 return qq($k:$v~$distance);
228             }
229              
230             sub _op_proximity {
231 6     6   12 my ( $self, $k ) = ( shift, shift );
232 6         8 my ( $v, $distance ) = @{ shift() };
  6         10  
233 6         12 $v = $self->escape( $v );
234 6         20 return qq($k:"$v"~$distance);
235             }
236              
237             sub _op_require {
238 8     8   14 my ( $self, $k, $v ) = @_;
239 8         16 $v = $self->escape( $v );
240 8         23 return qq(+$k:"$v");
241             }
242              
243             sub _op_prohibit {
244 14     14   22 my ( $self, $k, $v ) = @_;
245 14         24 $v = $self->escape( $v );
246 14         54 return qq(-$k:"$v");
247             }
248              
249             sub escape {
250 111     111 1 1122 my ( $self, $text ) = @_;
251 111         704 $text =~ s{([$escape_chars])}{\\$1}g;
252 111         256 return $text;
253             }
254              
255             sub unescape {
256 1     1 1 3 my ( $self, $text ) = @_;
257 1         25 $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         5  
  2         12  
282              
283             1;
284              
285             __END__