File Coverage

blib/lib/KinoSearch1/Search/PhraseQuery.pm
Criterion Covered Total %
statement 75 85 88.2
branch 10 18 55.5
condition n/a
subroutine 20 21 95.2
pod 2 5 40.0
total 107 129 82.9


line stmt bran cond sub pod time code
1             package KinoSearch1::Search::PhraseQuery;
2 18     18   28013 use strict;
  18         56  
  18         679  
3 18     18   99 use warnings;
  18         34  
  18         471  
4 18     18   658 use KinoSearch1::Util::ToolSet;
  18         40  
  18         2618  
5 18     18   106 use base qw( KinoSearch1::Search::Query );
  18         39  
  18         2673  
6              
7             BEGIN {
8 18     18   205 __PACKAGE__->init_instance_vars(
9             # constructor args / members
10             slop => 0,
11             # members
12             field => undef,
13             terms => undef,
14             positions => undef,
15             );
16 18         295 __PACKAGE__->ready_get_set(qw( slop ));
17 18         177 __PACKAGE__->ready_get(qw( terms ));
18             }
19              
20 18     18   9479 use KinoSearch1::Search::TermQuery;
  18         57  
  18         633  
21 18     18   1331 use KinoSearch1::Document::Field;
  18         46  
  18         509  
22 18     18   112 use KinoSearch1::Util::ToStringUtils qw( boost_to_string );
  18         40  
  18         12101  
23              
24             sub init_instance {
25 69     69 1 102 my $self = shift;
26 69         243 $self->{terms} = [];
27 69         218 $self->{positions} = [];
28             }
29              
30             # Add a term/position combo to the query. The position is specified
31             # explicitly in order to allow for phrases with gaps, two terms at the same
32             # position, etc.
33             sub add_term {
34 148     148 1 242 my ( $self, $term, $position ) = @_;
35 148         441 my $field = $term->get_field;
36 148 100       474 $self->{field} = $field unless defined $self->{field};
37 148 50       335 croak("Mismatched fields in phrase query: '$self->{field}' '$field'")
38             unless ( $field eq $self->{field} );
39 148 50       274 if ( !defined $position ) {
40 148         435 $position
41 148 100       180 = @{ $self->{positions} }
42             ? $self->{positions}[-1] + 1
43             : 0;
44             }
45 148         216 push @{ $self->{terms} }, $term;
  148         288  
46 148         204 push @{ $self->{positions} }, $position;
  148         534  
47             }
48              
49             sub create_weight {
50 70     70 0 137 my ( $self, $searcher ) = @_;
51              
52             # optimize for one-term phrases
53 70 50       90 if ( @{ $self->{terms} } == 1 ) {
  70         219  
54 0         0 my $term_query
55             = KinoSearch1::Search::TermQuery->new( term => $self->{terms}[0],
56             );
57 0         0 return $term_query->create_weight($searcher);
58             }
59             else {
60 70         503 return KinoSearch1::Search::PhraseWeight->new(
61             parent => $self,
62             searcher => $searcher,
63             );
64             }
65             }
66              
67 4     4 0 18 sub extract_terms { shift->{terms} }
68              
69             sub to_string {
70 0     0 0 0 my ( $self, $proposed_field ) = @_;
71 0 0       0 my $string
72             = $proposed_field eq $self->{field}
73             ? qq(")
74             : qq($proposed_field:");
75 0         0 $string .= ( $_->get_text . ' ' ) for @{ $self->{terms} };
  0         0  
76 0         0 $string .= qq(");
77 0 0       0 $string .= qq(~$self->{slop}) if $self->{slop};
78 0         0 $string .= boost_to_string( $self->get_boost );
79 0         0 return $string;
80             }
81              
82             package KinoSearch1::Search::PhraseWeight;
83 18     18   120 use strict;
  18         42  
  18         646  
84 18     18   101 use warnings;
  18         38  
  18         643  
85 18     18   102 use KinoSearch1::Util::ToolSet;
  18         38  
  18         2874  
86 18     18   114 use base qw( KinoSearch1::Search::Weight );
  18         55  
  18         1736  
87              
88 18     18   215 BEGIN { __PACKAGE__->init_instance_vars(); }
89              
90 18     18   13146 use KinoSearch1::Search::PhraseScorer;
  18         55  
  18         5133  
91              
92             sub init_instance {
93 70     70   105 my $self = shift;
94 70         379 $self->{similarity}
95             = $self->{parent}->get_similarity( $self->{searcher} );
96 70         313 $self->{idf} = $self->{similarity}
97             ->idf( $self->{parent}->get_terms, $self->{searcher} );
98              
99 70         215 undef $self->{searcher}; # don't want the baggage
100             }
101              
102             sub scorer {
103 70     70   145 my ( $self, $reader ) = @_;
104 70         132 my $query = $self->{parent};
105              
106             # look up each term
107 70         95 my @term_docs;
108 70         114 for my $term ( @{ $query->{terms} } ) {
  70         172  
109              
110             # bail if any one of the terms isn't in the index
111 142 100       433 return unless $reader->doc_freq($term);
112              
113 134         393 my $td = $reader->term_docs($term);
114 134         247 push @term_docs, $td;
115              
116             # turn on positions
117 134         632 $td->set_read_positions(1);
118             }
119              
120             # bail if there are no terms
121 62 50       197 return unless @term_docs;
122              
123 62         241 my $norms_reader = $reader->norms_reader( $query->{field} );
124 62         597 return KinoSearch1::Search::PhraseScorer->new(
125             weight => $self,
126             slop => $query->{slop},
127             similarity => $self->{similarity},
128             norms_reader => $norms_reader,
129             term_docs => \@term_docs,
130             phrase_offsets => $query->{positions},
131             );
132             }
133              
134             1;
135              
136             __END__