File Coverage

blib/lib/KinoSearch1/Search/MultiSearcher.pm
Criterion Covered Total %
statement 98 109 89.9
branch 6 10 60.0
condition n/a
subroutine 23 26 88.4
pod 1 10 10.0
total 128 155 82.5


line stmt bran cond sub pod time code
1             package KinoSearch1::Search::MultiSearcher;
2 3     3   31281 use strict;
  3         9  
  3         112  
3 3     3   18 use warnings;
  3         8  
  3         136  
4 3     3   681 use KinoSearch1::Util::ToolSet;
  3         10  
  3         444  
5 3     3   19 use base qw( KinoSearch1::Searcher );
  3         7  
  3         778  
6              
7             BEGIN {
8 3     3   45 __PACKAGE__->init_instance_vars(
9             # members / constructor args
10             searchables => undef,
11             # members
12             starts => undef,
13             max_doc => undef,
14             );
15             }
16              
17 3     3   18 use KinoSearch1::Search::Similarity;
  3         8  
  3         2766  
18              
19             sub init_instance {
20 2     2 1 16 my $self = shift;
21 2         26 $self->{field_sims} = {};
22              
23             # derive max_doc, relative start offsets
24 2         5 my $max_doc = 0;
25 2         5 my @starts;
26 2         5 for my $searchable ( @{ $self->{searchables} } ) {
  2         13  
27 4         9 push @starts, $max_doc;
28 4         21 $max_doc += $searchable->max_doc;
29             }
30 2         28 $self->{max_doc} = $max_doc;
31 2         6 $self->{starts} = \@starts;
32              
33             # default similarity
34 2 50       35 $self->{similarity} = KinoSearch1::Search::Similarity->new
35             unless defined $self->{similarity};
36             }
37              
38             sub get_field_names {
39 4     4 0 8 my $self = shift;
40 4         11 my %field_names;
41 4         7 for my $searchable ( @{ $self->{searchables} } ) {
  4         21  
42 8         34 my $sub_field_names = $searchable->get_field_names;
43 8         54 @field_names{@$sub_field_names} = (1) x scalar @$sub_field_names;
44             }
45 4         21 return [ keys %field_names ];
46             }
47              
48 4     4 0 32 sub max_doc { shift->{max_doc} }
49              
50 0     0 0 0 sub close { }
51              
52             sub subsearcher {
53 2     2 0 4 my ( $self, $doc_num ) = @_;
54 2         3 my $i = -1;
55 2         3 for ( @{ $self->{starts} } ) {
  2         8  
56 4 100       11 last if $_ > $doc_num;
57 3         6 $i++;
58             }
59 2         5 return $i;
60             }
61              
62             sub doc_freq {
63 0     0 0 0 my ( $self, $term ) = @_;
64 0         0 my $doc_freq = 0;
65 0         0 $doc_freq += $_->doc_freq($term) for @{ $self->{searchables} };
  0         0  
66 0         0 return $doc_freq;
67             }
68              
69             sub fetch_doc {
70 2     2 0 4 my ( $self, $doc_num ) = @_;
71 2         7 my $i = $self->subsearcher($doc_num);
72 2         5 my $searchable = $self->{searchables}[$i];
73 2         5 $doc_num -= $self->{starts}[$i];
74 2         12 return $searchable->fetch_doc($doc_num);
75             }
76              
77             my %search_hit_collector_args = (
78             hit_collector => undef,
79             weight => undef,
80             filter => undef,
81             sort_spec => undef,
82             );
83              
84             sub search_hit_collector {
85 4     4 0 10 my $self = shift;
86 4 50       16 confess kerror() unless verify_args( \%search_hit_collector_args, @_ );
87 4         26 my %args = ( %search_hit_collector_args, @_ );
88 4         10 my ( $searchables, $starts ) = @{$self}{qw( searchables starts )};
  4         14  
89              
90 4         23 for my $i ( 0 .. $#$searchables ) {
91 8         21 my $searchable = $searchables->[$i];
92 8         15 my $start = $starts->[$i];
93 8         63 my $collector = KinoSearch1::Search::OffsetCollector->new(
94             hit_collector => $args{hit_collector},
95             offset => $start
96             );
97 8         55 $searchable->search_hit_collector( %args,
98             hit_collector => $collector );
99             }
100             }
101              
102             sub rewrite {
103 4     4 0 10 my ( $self, $orig_query ) = @_;
104              
105             # not necessary to rewrite until we add query types that need it
106 4         11 return $orig_query;
107              
108             #my @queries = map { $_->rewrite($orig_query) } @{ $self->{searchables} };
109             #my $combined = $queries->[0]->combine(\@queries);
110             #return $combined;
111             }
112              
113             sub create_weight {
114 4     4 0 10 my ( $self, $query ) = @_;
115 4         9 my $searchables = $self->{searchables};
116              
117 4         22 my $rewritten_query = $self->rewrite($query);
118              
119             # generate an array of unique terms
120 4         22 my @terms = $rewritten_query->extract_terms;
121 4         8 my %unique_terms;
122 4         11 for my $term (@terms) {
123 4 50       14 if ( a_isa_b( $term, "KinoSearch1::Index::Term" ) ) {
124 4         21 $unique_terms{ $term->to_string } = $term;
125             }
126             else {
127             # PhraseQuery returns an array of terms
128 0         0 $unique_terms{ $_->to_string } = $_ for @$term;
129             }
130             }
131 4         16 @terms = values %unique_terms;
132 4         13 my @stringified = keys %unique_terms;
133              
134             # get an aggregated doc_freq for each term
135 4         12 my @aggregated_doc_freqs = (0) x scalar @terms;
136 4         14 for my $i ( 0 .. $#$searchables ) {
137 8         61 my $doc_freqs = $searchables->[$i]->doc_freqs( \@terms );
138 8         48 for my $j ( 0 .. $#terms ) {
139 8         35 $aggregated_doc_freqs[$j] += $doc_freqs->[$j];
140             }
141             }
142              
143             # prepare a hashmap of stringified_term => doc_freq pairs.
144 4         9 my %doc_freq_map;
145 4         11 @doc_freq_map{@stringified} = @aggregated_doc_freqs;
146              
147 4         21 my $cache_df_source = KinoSearch1::Search::CacheDFSource->new(
148             doc_freq_map => \%doc_freq_map,
149             max_doc => $self->max_doc,
150             similarity => $self->get_similarity,
151             );
152              
153 4         33 return $rewritten_query->to_weight($cache_df_source);
154             }
155              
156             package KinoSearch1::Search::CacheDFSource;
157 3     3   17 use strict;
  3         12  
  3         105  
