1# Vend::DbSearch - Search indexes with Interchange 2# 3# $Id: DbSearch.pm,v 2.26 2007-08-09 13:40:53 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 25package Vend::DbSearch; 26require Vend::Search; 27 28@ISA = qw(Vend::Search); 29 30$VERSION = substr(q$Revision: 2.26 $, 10); 31 32use Search::Dict; 33use strict; 34no warnings qw(uninitialized numeric); 35 36sub array { 37 my ($s, $opt) = @_; 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); 41} 42 43sub hash { 44 my ($s, $opt) = @_; 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); 49} 50 51sub list { 52 my ($s, $opt) = @_; 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); 57} 58 59my %Default = ( 60 matches => 0, 61 mv_head_skip => 0, 62 mv_index_delim => "\t", 63 mv_matchlimit => 50, 64 mv_min_string => 1, 65 verbatim_columns => 1, 66); 67 68sub init { 69 my ($s, $options) = @_; 70 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; 75 76 @{$s}{keys %Default} = (values %Default); 77 $s->{mv_all_chars} = [1]; 78 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} = []; 83 $s->{mv_case} = []; 84 $s->{mv_column_op} = []; 85 $s->{mv_negate} = []; 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} = []; 93 94 for(keys %$options) { 95 $s->{$_} = $options->{$_}; 96 } 97 $s->{mv_search_file} = [ @{ 98 $::Variable->{MV_DEFAULT_SEARCH_TABLE} 99 || $Vend::Cfg->{ProductFiles} 100 } ] 101 unless ref($s->{mv_search_file}) and scalar(@{$s->{mv_search_file}}); 102 103 return; 104} 105 106sub new { 107 my ($class, %options) = @_; 108 my $s = new Vend::Search; 109 bless $s, $class; 110#::logDebug("mv_search_file initted=" . ::uneval($options{mv_search_file})); 111 $s->init(\%options); 112#::logDebug("mv_search_file now=" . ::uneval($s->{mv_search_file})); 113 return $s; 114} 115 116sub search { 117 my($s,%options) = @_; 118 119 my(@out); 120 my($limit_sub,$return_sub,$delayed_return); 121 my($f,$key,$val); 122 my($searchfile,@searchfiles); 123 my(@specs); 124 my(@pats); 125 126 while (($key,$val) = each %options) { 127 $s->{$key} = $val; 128 } 129 130 $s->{mv_return_delim} = $s->{mv_index_delim} 131 unless defined $s->{mv_return_delim}; 132 133 @searchfiles = @{$s->{mv_search_file}}; 134 135 for(@searchfiles) { 136 s:.*/::; 137 s/\..*//; 138 } 139 my $dbref = $s->{table} || undef; 140 141 if( ! $dbref ) { 142 $s->{dbref} = $dbref = Vend::Data::database_exists_ref($searchfiles[0]); 143 } 144 if(! $dbref) { 145 return $s->search_error( 146 "search file '$searchfiles[0]' is not a valid database reference." 147 ); 148 } 149 $s->{dbref} = $dbref; 150 151 my (@fn) = $dbref->columns(); 152 153#::logDebug("specs=" . ::uneval($s->{mv_searchspec})); 154 @specs = @{$s->{mv_searchspec}}; 155 156 157 if(ref $s->{mv_like_field} and ref $s->{mv_like_spec}) { 158 my $ary = []; 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%"); 166 if( 167 ! $dbref->config('UPPER_COMPARE') 168 or 169 $s->{mv_case_sensitive} and $s->{mv_case_sensitive}[0] 170 ) 171 { 172 push @$ary, "$col like $val"; 173 } 174 else { 175 $val = uc $val; 176 push @$ary, "UPPER($col) like $val"; 177 } 178 } 179 if(@$ary) { 180 $s->{eq_specs_sql} = [] if ! $s->{eq_specs_sql}; 181 push @{$s->{eq_specs_sql}}, @$ary; 182 } 183 } 184 185 # pass mv_min_string check if a valid pair of mv_like_field 186 # and mv_like_spec has been specified 187 188 my $min_string = $s->{mv_min_string}; 189 190 if ($s->{eq_specs_sql}) { 191 $s->{mv_min_string} = 0; 192 } 193 194 @pats = $s->spec_check(@specs); 195 196 $s->{mv_min_string} = $min_string; 197 198#::logDebug("specs now=" . ::uneval(\@pats)); 199 200 if ($s->{mv_search_error}) { 201 return $s; 202 } 203 204 if ($s->{mv_coordinate}) { 205 undef $f; 206 } 207 elsif ($s->{mv_return_all}) { 208 $f = sub {1}; 209 } 210 elsif ($s->{mv_orsearch}[0]) { 211 eval {$f = $s->create_search_or( 212 $s->get_scalar( 213 qw/mv_case mv_substring_match mv_negate/ 214 ), 215 @pats )}; 216 } 217 else { 218 eval {$f = $s->create_search_and( 219 $s->get_scalar( 220 qw/mv_case mv_substring_match mv_negate/ 221 ), 222 @pats )}; 223 } 224 225 $@ and return $s->search_error("Function creation: $@"); 226 227 my $qual; 228 if($s->{eq_specs_sql}) { 229 $qual = ' WHERE '; 230 my $joiner = $s->{mv_orsearch}[0] ? ' OR ' : ' AND '; 231 $qual .= join $joiner, @{$s->{eq_specs_sql}}; 232 } 233 234 $s->save_specs(); 235 foreach $searchfile (@searchfiles) { 236 my $lqual = $qual || ''; 237 $searchfile =~ s/\..*//; 238 my $db; 239 if (! $s->{mv_one_sql_table} ) { 240 $db = Vend::Data::database_exists_ref($searchfile) 241 or ::logError( 242 "Attempt to search non-existent database %s", 243 $searchfile, 244 ), next; 245 246 $dbref = $s->{dbref} = $db->ref(); 247 $dbref->reset(); 248 @fn = $dbref->columns(); 249 } 250 251 if(! $s->{mv_no_hide} and my $hf = $dbref->config('HIDE_FIELD')) { 252#::logDebug("found hide_field $hf"); 253 $lqual =~ s/^\s*WHERE\s+/ WHERE $hf <> 1 AND / 254 or $lqual = " WHERE $hf <> 1"; 255#::logDebug("lqual now '$lqual'"); 256 } 257 $s->hash_fields(\@fn); 258 my $prospect; 259 eval { 260 ($limit_sub, $prospect) = $s->get_limit($f); 261 }; 262 263 $@ and return $s->search_error("Limit subroutine creation: $@"); 264 265 $f = $prospect if $prospect; 266 267 eval {($return_sub, $delayed_return) = $s->get_return()}; 268 269 $@ and return $s->search_error("Return subroutine creation: $@"); 270 271 if(! defined $f and defined $limit_sub) { 272#::logDebug("no f, limit, dbref=$dbref"); 273 local($_); 274 my $ref; 275 while($ref = $dbref->each_nokey($lqual) ) { 276 next unless $limit_sub->($ref); 277 push @out, $return_sub->($ref); 278 } 279 } 280 elsif(defined $limit_sub) { 281#::logDebug("f and limit, dbref=$dbref"); 282 local($_); 283 my $ref; 284 while($ref = $dbref->each_nokey($lqual) ) { 285 $_ = join "\t", @$ref; 286 next unless &$f(); 287 next unless $limit_sub->($ref); 288 push @out, $return_sub->($ref); 289 } 290 } 291 elsif (!defined $f) { 292 return $s->search_error('No search definition'); 293 } 294 else { 295#::logDebug("f and no limit, dbref=$dbref"); 296 local($_); 297 my $ref; 298 while($ref = $dbref->each_nokey($lqual) ) { 299#::logDebug("f and no limit, ref=$ref"); 300 $_ = join "\t", @$ref; 301 next unless &$f(); 302 push @out, $return_sub->($ref); 303 } 304 } 305 $s->restore_specs(); 306 } 307 308 # Search the results and return 309 if($s->{mv_next_search}) { 310 @out = $s->search_reference(\@out); 311#::logDebug("did next_search: " . ::uneval(\@out)); 312 } 313 314 $s->{matches} = scalar(@out); 315 316#::logDebug("before delayed return: self=" . ::Vend::Util::uneval_it({%$s})); 317 318 if($delayed_return and $s->{matches} > 0) { 319 $s->hash_fields($s->{mv_field_names}, qw/mv_sort_field/); 320 $s->sort_search_return(\@out); 321 $delayed_return = $s->get_return(1); 322 @out = map { $delayed_return->($_) } @out; 323 } 324#::logDebug("after delayed return: self=" . ::Vend::Util::uneval({%$s})); 325 326 if($s->{mv_unique}) { 327 my %seen; 328 @out = grep ! $seen{$_->[0]}++, @out; 329 } 330 331 if($s->{mv_max_matches} and $s->{mv_max_matches} > 0) { 332 splice @out, $s->{mv_max_matches}; 333 } 334 335 $s->{matches} = scalar(@out); 336 337 if ($s->{matches} > $s->{mv_matchlimit} and $s->{mv_matchlimit} > 0) { 338 $s->save_more(\@out) 339 or ::logError("Error saving matches: $!"); 340 if ($s->{mv_first_match}) { 341 splice(@out,0,$s->{mv_first_match}) if $s->{mv_first_match}; 342 $s->{mv_next_pointer} = $s->{mv_first_match} + $s->{mv_matchlimit}; 343 $s->{mv_next_pointer} = 0 344 if $s->{mv_next_pointer} > $s->{matches}; 345 } 346 elsif ($s->{mv_start_match}) { 347 my $comp = $s->{mv_start_match}; 348 my $i = -1; 349 my $found; 350 for(@out) { 351 $i++; 352 next unless $_->[0] eq $comp; 353 $found = $i; 354 last; 355 } 356 if(! $found and $s->{mv_numeric}[0]) { 357 for(@out) { 358 $i++; 359 next unless $_->[0] >= $comp; 360 $found = $i; 361 last; 362 } 363 } 364 elsif (! $found) { 365 for(@out) { 366 $i++; 367 next unless $_->[0] ge $comp; 368 $found = $i; 369 last; 370 } 371 } 372 if($found) { 373 splice(@out,0,$found); 374 $s->{mv_first_match} = $found; 375 $s->{mv_next_pointer} = $found + $s->{mv_matchlimit}; 376 $s->{mv_next_pointer} = 0 377 if $s->{mv_next_pointer} > $s->{matches}; 378 } 379 } 380 $#out = $s->{mv_matchlimit} - 1; 381 } 382#::logDebug("after hash fields: self=" . ::Vend::Util::uneval_it({%$s})); 383 384 if(! $s->{mv_return_reference}) { 385 $s->{mv_results} = \@out; 386 } 387 elsif($s->{mv_return_reference} eq 'LIST') { 388 @out = map { join $s->{mv_return_delim}, @$_ } @out; 389 $s->{mv_results} = join $s->{mv_record_delim}, @out; 390 } 391 else { 392 my @names; 393 @names = @{ $s->{mv_field_names} }[ @{$s->{mv_return_fields}} ]; 394 $names[0] eq '0' and $names[0] = 'code'; 395 my @ary; 396 for (@out) { 397 my $h = {}; 398 @{ $h } {@names} = @$_; 399 push @ary, $h; 400 } 401 $s->{mv_results} = \@ary; 402 } 403 return $s; 404} 405 406# Unfortunate hack need for Safe searches 407*create_search_and = \&Vend::Search::create_search_and; 408*create_search_or = \&Vend::Search::create_search_or; 409*dump_options = \&Vend::Search::dump_options; 410*escape = \&Vend::Search::escape; 411*get_limit = \&Vend::Search::get_limit; 412*get_return = \&Vend::Search::get_return; 413*get_scalar = \&Vend::Search::get_scalar; 414*hash_fields = \&Vend::Search::hash_fields; 415*map_ops = \&Vend::Search::map_ops; 416*more_matches = \&Vend::Search::more_matches; 417*range_check = \&Vend::Search::range_check; 418*restore_specs = \&Vend::Search::restore_specs; 419*save_context = \&Vend::Search::save_context; 420*save_more = \&Vend::Search::save_more; 421*save_specs = \&Vend::Search::save_specs; 422*saved_params = \&Vend::Search::saved_params; 423*search_error = \&Vend::Search::search_error; 424*sort_search_return = \&Vend::Search::sort_search_return; 425*spec_check = \&Vend::Search::spec_check; 426*splice_specs = \&Vend::Search::splice_specs; 427 4281; 429__END__ 430