File Coverage

blib/lib/Search/Query/Dialect/KSx/WildcardCompiler.pm
Criterion Covered Total %
statement 91 101 90.1
branch 21 32 65.6
condition 7 14 50.0
subroutine 14 17 82.3
pod 9 9 100.0
total 142 173 82.0


line stmt bran cond sub pod time code
1             package Search::Query::Dialect::KSx::WildcardCompiler;
2 3     3   17 use strict;
  3         6  
  3         97  
3 3     3   14 use warnings;
  3         6  
  3         83  
4 3     3   22 use base qw( KinoSearch::Search::Compiler );
  3         7  
  3         2715  
5 3     3   301 use Carp;
  3         6  
  3         157  
6 3     3   2240 use Search::Query::Dialect::KSx::WildcardScorer;
  3         8  
  3         99  
7 3     3   18 use Data::Dump qw( dump );
  3         6  
  3         3344  
8              
9             our $VERSION = '0.201';
10              
11             # inside out vars
12             my (%include, %searchable, %idf,
13             %raw_impact, %lex_terms, %doc_freq,
14             %query_norm_factor, %normalized_impact, %term_freq
15             );
16              
17             sub DESTROY {
18 27     27   58 my $self = shift;
19 27         39 delete $include{$$self};
20 27         31 delete $raw_impact{$$self};
21 27         29 delete $query_norm_factor{$$self};
22 27         36 delete $searchable{$$self};
23 27         36 delete $lex_terms{$$self};
24 27         1073 delete $normalized_impact{$$self};
25 27         30 delete $idf{$$self};
26 27         38 delete $doc_freq{$$self};
27 27         24 delete $term_freq{$$self};
28 27         257 $self->SUPER::DESTROY;
29             }
30              
31             =head1 NAME
32              
33             Search::Query::Dialect::KSx::Compiler - KinoSearch query extension
34              
35             =head1 SYNOPSIS
36              
37             # see KinoSearch::Search::Compiler
38              
39             =head1 METHODS
40              
41             This class isa KinoSearch::Search::Compiler subclass . Only new
42             or overridden methods are documented .
43              
44             =cut
45              
46             =head2 new( I )
47              
48             Returns a new Compiler object.
49              
50             =cut
51              
52             sub new {
53 27     27 1 34 my $class = shift;
54 27         64 my %args = @_;
55 27   50     70 my $include = delete $args{include} || 0;
56 27   33     1231 my $searchable = $args{searchable} || $args{searcher};
57 27 50       54 if ( !$searchable ) {
58 0         0 croak "searcher required";
59             }
60 27         131 my $self = $class->SUPER::new(%args);
61 27         1109 $include{$$self} = $include;
62 27         46 $searchable{$$self} = $searchable;
63 27         258 return $self;
64             }
65              
66             =head2 make_matcher( I )
67              
68             Returns a Search::Query::Dialect::KSx::WildcardScorer object.
69              
70             =cut
71              
72             sub make_matcher {
73 27     27 1 68 my ( $self, %args ) = @_;
74              
75 27         48 my $seg_reader = $args{reader};
76 27         43 my $searchable = $searchable{$$self};
77              
78             # Retrieve low-level components LexiconReader and PostingListReader.
79 27         143 my $lex_reader = $seg_reader->obtain("KinoSearch::Index::LexiconReader");
80 27         93 my $plist_reader
81             = $seg_reader->obtain("KinoSearch::Index::PostingListReader");
82              
83             # Acquire a Lexicon and seek it to our query string.
84 27         100 my $parent = $self->get_parent;
85 27         116 my $term = $parent->get_term;
86 27         73 my $regex = $parent->get_regex;
87 27         72 my $suffix = $parent->get_suffix;
88 27         70 my $field = $parent->get_field;
89 27         72 my $prefix = $parent->get_prefix;
90 27         944 my $lexicon = $lex_reader->lexicon( field => $field );
91 27 50       70 return unless $lexicon;
92              
93             # Retrieve the correct Similarity for the Query's field.
94 27   33     275 my $sim = $args{similarity} || $searchable->get_schema->fetch_sim($field);
95              
96 27 50       184 $lexicon->seek( defined $prefix ? $prefix : '' );
97              
98             # Accumulate PostingLists for each matching term.
99 27         31 my @posting_lists;
100             my @lex_terms;
101 27         46 my $include = $include{$$self};
102 27         126 while ( defined( my $lex_term = $lexicon->get_term ) ) {
103              
104             #warn
105             # "lex_term=$lex_term prefix=$prefix suffix=$suffix regex=$regex";
106              
107             # weed out non-matchers early.
108 197 100 100     510 if ( defined $suffix and index( $lex_term, $suffix ) < 0 ) {
109 40 100       151 last unless $lexicon->next;
110 34         158 next;
111             }
112 157 50 33     287 last if defined $prefix and index( $lex_term, $prefix ) != 0;
113              
114             #carp "$term field:$field: term>$lex_term<";
115              
116 157 50       212 if ($include) {
117 157 100       577 unless ( $lex_term =~ $regex ) {
118 132 100       408 last unless $lexicon->next;
119 113         639 next;
120             }
121             }
122             else {
123 0 0       0 if ( $lex_term =~ $regex ) {
124 0 0       0 last unless $lexicon->next;
125 0         0 next;
126             }
127             }
128 25         706 my $posting_list = $plist_reader->posting_list(
129             field => $field,
130             term => $lex_term,
131             );
132              
133             #carp "check posting_list";
134 25 50       66 if ($posting_list) {
135 25         34 push @posting_lists, $posting_list;
136 25         38 push @lex_terms, $lex_term;
137             }
138 25 100       183 last unless $lexicon->next;
139             }
140 27 100       207 return unless @posting_lists;
141              
142 12         21 $doc_freq{$$self} = scalar(@posting_lists);
143 12         22 $lex_terms{$$self} = \@lex_terms;
144              
145             #carp dump \@posting_lists;
146              
147             # Calculate and store the IDF
148 12         55 my $max_doc = $searchable->doc_max;
149 12 50       198 my $idf = $idf{$$self}
150             = $max_doc
151             ? $searchable->get_schema->fetch_type($field)->get_boost
152             + log( $max_doc / ( 1 + $doc_freq{$$self} ) )
153             : $searchable->get_schema->fetch_type($field)->get_boost;
154              
155 12         67 $raw_impact{$$self} = $idf * $parent->get_boost;
156              
157             #carp "raw_impact{$$self}= $raw_impact{$$self}";
158              
159             # make final preparations
160 12         37 $self->_perform_query_normalization($searchable);
161              
162 12         65 return Search::Query::Dialect::KSx::WildcardScorer->new(
163             posting_lists => \@posting_lists,
164             compiler => $self,
165             );
166             }
167              
168             =head2 get_searchable
169              
170             Returns the Searchable object for this Compiler.
171              
172             =cut
173              
174             sub get_searchable {
175 0     0 1 0 my $self = shift;
176 0         0 return $searchable{$$self};
177             }
178              
179             =head2 get_doc_freq
180              
181             Returns the document frequency for this Compiler.
182              
183             =cut
184              
185             sub get_doc_freq {
186 0     0 1 0 my $self = shift;
187 0         0 return $doc_freq{$$self};
188             }
189              
190             =head2 get_lex_terms
191              
192             Returns array ref of the terms in the lexicon that matched.
193              
194             =cut
195              
196             sub get_lex_terms {
197 0     0 1 0 my $self = shift;
198 0         0 return $lex_terms{$$self};
199             }
200              
201             sub _perform_query_normalization {
202              
203             # copied from KinoSearch::Search::Weight originally
204 12     12   18 my ( $self, $searcher ) = @_;
205 12         56 my $sim = $self->get_similarity;
206 12         25 my $factor = $self->sum_of_squared_weights; # factor = ( tf_q * idf_t )
207 12         53 $factor = $sim->query_norm($factor); # factor /= norm_q
208 12         33 $self->normalize($factor); # impact *= factor
209              
210             #carp "normalize factor=$factor";
211             }
212              
213             =head2 apply_norm_factor( I )
214              
215             Overrides base class. Currently just passes I on to parent method.
216              
217             =cut
218              
219             sub apply_norm_factor {
220              
221             # pass-through for now
222 48     48 1 62 my ( $self, $factor ) = @_;
223 48         300 $self->SUPER::apply_norm_factor($factor);
224             }
225              
226             =head2 get_boost
227              
228             Returns the boost for the parent Query object.
229              
230             =cut
231              
232 12     12 1 73 sub get_boost { shift->get_parent->get_boost }
233              
234             =head2 sum_of_squared_weights
235              
236             Returns imact of term on score.
237              
238             =cut
239              
240             sub sum_of_squared_weights {
241              
242             # pass-through for now
243 58     58 1 64 my $self = shift;
244 58 100       240 return exists $raw_impact{$$self} ? $raw_impact{$$self}**2 : '1.0';
245             }
246              
247             =head2 normalize()
248              
249             Affects the score of the term. See KinoSearch::Search::Compiler.
250              
251             =cut
252              
253             sub normalize { # copied from TermQuery
254 12     12 1 17 my ( $self, $query_norm_factor ) = @_;
255 12         31 $query_norm_factor{$$self} = $query_norm_factor;
256              
257             # Multiply raw impact by ( tf_q * idf_q / norm_q )
258             #
259             # Note: factoring in IDF a second time is correct. See formula.
260 12         29 $normalized_impact{$$self}
261             = $raw_impact{$$self} * $idf{$$self} * $query_norm_factor;
262              
263             #carp "normalized_impact{$$self} = $normalized_impact{$$self}";
264 12         24 return $normalized_impact{$$self};
265             }
266              
267             1;
268              
269             __END__