158 3     3   17 use warnings;
  3         9  
  3         89  
159 3     3   15 use KinoSearch1::Util::ToolSet;
  3         7  
  3         414  
160 3     3   16 use base qw( KinoSearch1::Search::Searchable );
  3         7  
  3         260  
161              
162             BEGIN {
163 3     3   27 __PACKAGE__->init_instance_vars(
164             doc_freq_map => {},
165             max_doc => undef,
166             );
167 3         27 __PACKAGE__->ready_get(qw( max_doc ));
168             }
169              
170 4     4   11 sub init_instance { }
171              
172             sub doc_freq {
173 8     8   16 my ( $self, $term ) = @_;
174 8         27 my $df = $self->{doc_freq_map}{ $term->to_string };
175 8 50       61 confess( "df for " . $term->to_string . " not available" )
176             unless defined $df;
177             }
178              
179             sub doc_freqs {
180 0     0   0 my $self = shift;
181 0         0 my @doc_freqs = map { $self->doc_freq($_) } @_;
  0         0  
182 0         0 return \@doc_freqs;
183             }
184              
185 4     4   14 sub max_doc { shift->{max_doc} }
186              
187             sub rewrite {
188 4     4   16 return $_[1];
189             }
190              
191             =for comment
192              
193             Dummy class, only here to support initialization of Weights from Queries.
194              
195             =cut
196              
197             1;
198              
199             __END__