UserDB: log timestamps to second granularity
[interchange.git] / lib / Vend / RefSearch.pm
1 # Vend::DbSearch - Search indexes with Interchange
2 #
3 # $Id: RefSearch.pm,v 2.11 2007-08-09 13:40:54 pajamian Exp $
4 #
5 # Adapted for use with Interchange from Search::TextSearch
6 #
7 # Copyright (C) 2002-2007 Interchange Development Group
8 # Copyright (C) 1996-2002 Red Hat, Inc.
9 #
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public
21 # License along with this program; if not, write to the Free
22 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
23 # MA  02110-1301  USA.
24
25 package Vend::RefSearch;
26 require Vend::Search;
27
28 @ISA = qw(Vend::Search);
29
30 $VERSION = substr(q$Revision: 2.11 $, 10);
31
32 use strict;
33 no warnings qw(uninitialized numeric);
34
35 sub array {
36         my ($s, $opt) = @_;
37         $s->{mv_one_sql_table} = 1;
38         $s->{mv_list_only} = 1; # makes perform_search only return results array
39         return Vend::Scan::perform_search($opt, undef, $s);
40 }
41
42 sub hash {
43         my ($s, $opt) = @_;
44         $s->{mv_return_reference} = 'HASH';
45         $s->{mv_one_sql_table} = 1;
46         $s->{mv_list_only} = 1; # makes perform_search only return results array
47         return Vend::Scan::perform_search($opt, undef, $s);
48 }
49
50 sub list {
51         my ($s, $opt) = @_;
52         $s->{mv_return_reference} = 'LIST';
53         $s->{mv_one_sql_table} = 1;
54         $s->{mv_list_only} = 1; # makes perform_search only return results array
55         return Vend::Scan::perform_search($opt, undef, $s);
56 }
57
58 my %Default = (
59         matches                 => 0,
60         mv_head_skip            => 0,
61         mv_index_delim          => "\t",
62         mv_matchlimit           => 50,
63         mv_min_string           => 1,
64         verbatim_columns        => 1,
65 );
66
67 sub init {
68         my ($s, $options) = @_;
69
70         # autovivify references of nested data structures we use below, since they
71         # don't yet exist at daemon startup time before configuration is done
72         $Vend::Cfg->{ProductFiles}[0] or 1;
73         $::Variable->{MV_DEFAULT_SEARCH_TABLE} or 1;
74
75         @{$s}{keys %Default} = (values %Default);
76         $s->{mv_all_chars}              = [1];
77         
78         ### This is a bit of a misnomer, for really it is the base table
79         ### that we will use if no base=table param is specified
80         $s->{mv_base_directory}     = $Vend::Cfg->{ProductFiles}[0];
81         $s->{mv_begin_string}       = [];
82         $s->{mv_case}               = [];
83         $s->{mv_column_op}          = [];
84         $s->{mv_negate}             = [];
85         $s->{mv_numeric}            = [];
86         $s->{mv_orsearch}           = [];
87         $s->{mv_search_field}       = [];
88         $s->{mv_search_file}        =   [ @{
89                                                                                 $::Variable->{MV_DEFAULT_SEARCH_TABLE}
90                                                                                 ||      $Vend::Cfg->{ProductFiles}
91                                                                                 } ];
92         $s->{mv_search_group}       = [];
93         $s->{mv_searchspec}         = [];
94         $s->{mv_sort_option}        = [];
95         $s->{mv_substring_match}    = [];
96
97         for(keys %$options) {
98                 $s->{$_} = $options->{$_};
99         }
100
101         return;
102 }
103
104 sub new {
105         my ($class, %options) = @_;
106         my $s = new Vend::Search;
107         bless $s, $class;
108         $s->init(\%options);
109         return $s;
110 }
111
112 sub search {
113         my($s,%options) = @_;
114
115         my(@out);
116         my($limit_sub,$return_sub,$delayed_return);
117         my($f,$key,$val);
118         my($searchfile,@searchfiles);
119         my(@specs);
120         my(@pats);
121
122         while (($key,$val) = each %options) {
123                 $s->{$key} = $val;
124         }
125
126         $s->{mv_return_delim} = $s->{mv_index_delim}
127                 unless defined $s->{mv_return_delim};
128
129 #::logDebug("Called RefSearch, object=" . ::uneval($s));
130         @specs = @{$s->{mv_searchspec}};
131
132         @pats = $s->spec_check(@specs);
133
134         if ($s->{mv_coordinate}) {
135                 undef $f;
136         }
137         elsif ($s->{mv_orsearch}[0]) {
138                 eval {$f = $s->create_search_or(
139                                                                         $s->get_scalar(
140                                                                                         qw/mv_case mv_substring_match mv_negate/
141                                                                                         ),
142                                                                                 @pats                                   )};
143         }
144         else  { 
145                 eval {$f = $s->create_search_and(
146                                                                         $s->get_scalar(
147                                                                                         qw/mv_case mv_substring_match mv_negate/
148                                                                                         ),
149                                                                                 @pats                                   )};
150         }
151
152         $@  and  return $s->search_error("Function creation: $@");
153
154         $s->save_specs();
155 #::logDebug("searchfiles=@searchfiles");
156         
157
158         ### Now we search
159
160         my $target = $s->{mv_search_reference};
161
162 #::logDebug("target: " . ::uneval($target));
163         if(! ref $target) {
164                 my $label = $target;
165                 my $sr = $::Instance->{SearchObject}{$label};
166                 if(! $sr) {
167                         return $s->search_error("$label is not a target nor a label referencing a previous search");
168                 }
169                 $target= $sr->{mv_results};
170                 $s->{mv_field_names} ||= $sr->{mv_field_names};
171         }
172
173 #::logDebug("field names=" . ::uneval($s->{mv_field_names}));
174 #::logDebug("target now: " . ::uneval($target));
175         if(
176                 ref($target) ne 'ARRAY'
177                         or
178                 (defined $target->[0] && ref($target->[0]) ne 'ARRAY')
179           )
180         {
181                 return $s->search_error("Reference for search must be array of arrays");
182         }
183
184         if(! $s->{mv_field_names}) {
185                 if($s->{head_skip}) {
186                         my @lines = splice(@$target,0,$s->{mv_head_skip});
187                         $s->{mv_field_names} = pop(@lines);
188                 }
189                 else {
190                         $s->{mv_field_names} = [ 0 .. (@{$target->[0]} - 1) ]
191                 }
192         }
193
194         $s->hash_fields($s->{mv_field_names});
195         
196         my $prospect;
197         eval {
198                 ($limit_sub, $prospect) = $s->get_limit($f);
199         };
200
201         $@  and  return $s->search_error("Limit subroutine creation: $@");
202
203         $f = $prospect if $prospect;
204
205         eval {($return_sub, $delayed_return) = $s->get_return()};
206
207         $@  and  return $s->search_error("Return subroutine creation: $@");
208
209         if(! defined $f and defined $limit_sub) {
210 #::logDebug("no f, limit, dbref=$dbref");
211                 local($_);
212                 my $ref;
213                 for $ref (@$target) {
214                         next unless $limit_sub->($ref);
215                         push @out, $return_sub->($ref);
216                 }
217         }
218         elsif(defined $limit_sub) {
219 #::logDebug("f and limit, dbref=$dbref");
220                 local($_);
221                 my $ref;
222                 for $ref (@$target) {
223                         $_ = join "\t", @$ref;
224                         next unless &$f();
225                         next unless $limit_sub->($ref);
226                         push @out, $return_sub->($ref);
227                 }
228         }
229         elsif (!defined $f) {
230                 return $s->search_error('No search definition');
231         }
232         else {
233 #::logDebug("f and no limit, dbref=$dbref");
234                 local($_);
235                 my $ref;
236                 for $ref (@$target) {
237 #::logDebug("f and no limit, ref=$ref");
238                         $_ = join "\t", @$ref;
239                         next unless &$f();
240                         push @out, $return_sub->($ref);
241                 }
242         }
243         $s->restore_specs();
244
245         $s->{matches} = scalar(@out);
246 #::logDebug("before delayed return: self=" . ::Vend::Util::uneval_it({%$s}));
247
248         if($delayed_return and $s->{matches} > 0) {
249                 $s->hash_fields($s->{mv_field_names}, qw/mv_sort_field/);
250                 $s->sort_search_return(\@out);
251                 $delayed_return = $s->get_return(1);
252                 @out = map { $delayed_return->($_) } @out;
253         }
254 #::logDebug("after delayed return: self=" . ::Vend::Util::uneval({%$s}));
255
256         if($s->{mv_unique}) {
257                 my %seen;
258                 @out = grep ! $seen{$_->[0]}++, @out;
259         }
260
261         if($s->{mv_max_matches} > 0) {
262                 splice @out, $s->{mv_max_matches};
263         }
264
265         $s->{matches} = scalar(@out);
266
267         ## This is the normal return point unless RefSearch called by program
268         ## or ITL
269         return @out if $s->{mv_return_filtered};
270
271         if ($s->{matches} > $s->{mv_matchlimit} and $s->{mv_matchlimit} > 0) {
272                 $s->save_more(\@out)
273                         or ::logError("Error saving matches: $!");
274                 if ($s->{mv_first_match}) {
275                         splice(@out,0,$s->{mv_first_match}) if $s->{mv_first_match};
276                         $s->{mv_next_pointer} = $s->{mv_first_match} + $s->{mv_matchlimit};
277                         $s->{mv_next_pointer} = 0
278                                 if $s->{mv_next_pointer} > $s->{matches};
279                 }
280                 elsif ($s->{mv_start_match}) {
281                         my $comp = $s->{mv_start_match};
282                         my $i = -1;
283                         my $found;
284                         for(@out) {
285                                 $i++;
286                                 next unless $_->[0] eq $comp;
287                                 $found = $i;
288                                 last;
289                         }
290                         if(! $found and $s->{mv_numeric}[0]) {
291                                 for(@out) {
292                                         $i++;
293                                         next unless $_->[0] >= $comp;
294                                         $found = $i;
295                                         last;
296                                 }
297                         }
298                         elsif (! $found) {
299                                 for(@out) {
300                                         $i++;
301                                         next unless $_->[0] ge $comp;
302                                         $found = $i;
303                                         last;
304                                 }
305                         }
306                         if($found) {
307                                 splice(@out,0,$found);
308                                 $s->{mv_first_match} = $found;
309                                 $s->{mv_next_pointer} = $found + $s->{mv_matchlimit};
310                                 $s->{mv_next_pointer} = 0
311                                         if $s->{mv_next_pointer} > $s->{matches};
312                         }
313                 }
314                 $#out = $s->{mv_matchlimit} - 1;
315         }
316 #::logDebug("after hash fields: self=" . ::Vend::Util::uneval_it({%$s}));
317 #::logDebug("after delayed return: self=" . ::Vend::Util::uneval_it({%$s}));
318
319         if(! $s->{mv_return_reference}) {
320                 $s->{mv_results} = \@out;
321         }
322         elsif($s->{mv_return_reference} eq 'LIST') {
323                 @out = map { join $s->{mv_return_delim}, @$_ } @out;
324                 $s->{mv_results} = join $s->{mv_record_delim}, @out;
325         }
326         else {
327                 my @names;
328                 @names = @{ $s->{mv_field_names} }[ @{$s->{mv_return_fields}} ];
329                 $names[0] eq '0' and $names[0] = 'code';
330                 my @ary;
331                 for (@out) {
332                         my $h = {};
333                         @{ $h } {@names} = @$_;
334                         push @ary, $h;
335                 }
336                 $s->{mv_results} = \@ary;
337         }
338         return $s;
339 }
340
341 # Unfortunate hack need for Safe searches
342 *create_search_and  = \&Vend::Search::create_search_and;
343 *create_search_or   = \&Vend::Search::create_search_or;
344 *dump_options       = \&Vend::Search::dump_options;
345 *escape             = \&Vend::Search::escape;
346 *get_limit          = \&Vend::Search::get_limit;
347 *get_return         = \&Vend::Search::get_return;
348 *get_scalar         = \&Vend::Search::get_scalar;
349 *hash_fields        = \&Vend::Search::hash_fields;
350 *map_ops            = \&Vend::Search::map_ops;
351 *more_matches       = \&Vend::Search::more_matches;
352 *range_check        = \&Vend::Search::range_check;
353 *restore_specs      = \&Vend::Search::restore_specs;
354 *save_context       = \&Vend::Search::save_context;
355 *save_more          = \&Vend::Search::save_more;
356 *save_specs         = \&Vend::Search::save_specs;
357 *saved_params       = \&Vend::Search::saved_params;
358 *search_error       = \&Vend::Search::search_error;
359 *sort_search_return = \&Vend::Search::sort_search_return;
360 *spec_check         = \&Vend::Search::spec_check;
361 *splice_specs       = \&Vend::Search::splice_specs;
362
363 1;
364 __END__