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