File Coverage

blib/lib/Catmandu/Store/Lucy/Bag.pm
Criterion Covered Total %
statement 84 87 96.5
branch 22 30 73.3
condition 8 9 88.8
subroutine 17 18 94.4
pod 0 5 0.0
total 131 149 87.9


line stmt bran cond sub pod time code
1             package Catmandu::Store::Lucy::Bag;
2              
3 1     1   3 use Catmandu::Sane;
  1         1  
  1         9  
4 1     1   177 use Carp qw(confess);
  1         1  
  1         34  
5 1     1   396 use Catmandu::Hits;
  1         17200  
  1         34  
6 1     1   411 use Lucy::Search::ANDQuery;
  1         104  
  1         44  
7 1     1   356 use Lucy::Search::TermQuery;
  1         97  
  1         52  
8 1     1   381 use Lucy::Search::QueryParser;
  1         100  
  1         26  
9 1     1   362 use Lucy::Search::SortSpec;
  1         94  
  1         26  
10 1     1   345 use Lucy::Search::SortRule;
  1         94  
  1         23  
11 1     1   4 use Moo;
  1         1  
  1         5  
12              
13             with 'Catmandu::Bag';
14             with 'Catmandu::Searchable';
15              
16             our $VERSION = '0.0102';
17              
18             has _bag_query => (is => 'ro', lazy => 1, builder => '_build_bag_query');
19              
20 1     1   442 sub _build_bag_query { Lucy::Search::TermQuery->new(field => '_bag', term => $_[0]->name) }
21              
22             sub _searcher {
23 12     12   11 my ($self) = @_;
24             eval {
25 12         198 $self->store->_searcher;
26 12 100       15 } or do {
27 1 50       478 my $e = $@; die $e if $e !~ /index doesn't seem to contain any data/i;
  1         35  
28             };
29             }
30              
31             sub generator {
32 1     1 0 448 my ($self) = @_;
33             sub {
34 4   50 4   23 state $searcher = $self->_searcher || return;
35 4         15 state $messagepack = $self->store->_messagepack;
36 4         3 state $start = 0;
37 4         3 state $limit = 100;
38 4         3 state $hits;
39              
40 4         4 my $hit;
41 4 100 100     26 unless ($hits and $hit = $hits->next) {
42 2         33 $hits = $searcher->hits(query => $self->_bag_query, num_wanted => $limit, offset => $start);
43 2         82 $start += $limit;
44 2   100     16 $hit = $hits->next || return;
45             }
46 3         19 $messagepack->unpack($hit->{_data});
47 1         5 };
48             }
49              
50             sub count {
51 5     5 0 276 my ($self) = @_;
52 5   100     12 my $searcher = $self->_searcher || return 0;
53 4         1677 $searcher->hits(
54             query => $self->_bag_query,
55             num_wanted => 0,
56             )->total_hits;
57             }
58              
59             sub get {
60             my ($self, $id) = @_;
61             my $searcher = $self->_searcher || return;
62             my $hits = $searcher->hits(
63             query => Lucy::Search::ANDQuery->new(children => [
64             Lucy::Search::TermQuery->new(field => '_id', term => $id),
65             $self->_bag_query,
66             ]),
67             num_wanted => 1,
68             );
69             $hits->total_hits || return;
70             $self->store->_messagepack->unpack($hits->next->{_data});
71             }
72              
73             sub add {
74             my ($self, $data) = @_;
75              
76             my $store = $self->store;
77             my $bag = $self->name;
78             my $data_blob = $store->_messagepack->pack($data);
79              
80             $data = $self->_flatten_data($data);
81              
82             my $type = $store->_ft_field_type;
83             my $schema = $store->_schema;
84             for my $key (keys %$data) {
85             next if $key eq '_id';
86             $schema->spec_field(name => $key, type => $type);
87             }
88              
89             $data->{_data} = $data_blob;
90             $data->{_bag} = $bag;
91             $store->_indexer->add_doc($data);
92             $data;
93             }
94              
95             sub commit {
96             my ($self) = @_;
97             $self->store->_commit;
98             }
99              
100             sub search {
101             my ($self, %args) = @_;
102              
103             my $start = delete $args{start};
104             my $limit = delete $args{limit};
105             my $sort = delete $args{sort};
106             my $bag = delete $args{reify};
107              
108             if ($sort) {
109             $args{sort_spec} = $sort;
110             }
111              
112             my $searcher = $self->_searcher || return Catmandu::Hits->new(
113             start => $start,
114             limit => $limit,
115             total => 0,
116             hits => [],
117             );
118              
119             my $lucy_hits = $searcher->hits(
120             %args,
121             num_wanted => $limit,
122             offset => $start,
123             );
124              
125             my $hits = [];
126              
127             if ($bag) {
128             while (my $hit = $lucy_hits->next) {
129             push @$hits, $bag->get($hit->{_id});
130             }
131             } else {
132             while (my $hit = $lucy_hits->next) {
133             push @$hits, $self->store->_messagepack->unpack($hit->{_data});
134             }
135             }
136              
137             Catmandu::Hits->new(
138             start => $start,
139             limit => $limit,
140             total => $lucy_hits->total_hits,
141             hits => $hits,
142             );
143             }
144              
145             sub searcher {
146             confess 'TODO';
147             }
148              
149             sub delete {
150             my ($self, $id) = @_;
151             $self->store->_indexer->delete_by_query(Lucy::Search::ANDQuery->new(children => [
152             Lucy::Search::TermQuery->new(field => '_id', term => $id),
153             $self->_bag_query,
154             ]));
155             }
156              
157             sub delete_all {
158             my ($self) = @_;
159             $self->store->_indexer->delete_by_query($self->_bag_query);
160             }
161              
162             sub delete_by_query {
163             my ($self, %args) = @_;
164             $self->store->_indexer->delete_by_query($args{query});
165              
166             }
167              
168             sub translate_sru_sortkeys { # TODO score, cql mapping
169 2     2 0 538 my ($self, $sortkeys) = @_;
170 2         4 my $rules = [];
171 2         8 for my $sortkey (split /\s+/, $sortkeys) {
172 2         5 my ($field, $schema, $asc) = split /,/, $sortkey;
173 2 50       6 $field || next;
174 2 50       5 if ($field eq 'relevance') {
175 0 0       0 push @$rules, Lucy::Search::SortRule->new(type => 'score', reverse => $asc ? 1 : 0);
176             } else {
177 2 100       13 push @$rules, Lucy::Search::SortRule->new(type => 'field', field => $field, reverse => $asc ? 0 : 1);
178             }
179             }
180 2         53 Lucy::Search::SortSpec->new(rules => $rules);
181             }
182              
183             sub translate_cql_query {
184 0     0 0 0 confess 'TODO';
185             }
186              
187             sub normalize_query {
188 5     5 0 2199 my ($self, $query) = @_;
189 5 100       12 if (!defined $query) {
190 3         49 return $self->_bag_query;
191             }
192 2 50       6 if (ref $query) {
193 0         0 return Lucy::Search::ANDQuery->new(children => [
194             $self->_bag_query,
195             $query,
196             ]);
197             }
198 2         30 Lucy::Search::ANDQuery->new(children => [
199             $self->_bag_query,
200             Lucy::Search::QueryParser->new(default_boolop => 'AND', schema => $self->store->_schema)->parse($query),
201             ]);
202             }
203              
204             sub _flatten_data {
205 4     4   5595 my ($self, $data) = @_;
206              
207 4         6 my $flat = {};
208              
209 4         7 my @ref_stack = ($data);
210 4         4 my @key_stack;
211 4         9 while (@ref_stack) {
212 10         8 my $ref = shift @ref_stack;
213 10         10 my $key = shift @key_stack;
214              
215 10 100       19 if (ref $ref eq 'ARRAY') {
216 4         5 for my $val (@$ref) {
217 6 100       11 if (ref $val) {
    50          
218 2         1 push @key_stack, $key;
219 2         3 push @ref_stack, $val;
220             } elsif (defined $val) {
221 4         5 $flat->{$key} = $val;
222             }
223             }
224 4         5 next;
225             }
226              
227 6         12 for my $k (keys %$ref) {
228 13         12 my $val = $ref->{$k};
229 13 100       18 $k = "$key.$k" if defined $key;
230 13 100       21 if (ref $val) {
    50          
231 4         5 push @key_stack, $k;
232 4         6 push @ref_stack, $val;
233             } elsif (defined $val) {
234 9         21 $flat->{$k} = $val;
235             }
236             }
237             }
238              
239 4         14 $flat;
240             }
241              
242             =head1 SEE ALSO
243              
244             L<Catmandu::Bag>, L<Catmandu::Searchable>
245              
246             =cut
247              
248             1;