1 # Vend::DbSearch - Search indexes with Interchange
3 # $Id: DbSearch.pm,v 2.27 2008-09-06 05:22:56 mheins Exp $
5 # Adapted for use with Interchange from Search::TextSearch
7 # Copyright (C) 2002-2007 Interchange Development Group
8 # Copyright (C) 1996-2002 Red Hat, Inc.
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.
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.
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,
25 package Vend::DbSearch;
28 @ISA = qw(Vend::Search);
30 $VERSION = substr(q$Revision: 2.27 $, 10);
34 no warnings qw(uninitialized numeric);
38 $s->{mv_one_sql_table} = 1;
39 $s->{mv_list_only} = 1; # makes perform_search only return results array
40 return Vend::Scan::perform_search($opt, undef, $s);
45 $s->{mv_return_reference} = 'HASH';
46 $s->{mv_one_sql_table} = 1;
47 $s->{mv_list_only} = 1; # makes perform_search only return results array
48 return Vend::Scan::perform_search($opt, undef, $s);
53 $s->{mv_return_reference} = 'LIST';
54 $s->{mv_one_sql_table} = 1;
55 $s->{mv_list_only} = 1; # makes perform_search only return results array
56 return Vend::Scan::perform_search($opt, undef, $s);
62 mv_index_delim => "\t",
65 verbatim_columns => 1,
69 my ($s, $options) = @_;
71 # autovivify references of nested data structures we use below, since they
72 # don't yet exist at daemon startup time before configuration is done
73 $Vend::Cfg->{ProductFiles}[0] or 1;
74 $::Variable->{MV_DEFAULT_SEARCH_TABLE} or 1;
76 @{$s}{keys %Default} = (values %Default);
77 $s->{mv_all_chars} = [1];
79 ### This is a bit of a misnomer, for really it is the base table
80 ### that we will use if no base=table param is specified
81 $s->{mv_base_directory} = $Vend::Cfg->{ProductFiles}[0];
82 $s->{mv_begin_string} = [];
84 $s->{mv_column_op} = [];
86 $s->{mv_numeric} = [];
87 $s->{mv_orsearch} = [];
88 $s->{mv_search_field} = [];
89 $s->{mv_search_group} = [];
90 $s->{mv_searchspec} = [];
91 $s->{mv_sort_option} = [];
92 $s->{mv_substring_match} = [];
95 $s->{$_} = $options->{$_};
97 $s->{mv_search_file} = [ @{
98 $::Variable->{MV_DEFAULT_SEARCH_TABLE}
99 || $Vend::Cfg->{ProductFiles}
101 unless ref($s->{mv_search_file}) and scalar(@{$s->{mv_search_file}});
107 my ($class, %options) = @_;
108 my $s = new Vend::Search;
110 #::logDebug("mv_search_file initted=" . ::uneval($options{mv_search_file}));
112 #::logDebug("mv_search_file now=" . ::uneval($s->{mv_search_file}));
117 my($s,%options) = @_;
120 my($limit_sub,$return_sub,$delayed_return);
122 my($searchfile,@searchfiles);
126 while (($key,$val) = each %options) {
130 $s->{mv_return_delim} = $s->{mv_index_delim}
131 unless defined $s->{mv_return_delim};
133 @searchfiles = @{$s->{mv_search_file}};
139 my $dbref = $s->{table} || undef;
142 $s->{dbref} = $dbref = Vend::Data::database_exists_ref($searchfiles[0]);
145 return $s->search_error(
146 "search file '$searchfiles[0]' is not a valid database reference."
149 $s->{dbref} = $dbref;
151 my (@fn) = $dbref->columns();
153 #::logDebug("specs=" . ::uneval($s->{mv_searchspec}));
154 @specs = @{$s->{mv_searchspec}};
157 if(ref $s->{mv_like_field} and ref $s->{mv_like_spec}) {
159 for(my $i = 0; $i < @{$s->{mv_like_field}}; $i++) {
160 my $col = $s->{mv_like_field}[$i];
161 next unless length($col);
162 my $val = $s->{mv_like_spec}[$i];
163 length($val) or next;
164 next unless defined $dbref->test_column($col);
165 $val = $dbref->quote("$val%");
167 ! $dbref->config('UPPER_COMPARE')
169 $s->{mv_case_sensitive} and $s->{mv_case_sensitive}[0]
172 push @$ary, "$col like $val";
176 push @$ary, "UPPER($col) like $val";
180 $s->{eq_specs_sql} = [] if ! $s->{eq_specs_sql};
181 push @{$s->{eq_specs_sql}}, @$ary;
185 # pass mv_min_string check if a valid pair of mv_like_field
186 # and mv_like_spec has been specified
188 my $min_string = $s->{mv_min_string};
190 if ($s->{eq_specs_sql}) {
191 $s->{mv_min_string} = 0;
194 @pats = $s->spec_check(@specs);
196 $s->{mv_min_string} = $min_string;
198 #::logDebug("specs now=" . ::uneval(\@pats));
200 if ($s->{mv_search_error}) {
204 if ($s->{mv_coordinate}) {
207 elsif ($s->{mv_return_all}) {
210 elsif ($s->{mv_orsearch}[0]) {
211 eval {$f = $s->create_search_or(
213 qw/mv_case mv_substring_match mv_negate/
218 eval {$f = $s->create_search_and(
220 qw/mv_case mv_substring_match mv_negate/
225 $@ and return $s->search_error("Function creation: $@");
228 if($s->{eq_specs_sql}) {
230 my $joiner = $s->{mv_orsearch}[0] ? ' OR ' : ' AND ';
231 $qual .= join $joiner, @{$s->{eq_specs_sql}};
236 # set max_matches based on the lower of the pragma & the search parameter
237 # (so end-users can further restrict the size of the result set, but not increase it)
240 no warnings 'uninitialized';
241 $max_matches = $::Pragma->{max_matches};
242 undef $max_matches if $max_matches < 1;
243 my $search_mm = $s->{mv_max_matches};
244 $max_matches = $search_mm
245 if $search_mm > 0 and (!$max_matches or $search_mm < $max_matches);
248 SEARCHFILE: for $searchfile (@searchfiles) {
249 my $lqual = $qual || '';
250 $searchfile =~ s/\..*//;
252 if (! $s->{mv_one_sql_table} ) {
253 $db = Vend::Data::database_exists_ref($searchfile)
255 "Attempt to search non-existent database %s",
259 $dbref = $s->{dbref} = $db->ref();
261 @fn = $dbref->columns();
264 if(! $s->{mv_no_hide} and my $hf = $dbref->config('HIDE_FIELD')) {
265 #::logDebug("found hide_field $hf");
266 my $ss = $dbref->quote(1, $hf);
267 $lqual =~ s/^\s*WHERE\s+/ WHERE $hf <> $ss AND /
268 or $lqual = " WHERE $hf <> $ss";
269 #::logDebug("lqual now '$lqual'");
271 $s->hash_fields(\@fn);
274 ($limit_sub, $prospect) = $s->get_limit($f);
277 $@ and return $s->search_error("Limit subroutine creation: $@");
279 $f = $prospect if $prospect;
281 eval {($return_sub, $delayed_return) = $s->get_return()};
283 $@ and return $s->search_error("Return subroutine creation: $@");
285 if(! defined $f and defined $limit_sub) {
286 #::logDebug("no f, limit, dbref=$dbref");
289 while($ref = $dbref->each_nokey($lqual) ) {
290 next unless $limit_sub->($ref);
291 push @out, $return_sub->($ref);
292 last SEARCHFILE if $max_matches and @out >= $max_matches;
295 elsif(defined $limit_sub) {
296 #::logDebug("f and limit, dbref=$dbref");
299 while($ref = $dbref->each_nokey($lqual) ) {
300 $_ = join "\t", @$ref;
302 next unless $limit_sub->($ref);
303 push @out, $return_sub->($ref);
304 last SEARCHFILE if $max_matches and @out >= $max_matches;
307 elsif (!defined $f) {
308 return $s->search_error('No search definition');
311 #::logDebug("f and no limit, dbref=$dbref");
314 while($ref = $dbref->each_nokey($lqual) ) {
315 #::logDebug("f and no limit, ref=$ref");
316 $_ = join "\t", @$ref;
318 push @out, $return_sub->($ref);
319 last SEARCHFILE if $max_matches and @out >= $max_matches;
325 # Search the results and return
326 if($s->{mv_next_search}) {
327 @out = $s->search_reference(\@out);
328 #::logDebug("did next_search: " . ::uneval(\@out));
331 $s->{matches} = scalar(@out);
333 #::logDebug("before delayed return: self=" . ::Vend::Util::uneval_it({%$s}));
335 if($delayed_return and $s->{matches} > 0) {
336 $s->hash_fields($s->{mv_field_names}, qw/mv_sort_field/);
337 $s->sort_search_return(\@out);
338 $delayed_return = $s->get_return(1);
339 @out = map { $delayed_return->($_) } @out;
341 #::logDebug("after delayed return: self=" . ::Vend::Util::uneval({%$s}));
343 if($s->{mv_unique}) {
345 @out = grep ! $seen{$_->[0]}++, @out;
348 splice @out, $max_matches if $max_matches;
350 $s->{matches} = scalar(@out);
352 if ($s->{matches} > $s->{mv_matchlimit} and $s->{mv_matchlimit} > 0) {
354 or ::logError("Error saving matches: $!");
355 if ($s->{mv_first_match}) {
356 splice(@out,0,$s->{mv_first_match}) if $s->{mv_first_match};
357 $s->{mv_next_pointer} = $s->{mv_first_match} + $s->{mv_matchlimit};
358 $s->{mv_next_pointer} = 0
359 if $s->{mv_next_pointer} > $s->{matches};
361 elsif ($s->{mv_start_match}) {
362 my $comp = $s->{mv_start_match};
367 next unless $_->[0] eq $comp;
371 if(! $found and $s->{mv_numeric}[0]) {
374 next unless $_->[0] >= $comp;
382 next unless $_->[0] ge $comp;
388 splice(@out,0,$found);
389 $s->{mv_first_match} = $found;
390 $s->{mv_next_pointer} = $found + $s->{mv_matchlimit};
391 $s->{mv_next_pointer} = 0
392 if $s->{mv_next_pointer} > $s->{matches};
395 $#out = $s->{mv_matchlimit} - 1;
397 #::logDebug("after hash fields: self=" . ::Vend::Util::uneval_it({%$s}));
399 if(! $s->{mv_return_reference}) {
400 $s->{mv_results} = \@out;
402 elsif($s->{mv_return_reference} eq 'LIST') {
403 @out = map { join $s->{mv_return_delim}, @$_ } @out;
404 $s->{mv_results} = join $s->{mv_record_delim}, @out;
408 @names = @{ $s->{mv_field_names} }[ @{$s->{mv_return_fields}} ];
409 $names[0] eq '0' and $names[0] = 'code';
413 @{ $h } {@names} = @$_;
416 $s->{mv_results} = \@ary;
421 # Unfortunate hack need for Safe searches
422 *create_search_and = \&Vend::Search::create_search_and;
423 *create_search_or = \&Vend::Search::create_search_or;
424 *dump_options = \&Vend::Search::dump_options;
425 *escape = \&Vend::Search::escape;
426 *get_limit = \&Vend::Search::get_limit;
427 *get_return = \&Vend::Search::get_return;
428 *get_scalar = \&Vend::Search::get_scalar;
429 *hash_fields = \&Vend::Search::hash_fields;
430 *map_ops = \&Vend::Search::map_ops;
431 *more_matches = \&Vend::Search::more_matches;
432 *range_check = \&Vend::Search::range_check;
433 *restore_specs = \&Vend::Search::restore_specs;
434 *save_context = \&Vend::Search::save_context;
435 *save_more = \&Vend::Search::save_more;
436 *save_specs = \&Vend::Search::save_specs;
437 *saved_params = \&Vend::Search::saved_params;
438 *search_error = \&Vend::Search::search_error;
439 *sort_search_return = \&Vend::Search::sort_search_return;
440 *spec_check = \&Vend::Search::spec_check;
441 *splice_specs = \&Vend::Search::splice_specs;