CPAN::Search::Lite::Query - perform queries on the database
my $max_results = 200;
my $query = CPAN::Search::Lite::Query->new(db => $db,
user => $user,
passwd => $passwd,
max_results => $max_results);
$query->query(mode => 'module', name => 'Net::FTP');
my $results = $query->{results};
This module queries the database via various types of queries
and returns the results for subsequent display. The
CPAN::Search::Lite::Query object is created via the new method as
my $query = CPAN::Search::Lite::Query->new(db => $db,
user => $user,
passwd => $passwd,
max_results => $max_results);
which takes as arguments
CPAN::Search::Lite::Query will be used.
%langs of CPAN::Search::Lite::Util, the
default of en (English) will be used.
A basic query then is constructed as
$query->query(mode => $mode, $type => $value);
with the results available as
my $results = $query->{results}
There are four basic modes:
module, dist, and author modesFor a mode of module, dist, and author, there are
four basic options to be used for the $type => $value option:
module, dist, and author modes
respectively). The results generally are case insensitive.
Matches are reported that match all search terms supplied -
for example, $query_term = 'foo bar' will find occurences
of foo and bar. To exclude a term in $query_term,
prepend that term with a minus sign = $query_term = 'foo -bar'
will find all instances foo that don't include bar.
Regular expressions (as used by mysql) are also supported.
module, dist, and author modes
respectively.
$letter is a single letter, this will find all
modules, distributions, or CPAN author ids beginning
with that letter (for module, dist, and author modes
respectively). If $letter is more than one letter,
this will find all distribtion names matching
$letter-* (for the dist mode) or all module
names matching $letter::* (for the module mode).
As well, for the dist mode there is an additional type:
recent => $age, which will report all distribtions
uploaded in the last $age days. If $age is not
specified, it will default to 7.
chapter modeFor a mode of chapter, one can specify two additional
arguments:
$chapterid (see %chaps
of the CPAN::Search::Lite::Util manpage for a description).
$subchapter within the given chapter
specified by $chapterid.
After making the query, the results can be accessed through
my $results = $query->{results};
No results either can mean no matches were found, or
else an error in making the query resulted (in which case,
a brief error message is contained in $query->{error}).
Assuming there are results, what is returned depends on
the mode and on the type of query. See the CPAN::Search::Lite::Populate manpage
for a description of the fields in the various tables
listed below - these fields are used as the keys of the
hash references that arise.
author modename or id queryauth_id, cpanid, email, and fullname
of the auths table. As well, an array reference
$results->{dists} is returned representing
all distributions associated with that cpanid - each
member of the array reference is a hash reference
describing the dist_id, dist_name, birth,
dist_abs, dist_vers, and dist_file fields in the
dists table. An additional entry, download, is
supplied, which can be used as $CPAN/authors/id/$download
to specify the url of the distribution.
letter queryauth_id, cpanid,
and fullname fields.
query queryauth_id, cpanid, and fullname fields. If there
is only one result found, a name query based on the
matched cpanid is performed.
module modename or id querymod_id, mod_name, mod_abs, doc, mod_vers,
dslip, chapterid, dist_id, dist_name, dist_file,
auth_id, cpanid, and fullname
of the auths, mods, and dists tables.
As well, the following entries may be present.
htmldoc is true, an entry html is constructed giving the
location (relative to html_root) of the html file.
download$CPAN/authors/id/$download
to specify the url of the distribution.
chap_descchap_desc is supplied giving a
description of chapterid, if present. This is given in
the language specified, if present, with a default of English.
chap_linkchap_link is supplied giving a
string (in English) suitable for use in a link for
chapterid, if present.
dslip_infodslip is available, an array reference dslip_info is supplied,
each entry being a hash reference. The hash reference contains
two keys - desc, whose value is a general description of the
what the dslip entry represents, and what, whose value is
a description of the entry itself.
ppmsppms is supplied,
each item of which is a hash reference.
There are three keys in this hash reference (coming from
$repositories of the CPAN::Search::Lite::Util manpage) - rep_id,
giving the repository's rep_id, desc, giving a description
of the repository, and browse, giving a url to the
repository.
letter queryFOO::* at the top level, then the entry
is a hash reference with key letter and associated value FOO,
as well as a key count with value giving the number of matching
entries. If there is only one module matching FOO::* at the
top level, then the entry is
a hash reference containing the mod_name, mod_id, and
mod_abs fields.
query querymod_id, mod_name, and mod_abs fields. If there
is only one result found, a name query based on the
matched mod_name is performed.
dist modename or id querydist_id, dist_name, dist_abs, dist_vers,
dist_file, size, birth, readme, changes, meta,
install, auth_id, cpanid, and fullname
of the auths, mods, and dists tables. Note that
readme, changes, meta, and install are boolean values
just indicating if the corresponding file is present.
As well, the following entries may be present.
download$CPAN/authors/id/$download
to specify the url of the distribution.
modsmod_id, mod_name, mod_abs, mod_vers, doc, and dslip
fields for the module. If doc is present, an html entry
is created giving the location (relative to html_root) of
the documentation.
dslip and dslip_infos/::/-, the dslip and dslip_info entries for
that module are returned.
chapschaps is returned, each
entry of which is a hash reference containing chapterid,
subchapter, chap_desc (a description of the
chapter id, in the language specified), and chap_link
(a string in English suitable for use as a link to
chapterid).
reqsreqs is returned, each item of
which is a hash reference containing mod_id, req_vers,
mod_name, and mod_abs for each prerequisite.
ppmsppms is supplied,
each item of which is a hash reference.
There are three keys in this hash reference (coming from
$repositories of the CPAN::Search::Lite::Util manpage) - rep_id,
giving the repository's rep_id, desc, giving a description
of the repository, and browse, giving a url to the
repository.
letter queryFOO-* at the top level, then the entry
is a hash reference with key letter and associated value FOO,
as well as a key count with value giving the number of matching
entries. If there is only one distribution matching FOO-* at the
top level, then the entry is
a hash reference containing the dist_name, dist_id, and
dist_abs fields.
query querydist_id, dist_name, and dist_abs fields. If there
is only one result found, a name query based on the
matched dist_name is performed.
recent querybirth, dist_id, dist_name, dist_abs, dist_vers,
dist_file, auth_id, and cpanid fields.
As well, for each entry a download entry is present,
which can be used as $CPAN/authors/id/$download
to specify the url of the distribution.
chapter modesubchapter field. If there is only one entry within
a subchapter, the dist_abs and dist_id of the associated
distribution is also returned, while if there is more than one entry,
a key count with value giving the number of matching
entries is returned.
dist_name, dist_id, and dist_abs of the
distribution.
For a name or id query of dist, author, or
module, if the query is constructed as
$query->query(mode => $mode, $type => $value, fields => $fields);
where $fields is an array reference, then only those
fields specified will be returned. For author, only the
auths table is searched, for module, the mods,
auths, and dists tables are searched, and for
dist, the dists and auths tables are searched.
Apache::CPAN::Search and Apache::CPAN::Query.
This software is copyright 2004 by Randy Kobes <randy@theoryx5.uwinnipeg.ca>. Use and redistribution are under the same terms as Perl itself.
package CPAN::Search::Lite::Query; use strict; use warnings; use utf8; no warnings qw(redefine); use CPAN::Search::Lite::Util qw($repositories %chaps $full_id $mode_info); our $months = {}; our $chaps_desc = {}; our $pages = {}; our $dslip = {}; use CPAN::Search::Lite::Lang qw(load); use CPAN::Search::Lite::DBI::Query; use CPAN::Search::Lite::DBI qw($dbh); use Lingua::Stem qw(:stem); use constant GB => 1024 * 1024 * 1024; use constant MB => 1024 * 1024; use constant KB => 1024; our ($lang); our $max_results = 200; our $VERSION = 0.66; my $cdbi_query; my %mode2obj; $mode2obj{$_} = __PACKAGE__ . '::' . $_ for (qw(dist author module chapter)); sub new { my ($class, %args) = @_; foreach (qw(db user passwd)) { die "Please supply a '$_' argument" unless defined $args{$_}; } $cdbi_query = CPAN::Search::Lite::DBI::Query->new(%args); $max_results = $args{max_results} if $args{max_results}; $lang = 'en' unless $lang; my $self = {results => undef, error => ''}; bless $self, $class; } sub query { my ($self, %args) = @_; unless ($months->{$lang}) { my $rc = load(lang => $lang, dslip => $dslip, pages => $pages, months => $months, chaps_desc => $chaps_desc); unless ($rc == 1) { $self->{error} = $rc; return; } } my $mode = $args{mode} || 'module'; unless ($mode) { $self->{error} = q{Please specify a 'mode' argument}; return; } my $info = $mode_info->{$mode}; my $table = $info->{table}; unless ($table) { $self->{error} = qq{No table exists for '$mode'}; return; } my $cdbi = $cdbi_query->{objs}->{$table}; my $class = 'CPAN::Search::Lite::DBI::Query::' . $table; unless ($cdbi and ref($cdbi) eq $class) { $self->{error} = qq{No cdbi object exists for '$table'}; return; } my $fields = $args{fields}; if ($fields and ref($fields) ne 'ARRAY') { $self->{error} = q{Please supply an array reference for fields}; return; } my $obj; eval {$obj = $mode2obj{$mode}->make(table => $table, cdbi => $cdbi);}; if ($@) { $self->{error} = qq{Mode '$mode' is not known}; return; } my ($value, $method); METHOD: { ($mode eq 'dist' and exists $args{recent}) and do { $args{search} = {field => 'birth', value => $args{recent} || 7 }; $method = 'recent'; last METHOD; }; ($mode eq 'chapter') and do { $value = $args{id} or do { $self->{error} = q{Must supply a chapterid}; return; }; push @{$args{search}}, {field => $info->{id}, value => $value}; if (my $subvalue = $args{subchapter}) { push @{$args{search}}, {field => $info->{name}, value => $subvalue}; $method = 'search'; } else { $method = 'info'; } last METHOD; }; ($value = $args{query}) and do { $args{search} = {field => {name => $info->{name}, text => $info->{text} }, value => $value }; $method = 'search'; last METHOD; }; ($value = $args{letter}) and do { $args{search} = {field => $info->{name}, value => $value }; $method = 'letter'; last METHOD; }; ($value = $args{id}) and do { $args{search} = {field => $info->{id}, value => $value }; $method = 'info'; last METHOD; }; ($value = $args{name}) and do { $args{search} = {field => $info->{name}, value => $value }; $method = 'info'; last METHOD; }; $self->{error} = q{Cannot determine a method name}; return; } $obj->$method(search => $args{search}, user_fields => $fields); $self->{results} = $obj->{results}; if (my $error = $obj->{error}) { $self->{error} = $error; return; } return 1; } sub make { my ($class, %args) = @_; for (qw(table cdbi)) { die qq{Must supply an '$_' arg} unless defined $args{$_}; } my $self = {results => undef, error => '', table => $args{table}, cdbi => $args{cdbi}}; bless $self, $class; } package CPAN::Search::Lite::Query::author; use base qw(CPAN::Search::Lite::Query); use CPAN::Search::Lite::DBI qw($dbh); sub info { my ($self, %args) = @_; return unless $args{search}; $args{fields} = $args{user_fields} || [ qw(auth_id cpanid email fullname) ]; $args{table} = 'auths'; return unless ($self->{results} = $self->fetch(%args, distinct => 1)); return 1 if $args{user_fields}; $args{fields} = [qw(dist_id dist_name birth dist_abs dist_vers dist_file)]; $args{table} = 'dists'; $args{search} = {field => 'auth_id', value => $self->{results}->{auth_id}}; $args{order_by} = 'dist_name'; my $cpanid = $self->{results}->{cpanid}; if ($self->{results}->{dists} = $self->fetch(%args, wantarray => 1)) { foreach my $dist (@{$self->{results}->{dists}}) { $dist->{download} = $self->download($cpanid, $dist->{dist_file}); $dist->{birth} = $self->date_format($dist->{birth}); } } return 1; } sub search { my ($self, %args) = @_; return unless $args{search}; $args{fields} = [ qw(auth_id cpanid fullname) ]; $args{table} = 'auths'; $args{limit} = $max_results; $args{order_by} = 'cpanid'; return unless $self->{results} = $self->fetch(%args); if (ref($self->{results}) ne 'ARRAY') { return $self->query(mode => 'author', id => $self->{results}->{auth_id}); } return 1; } sub letter { my ($self, %args) = @_; return unless $args{search}; $args{fields} = [ qw(auth_id cpanid fullname) ]; $args{table} = 'auths'; $args{order_by} = 'cpanid'; return unless $self->{results} = $self->fetch(%args, letter => 1, wantarray => 1); return 1; } package CPAN::Search::Lite::Query::module; use base qw(CPAN::Search::Lite::Query); use CPAN::Search::Lite::Util qw(%chaps); use CPAN::Search::Lite::DBI qw($dbh); sub info { my ($self, %args) = @_; return unless $args{search}; $args{fields} = $args{user_fields} || [ qw(mod_id mod_name mod_abs doc src mod_vers dslip chapterid dist_id dist_name dist_file auth_id cpanid fullname) ]; $args{table} = 'dists'; $args{join} = { mods => 'dist_id', auths => 'auth_id', }; return unless ($self->{results} = $self->fetch(%args, distinct => 1, case_sensitive => 1)); return 1 if $args{user_fields}; my $mod_name = $self->{results}->{mod_name}; if ($self->{results}->{doc}) { (my $mod_link = $mod_name) =~ s{::}{/}g; my $html = $self->{results}->{dist_name} . '/' . $mod_link . '.html'; $self->{results}->{html} = $html; } if ($self->{results}->{src}) { (my $mod_link = $mod_name) =~ s{::}{/}g; my $html = $self->{results}->{dist_name} . '/' . $mod_link . '.pm.html'; $self->{results}->{htmlsrc} = $html; } $self->{results}->{download} = $self->download($self->{results}->{cpanid}, $self->{results}->{dist_file}); if (my $chapterid = $self->{results}->{chapterid}) { $self->{results}->{chap_link} = $self->chap_link($chapterid); $self->{results}->{chap_desc} = $self->chap_desc($chapterid); $self->{results}->{subchapter} = $self->mod_subchapter($mod_name); } if (my $what = $self->{results}->{dslip}) { $self->{results}->{dslip_info} = $self->expand_dslip($what); } $args{fields} = [ qw(rep_id ppm_vers browse abs)]; $args{table} = 'ppms'; $args{join} = {reps => 'rep_id'}; $args{search} = {field => 'dist_id', value => $self->{results}->{dist_id}}; $self->{results}->{ppms} = $self->fetch(%args, wantarray => 1); return 1; } sub search { my ($self, %args) = @_; return unless $args{search}; $args{fields} = [ qw(mod_id mod_name mod_abs chapterid) ]; $args{table} = 'mods'; $args{order_by} = 'mod_name'; $args{limit} = $max_results; return unless $self->{results} = $self->fetch(%args); my $results = $self->{results}; if (ref($results) ne 'ARRAY') { return $self->query(mode => 'module', id => $self->{results}->{mod_id}); } else { foreach my $result (@$results) { next unless my $id = $result->{chapterid}; next unless my $chap = $chaps{$id}; next unless my $mod_name = $result->{mod_name}; my $sub_chapter = $self->mod_subchapter($mod_name); $result->{chapter} = $chap . '/' . $sub_chapter; } } if (scalar @$results == 1) { return $self->query(mode => 'module', id => $self->{results}->[0]->{mod_id}); } return 1; } sub letter { my ($self, %args) = @_; return unless $args{search}; $args{fields} = [ qw(mod_id mod_name mod_abs) ]; $args{table} = 'mods'; $args{order_by} = 'mod_name'; my $match; return unless $match = $self->fetch(%args, letter => 1, wantarray => 1); $self->{results} = $match; my $mod_re = qr{^([^:]+)::}; if ($args{search}->{value} =~ /^\w$/) { my %count; foreach my $result (@$match) { if ($result->{mod_name} =~ /$mod_re/) { $count{$1}++; } } my %seen; my $results = []; foreach my $result (@$match) { if ($result->{mod_name} =~ /$mod_re/) { my $letter = $1; my $count = $count{$letter}; if ( $count == 1) { push @$results, $result; } else { next if $seen{$letter}; push @$results, {letter => $letter, count => $count}; $seen{$letter}++; } } else { push @$results, $result; } } $self->{results} = $results; } return 1; } package CPAN::Search::Lite::Query::dist; use base qw(CPAN::Search::Lite::Query); use CPAN::Search::Lite::Util qw(%chaps); use CPAN::Search::Lite::DBI qw($dbh); sub info { my ($self, %args) = @_; return unless $args{search}; $args{fields} = $args{user_fields} || [ qw(dist_id dist_name dist_abs dist_vers md5 dist_file size birth readme changes meta install auth_id cpanid fullname) ]; $args{table} = 'dists'; $args{join} = {auths => 'auth_id'}; return unless ($self->{results} = $self->fetch(%args, distinct => 1, case_sensitive => 1)); $self->{results}->{birth} = $self->date_format($self->{results}->{birth}); $self->{results}->{size} = $self->size_format($self->{results}->{size}); return 1 if $args{user_fields}; $self->{results}->{download} = $self->download($self->{results}->{cpanid}, $self->{results}->{dist_file}); $args{join} = {reps => 'rep_id'}; $args{table} = 'ppms'; $args{fields} = [ qw(rep_id ppm_vers browse abs) ]; $args{search} = {field => 'dist_id', value => $self->{results}->{dist_id} }; $self->{results}->{ppms} = $self->fetch(%args, wantarray => 1); $args{join} = undef; $args{table} = 'mods'; $args{fields} = [ qw(mod_id mod_name mod_abs mod_vers doc dslip src) ]; $args{order_by} = 'mod_name'; $self->{results}->{mods} = $self->fetch(%args, wantarray => 1); my $mod_dslip; my $distname = $self->{results}->{dist_name}; if (my $mods = $self->{results}->{mods}) { foreach my $mod (@$mods) { my $mod_name = $mod->{mod_name}; (my $trial_dist = $mod_name) =~ s!::!-!g; $mod_dslip = $mod->{dslip} if ($trial_dist eq $distname and $mod->{dslip}); if ($mod->{doc}) { (my $docpath = $mod_name) =~ s!::!/!g; $mod->{html} = $distname . '/' . $docpath . '.html'; } if ($mod->{src}) { (my $mod_link = $mod_name) =~ s{::}{/}g; my $html = $self->{results}->{dist_name} . '/' . $mod_link . '.pm.html'; $mod->{htmlsrc} = $html; } } } if ($mod_dslip) { $self->{results}->{dslip} = $mod_dslip; $self->{results}->{dslip_info} = $self->expand_dslip($mod_dslip); } $args{table} = 'chaps'; $args{fields} = [ qw(chaps.chapterid subchapter chap_link) ]; $args{join} = {chapters => 'chapterid'}; $args{order_by} = 'chapterid'; if ($self->{results}->{chaps} = $self->fetch(%args, wantarray => 1)) { foreach my $chap (@{$self->{results}->{chaps}}) { my $chapterid = $chap->{'chaps.chapterid'} + 0; next unless $chapterid; $chap->{chap_desc} = $self->chap_desc($chapterid); } } $args{search} = {field => 'reqs.dist_id', value => $self->{results}->{dist_id}, }; $args{table} = 'reqs'; $args{join} = {mods => 'mod_id'}; $args{fields} = [ qw(mod_id req_vers mod_name mod_abs) ]; $args{order_by} = 'mod_name'; $self->{results}->{reqs} = $self->fetch(%args, wantarray => 1); return 1; } sub search { my ($self, %args) = @_; return unless $args{search}; $args{fields} = [ qw(dist_id dist_name dist_abs chapterid subchapter) ]; $args{table} = 'dists'; $args{order_by} = 'dist_name'; $args{limit} = $max_results; $args{left_join} = {chaps => 'dist_id'}; return unless $self->{results} = $self->fetch(%args); my $results = $self->{results}; if (ref($results) ne 'ARRAY') { return $self->query(mode => 'dist', id => $self->{results}->{dist_id}); } else { foreach my $result (@$results) { next unless my $id = $result->{chapterid}; next unless my $subchapter = $result->{subchapter}; next unless my $chap = $chaps{$id}; $result->{chapter} = $chap . '/' . $subchapter; } } my ($tmp, $chapters); foreach my $result (@$results) { my $dist_name = $result->{dist_name}; $tmp->{$dist_name} = {dist_id => $result->{dist_id}, dist_abs => $result->{dist_abs}}; next unless $result->{chapter}; push @{$chapters->{$dist_name}}, $result->{chapter}; } my $pruned; foreach my $dist_name(sort keys %$tmp) { push @$pruned, {dist_name => $dist_name, %{$tmp->{$dist_name}}, chapters => $chapters->{$dist_name}, }; } if (scalar @$pruned == 1) { return $self->query(mode => 'dist', id => $pruned->[0]->{dist_id}); } $self->{results} = $pruned; return 1; } sub letter { my ($self, %args) = @_; return unless $args{search}; $args{fields} = [ qw(dist_id dist_name dist_abs) ]; $args{table} = 'dists'; $args{order_by} = 'dist_name'; my $match; return unless $match = $self->fetch(%args, letter => 1, wantarray => 1); $self->{results} = $match; my $dist_re = qr{^([^-]+)-}; if ($args{search}->{value} =~ /^\w$/) { my %count; foreach my $result(@$match) { if ($result->{dist_name} =~ /$dist_re/) { $count{$1}++; } } my %seen; my $results = []; foreach my $result (@$match) { if ($result->{dist_name} =~ /$dist_re/) { my $letter = $1; my $count = $count{$letter}; if ( $count == 1) { push @$results, $result; } else { next if $seen{$letter}; push @$results, {letter => $letter, count => $count}; $seen{$letter}++; } } else { push @$results, $result; } } $self->{results} = $results; } return 1; } sub recent { my ($self, %args) = @_; return unless $args{search}; $args{fields} = [ qw(birth dist_id dist_name dist_abs dist_vers dist_file auth_id cpanid) ]; $args{table} = 'dists'; $args{join} = {auths => 'auth_id'}; $args{order_by} = 'birth desc,dist_name'; my $results; return unless $results = $self->fetch(%args, wantarray => 1, age => 1); foreach my $result(@$results) { $result->{download} = $self->download($result->{cpanid}, $result->{dist_file}); $result->{birth} = $self->date_format($result->{birth}); } $self->{results} = $results; return 1; } package CPAN::Search::Lite::Query::chapter; use base qw(CPAN::Search::Lite::Query); use CPAN::Search::Lite::DBI qw($dbh); sub info { my ($self, %args) = @_; return unless $args{search}; $args{fields} = [ qw(dist_id dist_abs subchapter) ]; $args{table} = 'chaps'; $args{order_by} = 'subchapter'; $args{join} = {dists => 'dist_id'}; my $match; return unless $match = $self->fetch(%args, wantarray => 1, distinct => 1); my %count; $count{$_->{subchapter}}++ for @$match; my $results = []; my %seen; foreach my $result (@$match) { my $subchapter = $result->{subchapter}; next if $seen{$subchapter}; my $count = $count{$subchapter}; if ($count > 1) { push @$results, {subchapter => $subchapter, count => $count}; $seen{$subchapter}++; } else { push @$results, $result; } } $self->{results} = $results; return 1; } sub search { my ($self, %args) = @_; return unless $args{search}; $args{fields} = [ qw(dist_id dist_name dist_abs) ]; $args{table} = 'chaps'; $args{join} = {dists => 'dist_id'}; $args{order_by} = 'dist_name'; return unless $self->{results} = $self->fetch(%args, wantarray => 1); return 1; } package CPAN::Search::Lite::Query; sub fetch { my ($self, %args) = @_; my $fields = $args{fields}; my @fields = ref($fields) eq 'ARRAY' ? @{$fields} : ($fields); my $sql = $self->sql_statement(%args) or do { $self->{error} = 'Error constructing sql statement: ' . $self->{error}; return; }; my $sth = $dbh->prepare($sql) or do { $self->db_error(); return; }; $sth->execute(); if ($sth->rows == 0) { $sth->finish; return; } if ($sth->rows == 1 and not $args{wantarray}) { my %results; @results{@fields} = $sth->fetchrow_array; $sth->finish; return \%results; } else { my (%hash, $results); while ( @hash{@fields} = $sth->fetchrow_array) { my %tmp = %hash; push @{$results}, \%tmp; } $sth->finish; return (defined $args{distinct} and not defined $args{wantarray}) ? $results->[0] : $results; } } sub sql_statement { my ($self, %args) = @_; my $search = $args{search}; my $chap = (ref($search) eq 'ARRAY'); my $distinct = $args{distinct} ? 'DISTINCT' : ''; my $binary = $args{case_sensitive} ? 'BINARY' : ''; my $text_search = (not $chap and ref($search->{field}) eq 'HASH'); my $regex = (not $distinct and not $chap and not ref($search->{value}) and $search->{value} =~ /\^|\$|\*|\+|\?|\||::|\b-\b/); if ($regex) { my $v = $search->{value}; eval{$v =~ /$v/}; if ($@) { $self->{error} = $@; return; } } my $letter = $args{letter}; my $age = $args{age}; my $not = ($regex or $letter or $chap or $age); my ($match, @words); if ($text_search and not $not) { @words = split ' ', $search->{value}; my %excl = map {$_ => 1} grep /^-/, @words; my $stems = stem(@words); my @stems = @$stems; for (0 .. $#stems) { $stems[$_] = "-$stems[$_]" if $excl{$words[$_]}; } my $join = join ' ', map { /^-/ ? "$_*" : "+$_*" } @stems; $match = q/ MATCH (/ . $search->{field}->{text} . q/) AGAINST ('/ . $join . q/' IN BOOLEAN MODE )/; } my $table = $args{table}; my @tables = ($table); my $fields = $args{fields}; my @fields = ref($fields) eq 'ARRAY' ? @{$fields} : ($fields); for (@fields) { $_ = $full_id->{$_} if $full_id->{$_}; # $_ = $table . '.chapterid' if $_ eq 'chapterid'; # $_ = qq{DATE_FORMAT($_, '%e %b %Y')} if $_ eq 'birth'; # $_ = qq{FORMAT($_, 0)} if $_ eq 'size'; } push @fields, "$match as score" if defined $match; my $sql = qq{SELECT $distinct } . join(',', @fields); my $where; QUERY: { $chap and do { $where = qq{ $search->[0]->{field} = $search->[0]->{value} }; if (defined $search->[1]) { $where .= ' AND ' . qq{ $search->[1]->{field} = '$search->[1]->{value}' }; } last QUERY; }; $letter and do { my $value = $search->{value}; my $star = ($value =~ /^\w$/) ? '%' : ($table eq 'mods' ? '::%' : '-%'); $where = qq{ $search->{field} LIKE '$value$star' }; last QUERY; }; $age and do { $where = qq{ TO_DAYS(NOW()) - TO_DAYS($search->{field}) <= $search->{value} }; last QUERY; }; $regex and do { $where = qq{ $search->{field}->{name} REGEXP '$search->{value}' }; last QUERY; }; $text_search and do { my $name = $search->{field}->{name}; $where = join ' AND ', map {" $name ". (s/^-// ? 'NOT ' : '') . "LIKE '%$_%' "} @words; last QUERY; }; $full_id->{$search->{field}} and do { $where = qq{ $search->{field} = $search->{value} }; last QUERY; }; $where = qq{ $binary $search->{field} = '$search->{value}' }; } my $join; # if (defined $args{join}) { # my @join = (); # while (my ($join, $id) = each %{$args{join}}) { # push @tables, $join; # push @join, ($table.'.'.$id. ' = ' . $join.'.'.$id); # } # $join = join ' AND ', @join; # } # $sql .= ' FROM ' . join ',', @tables; $sql .= ' FROM ' . $table; my $left_join = $args{join} || $args{left_join}; if ($left_join) { if (ref($left_join) eq 'HASH') { foreach my $key(keys %$left_join) { # $sql .= " LEFT JOIN $key using ($left_join->{$key}) "; my $id = $left_join->{$key}; $sql .= " LEFT JOIN $key ON $table.$id=$key.$id "; } } } if ($text_search and not $not) { $sql .= ' WHERE ( ( ' . $where . ' ) OR ( ' . $match . ' ) )'; } else { $sql .= ' WHERE ( ' . $where . ' )'; } $sql .= ' AND (' . $join . ')' if $join; my $order_by = ''; if ($text_search and not $not) { $order_by = 'score'; } if (my $user_order_by = $args{order_by}) { $order_by = $order_by ? "$order_by,$user_order_by" : $user_order_by; } if ($order_by) { $sql .= qq{ ORDER BY $order_by }; } if (my $limit = $args{limit}) { my ($min, $max) = ref($limit) eq 'HASH' ? ( $limit->{min} || 0, $limit->{max} ) : (0, $limit ); $sql .= qq{ LIMIT $min,$max }; } return $sql; } sub expand_dslip { my ($self, $string) = @_; my $entries = []; my @info = split '', $string; my @given = qw(d s l i p); for (0 .. 4) { my $entry; my $given = $given[$_]; my $info = $info[$_]; $entry->{desc} = $dslip->{$lang}->{$given}->{desc}; $entry->{what} = (not $info or $info eq '?') ? $pages->{$lang}->{na} : $dslip->{$lang}->{$given}->{$info}; push @$entries, $entry; } return $entries; } sub download { my ($self, $cpanid, $dist_file) = @_; (my $fullid = $cpanid) =~ s!^(\w)(\w)(.*)!$1/$1$2/$1$2$3!; my $download = $fullid . '/' . $dist_file; return $download; } sub chap_link { my ($self, $id) = @_; return $chaps{$id}; } sub chap_desc { my ($self, $id) = @_; return $chaps_desc->{$lang}->{$id}; } sub mod_subchapter { my ($self, $mod_name) = @_; (my $sc = $mod_name) =~ s{^([^:]+).*}{$1}; return $sc; } sub dist_subchapter { my ($self, $dist_name) = @_; (my $sc = $dist_name) =~ s{^([^-]+).*}{$1}; return $sc; } sub db_error { my ($obj, $sth) = @_; return unless $dbh; $sth->finish if $sth; $obj->{error} = q{Database error: } . $dbh->errstr; } sub date_format { my ($self, $date) = @_; my @e = split /-/, $date; return sprintf("%d %s %d", $e[2], $months->{$lang}->{$e[1]}, $e[0]); } sub size_format { my ($self, $size) = @_; my ($test, $string); SWITCH: { ( ($test = $size / GB) && int($test) > 0) and do { $string = sprintf('%.1f GB', $test); last SWITCH; }; ( ($test = $size / MB) && int($test) > 0) and do { $string = sprintf('%.1f MB', $test); last SWITCH; }; ( ($test = $size / KB) && int($test) > 0) and do { $string = sprintf('%.1f KB', $test); last SWITCH; }; $string = sprintf("%d $pages->{$lang}->{bytes}", $size); } $string =~ s{\.}{,} unless ($lang eq 'en'); return $string; } 1; __END__