File Coverage

blib/lib/KSx/Search/Filter.pm
Criterion Covered Total %
statement 91 92 98.9
branch 9 14 64.2
condition 2 6 33.3
subroutine 24 24 100.0
pod 4 6 66.6
total 130 142 91.5


line stmt bran cond sub pod time code
1 2     2   122189 use strict;
  2         4  
  2         70  
2 2     2   10 use warnings;
  2         5  
  2         129  
3              
4             package KSx::Search::Filter;
5 2     2   66 BEGIN { our @ISA = qw( KinoSearch::Search::Query ) }
6 2     2   10 use Carp;
  2         3  
  2         129  
7 2     2   10 use Storable qw( nfreeze thaw );
  2         4  
  2         111  
8 2     2   10 use Scalar::Util qw( blessed weaken );
  2         4  
  2         94  
9 2     2   10 use bytes;
  2         4  
  2         18  
10 2     2   55 no bytes;
  2         4  
  2         9  
11              
12             # Inside-out member vars.
13             our %query;
14             our %cached_bits;
15              
16             sub new {
17 2     2 1 66 my ( $either, %args ) = @_;
18 2         6 my $query = delete $args{query};
19 2 50 33     67 confess("required parameter query is not a KinoSearch::Search::Query")
20             unless ( blessed($query)
21             && $query->isa('KinoSearch::Search::Query') );
22 2         30 my $self = $either->SUPER::new(%args);
23 2         7 $self->_init_cache;
24 2         6 $query{$$self} = $query;
25 2         15 $self->set_boost(0);
26 2         7 return $self;
27             }
28              
29             sub DESTROY {
30 3     3   624 my $self = shift;
31 3         22 delete $query{$$self};
32 3         24 delete $cached_bits{$$self};
33 3         155 $self->SUPER::DESTROY;
34             }
35              
36             sub make_compiler {
37 6     6 1 6230 my $self = shift;
38 6         24 return KSx::Search::FilterCompiler->new( @_, parent => $self );
39             }
40              
41             sub serialize {
42 1     1 0 80 my ( $self, $outstream ) = @_;
43 1         15 $self->SUPER::serialize($outstream);
44 1         8 my $frozen = nfreeze( $query{$$self} );
45 1         190 $outstream->write_c32( bytes::length($frozen) );
46 1         1227 $outstream->print($frozen);
47             }
48              
49             sub deserialize {
50 1     1 0 56 my ( $self, $instream ) = @_;
51 1         17 $self->SUPER::deserialize($instream);
52 1         11 my $len = $instream->read_c32;
53 1         4 my $frozen;
54 1         6 $instream->read( $frozen, $len );
55 1         7 $query{$$self} = thaw($frozen);
56 1         53 return $self;
57             }
58              
59             sub equals {
60 2     2 1 12 my ( $self, $other ) = @_;
61 2 50       14 return 0 unless $other->isa(__PACKAGE__);
62 2 50       196 return 0 unless $query{$$self}->equals( $query{$$other} );
63 2 50       27 return 0 unless $self->get_boost == $other->get_boost;
64 2         12 return 1;
65             }
66              
67             sub to_string {
68 1     1 1 3 my $self = shift;
69 1         21 return 'Filter(' . $query{$$self}->to_string . ')';
70             }
71              
72             sub _bits {
73 12     12   606 my ( $self, $seg_reader ) = @_;
74              
75 12         24 my $cached_bits = $self->_fetch_cached_bits($seg_reader);
76              
77             # Fill the cache.
78 12 100       24 if ( !defined $cached_bits ) {
79 3         41 $cached_bits = KinoSearch::Object::BitVector->new(
80             capacity => $seg_reader->doc_max + 1 );
81 3         10 $self->_store_cached_bits( $seg_reader, $cached_bits );
82              
83 3         38 my $collector = KinoSearch::Search::Collector::BitCollector->new(
84             bit_vector => $cached_bits );
85              
86 3         135 my $polyreader = KinoSearch::Index::PolyReader->new(
87             schema => $seg_reader->get_schema,
88             folder => $seg_reader->get_folder,
89             snapshot => $seg_reader->get_snapshot,
90             sub_readers => [$seg_reader],
91             );
92 3         32 my $searcher
93             = KinoSearch::Search::IndexSearcher->new( index => $polyreader );
94              
95             # Perform the search.
96 3         171 $searcher->collect(
97             query => $query{$$self},
98             collector => $collector,
99             );
100             }
101              
102 12         25 return $cached_bits;
103             }
104              
105             # Store a cached BitVector associated with a particular SegReader. Store a
106             # weak reference to the SegReader as an indicator of cache validity.
107             sub _store_cached_bits {
108 3     3   6 my ( $self, $seg_reader, $bits ) = @_;
109 3         11 my $pair = { seg_reader => $seg_reader, bits => $bits };
110 3         10 weaken( $pair->{seg_reader} );
111 3         15 $cached_bits{$$self}{ $seg_reader->hash_sum } = $pair;
112             }
113              
114             # Retrieve a cached BitVector associated with a particular SegReader. As a
115             # side effect, clear away any BitVectors which are no longer valid because
116             # their SegReaders have gone away.
117             sub _fetch_cached_bits {
118 12     12   17 my ( $self, $seg_reader ) = @_;
119 12         19 my $cached_bits = $cached_bits{$$self};
120              
121             # Sweep.
122 12         38 while ( my ( $hash_sum, $pair ) = each %$cached_bits ) {
123             # If weak ref has decomposed into undef, SegReader is gone... so
124             # delete.
125 15 50       70 next if defined $pair->{seg_reader};
126 0         0 delete $cached_bits->{$hash_sum};
127             }
128              
129             # Fetch.
130 12         57 my $pair = $cached_bits->{ $seg_reader->hash_sum };
131 12 100       41 return $pair->{bits} if defined $pair;
132 3         6 return;
133             }
134              
135             # Kill any existing cached filters.
136             sub _init_cache {
137 2     2   3 my $self = shift;
138 2         15 $cached_bits{$$self} = {};
139             }
140              
141             # Testing only.
142             sub _cached_count {
143 4     4   2809 my $self = shift;
144 6         44 return scalar grep { defined $cached_bits{$$self}{$_}{seg_reader} }
  4         17  
145 4         7 keys %{ $cached_bits{$$self} };
146             }
147              
148             package KSx::Search::FilterCompiler;
149 2     2   2245 BEGIN { our @ISA = qw( KinoSearch::Search::Compiler ) }
150              
151             sub new {
152 6     6   23 my ( $class, %args ) = @_;
153 6   33     78 $args{similarity} ||= $args{searcher}->get_schema->get_similarity;
154 6         56 return $class->SUPER::new(%args);
155             }
156              
157             sub make_matcher {
158 6     6   20 my ( $self, %args ) = @_;
159 6         11 my $seg_reader = $args{reader};
160 6         30 my $bits = $self->get_parent->_bits($seg_reader);
161 6         515 return KSx::Search::FilterScorer->new(
162             bits => $bits,
163             doc_max => $seg_reader->doc_max,
164             );
165             }
166              
167             package KSx::Search::FilterScorer;
168 2     2   97 BEGIN { our @ISA = qw( KinoSearch::Search::Matcher ) }
169              
170             1;
171              
172             __END__