File Coverage

lib/Metadata/DB/Search.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             package Metadata::DB::Search;
2 3     3   206292 use strict;
  3         8  
  3         129  
3 3     3   17 use vars qw($VERSION);
  3         4  
  3         171  
4 3     3   2936 use LEOCHARRE::Class2;
  3         24088  
  3         394  
5 3     3   32 use base 'Metadata::DB::Base';
  3         6  
  3         7483  
6             use LEOCHARRE::DEBUG;
7             use Carp;
8             $VERSION = sprintf "%d.%02d", q$Revision: 1.5 $ =~ /(\d+)/g;
9              
10             __PACKAGE__->make_constructor();
11             __PACKAGE__->make_accessor_setget({
12             search_params => [],
13             _searches_run_count => 0,
14             _results_hashref => {},
15             _default_search_type => 'like',
16             });
17              
18              
19             *{search_reset} = \&_search_reset;
20             sub _search_reset {
21             my $self = shift;
22             $self->_searches_run_count(0);
23             $self->search_params([]);
24             $self->_results_hashref({});
25             debug();
26             return 1;
27             }
28              
29              
30             # =============================== search params
31             *{search_params_arrayref} = \&search_params;
32              
33             sub search_params_add {
34             my $self = shift;
35             my $a = $self->search_params;
36             while( scalar @_ ){
37             my ($key, $val, $type ) = (shift, shift, shift);
38             my $arref = $self->__array_to_search_params($key,$val,$type);
39             #print STDERR " - $key, $val, $type\n" if DEBUG;
40             push @$a, $arref;
41             }
42             return $a;
43             }
44             sub search_params_count {
45             my $self = shift;
46             my $a = $self->search_params_arrayref or return 0;
47             return ( scalar @$a );
48             }
49              
50             # TODO deprecate this ?
51             sub constriction_keys {
52             my $self = shift;
53             my @ck;
54             for( @{$self->search_params_arrayref} ){
55             push @ck, $_->[0];
56             }
57             return \@ck;
58             }
59             # hack
60             sub __array_to_search_params {
61             my $self = shift;
62             my($att,$val,$type) = @_;
63             if ( $att=~s/:(\w+)$// ) {
64             $type ||= $1;
65             }
66             $type ||= $self->_default_search_type;
67             return [$att,$val,$type];
68             }
69              
70              
71              
72              
73              
74              
75             *{search} = \&layered_search;
76             # a layered search is searching on multiple vectors(?), mutiple conditions (constraints)
77             sub layered_search { # multiple key lookup and ranked
78             my $self = shift;
79              
80             my @strayargs=();
81              
82             ARG: while( scalar @_ ){
83             my $arg = shift;
84             defined $arg or next;
85              
86            
87             if( ref $arg eq 'HASH' ){
88             debug('hash');
89             while( my($k,$v) = each %{$arg} ){
90             #print STDERR "$k $v, " if DEBUG;
91             $self->search_params_add($k,$v);
92             }
93             next ARG;
94             }
95              
96             elsif( ref $arg eq 'ARRAY' ){
97             $self->search_params_add(@$arg);
98             debug('array');
99             next ARG;
100             }
101             else {
102             debug('stay arg');
103             push @strayargs, $arg;
104             }
105             }
106              
107            
108             if( my $argcount = scalar @strayargs ){
109             $argcount == 2 or $argcount == 3 or die('bad args to search()');
110             $self->search_params_add(@strayargs);
111             }
112              
113              
114             $self->search_params_count or die('missing search params');
115            
116              
117            
118             QUERY: for( @{$self->search_params_arrayref} ){
119             my $ids = $self->_execute_search_run( @$_ );
120             scalar @$ids
121             or next QUERY; # TODO actually if none here we should go ahead and return no results
122              
123             next QUERY;
124             }
125              
126             return $self->ids; # will only return those in all matches
127             }
128              
129              
130              
131              
132              
133             sub _id_was_in_results_how_many_times {
134             my($self, $id) = @_;
135             defined $id or die;
136             my $count = $self->_results_hashref->{$id};
137             $count ||= 0;
138             return $count;
139             }
140              
141             # also known as ids
142             *{ids} = \&_ids_present_in_all_search_results;
143             sub _ids_present_in_all_search_results {
144             my $self = shift;
145              
146             # if no search was run, then forget it
147             my $runcount = $self->_searches_run_count or die('no searches run yet');
148             debug("runs: $runcount");
149            
150             # if we only ran one search, then we dont need to filter
151             if ( $runcount == 1 ){
152             return $self->_results_arrayref;
153             }
154              
155             # if we ran many, we only want the ones that match ALL searches
156             my $results = $self->_results_hashref;
157             my @inall = grep { $results->{$_} == $runcount } keys %$results;
158              
159             # print STDERR "\n" if DEBUG;
160              
161             return \@inall;
162             }
163              
164             sub ids_count {
165             my $self = shift;
166             return ( scalar @{$self->ids} );
167             }
168              
169             # wrappers...
170              
171             sub search_morethan {
172             my($self,$att,$val) = @_;
173             defined $val or confess('missing val arg');
174             return ($self->search( $att, $val, 'morethan'));
175             }
176              
177             sub search_like {
178             my($self,$att,$val) = @_;
179             defined $val or confess('missing val arg');
180             return ($self->search( $att, $val, 'like'));
181             }
182              
183             sub search_lessthan {
184             my($self,$att,$val) = @_;
185             defined $val or confess('missing val arg');
186             return ($self->search( $att, $val, 'lessthan'));
187             }
188              
189             sub search_exact {
190             my($self,$att,$val) = @_;
191             defined $val or confess('missing val arg');
192             return ($self->search( $att, $val, 'exact'));
193             }
194              
195             # ONE search type,
196             # this only returns ids, that's all
197             # does not calculate overlaps
198             #
199             # my $ids = _search( 'name:like' => 'joe' );
200             # my $ids = _search( name => 'joe' );
201             # my $ids = _search( 'name', 'joe', 'like' );
202             # my $ids = _search( 'name:like' => 'joe', 'exact' ); exact overrides, usage warning is issued
203             sub _execute_search_run { # execute_search_run
204             my $self = shift;
205             my ($att, $val, $type) = @_;
206            
207             my $runcount = $self->_searches_run_count;
208              
209             my $_type;
210             if ( $att=~s/:(\w+)$// ) {
211             $type ||= $_type;
212             }
213             $type ||= $self->_default_search_type;
214              
215             defined $val or confess('missing val argument');
216              
217             if ( $type eq 'like' ){
218             $val = "\%$val\%";
219             }
220              
221             debug(" QUERY : $type, $att, $val ..");
222              
223              
224              
225             my $sth = $self->get_search_type_handle($type);
226             $sth->execute($att, $val) or die($self->dbh->errstr);
227              
228             my $id; # for binding
229             my @ids = ();
230             $sth->bind_columns(\$id);
231              
232              
233             my $results = $self->_results_hashref;
234              
235             while( $sth->fetch ){
236             push @ids, $id;
237              
238             # if there are previous runs.. only record a hit if it exists alrady
239             if( $runcount ){
240             if( exists $results->{$id} ){
241             $results->{$id}++; # record a hit
242             }
243             }
244             else {
245             $results->{$id}++; # record a hit
246             }
247             }
248              
249             # increment the searches run counter
250             $self->_searches_run_count( ++$runcount );
251              
252             return \@ids;
253             }
254              
255              
256             sub _results_arrayref {
257             my $self = shift;
258             my @a = keys %{$self->_results_hashref};
259             return \@a;
260             }
261              
262              
263             # ----------------------
264             # search types code etc
265              
266             sub search_types_arrayref {
267             my $self = shift;
268             my @t = keys %{$self->search_types_hashref};
269             return \@t;
270             }
271              
272             sub search_types_hashref {
273             my $self = shift;
274            
275             unless( $self->{_get_sth_statement} ){
276              
277             my ($table,$colk,$colv,$coli) = (
278             $self->table_metadata_name,
279             $self->table_metadata_column_name_key,
280             $self->table_metadata_column_name_value,
281             $self->table_metadata_column_name_id );
282              
283             $self->{_get_sth_statement} = {
284             'like' => "SELECT $coli FROM $table WHERE $colk=? and $colv LIKE ?",
285             'exact' => "SELECT $coli FROM $table WHERE $colk=? and $colv = ?",
286             'lessthan' => "SELECT $coli FROM $table WHERE $colk=? and $colv < CAST( ? AS SIGNED )",
287             'morethan' => "SELECT $coli FROM $table WHERE $colk=? and $colv > CAST( ? AS SIGNED )",
288             };
289             }
290             return $self->{_get_sth_statement};
291             }
292              
293             sub get_search_type_handle {
294             my($self,$type) = @_;
295             defined $type or confess('missing type');
296             my $sql = $self->search_type_exists($type) or confess("search type $type does not exist");
297              
298             my $sth = $self->dbh->prepare_cached($sql) or die($self->dbh->errstr);
299             return $sth;
300             }
301              
302             sub search_type_exists {
303             my($self,$type) = @_;
304             defined $type or confess('missing type');
305             return ( $self->search_types_hashref->{$type} or 0 );
306             }
307              
308              
309             # end search types ------
310              
311              
312             1;
313              
314             __END__