File Coverage

blib/lib/LucyX/Simple.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package LucyX::Simple;
2              
3             our $VERSION = '0.008002';
4             $VERSION = eval $VERSION;
5              
6 1     1   28841 use Moo;
  1         17990  
  1         7  
7              
8 1     1   3551 use Lucy::Analysis::PolyAnalyzer;
  0            
  0            
9             use Lucy::Plan::Schema;
10             use Lucy::Index::Indexer;
11             use Lucy::Search::IndexSearcher;
12             use Lucy::Search::QueryParser;
13             use Lucy::Plan::FullTextType;
14             use Lucy::Plan::BlobType;
15             use Lucy::Plan::Float32Type;
16             use Lucy::Plan::Float64Type;
17             use Lucy::Plan::Int32Type;
18             use Lucy::Plan::Int64Type;
19             use Lucy::Plan::StringType;
20              
21             use Data::Page;
22             use Exception::Simple;
23              
24             has _language => (
25             'is' => 'ro',
26             'default' => sub{ 'en' },
27             'init_arg' => 'language',
28             );
29              
30             has _index_path => (
31             'is' => 'ro',
32             'required' => 1,
33             'init_arg' => 'index_path',
34             );
35              
36             has _analyser => (
37             'is' => 'ro',
38             'init_arg' => 'analyser',
39             'default' => sub { return Lucy::Analysis::PolyAnalyzer->new( language => shift->_language ) },
40             'lazy' => 1,
41             );
42              
43             has schema => (
44             'is' => 'ro',
45             'required' => 1,
46             );
47              
48             has '_index_schema' => (
49             'is' => 'lazy',
50             'init_arg' => undef,
51             );
52              
53             sub _build__index_schema{
54             my $self = shift;
55            
56             my $schema = Lucy::Plan::Schema->new;
57              
58             my $types = {
59             'fulltext' => 'Lucy::Plan::FullTextType',
60             'blob' => 'Lucy::Plan::BlobType',
61             'float32' => 'Lucy::Plan::Float32Type',
62             'float64' => 'Lucy::Plan::Float64Type',
63             'int32' => 'Lucy::Plan::Int32Type',
64             'int64' => 'Lucy::Plan::Int64Type',
65             'string' => 'Lucy::Plan::StringType',
66             };
67              
68             foreach my $field ( @{$self->schema} ){
69             my $type_options = {};
70             foreach my $option ( qw/boost indexed stored sortable/ ){
71             my $field_option = delete( $field->{ $option } );
72             if ( defined( $field_option ) ){
73             $type_options->{ $option } = $field_option;
74             }
75             }
76              
77             my $type = $field->{'type'} || 'fulltext';
78             if ( $type eq 'fulltext' ){
79             $type_options->{'analyzer'} = $self->_analyser;
80             $type_options->{'highlightable'} = delete $field->{'highlightable'} || 0;
81             }
82             $field->{'type'} = $types->{ $type }->new( %{$type_options} );
83             $schema->spec_field( %{$field} );
84             }
85             return $schema;
86             }
87              
88             has _indexer => (
89             'is' => 'lazy',
90             'init_arg' => undef,
91             'clearer' => 1,
92             );
93              
94             sub _build__indexer{
95             my $self = shift;
96              
97             return Lucy::Index::Indexer->new(
98             schema => $self->_index_schema,
99             index => $self->_index_path,
100             create => 1,
101             );
102             }
103              
104             has _searcher => (
105             'is' => 'lazy',
106             'init_arg' => undef,
107             'clearer' => 1,
108             );
109              
110             sub _build__searcher{
111             return Lucy::Search::IndexSearcher->new(
112             'index' => shift->_index_path,
113             );
114             }
115              
116             has search_fields => (
117             'is' => 'ro',
118             'required' => 1,
119             );
120              
121             has search_boolop => (
122             'is' => 'ro',
123             'default' => sub{ return 'OR' },
124             );
125              
126             has _query_parser => (
127             'is' => 'lazy',
128             'init_arg' => undef,
129             );
130              
131             sub _build__query_parser{
132             my $self = shift;
133              
134             my $query_parser = Lucy::Search::QueryParser->new(
135             schema => $self->_searcher->get_schema,
136             fields => $self->search_fields,
137             default_boolop => $self->search_boolop,
138             );
139              
140             $query_parser->set_heed_colons(1);
141              
142             return $query_parser;
143             }
144              
145             has resultclass => (
146             'is' => 'rw',
147             'lazy' => 1,
148             'coerce' => sub{my $class = shift; eval "use ${class}"; return $class},
149             'default' => sub{ return 'LucyX::Simple::Result::Object' },
150             );
151              
152             has entries_per_page => (
153             'is' => 'rw',
154             'lazy' => 1,
155             'default' => sub{ return 100 },
156             );
157              
158             sub sorted_search{
159             my ( $self, $query, $criteria, $page ) = @_;
160              
161             my @rules;
162             foreach my $key ( keys( %{$criteria} ) ){
163             push(
164             @rules,
165             Lucy::Search::SortRule->new(
166             field => $key,
167             reverse => $criteria->{ $key },
168             )
169             );
170             }
171              
172             return $self->search( $query, $page, Lucy::Search::SortSpec->new( rules => \@rules ) );
173             }
174              
175             sub search{
176             my ( $self, $query_string, $page, $sort_spec ) = @_;
177              
178             Exception::Simple->throw('no query string') if !$query_string;
179             $page ||= 1;
180              
181             my $query = $self->_query_parser->parse( $query_string );
182              
183             my $search_options = {
184             'query' => $query,
185             'offset' => ( ( $self->entries_per_page * $page ) - $self->entries_per_page ),
186             'num_wanted' => $self->entries_per_page,
187             };
188             $search_options->{'sort_spec'} = $sort_spec if $sort_spec;
189              
190             my $hits = $self->_searcher->hits( %{$search_options} );
191             my $pager = Data::Page->new($hits->total_hits, $self->entries_per_page, $page);
192              
193             my @results;
194             while ( my $hit = $hits->next ) {
195             my $result = {};
196             foreach my $field ( @{$self->schema} ){
197             $result->{ $field->{'name'} } = $hit->{ $field->{'name'} };
198             }
199             push( @results, $self->resultclass->new( $result ) );
200             }
201              
202             return ( \@results, $pager ) if scalar(@results);
203             Exception::Simple->throw('no results');
204              
205             }
206              
207             sub create{
208             my ( $self, $document ) = @_;
209              
210             Exception::Simple->throw('no document') if ( !$document );
211              
212             $self->_indexer->add_doc( $document );
213             }
214              
215             sub update_or_create{
216             my ( $self, $document, $pk ) = @_;
217              
218             Exception::Simple->throw('no document') if !$document;
219             $pk ||= 'id';
220             my $pv = $document->{ $pk };
221              
222             Exception::Simple->throw('no primary key value') if !$pv;
223             $self->delete( $pk, $pv );
224              
225             $self->create( $document );
226             }
227              
228             sub delete{
229             my ( $self, $key, $value ) = @_;
230              
231             Exception::Simple->throw( 'missing key' ) if !defined( $key );
232             Exception::Simple->throw( 'missing value' ) if !defined( $value );
233              
234             #delete only works on finished indexes
235             $self->commit;
236             $self->_indexer->delete_by_term(
237             'field' => $key,
238             'term' => $value,
239             );
240             }
241              
242             sub commit{
243             my ( $self, $optimise ) = @_;
244              
245             $self->_indexer->optimize if $optimise;
246             $self->_indexer->commit;
247              
248             $self->_clear_indexer;
249             $self->_clear_searcher;
250             }
251              
252             __PACKAGE__->meta->make_immutable;