# # Copyright (c) 2005, Presicient Corp., USA # # Permission is granted to use this software according to the terms of the # Artistic License, as specified in the Perl README file, # with the exception that commercial redistribution, either # electronic or via physical media, as either a standalone package, # or incorporated into a third party product, requires prior # written approval of the author. # # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # Presicient Corp. reserves the right to provide support for this software # to individual sites under a separate (possibly fee-based) # agreement. # # History: # # 2005-Jan-27 D. Arnold # Coded. # package SQL::Amazon::Request::ItemSearch; use SQL::Amazon::Request::ItemLookup; use SQL::Amazon::Parser qw(:pred_node_codes); use base qw(SQL::Amazon::Request::ItemLookup); use strict; my %is_power_col = ( 'TITLE', [ 'title', 1 ], 'SUBJECT', [ 'subject', undef ], 'AUTHORS', [ 'author', 1 ], 'PUBLISHER', [ 'publisher', 1 ], 'LANGUAGE', [ 'language', undef ], ); my %is_search_col = ( 'TITLE', [ 'Title', '*', 1 ], 'AUTHORS', [ 'Author', 'Books', 1 ], 'PUBLISHER', [ 'Publisher', 'Books', 1 ], 'ARTIST', [ 'Artist', '*', 1 ], 'ACTOR', [ 'Actor', '*', 1 ], 'DIRECTOR', [ 'Director', '*', 1 ], 'MANUFACTURER', [ 'Manufacturer', '*', 1 ], 'MUSICLABEL', [ 'MusicLabel', '*', 1 ], 'COMPOSER', [ 'Composer', '*', 1 ], 'BRAND', [ 'Brand', '*', 1 ], 'CONDUCTOR', [ 'Conductor', '*', 1 ], 'ORCHESTRA', [ 'Orchestra', '*', 1 ], 'CITY', [ 'City', 'Restruants', 1 ], 'CUISINE', [ 'Cuisine', 'Restruants', 1 ], 'NEIGHBORHOOD', [ 'Neighborhood', 'Restruants', 1 ], 'MERCHANTID', [ 'MerchantID', 'Offers', undef ], 'CONDITION', [ 'Condition', 'Offers', undef ], 'DELIVERYMETHOD', [ 'DeliveryMethod', 'Offers', undef ], ); my %can_power_search = qw( BOOKS 1 ); my %is_amzn_function = qw( AMZN_POWER_SEARCH 1 AMZN_IN_ANY 1 AMZN_MATCH_ANY 1 AMZN_MATCH_ALL 1 AMZN_MATCH_TEXT 1 ); our $errstr; sub errstr { return $errstr if $errstr && $errstr ne ''; return shift->{_errstr}; } sub new { my $class = shift; my $req_attrs = { Power => { Explicit => [], Keywords => [], title => undef, subject => [], author => [], publisher => undef, language => undef }, Keywords => [], }; $errstr = undef; my $predicate = create_search_request($req_attrs, @_); return (undef, undef) if $errstr; return ($predicate, undef) unless scalar keys %$req_attrs; my $obj = $class->SUPER::new($req_attrs); $obj->{url_params}{Operation} = 'ItemSearch'; $obj->{req_attrs} = $req_attrs; return ($predicate, $obj); } sub create_search_request { my ($request, $expr, $table, $parser) = @_; $request->{SearchIndex} = $table; my $conjoins = $expr->[0]; my @finalcjs = (); foreach (@$conjoins) { my ($op, $left, $right, $neg) = @$_; $table = uc $table; my ($name, $value); if ($op eq 'USER_DEFINED') { $name = (ref $left ne 'HASH') ? $left->name() : $left->{name}; push (@finalcjs, $_), next unless $is_amzn_function{uc $name}; if ($name eq 'AMZN_IN_ANY') { $op = 'IN'; $value = (ref $left ne 'HASH') ? $left->args()->{value}[0] : $left->{value}{value}[0]; } } push (@finalcjs, $_), next if ($neg && ($op eq 'IN')); push (@finalcjs, $_), next if ((ref $right eq 'HASH') && ($right->{type} eq 'column')); $name = ((ref $left eq 'HASH') && ($left->{type} eq 'column')) ? $left->{value} : ($op eq 'IN') ? $value : $name; $name = uc $name; $table = uc $1 if ($name=~s/^([A-Z]\w*)\.(\w+)/$2/); my $aliases = $parser->{struct}{column_aliases}; $name = $aliases->{$name} if $aliases->{$name}; push (@finalcjs, $_), next unless ($name=~/^[A-Z]\w*$/i); $name = 'ASIN' if ($name eq 'ISBN'); if ($name eq 'ASIN') { push (@finalcjs, $_), next unless ($table eq 'BOOKS'); push (@finalcjs, $_), next if ($op eq '<>'); $request->{Power}{ASIN} = $right, next if ($op eq '='); push (@finalcjs, $_); } elsif ($can_power_search{$table} && $is_power_col{$name}) { push (@finalcjs, $_), next if ($op eq '<>'); $errstr = "Invalid range predicate for $name.", return undef if ($op =~/^[<>]/); if (($op eq 'LIKE') || ($op eq 'CLIKE')) { push (@finalcjs, $_), next if $neg; unless (defined($request->{Power}{$is_power_col{$name}[0]}) && ref $request->{Power}{$is_power_col{$name}[0]}) { $request->{Power}{$is_power_col{$name}[0]} = $right; } else { push @{$request->{Power}{$is_power_col{$name}[0]}}, $right; } push (@finalcjs, $_) if $is_power_col{$name}[1]; } elsif ($is_search_col{$name}) { $request->{$is_search_col{$name}[0]} = $right; push (@finalcjs, $_) if $is_search_col{$name}[2]; } else { push (@finalcjs, $_); } } elsif ($name=~/^AMZN_MATCH_(ANY|ALL|TEXT)$/) { if ($name eq 'AMZN_MATCH_ANY') { $errstr = "Invalid MATCHES predicate for $table table.", return undef unless ($table eq 'BOOKS'); push @{$request->{Power}{Keywords}}, $left->args(); } elsif ($name eq 'AMZN_MATCH_ALL') { push @{$request->{Keywords}}, $left->args(); } elsif ($name eq 'AMZN_MATCH_TEXT') { $request->{TextStream} = $left->args(); } } elsif ($name=~/^AMZN_POWER_SEARCH$/) { $errstr = "Invalid POWER_SEARCH predicate for $table table.", return undef unless ($table eq 'BOOKS'); push @{$request->{Power}{Explicit}}, $left->args(); } elsif ($name eq 'AUDIENCERATING') { $request->{AudienceRating}{Values} = $right; $request->{AudienceRating}{Operator} = (($op eq 'IN') && $neg) ? 'NOT IN' : $op; push (@finalcjs, $_); } elsif ($name eq 'LISTPRICEAMT') { $errstr = 'Invalid predicate: ListPriceAmt not valid with IN/LIKE/CLIKE.', return undef if (($op eq 'IN') || ($op eq 'LIKE') || ($op eq 'CLIKE')); push (@finalcjs, $_), next if (($op eq '=') || ($op eq '<>')); $request->{MaximumPrice} = $right if (($op eq '<') || ($op eq '<=')); $request->{MinimumPrice} = $right if (($op eq '>') || ($op eq '>=')); } elsif ($is_search_col{$name}) { $errstr = "Unknown column $name for table $table.", return undef unless (($is_search_col{$name}[1] eq '*') || (uc $is_search_col{$name}[1] eq $table)); push (@finalcjs, $_), next if (($op eq '<>') || ($op eq 'LIKE') || ($op eq 'CLIKE') || ($op eq 'IN')); $errstr = "Invalid range expression for $name.", return undef unless ($op eq '='); $request->{$is_search_col{$name}[0]} = $right; push (@finalcjs, $_) if $is_search_col{$name}[2]; } else { push (@finalcjs, $_); } } $expr->[0] = \@finalcjs; return $expr; } sub create_power_search { my ($obj, $stmt) = @_; $obj->{_errstr} = undef; my $power = $obj->{req_attrs}->{Power}; my $pwrstr = ''; my $val; my @pwrparms = (); foreach my $expl (@{$power->{Explicit}}) { if (ref $expl eq 'ARRAY') { foreach (@$expl) { $val = $stmt->get_row_value($_, undef, {}); push @pwrparms, $val if defined($val); } } else { $val = $stmt->get_row_value($expl, undef, {}); push @pwrparms, $val if defined($val); } } $pwrstr = '(' . join(') and (', @pwrparms) . ')' if scalar @pwrparms; @pwrparms = (); foreach my $keys (@{$power->{Keywords}}) { if (ref $keys eq 'ARRAY') { foreach (@$keys) { $val = $stmt->get_row_value($_, undef, {}); push @pwrparms, $val if defined($val); } } else { $val = $stmt->get_row_value($keys, undef, {}); push @pwrparms, $val if defined($val); } } $pwrstr .= (($pwrstr ne '') ? ' and (keywords: ("' : '(keywords: ("') . join('" or "', @pwrparms) . '"))' if scalar @pwrparms; @pwrparms = (); foreach (keys %$power) { next if (($_ eq 'Explicit') || ($_ eq 'Keywords')); if (ref $power->{$_} eq 'HASH') { $val = $stmt->get_row_value($power->{$_}, undef, {}); next unless defined($val); $val=~s/%%/\0/g; next if (substr($val,0, 1) eq '%'); $val=~s/^(.+?)%/$1\*/; $val=~s/\0/%/g; push @pwrparms, (lc $_) . ': "' . $val . '"'; } elsif (ref $power->{$_} eq 'ARRAY') { my @vals = (); foreach my $pwr (@{$power->{$_}}) { my $val = $stmt->get_row_value($pwr, undef, {}); next unless defined($val); $val=~s/%%/\0/g; next if (substr($val,0, 1) eq '%'); $val=~s/^(.+?)%/$1\*/; $val=~s/\0/%/g; push @vals, $val; } push @pwrparms, (lc $_) . ': ("'. join('" and "', @vals) . '")' if scalar @vals; } } $pwrstr .= (($pwrstr ne '') ? ' and (' : '(') . join(') and (', @pwrparms) . ')' if scalar @pwrparms; $obj->{url_params}{Power} = ($pwrstr eq '') ? undef : $pwrstr; return $obj; } sub populate_request { my $obj = shift; my ($subid, $locale, $stmt, $max_pages, $resp_group) = @_; my $val; my $req_attrs = $obj->{req_attrs}; my $url_params = $obj->{url_params}; $url_params->{ResponseGroup} = $resp_group; $obj->{_max_pages} = $max_pages; return undef if ($req_attrs->{Power} && (! $obj->create_power_search($stmt))); foreach (keys %$req_attrs) { next if ($_ eq 'Power'); if ($_ ne 'Keywords') { $url_params->{$_} = $stmt->get_row_value( (ref $req_attrs->{$_} eq 'ARRAY' ? $req_attrs->{$_}[0] : $req_attrs->{$_}), undef, {}), next; } my $keywords = ''; foreach my $key (@{$req_attrs->{$_}}) { $keywords .= $stmt->get_row_value($key, undef, {}), next unless (ref $key eq 'ARRAY'); foreach my $keyword (@$key) { my $val = $stmt->get_row_value($keyword, undef, {}); $keywords .= "$val " if defined($val); } } $url_params->{$_} = ($keywords eq '') ? undef : $keywords; } foreach ('MaximumPrice', 'MinimumPrice') { $url_params->{$_} = int($url_params->{$_} * 100) if defined($url_params->{$_}); } return $obj->SUPER::populate_request(@_); } 1;