File Coverage

blib/lib/KSx/Simple.pm
Criterion Covered Total %
statement 69 69 100.0
branch 11 18 61.1
condition 2 6 33.3
subroutine 16 16 100.0
pod 3 4 75.0
total 101 113 89.3


line stmt bran cond sub pod time code
1 3     3   193528 use strict;
  3         10  
  3         126  
2 3     3   14 use warnings;
  3         9  
  3         164  
3              
4             package KSx::Simple;
5 3     3   19 use Carp;
  3         5  
  3         480  
6 3     3   18 use Scalar::Util qw( weaken reftype refaddr );
  3         8  
  3         305  
7              
8 3     3   1774 use KinoSearch::Plan::Schema;
  3         20  
  3         104  
9 3     3   1942 use KinoSearch::Analysis::PolyAnalyzer;
  3         187  
  3         101  
10 3     3   2220 use KinoSearch::Index::Indexer;
  3         10  
  3         100  
11 3     3   57935 use KinoSearch::Search::IndexSearcher;
  3         8  
  3         2457  
12              
13             my %obj_cache;
14              
15             sub new {
16 6     6 1 2119462 my ( $either, %args ) = @_;
17 6         64 my $path = delete $args{path};
18 6         106 my $language = lc( delete $args{language} );
19 6 50       52 confess("Missing required parameter 'path'") unless defined $path;
20 6 50       170 confess("Invalid language: '$language'")
21             unless $language =~ /^(?:da|de|en|es|fi|fr|it|nl|no|pt|ru|sv)$/;
22 6         32 my @remaining = keys %args;
23 6 50       36 confess("Invalid params: @remaining") if @remaining;
24 6   33     198 my $self = bless {
25             type => undef,
26             schema => undef,
27             indexer => undef,
28             searcher => undef,
29             hits => undef,
30             language => $language,
31             path => $path,
32             },
33             ref($either) || $either;
34              
35             # Get type and schema.
36 6         726 my $analyzer
37             = KinoSearch::Analysis::PolyAnalyzer->new( language => $language );
38 6         277 $self->{type}
39             = KinoSearch::Plan::FullTextType->new( analyzer => $analyzer, );
40 6         215 my $schema = $self->{schema} = KinoSearch::Plan::Schema->new;
41              
42             # Cache the object for later clean-up.
43 6         120 weaken( $obj_cache{ refaddr $self } = $self );
44              
45 6         102 return $self;
46             }
47              
48             sub _lazily_create_indexer {
49 9     9   21 my $self = shift;
50 9 50       58 if ( !defined $self->{indexer} ) {
51 9         139 $self->{indexer} = KinoSearch::Index::Indexer->new(
52             schema => $self->{schema},
53             index => $self->{path},
54             );
55             }
56             }
57              
58             sub add_doc {
59 9     9 1 76 my ( $self, $hashref ) = @_;
60 9         24 my $schema = $self->{schema};
61 9         24 my $type = $self->{type};
62 9 50 33     134 croak("add_doc requires exactly one argument: a hashref")
63             unless ( @_ == 2 and reftype($hashref) eq 'HASH' );
64 9         39 $self->_lazily_create_indexer;
65 9         301 $schema->spec_field( name => $_, type => $type ) for keys %$hashref;
66 9         896 $self->{indexer}->add_doc($hashref);
67             }
68              
69             sub _finish_indexing {
70 15     15   29 my $self = shift;
71              
72             # Don't bother to throw an error if index not modified.
73 15 100       99 if ( defined $self->{indexer} ) {
74 9         2941 $self->{indexer}->commit;
75              
76             # Trigger searcher and indexer refresh.
77 9         48 undef $self->{indexer};
78 9         1915 undef $self->{searcher};
79             }
80             }
81              
82             sub search {
83 9     9 1 2251 my ( $self, %args ) = @_;
84              
85             # Flush recent adds; lazily create searcher.
86 9         43 $self->_finish_indexing;
87 9 50       385 if ( !defined $self->{searcher} ) {
88 9         289 $self->{searcher} = KinoSearch::Search::IndexSearcher->new(
89             index => $self->{path} );
90             }
91              
92 9         1631 $self->{hits} = $self->{searcher}->hits(%args);
93              
94 9         496 return $self->{hits}->total_hits;
95             }
96              
97             sub next {
98 8     8 0 3580 my $self = shift;
99 8 50       32 return unless defined $self->{hits};
100              
101             # Get the hit, bail if hits are exhausted.
102 8         354 my $hit = $self->{hits}->next;
103 8 100       28 if ( !defined $hit ) {
104 2         8 undef $self->{hits};
105 2         20 return;
106             }
107              
108 6         22 return $hit;
109             }
110              
111             sub DESTROY {
112 5     5   1654 for (shift) {
113 5         23 $_->_finish_indexing;
114 5         1672 delete $obj_cache{ refaddr $_ };
115             }
116             }
117              
118             END {
119             # Finish indexing for any objects that still exist, since, if we wait
120             # until global destruction, our Indexer might no longer exist,
121             # (see bug #32689)
122 3     3   803 $_->_finish_indexing for values %obj_cache;
123             }
124              
125             1;
126              
127             __END__