@@ -, +, @@ % prove t/db_dependent/Search.t --- C4/AuthoritiesMarc.pm | 34 +- C4/Context.pm | 47 - C4/Matcher.pm | 28 +- C4/Search.pm | 174 +- C4/UsageStats.pm | 1 - Koha/QueryParser/Driver/PQF.pm | 925 -------- Koha/QueryParser/Driver/PQF/Util.pm | 54 - Koha/QueryParser/Driver/PQF/query_plan.pm | 70 - Koha/QueryParser/Driver/PQF/query_plan/facet.pm | 46 - Koha/QueryParser/Driver/PQF/query_plan/filter.pm | 51 - Koha/QueryParser/Driver/PQF/query_plan/modifier.pm | 50 - Koha/QueryParser/Driver/PQF/query_plan/node.pm | 106 - .../QueryParser/Driver/PQF/query_plan/node/atom.pm | 49 - OpenILS/QueryParser.pm | 2242 -------------------- about.pl | 29 - cataloguing/addbooks.pl | 11 +- .../value_builder/marc21_linking_section.pl | 9 +- cataloguing/value_builder/unimarc_field_4XX.pl | 9 +- debian/templates/koha-conf-site.xml.in | 1 - etc/koha-conf.xml | 1 - etc/searchengine/queryparser.yaml | 1647 -------------- installer/data/mysql/atomicupdate/bug_xxxxx.perl | 9 + installer/data/mysql/sysprefs.sql | 1 - koha-tmpl/intranet-tmpl/prog/en/modules/about.tt | 23 +- .../en/modules/admin/preferences/cataloguing.pref | 4 +- .../en/modules/admin/preferences/searching.pref | 9 +- labels/label-item-search.pl | 21 +- misc/migration_tools/bulkmarcimport.pl | 18 +- serials/subscription-bib-search.pl | 2 +- t/QueryParser.t | 136 -- t/db_dependent/QueryParser.t | 70 - t/db_dependent/Search.t | 110 +- t/db_dependent/UsageStats.t | 1 - t/db_dependent/default_search_class.pl | 37 - t/db_dependent/zebra_config.pl | 1 - 35 files changed, 44 insertions(+), 5982 deletions(-) delete mode 100644 Koha/QueryParser/Driver/PQF.pm delete mode 100644 Koha/QueryParser/Driver/PQF/Util.pm delete mode 100644 Koha/QueryParser/Driver/PQF/query_plan.pm delete mode 100644 Koha/QueryParser/Driver/PQF/query_plan/facet.pm delete mode 100644 Koha/QueryParser/Driver/PQF/query_plan/filter.pm delete mode 100644 Koha/QueryParser/Driver/PQF/query_plan/modifier.pm delete mode 100644 Koha/QueryParser/Driver/PQF/query_plan/node.pm delete mode 100644 Koha/QueryParser/Driver/PQF/query_plan/node/atom.pm delete mode 100644 OpenILS/QueryParser.pm delete mode 100644 etc/searchengine/queryparser.yaml create mode 100644 installer/data/mysql/atomicupdate/bug_xxxxx.perl delete mode 100644 t/QueryParser.t delete mode 100644 t/db_dependent/QueryParser.t delete mode 100755 t/db_dependent/default_search_class.pl --- a/C4/AuthoritiesMarc.pm +++ a/C4/AuthoritiesMarc.pm @@ -113,8 +113,6 @@ sub SearchAuthorities { $sortby="" unless $sortby; my $query; my $qpquery = ''; - my $QParser; - $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser')); my $attr = ''; # the marclist may contain "mainentry". In this case, search the tag_to_report, that depends on # the authtypecode. Then, search on $a of this tag_to_report @@ -133,9 +131,6 @@ sub SearchAuthorities { if ($n>1){ while ($n>1){$query= "\@or ".$query;$n--;} } - if ($QParser) { - $qpquery .= '(authtype:' . join('|| authtype:', @auths) . ')'; - } } my $dosearch; @@ -203,9 +198,6 @@ sub SearchAuthorities { $q2 .= $attr; $dosearch = 1; ++$attr_cnt; - if ($QParser) { - $qpquery .= " $tags->[$i]:\"$value->[$i]\""; - } } #if value } ##Add how many queries generated @@ -226,21 +218,8 @@ sub SearchAuthorities { } elsif ($sortby eq 'AuthidDsc') { $orderstring = '@attr 7=2 @attr 4=109 @attr 1=Local-Number 0'; } - if ($QParser) { - $qpquery .= ' all:all' unless $value->[0]; - - if ( $value->[0] =~ m/^qp=(.*)$/ ) { - $qpquery = $1; - } - - $qpquery .= " #$sortby" unless $sortby eq ''; - - $QParser->parse( $qpquery ); - $query = $QParser->target_syntax('authorityserver'); - } else { - $query=($query?$query:"\@attr 1=_ALLRECORDS \@attr 2=103 ''"); - $query="\@or $orderstring $query" if $orderstring; - } + $query=($query?$query:"\@attr 1=_ALLRECORDS \@attr 2=103 ''"); + $query="\@or $orderstring $query" if $orderstring; $offset = 0 if not defined $offset or $offset < 0; my $counter = $offset; @@ -758,14 +737,7 @@ sub FindDuplicateAuthority { my $auth_tag_to_report = Koha::Authority::Types->find($authtypecode)->auth_tag_to_report; # warn "record :".$record->as_formatted." auth_tag_to_report :$auth_tag_to_report"; # build a request for SearchAuthorities - my $QParser; - $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser')); - my $op; - if ($QParser) { - $op = '&&'; - } else { - $op = 'AND'; - } + my $op = 'AND'; $authtypecode =~ s#/#\\/#; # GENRE/FORM contains forward slash which is a reserved character my $query='at:'.$authtypecode.' '; my $filtervalues=qr([\001-\040\Q!'"`#$%&*+,-./:;<=>?@(){[}_|~\E\]]); --- a/C4/Context.pm +++ a/C4/Context.pm @@ -783,53 +783,6 @@ sub restore_dbh # return something, then this function should, too. } -=head2 queryparser - - $queryparser = C4::Context->queryparser - -Returns a handle to an initialized Koha::QueryParser::Driver::PQF object. - -=cut - -sub queryparser { - my $self = shift; - unless (defined $context->{"queryparser"}) { - $context->{"queryparser"} = &_new_queryparser(); - } - - return - defined( $context->{"queryparser"} ) - ? $context->{"queryparser"}->new - : undef; -} - -=head2 _new_queryparser - -Internal helper function to create a new QueryParser object. QueryParser -is loaded dynamically so as to keep the lack of the QueryParser library from -getting in anyone's way. - -=cut - -sub _new_queryparser { - my $qpmodules = { - 'OpenILS::QueryParser' => undef, - 'Koha::QueryParser::Driver::PQF' => undef - }; - if ( can_load( 'modules' => $qpmodules ) ) { - my $QParser = Koha::QueryParser::Driver::PQF->new(); - my $config_file = $context->config('queryparser_config'); - $config_file ||= '/etc/koha/searchengine/queryparser.yaml'; - if ( $QParser->load_config($config_file) ) { - # Set 'keyword' as the default search class - $QParser->default_search_class('keyword'); - # TODO: allow indexes to be configured in the database - return $QParser; - } - } - return; -} - =head2 userenv C4::Context->userenv; --- a/C4/Matcher.pm +++ a/C4/Matcher.pm @@ -622,26 +622,18 @@ sub get_matches { my $matches = {}; - my $QParser; - $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser')); foreach my $matchpoint ( @{ $self->{'matchpoints'} } ) { my @source_keys = _get_match_keys( $source_record, $matchpoint ); next if scalar(@source_keys) == 0; - # FIXME - because of a bug in QueryParser, an expression ofthe - # format 'isbn:"isbn1" || isbn:"isbn2" || isbn"isbn3"...' - # does not get parsed correctly, so we will not - # do AggressiveMatchOnISBN if UseQueryParser is on @source_keys = C4::Koha::GetVariationsOfISBNs(@source_keys) if ( $matchpoint->{index} =~ /^isbn$/i - && C4::Context->preference('AggressiveMatchOnISBN') ) - && !C4::Context->preference('UseQueryParser'); + && C4::Context->preference('AggressiveMatchOnISBN') ); @source_keys = C4::Koha::GetVariationsOfISSNs(@source_keys) if ( $matchpoint->{index} =~ /^issn$/i - && C4::Context->preference('AggressiveMatchOnISSN') ) - && !C4::Context->preference('UseQueryParser'); + && C4::Context->preference('AggressiveMatchOnISSN') ); # build query my $query; @@ -650,18 +642,10 @@ sub get_matches { my $total_hits; if ( $self->{'record_type'} eq 'biblio' ) { - #NOTE: The QueryParser can't handle the CCL syntax of 'qualifier','qualifier', so fallback to non-QueryParser. - #NOTE: You can see this in C4::Search::SimpleSearch() as well in a different way. - if ($QParser && $matchpoint->{'index'} !~ m/\w,\w/) { - $query = join( " || ", - map { "$matchpoint->{'index'}:$_" } @source_keys ); - } - else { - my $phr = ( C4::Context->preference('AggressiveMatchOnISBN') || C4::Context->preference('AggressiveMatchOnISSN') ) ? ',phr' : q{}; - $query = join( " OR ", - map { "$matchpoint->{'index'}$phr=\"$_\"" } @source_keys ); - #NOTE: double-quote the values so you don't get a "Embedded truncation not supported" error when a term has a ? in it. - } + my $phr = ( C4::Context->preference('AggressiveMatchOnISBN') || C4::Context->preference('AggressiveMatchOnISSN') ) ? ',phr' : q{}; + $query = join( " OR ", + map { "$matchpoint->{'index'}$phr=\"$_\"" } @source_keys ); + #NOTE: double-quote the values so you don't get a "Embedded truncation not supported" error when a term has a ? in it. # Use state variables to avoid recreating the objects every time. # With Elasticsearch this also avoids creating a massive amount of --- a/C4/Search.pm +++ a/C4/Search.pm @@ -99,22 +99,10 @@ sub FindDuplicate { $query = "isbn:$result->{isbn}"; } else { - my $QParser; - $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser')); - my $titleindex; - my $authorindex; - my $op; - - if ($QParser) { - $titleindex = 'title|exact'; - $authorindex = 'author|exact'; - $op = '&&'; - $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate'); - } else { - $titleindex = 'ti,ext'; - $authorindex = 'au,ext'; - $op = 'and'; - } + + my $titleindex = 'ti,ext'; + my $authorindex = 'au,ext'; + my $op = 'and'; $result->{title} =~ s /\\//g; $result->{title} =~ s /\"//g; @@ -233,25 +221,12 @@ sub SimpleSearch { my $results = []; my $total_hits = 0; - my $QParser; - $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') && ! ($query =~ m/\w,\w|\w=\w/)); - if ($QParser) { - $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate'); - } - # Initialize & Search Zebra for ( my $i = 0 ; $i < @servers ; $i++ ) { eval { $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 ); - if ($QParser) { - $query =~ s/=/:/g unless $options{skip_normalize}; - $QParser->parse( $query ); - $query = $QParser->target_syntax($servers[$i]); - $zoom_queries[$i] = new ZOOM::Query::PQF( $query, $zconns[$i]); - } else { - $query =~ s/:/=/g unless $options{skip_normalize}; - $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]); - } + $query =~ s/:/=/g unless $options{skip_normalize}; + $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]); $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] ); # error handling @@ -1277,134 +1252,6 @@ sub getIndexes{ return \@indexes; } -=head2 _handle_exploding_index - - my $query = _handle_exploding_index($index, $term) - -Callback routine to generate the search for "exploding" indexes (i.e. -those indexes which are turned into multiple or-connected searches based -on authority data). - -=cut - -sub _handle_exploding_index { - my ($QParser, $filter, $params, $negate, $server) = @_; - my $index = $filter; - my $term = join(' ', @$params); - - return unless ($index =~ m/(su-br|su-na|su-rl)/ && $term); - - my $marcflavour = C4::Context->preference('marcflavour'); - - my $codesubfield = $marcflavour eq 'UNIMARC' ? '5' : 'w'; - my $wantedcodes = ''; - my @subqueries = ( "\@attr 1=Subject \@attr 4=1 \"$term\""); - my ($error, $results, $total_hits) = SimpleSearch( "he:$term", undef, undef, [ "authorityserver" ] ); - foreach my $auth (@$results) { - my $record = MARC::Record->new_from_usmarc($auth); - my @references = $record->field('5..'); - if (@references) { - if ($index eq 'su-br') { - $wantedcodes = 'g'; - } elsif ($index eq 'su-na') { - $wantedcodes = 'h'; - } elsif ($index eq 'su-rl') { - $wantedcodes = ''; - } - foreach my $reference (@references) { - my $codes = $reference->subfield($codesubfield); - push @subqueries, '@attr 1=Subject @attr 4=1 "' . $reference->as_string('abcdefghijlmnopqrstuvxyz') . '"' if (($codes && $codes eq $wantedcodes) || !$wantedcodes); - } - } - } - my $query = ' @or ' x (scalar(@subqueries) - 1) . join(' ', @subqueries); - return $query; -} - -=head2 parseQuery - - ( $operators, $operands, $indexes, $limits, - $sort_by, $scan, $lang ) = - parseQuery ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang); - -Shim function to ease the transition from buildQuery to a new QueryParser. -This function is called at the beginning of buildQuery, and modifies -buildQuery's input. If it can handle the input, it returns a query that -buildQuery will not try to parse. - -=cut - -sub parseQuery { - my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_; - - my @operators = $operators ? @$operators : (); - my @indexes = $indexes ? @$indexes : (); - my @operands = $operands ? @$operands : (); - my @limits = $limits ? @$limits : (); - my @sort_by = $sort_by ? @$sort_by : (); - - my $query = $operands[0]; - my $index; - my $term; - my $query_desc; - - my $QParser; - $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') || $query =~ s/^qp=//); - undef $QParser if ($query =~ m/^(ccl=|pqf=|cql=)/ || grep (/\w,\w|\w=\w/, @operands, @indexes) ); - undef $QParser if (scalar @limits > 0); - - if ($QParser) - { - $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate'); - $query = ''; - for ( my $ii = 0 ; $ii <= @operands ; $ii++ ) { - next unless $operands[$ii]; - $query .= $operators[$ii-1] && $operators[ $ii - 1 ] eq 'or' ? ' || ' : ' && ' - if ($query); - if ( $operands[$ii] =~ /^[^"]\W*[-|_\w]*:\w.*[^"]$/ ) { - $query .= $operands[$ii]; - } - elsif ( $indexes[$ii] && $indexes[$ii] =~ m/su-/ ) { - $query .= $indexes[$ii] . '(' . $operands[$ii] . ')'; - } - else { - $query .= - ( $indexes[$ii] ? "$indexes[$ii]:" : '' ) . $operands[$ii]; - } - } - foreach my $limit (@limits) { - } - if ( scalar(@sort_by) > 0 ) { - my $modifier_re = - '#(' . join( '|', @{ $QParser->modifiers } ) . ')'; - $query =~ s/$modifier_re//g; - foreach my $modifier (@sort_by) { - $query .= " #$modifier"; - } - } - - $query_desc = $query; - $query_desc =~ s/\s+/ /g; - if ( C4::Context->preference("QueryWeightFields") ) { - } - $QParser->add_bib1_filter_map( 'su-br' => 'biblioserver' => - { 'target_syntax_callback' => \&_handle_exploding_index } ); - $QParser->add_bib1_filter_map( 'su-na' => 'biblioserver' => - { 'target_syntax_callback' => \&_handle_exploding_index } ); - $QParser->add_bib1_filter_map( 'su-rl' => 'biblioserver' => - { 'target_syntax_callback' => \&_handle_exploding_index } ); - $QParser->parse($query); - $operands[0] = "pqf=" . $QParser->target_syntax('biblioserver'); - } - else { - require Koha::QueryParser::Driver::PQF; - my $modifier_re = '#(' . join( '|', @{Koha::QueryParser::Driver::PQF->modifiers}) . ')'; - s/$modifier_re//g for @operands; - } - - return ( $operators, \@operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc); -} - =head2 buildQuery ( $error, $query, @@ -1427,7 +1274,6 @@ sub buildQuery { warn "---------\nEnter buildQuery\n---------" if $DEBUG; my $query_desc; - ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc) = parseQuery($operators, $operands, $indexes, $limits, $sort_by, $scan, $lang); # dereference my @operators = $operators ? @$operators : (); @@ -1487,12 +1333,8 @@ sub buildQuery { return ( undef, $', $', "q=cql=".uri_escape_utf8($'), $', '', '', '', 'cql' ); } if ( $query =~ /^pqf=/ ) { - if ($query_desc) { - $query_cgi = "q=".uri_escape_utf8($query_desc); - } else { - $query_desc = $'; - $query_cgi = "q=pqf=".uri_escape_utf8($'); - } + $query_desc = $'; + $query_cgi = "q=pqf=".uri_escape_utf8($'); return ( undef, $', $', $query_cgi, $query_desc, '', '', '', 'pqf' ); } --- a/C4/UsageStats.pm +++ a/C4/UsageStats.pm @@ -304,7 +304,6 @@ sub BuildReport { TraceCompleteSubfields TraceSubjectSubdivisions UseICU - UseQueryParser defaultSortField displayFacetCount OPACdefaultSortField --- a/Koha/QueryParser/Driver/PQF.pm +++ a/Koha/QueryParser/Driver/PQF.pm @@ -1,925 +0,0 @@ -package Koha::QueryParser::Driver::PQF; - -# This file is part of Koha. -# -# Copyright 2012 C & P Bibliography Services -# -# Koha is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Koha 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Koha; if not, see . - -use base qw(OpenILS::QueryParser Class::Accessor); - -use strict; -use warnings; - -use Module::Load::Conditional qw(can_load); -use Koha::QueryParser::Driver::PQF::Util; -use Koha::QueryParser::Driver::PQF::query_plan; -use Koha::QueryParser::Driver::PQF::query_plan::facet; -use Koha::QueryParser::Driver::PQF::query_plan::filter; -use Koha::QueryParser::Driver::PQF::query_plan::modifier; -use Koha::QueryParser::Driver::PQF::query_plan::node; -use Koha::QueryParser::Driver::PQF::query_plan::node::atom; -use Koha::QueryParser::Driver::PQF::query_plan::node::atom; - - -=head1 NAME - -Koha::QueryParser::Driver::PQF - QueryParser driver for PQF - -=head1 SYNOPSIS - - use Koha::QueryParser::Driver::PQF; - my $QParser = Koha::QueryParser::Driver::PQF->new(%args); - -=head1 DESCRIPTION - -Main entrypoint into the QueryParser PQF driver. PQF is the Prefix Query -Language, the syntax used to serialize Z39.50 queries. - -=head1 ACCESSORS - -In order to simplify Bib-1 attribute mapping, this driver uses Class::Accessor -for accessing the following maps: - -=over 4 - -=item B - search class/field Bib-1 mappings - -=item B - search modifier mappings - -=item B - search filter mappings - -=item B - relevance bump mappings - -=back - -=cut - -__PACKAGE__->mk_accessors(qw(bib1_field_map bib1_modifier_map bib1_filter_map bib1_relevance_bump_map)); - -=head1 FUNCTIONS - -=cut - -=head2 get - -Overridden accessor method for Class::Accessor. (Do not call directly) - -=cut - -sub get { - my $self = shift; - return $self->_map(@_); -} - -=head2 set - -Overridden mutator method for Class::Accessor. (Do not call directly) - -=cut - -sub set { - my $self = shift; - return $self->_map(@_); -} - -=head2 add_bib1_field_map - - $QParser->add_bib1_field_map($class => $field => $server => \%attributes); - - $QParser->add_bib1_field_map('author' => 'personal' => 'biblioserver' => - { '1' => '1003' }); - -Adds a search field<->bib1 attribute mapping for the specified server. The -%attributes hash contains maps Bib-1 Attributes to the appropropriate -values. Not all attributes must be specified. - -=cut - -sub add_bib1_field_map { - my ($self, $class, $field, $server, $attributes) = @_; - - $self->add_search_field( $class => $field ); - return $self->_add_field_mapping($self->bib1_field_map, $class, $field, $server, $attributes); -} - -=head2 add_bib1_modifier_map - - $QParser->add_bib1_modifier_map($name => $server => \%attributes); - - $QParser->add_bib1_modifier_map('ascending' => 'biblioserver' => - { '7' => '1' }); - -Adds a search modifier<->bib1 attribute mapping for the specified server. The -%attributes hash contains maps Bib-1 Attributes to the appropropriate -values. Not all attributes must be specified. - -=cut - -sub add_bib1_modifier_map { - my ($self, $name, $server, $attributes) = @_; - - $self->add_search_modifier( $name ); - - return $self->_add_mapping($self->bib1_modifier_map, $name, $server, $attributes); -} - -=head2 add_bib1_filter_map - - $QParser->add_bib1_filter_map($name => $server => \%attributes); - - $QParser->add_bib1_filter_map('date' => 'biblioserver' => - { 'callback' => &_my_callback }); - -Adds a search filter<->bib1 attribute mapping for the specified server. The -%attributes hash maps Bib-1 Attributes to the appropropriate values and -provides a callback for the filter. Not all attributes must be specified. - -=cut - -sub add_bib1_filter_map { - my ($self, $name, $server, $attributes) = @_; - - $self->add_search_filter( $name, $attributes->{'callback'} ); - - return $self->_add_mapping($self->bib1_filter_map, $name, $server, $attributes); -} - -=head2 add_relevance_bump - - $QParser->add_relevance_bump($class, $field, $server, $multiplier, $active); - $QParser->add_relevance_bump('title' => 'exact' => 'biblioserver' => 34, 1); - -Add a relevance bump to the specified field. When searching for a class without -any fields, all the relevance bumps for the specified class will be 'OR'ed -together. - -=cut - -sub add_relevance_bump { - my ($self, $class, $field, $server, $multiplier, $active) = @_; - my $attributes = { '9' => $multiplier, '2' => '102', 'active' => $active }; - - $self->add_search_field( $class => $field ); - return $self->_add_field_mapping($self->bib1_relevance_bump_map, $class, $field, $server, $attributes); -} - - -=head2 target_syntax - - my $pqf = $QParser->target_syntax($server, [$query]); - my $pqf = $QParser->target_syntax('biblioserver', 'author|personal:smith'); - print $pqf; # assuming all the indexes are configured, - # prints '@attr 1=1003 @attr 4=6 "smith"' - -Transforms the current or specified query into a PQF query string for the -specified server. - -=cut - -sub target_syntax { - my ($self, $server, $query) = @_; - my $pqf = ''; - $self->parse($query) if $query; - warn "QP query for $server: " . $self->query . "\n" if $self->debug; - $pqf = $self->parse_tree->target_syntax($server); - warn "PQF query: $pqf\n" if $self->debug; - $pqf =~ s/ +/ /g; - $pqf =~ s/^ //; - $pqf =~ s/ $//; - return $pqf; -} - -=head2 date_filter_target_callback - - $QParser->add_bib1_filter_map($server, { 'target_syntax_callback' => \&Koha::QueryParser::Driver::PQF::date_filter_target_callback, '1' => 'pubdate' }); - -Callback for date filters. Note that although the first argument is the QParser -object, this is technically not an object-oriented routine. This has no -real-world implications. - -=cut - -sub date_filter_target_callback { - my ($QParser, $filter, $params, $negate, $server) = @_; - my $attr_string = $QParser->bib1_mapping_by_name( 'filter', $filter, $server )->{'attr_string'}; - my $pqf = ''; - foreach my $datespec (@$params) { - my $datepqf = ' '; - if ($datespec) { - if ($datespec =~ m/(.*)-(.*)/) { - if ($1) { - $datepqf .= $attr_string . ' @attr 2=4 "' . $1 . '"'; - } - if ($2) { - $datepqf .= $attr_string . ' @attr 2=2 "' . $2 . '"'; - $datepqf = ' @and ' . $datepqf if $1; - } - } else { - $datepqf .= $attr_string . ' "' . $datespec . '"'; - } - } - $pqf = ' @or ' . ($negate ? '@not @attr 1=_ALLRECORDS @attr 2=103 "" ' : '') . $pqf if $pqf; - $pqf .= $datepqf; - } - return $pqf; -} - -=head2 _map - - return $self->_map('bib1_field_map', $map); - -Retrieves or sets a map. - -=cut - -sub _map { - my ($self, $name, $map) = @_; - $self->custom_data->{$name} ||= {}; - $self->custom_data->{$name} = $map if ($map); - return $self->custom_data->{$name}; -} - -=head2 _add_mapping - - return $self->_add_mapping($map, $name, $server, $attributes) - -Adds a mapping. Note that this is not used for mappings relating to fields. - -=cut - -sub _add_mapping { - my ($self, $map, $name, $server, $attributes) = @_; - - my $attr_string = Koha::QueryParser::Driver::PQF::Util::attributes_to_attr_string($attributes); - $attributes->{'attr_string'} = $attr_string; - - $map->{'by_name'}{$name}{$server} = $attributes; - $map->{'by_attr'}{$server}{$attr_string} = { 'name' => $name, %$attributes }; - - return $map; -} - -=head2 _add_field_mapping - - return $self->_add_field_mapping($map, $class, $field, $server, $attributes) - -Adds a mapping for field-related data. - -=cut - -sub _add_field_mapping { - my ($self, $map, $class, $field, $server, $attributes) = @_; - my $attr_string = Koha::QueryParser::Driver::PQF::Util::attributes_to_attr_string($attributes); - $attributes->{'attr_string'} = $attr_string; - - $map->{'by_name'}{$class}{$field}{$server} = $attributes; - $map->{'by_attr'}{$server}{$attr_string} = { 'classname' => $class, 'field' => $field, %$attributes }; - return $map; -} - - -=head2 bib1_mapping_by_name - - my $attributes = $QParser->bib1_mapping_by_name($type, $name[, $subname], $server); - my $attributes = $QParser->bib1_mapping_by_name('field', 'author', 'personal', 'biblioserver'); - my $attributes = $QParser->bib1_mapping_by_name('filter', 'pubdate', 'biblioserver'); - -Retrieve the Bib-1 attribute set associated with the specified mapping. -=cut - -sub bib1_mapping_by_name { - my $server = pop; - my ($self, $type, $name, $field) = @_; - - return unless ($server && $name); - return unless ($type eq 'field' || $type eq 'modifier' || $type eq 'filter' || $type eq 'relevance_bump'); - if ($type eq 'field' || $type eq 'relevance_bump') { - # Unfortunately field is a special case thanks to the class->field hierarchy - return $self->_map('bib1_' . $type . '_map')->{'by_name'}{$name}{$field}{$server} if $field; - return $self->_map('bib1_' . $type . '_map')->{'by_name'}{$name}; - } else { - return $self->_map('bib1_' . $type . '_map')->{'by_name'}{$name}{$server}; - } -} - -=head2 bib1_mapping_by_attr - - my $field = $QParser->bib1_mapping_by_attr($type, $server, \%attr); - my $field = $QParser->bib1_mapping_by_attr('field', 'biblioserver', {'1' => '1004'}); - print $field->{'classname'}; # prints "author" - print $field->{'field'}; # prints "personal" - -Retrieve the search field/modifier/filter used for the specified Bib-1 attribute set. - -=cut - -sub bib1_mapping_by_attr { - my ($self, $type, $server, $attributes) = @_; - return unless ($server && $attributes); - - my $attr_string = Koha::QueryParser::Driver::PQF::Util::attributes_to_attr_string($attributes); - - return $self->bib1_mapping_by_attr_string($type, $server, $attr_string); -} - -=head2 bib1_mapping_by_attr_string - - my $field = $QParser->bib1_mapping_by_attr_string($type, $server, $attr_string); - my $field = $QParser->bib1_mapping_by_attr_string('field', 'biblioserver', '@attr 1=1004'); - print $field->{'classname'}; # prints "author" - print $field->{'field'}; # prints "personal" - -Retrieve the search field/modifier/filter used for the specified Bib-1 attribute string -(i.e. PQF snippet). - -=cut - -sub bib1_mapping_by_attr_string { - my ($self, $type, $server, $attr_string) = @_; - return unless ($server && $attr_string); - return unless ($type eq 'field' || $type eq 'modifier' || $type eq 'filter' || $type eq 'relevance_bump'); - - return $self->_map('bib1_' . $type . '_map')->{'by_attr'}{$server}{$attr_string}; -} - -=head2 clear_all_configuration - - $QParser->clear_all_configuration - -Clear all configuration. This is a highly destructive method. You may -not want to use it. - -=cut - -sub clear_all_configuration { - my ($self) = @_; - %OpenILS::QueryParser::parser_config = ( - 'OpenILS::QueryParser' => { - filters => [], - modifiers => [], - operators => { - 'and' => '&&', - 'or' => '||', - float_start => '{{', - float_end => '}}', - group_start => '(', - group_end => ')', - required => '+', - disallowed => '-', - modifier => '#', - negated => '!' - } - } - ); - return $self; -} - -=head2 clear_all_mappings - - $QParser->clear_all_mappings - -Clear all bib-1 mappings. - -=cut - -sub clear_all_mappings { - my ($self) = @_; - - foreach my $name (qw(field modifier filter relevance_bump)) { - $self->custom_data->{'bib1_' . $name . '_map'} = { }; - } - return $self; -} - - -=head2 _canonicalize_field_map - -Convert a field map into its canonical form for serialization. Used only for -fields and relevance bumps. - -=cut - -sub _canonicalize_field_map { - my ( $map, $aliases ) = @_; - my $canonical_map = {}; - - foreach my $class ( keys %{ $map->{'by_name'} } ) { - $canonical_map->{$class} ||= {}; - foreach my $field ( keys %{ $map->{'by_name'}->{$class} } ) { - my $field_map = { - 'index' => $field, - 'label' => ucfirst($field), - 'enabled' => '1', - }; - foreach - my $server ( keys %{ $map->{'by_name'}->{$class}->{$field} } ) - { - $field_map->{'bib1_mapping'} ||= {}; - $field_map->{'bib1_mapping'}->{$server} = - $map->{'by_name'}->{$class}->{$field}->{$server}; - delete $field_map->{'bib1_mapping'}->{$server}->{'attr_string'} - if defined( - $field_map->{'bib1_mapping'}->{$server} - ->{'attr_string'} - ); - } - if ($aliases) { - $field_map->{'aliases'} = []; - foreach my $alias ( @{ $aliases->{$class}->{$field} } ) { - push @{ $field_map->{'aliases'} }, - $alias; - } - } - $canonical_map->{$class}->{$field} = $field_map; - } - } - return $canonical_map; -} - -=head2 _canonicalize_map - -Convert a map into its canonical form for serialization. Not used for fields. - -=cut - -sub _canonicalize_map { - my ($map) = @_; - my $canonical_map = {}; - - foreach my $name ( keys %{ $map->{'by_name'} } ) { - $canonical_map->{$name} = { - 'label' => ucfirst($name), - 'enabled' => 1, - 'bib1_mapping' => {} - }; - foreach my $server ( keys %{ $map->{'by_name'}->{$name} } ) { - $canonical_map->{$name}->{'bib1_mapping'}->{$server} = - $map->{'by_name'}->{$name}->{$server}; - delete $canonical_map->{$name}->{'bib1_mapping'}->{$server} - ->{'attr_string'} - if defined( - $canonical_map->{$name}->{'bib1_mapping'}->{$server} - ->{'attr_string'} - ); - } - } - return $canonical_map; -} - -=head2 serialize_mappings - - my $yaml = $QParser->serialize_mappings; - my $json = $QParser->serialize_mappings('json'); - -Serialize Bib-1 mappings to YAML or JSON. - -=cut - -sub serialize_mappings { - my ( $self, $format ) = @_; - $format ||= 'yaml'; - my $config; - - $config->{'field_mappings'} = - _canonicalize_field_map( $self->bib1_field_map, - $self->search_field_aliases ); - $config->{'modifier_mappings'} = - _canonicalize_map( $self->bib1_modifier_map ); - $config->{'filter_mappings'} = _canonicalize_map( $self->bib1_filter_map ); - $config->{'relevance_bumps'} = - _canonicalize_field_map( $self->bib1_relevance_bump_map ); - - if ( $format eq 'json' && can_load( modules => { 'JSON' => undef } ) ) { - return JSON::to_json($config); - } - elsif ( can_load( modules => { 'YAML::Any' => undef } ) ) { - return YAML::Any::Dump($config); - } - return; -} - -=head2 initialize - - $QParser->initialize( { 'bib1_field_mappings' => \%bib1_field_mappings, - 'search_field_alias_mappings' => \%search_field_alias_mappings, - 'bib1_modifier_mappings' => \%bib1_modifier_mappings, - 'bib1_filter_mappings' => \%bib1_filter_mappings, - 'relevance_bumps' => \%relevance_bumps }); - -Initialize the QueryParser mapping tables based on the provided configuration. -This method was written to play nice with YAML configuration files loaded by load_config. - -=cut - -sub initialize { - my ( $self, $args ) = @_; - - my $field_mappings = $args->{'field_mappings'}; - my $modifier_mappings = $args->{'modifier_mappings'}; - my $filter_mappings = $args->{'filter_mappings'}; - my $relbumps = $args->{'relevance_bumps'}; - my ( $server, $bib1_mapping ); - foreach my $class ( keys %$field_mappings ) { - foreach my $field ( keys %{ $field_mappings->{$class} } ) { - if ( $field_mappings->{$class}->{$field}->{'enabled'} ) { - while ( ( $server, $bib1_mapping ) = - each - %{ $field_mappings->{$class}->{$field}->{'bib1_mapping'} } ) - { - $self->add_bib1_field_map( - $class => $field => $server => $bib1_mapping ); - } - foreach my $alias ( - @{ $field_mappings->{$class}->{$field}->{'aliases'} } ) - { - $self->add_search_field_alias( $class => $field => $alias ); - } - } - } - } - foreach my $modifier ( keys %$modifier_mappings ) { - if ( $modifier_mappings->{$modifier}->{'enabled'} ) { - while ( ( $server, $bib1_mapping ) = - each %{ $modifier_mappings->{$modifier}->{'bib1_mapping'} } ) - { - $self->add_bib1_modifier_map( - $modifier => $server => $bib1_mapping ); - } - } - } - foreach my $filter ( keys %$filter_mappings ) { - if ( $filter_mappings->{$filter}->{'enabled'} ) { - while ( ( $server, $bib1_mapping ) = - each %{ $filter_mappings->{$filter}->{'bib1_mapping'} } ) - { - if ( $bib1_mapping->{'target_syntax_callback'} eq - 'date_filter_target_callback' ) - { - $bib1_mapping->{'target_syntax_callback'} = - \&Koha::QueryParser::Driver::PQF::date_filter_target_callback; - } - $self->add_bib1_filter_map( - $filter => $server => $bib1_mapping ); - } - } - } - foreach my $class ( keys %$relbumps ) { - foreach my $field ( keys %{ $relbumps->{$class} } ) { - if ( $relbumps->{$class}->{$field}->{'enabled'} ) { - while ( ( $server, $bib1_mapping ) = - each %{ $relbumps->{$class}->{$field}->{'bib1_mapping'} } ) - { - $self->add_relevance_bump( - $class => $field => $server => $bib1_mapping, - 1 - ); - } - } - } - } - return $self; -} - -=head2 load_config - - $QParser->load_config($file_name); - -Load a YAML file with a parser configuration. The YAML file should match the following format: - - --- - field_mappings: - author: - "": - aliases: - - au - bib1_mapping: - biblioserver: - 1: 1003 - enabled: 1 - index: '' - label: '' - conference: - aliases: - - conference - - cfn - bib1_mapping: - biblioserver: - 1: 1006 - enabled: 1 - index: conference - label: Conference - filter_mappings: - acqdate: - bib1_mapping: - biblioserver: - 1: Date-of-acquisition - 4: 4 - target_syntax_callback: date_filter_target_callback - enabled: 1 - label: Acqdate - modifier_mappings: - AuthidAsc: - bib1_mapping: - authorityserver: - "": 0 - 1: Local-Number - 7: 1 - op: "@or" - enabled: 1 - label: AuthidAsc - ... - -=cut - -sub load_config { - my ($self, $file) = @_; - require YAML::Any; - return unless ($file && -f $file); - my $config = YAML::Any::LoadFile($file); - return unless ($config); - $self->initialize($config); - return 1; -} - -=head2 TEST_SETUP - - $QParser->TEST_SETUP - -This routine initializes the QueryParser driver with a reasonable set of -defaults. This is intended only for testing. Although such test stubs are -generally not included in Koha, this type of test stub is used by other -QueryParser implementations, and it seems sensible to maintain consistency -as much as possible. - -=cut - -sub TEST_SETUP { - my ($self) = @_; - - $self->default_search_class( 'keyword' ); - - $self->add_bib1_field_map('keyword' => 'abstract' => 'biblioserver' => { '1' => '62' } ); - $self->add_search_field_alias( 'keyword' => 'abstract' => 'ab' ); - $self->add_bib1_field_map('keyword' => '' => 'biblioserver' => { '1' => '1016' } ); - $self->add_search_field_alias( 'keyword' => '' => 'kw' ); - $self->add_bib1_field_map('author' => '' => 'biblioserver' => { '1' => '1003' } ); - $self->add_search_field_alias( 'author' => '' => 'au' ); - $self->add_bib1_field_map('author' => 'personal' => 'biblioserver' => { '1' => '1004' } ); - $self->add_bib1_field_map('author' => 'corporate' => 'biblioserver' => { '1' => '1005' } ); - $self->add_search_field_alias( 'author' => 'corporate' => 'cpn' ); - $self->add_bib1_field_map('author' => 'conference' => 'biblioserver' => { '1' => '1006' } ); - $self->add_search_field_alias( 'author' => 'conference' => 'cfn' ); - $self->add_bib1_field_map('keyword' => 'local-classification' => 'biblioserver' => { '1' => '20' } ); - $self->add_search_field_alias( 'keyword' => 'local-classification' => 'lcn' ); - $self->add_search_field_alias( 'keyword' => 'local-classification' => 'callnum' ); - $self->add_bib1_field_map('keyword' => 'bib-level' => 'biblioserver' => { '1' => '1021' } ); - $self->add_bib1_field_map('keyword' => 'code-institution' => 'biblioserver' => { '1' => '56' } ); - $self->add_bib1_field_map('keyword' => 'language' => 'biblioserver' => { '1' => '54' } ); - $self->add_search_field_alias( 'keyword' => 'language' => 'ln' ); - $self->add_bib1_field_map('keyword' => 'record-type' => 'biblioserver' => { '1' => '1001' } ); - $self->add_search_field_alias( 'keyword' => 'record-type' => 'rtype' ); - $self->add_search_field_alias( 'keyword' => 'record-type' => 'mc-rtype' ); - $self->add_search_field_alias( 'keyword' => 'record-type' => 'mus' ); - $self->add_bib1_field_map('keyword' => 'content-type' => 'biblioserver' => { '1' => '1034' } ); - $self->add_search_field_alias( 'keyword' => 'content-type' => 'ctype' ); - $self->add_bib1_field_map('keyword' => 'lc-card-number' => 'biblioserver' => { '1' => '9' } ); - $self->add_search_field_alias( 'keyword' => 'lc-card-number' => 'lc-card' ); - $self->add_bib1_field_map('keyword' => 'local-number' => 'biblioserver' => { '1' => '12' } ); - $self->add_search_field_alias( 'keyword' => 'local-number' => 'sn' ); - $self->add_bib1_filter_map( 'biblioserver', 'copydate', { 'target_syntax_callback' => \&Koha::QueryParser::Driver::PQF::date_filter_target_callback, '1' => '30', '4' => '4' }); - $self->add_bib1_filter_map( 'biblioserver', 'pubdate', { 'target_syntax_callback' => \&Koha::QueryParser::Driver::PQF::date_filter_target_callback, '1' => 'pubdate', '4' => '4' }); - $self->add_bib1_filter_map( 'biblioserver', 'acqdate', { 'target_syntax_callback' => \&Koha::QueryParser::Driver::PQF::date_filter_target_callback, '1' => 'Date-of-acquisition', '4' => '4' }); - $self->add_bib1_field_map('keyword' => 'isbn' => 'biblioserver' => { '1' => '7' } ); - $self->add_search_field_alias( 'keyword' => 'isbn' => 'nb' ); - $self->add_bib1_field_map('keyword' => 'issn' => 'biblioserver' => { '1' => '8' } ); - $self->add_search_field_alias( 'keyword' => 'issn' => 'ns' ); - $self->add_bib1_field_map('keyword' => 'identifier-standard' => 'biblioserver' => { '1' => '1007' } ); - $self->add_search_field_alias( 'keyword' => 'identifier-standard' => 'ident' ); - $self->add_bib1_field_map('keyword' => 'upc' => 'biblioserver' => { '1' => 'UPC' } ); - $self->add_search_field_alias( 'keyword' => 'upc' => 'upc' ); - $self->add_bib1_field_map('keyword' => 'ean' => 'biblioserver' => { '1' => 'EAN' } ); - $self->add_search_field_alias( 'keyword' => 'ean' => 'ean' ); - $self->add_bib1_field_map('keyword' => 'music' => 'biblioserver' => { '1' => 'Music-number' } ); - $self->add_search_field_alias( 'keyword' => 'music' => 'music' ); - $self->add_bib1_field_map('keyword' => 'stock-number' => 'biblioserver' => { '1' => '1028' } ); - $self->add_search_field_alias( 'keyword' => 'stock-number' => 'stock-number' ); - $self->add_bib1_field_map('keyword' => 'material-type' => 'biblioserver' => { '1' => '1031' } ); - $self->add_search_field_alias( 'keyword' => 'material-type' => 'material-type' ); - $self->add_bib1_field_map('keyword' => 'place-publication' => 'biblioserver' => { '1' => '59' } ); - $self->add_search_field_alias( 'keyword' => 'place-publication' => 'pl' ); - $self->add_bib1_field_map('keyword' => 'personal-name' => 'biblioserver' => { '1' => 'Personal-name' } ); - $self->add_search_field_alias( 'keyword' => 'personal-name' => 'pn' ); - $self->add_bib1_field_map('keyword' => 'publisher' => 'biblioserver' => { '1' => '1018' } ); - $self->add_search_field_alias( 'keyword' => 'publisher' => 'pb' ); - $self->add_bib1_field_map('keyword' => 'note' => 'biblioserver' => { '1' => '63' } ); - $self->add_search_field_alias( 'keyword' => 'note' => 'nt' ); - $self->add_bib1_field_map('keyword' => 'record-control-number' => 'biblioserver' => { '1' => '1045' } ); - $self->add_search_field_alias( 'keyword' => 'record-control-number' => 'rcn' ); - $self->add_bib1_field_map('subject' => '' => 'biblioserver' => { '1' => '21' } ); - $self->add_search_field_alias( 'subject' => '' => 'su' ); - $self->add_search_field_alias( 'subject' => '' => 'su-to' ); - $self->add_search_field_alias( 'subject' => '' => 'su-geo' ); - $self->add_search_field_alias( 'subject' => '' => 'su-ut' ); - $self->add_bib1_field_map('subject' => 'name-personal' => 'biblioserver' => { '1' => '1009' } ); - $self->add_search_field_alias( 'subject' => 'name-personal' => 'su-na' ); - $self->add_bib1_field_map('title' => '' => 'biblioserver' => { '1' => '4' } ); - $self->add_search_field_alias( 'title' => '' => 'ti' ); - $self->add_bib1_field_map('title' => 'cover' => 'biblioserver' => { '1' => '36' } ); - $self->add_search_field_alias( 'title' => 'cover' => 'title-cover' ); - $self->add_bib1_field_map('keyword' => 'host-item' => 'biblioserver' => { '1' => '1033' } ); - $self->add_bib1_field_map('keyword' => 'video-mt' => 'biblioserver' => { '1' => 'Video-mt' } ); - $self->add_bib1_field_map('keyword' => 'graphics-type' => 'biblioserver' => { '1' => 'Graphic-type' } ); - $self->add_bib1_field_map('keyword' => 'graphics-support' => 'biblioserver' => { '1' => 'Graphic-support' } ); - $self->add_bib1_field_map('keyword' => 'type-of-serial' => 'biblioserver' => { '1' => 'Type-Of-Serial' } ); - $self->add_bib1_field_map('keyword' => 'regularity-code' => 'biblioserver' => { '1' => 'Regularity-code' } ); - $self->add_bib1_field_map('keyword' => 'material-type' => 'biblioserver' => { '1' => 'Material-type' } ); - $self->add_bib1_field_map('keyword' => 'literature-code' => 'biblioserver' => { '1' => 'Literature-Code' } ); - $self->add_bib1_field_map('keyword' => 'biography-code' => 'biblioserver' => { '1' => 'Biography-code' } ); - $self->add_bib1_field_map('keyword' => 'illustration-code' => 'biblioserver' => { '1' => 'Illustration-code' } ); - $self->add_bib1_field_map('title' => 'series' => 'biblioserver' => { '1' => '5' } ); - $self->add_search_field_alias( 'title' => 'series' => 'title-series' ); - $self->add_search_field_alias( 'title' => 'series' => 'se' ); - $self->add_bib1_field_map('title' => 'uniform' => 'biblioserver' => { '1' => 'Title-uniform' } ); - $self->add_search_field_alias( 'title' => 'uniform' => 'title-uniform' ); - $self->add_bib1_field_map('subject' => 'authority-number' => 'biblioserver' => { '1' => 'Koha-Auth-Number' } ); - $self->add_search_field_alias( 'subject' => 'authority-number' => 'an' ); - $self->add_bib1_field_map('keyword' => 'control-number' => 'biblioserver' => { '1' => '9001' } ); - $self->add_bib1_field_map('keyword' => 'biblionumber' => 'biblioserver' => { '1' => '9002', '5' => '100' } ); - $self->add_bib1_field_map('keyword' => 'totalissues' => 'biblioserver' => { '1' => '9003' } ); - $self->add_bib1_field_map('keyword' => 'cn-bib-source' => 'biblioserver' => { '1' => '9004' } ); - $self->add_bib1_field_map('keyword' => 'cn-bib-sort' => 'biblioserver' => { '1' => '9005' } ); - $self->add_bib1_field_map('keyword' => 'itemtype' => 'biblioserver' => { '1' => '9006' } ); - $self->add_search_field_alias( 'keyword' => 'itemtype' => 'mc-itemtype' ); - $self->add_bib1_field_map('keyword' => 'cn-class' => 'biblioserver' => { '1' => '9007' } ); - $self->add_bib1_field_map('keyword' => 'cn-item' => 'biblioserver' => { '1' => '9008' } ); - $self->add_bib1_field_map('keyword' => 'cn-prefix' => 'biblioserver' => { '1' => '9009' } ); - $self->add_bib1_field_map('keyword' => 'cn-suffix' => 'biblioserver' => { '1' => '9010' } ); - $self->add_bib1_field_map('keyword' => 'suppress' => 'biblioserver' => { '1' => '9011' } ); - $self->add_bib1_field_map('keyword' => 'id-other' => 'biblioserver' => { '1' => '9012' } ); - $self->add_bib1_field_map('keyword' => 'date-entered-on-file' => 'biblioserver' => { '1' => 'date-entered-on-file' } ); - $self->add_bib1_field_map('keyword' => 'extent' => 'biblioserver' => { '1' => 'Extent' } ); - $self->add_bib1_field_map('keyword' => 'llength' => 'biblioserver' => { '1' => 'llength' } ); - $self->add_bib1_field_map('keyword' => 'summary' => 'biblioserver' => { '1' => 'Summary' } ); - $self->add_bib1_field_map('keyword' => 'withdrawn' => 'biblioserver' => { '1' => '8001' } ); - $self->add_bib1_field_map('keyword' => 'lost' => 'biblioserver' => { '1' => '8002' } ); - $self->add_bib1_field_map('keyword' => 'classification-source' => 'biblioserver' => { '1' => '8003' } ); - $self->add_bib1_field_map('keyword' => 'materials-specified' => 'biblioserver' => { '1' => '8004' } ); - $self->add_bib1_field_map('keyword' => 'damaged' => 'biblioserver' => { '1' => '8005' } ); - $self->add_bib1_field_map('keyword' => 'restricted' => 'biblioserver' => { '1' => '8006' } ); - $self->add_bib1_field_map('keyword' => 'cn-sort' => 'biblioserver' => { '1' => '8007' } ); - $self->add_bib1_field_map('keyword' => 'notforloan' => 'biblioserver' => { '1' => '8008', '4' => '109' } ); - $self->add_bib1_field_map('keyword' => 'ccode' => 'biblioserver' => { '1' => '8009' } ); - $self->add_search_field_alias( 'keyword' => 'ccode' => 'mc-ccode' ); - $self->add_bib1_field_map('keyword' => 'itemnumber' => 'biblioserver' => { '1' => '8010' } ); - $self->add_bib1_field_map('keyword' => 'homebranch' => 'biblioserver' => { '1' => 'homebranch' } ); - $self->add_search_field_alias( 'keyword' => 'homebranch' => 'branch' ); - $self->add_bib1_field_map('keyword' => 'holdingbranch' => 'biblioserver' => { '1' => '8012' } ); - $self->add_bib1_field_map('keyword' => 'location' => 'biblioserver' => { '1' => '8013' } ); - $self->add_search_field_alias( 'keyword' => 'location' => 'mc-loc' ); - $self->add_bib1_field_map('keyword' => 'acqsource' => 'biblioserver' => { '1' => '8015' } ); - $self->add_bib1_field_map('keyword' => 'coded-location-qualifier' => 'biblioserver' => { '1' => '8016' } ); - $self->add_bib1_field_map('keyword' => 'price' => 'biblioserver' => { '1' => '8017' } ); - $self->add_bib1_field_map('keyword' => 'stocknumber' => 'biblioserver' => { '1' => '1062' } ); - $self->add_search_field_alias( 'keyword' => 'stocknumber' => 'inv' ); - $self->add_bib1_field_map('keyword' => 'stack' => 'biblioserver' => { '1' => '8018' } ); - $self->add_bib1_field_map('keyword' => 'issues' => 'biblioserver' => { '1' => '8019' } ); - $self->add_bib1_field_map('keyword' => 'renewals' => 'biblioserver' => { '1' => '8020' } ); - $self->add_bib1_field_map('keyword' => 'reserves' => 'biblioserver' => { '1' => '8021' } ); - $self->add_bib1_field_map('keyword' => 'local-classification' => 'biblioserver' => { '1' => '8022' } ); - $self->add_bib1_field_map('keyword' => 'barcode' => 'biblioserver' => { '1' => '8023' } ); - $self->add_search_field_alias( 'keyword' => 'barcode' => 'bc' ); - $self->add_bib1_field_map('keyword' => 'onloan' => 'biblioserver' => { '1' => '8024' } ); - $self->add_bib1_field_map('keyword' => 'datelastseen' => 'biblioserver' => { '1' => '8025' } ); - $self->add_bib1_field_map('keyword' => 'datelastborrowed' => 'biblioserver' => { '1' => '8026' } ); - $self->add_bib1_field_map('keyword' => 'copynumber' => 'biblioserver' => { '1' => '8027' } ); - $self->add_bib1_field_map('keyword' => 'uri' => 'biblioserver' => { '1' => '8028' } ); - $self->add_bib1_field_map('keyword' => 'replacementprice' => 'biblioserver' => { '1' => '8029' } ); - $self->add_bib1_field_map('keyword' => 'replacementpricedate' => 'biblioserver' => { '1' => '8030' } ); - $self->add_bib1_field_map('keyword' => 'itype' => 'biblioserver' => { '1' => '8031' } ); - $self->add_search_field_alias( 'keyword' => 'itype' => 'mc-itype' ); - $self->add_bib1_field_map('keyword' => 'ff8-22' => 'biblioserver' => { '1' => '8822' } ); - $self->add_bib1_field_map('keyword' => 'ff8-23' => 'biblioserver' => { '1' => '8823' } ); - $self->add_bib1_field_map('keyword' => 'ff8-34' => 'biblioserver' => { '1' => '8834' } ); -# Audience - $self->add_bib1_field_map('keyword' => 'audience' => 'biblioserver' => { '1' => '8822' } ); - $self->add_search_field_alias( 'keyword' => 'audience' => 'aud' ); - -# Content and Literary form - $self->add_bib1_field_map('keyword' => 'fiction' => 'biblioserver' => { '1' => '8833' } ); - $self->add_search_field_alias( 'keyword' => 'fiction' => 'fic' ); - $self->add_bib1_field_map('keyword' => 'biography' => 'biblioserver' => { '1' => '8834' } ); - $self->add_search_field_alias( 'keyword' => 'biography' => 'bio' ); - -# Format - $self->add_bib1_field_map('keyword' => 'format' => 'biblioserver' => { '1' => '8823' } ); -# format used as a limit FIXME: needed? - $self->add_bib1_field_map('keyword' => 'l-format' => 'biblioserver' => { '1' => '8703' } ); - - $self->add_bib1_field_map('keyword' => 'illustration-code' => 'biblioserver' => { '1' => 'Illustration-code ' } ); - -# Lexile Number - $self->add_bib1_field_map('keyword' => 'lex' => 'biblioserver' => { '1' => '9903 r=r' } ); - -#Accelerated Reader Level - $self->add_bib1_field_map('keyword' => 'arl' => 'biblioserver' => { '1' => '9904 r=r' } ); - -#Accelerated Reader Point - $self->add_bib1_field_map('keyword' => 'arp' => 'biblioserver' => { '1' => '9014 r=r' } ); - -# Curriculum - $self->add_bib1_field_map('keyword' => 'curriculum' => 'biblioserver' => { '1' => '9658' } ); - -## Statuses - $self->add_bib1_field_map('keyword' => 'popularity' => 'biblioserver' => { '1' => 'issues' } ); - -## Type Limits - $self->add_bib1_field_map('keyword' => 'dt-bks' => 'biblioserver' => { '1' => '8700' } ); - $self->add_bib1_field_map('keyword' => 'dt-vis' => 'biblioserver' => { '1' => '8700' } ); - $self->add_bib1_field_map('keyword' => 'dt-sr' => 'biblioserver' => { '1' => '8700' } ); - $self->add_bib1_field_map('keyword' => 'dt-cf' => 'biblioserver' => { '1' => '8700' } ); - $self->add_bib1_field_map('keyword' => 'dt-map' => 'biblioserver' => { '1' => '8700' } ); - - $self->add_bib1_field_map('keyword' => 'name' => 'biblioserver' => { '1' => '1002' } ); - $self->add_bib1_field_map('keyword' => 'item' => 'biblioserver' => { '1' => '9520' } ); - $self->add_bib1_field_map('keyword' => 'host-item-number' => 'biblioserver' => { '1' => '8911' } ); - $self->add_search_field_alias( 'keyword' => 'host-item-number' => 'hi' ); - - $self->add_bib1_field_map('keyword' => 'alwaysmatch' => 'biblioserver' => { '1' => '_ALLRECORDS', '2' => '103' } ); - $self->add_bib1_field_map('subject' => 'complete' => 'biblioserver' => { '1' => '21', '3' => '1', '4' => '1', '5' => '100', '6' => '3' } ); - - $self->add_bib1_modifier_map('relevance' => 'biblioserver' => { '2' => '102' } ); - $self->add_bib1_modifier_map('title-sort-za' => 'biblioserver' => { '7' => '2', '1' => '36', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('title-sort-az' => 'biblioserver' => { '7' => '1', '1' => '36', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('relevance_dsc' => 'biblioserver' => { '2' => '102' } ); - $self->add_bib1_modifier_map('title_dsc' => 'biblioserver' => { '7' => '2', '1' => '4', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('title_asc' => 'biblioserver' => { '7' => '1', '1' => '4', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('author_asc' => 'biblioserver' => { '7' => '2', '1' => '1003', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('author_dsc' => 'biblioserver' => { '7' => '1', '1' => '1003', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('popularity_asc' => 'biblioserver' => { '7' => '2', '1' => '9003', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('popularity_dsc' => 'biblioserver' => { '7' => '1', '1' => '9003', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('call_number_asc' => 'biblioserver' => { '7' => '2', '1' => '8007', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('call_number_dsc' => 'biblioserver' => { '7' => '1', '1' => '8007', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('pubdate_asc' => 'biblioserver' => { '7' => '2', '1' => '31', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('pubdate_dsc' => 'biblioserver' => { '7' => '1', '1' => '31', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('acqdate_asc' => 'biblioserver' => { '7' => '2', '1' => '32', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('acqdate_dsc' => 'biblioserver' => { '7' => '1', '1' => '32', '' => '0', 'op' => '@or' } ); - - $self->add_bib1_modifier_map('title_za' => 'biblioserver' => { '7' => '2', '1' => '4', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('title_az' => 'biblioserver' => { '7' => '1', '1' => '4', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('author_za' => 'biblioserver' => { '7' => '2', '1' => '1003', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('author_az' => 'biblioserver' => { '7' => '1', '1' => '1003', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('ascending' => 'biblioserver' => { '7' => '1' } ); - $self->add_bib1_modifier_map('descending' => 'biblioserver' => { '7' => '2' } ); - - $self->add_bib1_field_map('title' => 'exacttitle' => 'biblioserver' => { '1' => '4', '4' => '1', '6' => '3' } ); - $self->add_search_field_alias( 'title' => 'exacttitle' => 'ti,ext' ); - $self->add_bib1_field_map('author' => 'exactauthor' => 'biblioserver' => { '1' => '1003', '4' => '1', '6' => '3' } ); - $self->add_search_field_alias( 'author' => 'exactauthor' => 'au,ext' ); - - $self->add_bib1_field_map('subject' => 'headingmain' => 'authorityserver' => { '1' => 'Heading-Main' } ); - $self->add_bib1_field_map('subject' => 'heading' => 'authorityserver' => { '1' => 'Heading' } ); - $self->add_bib1_field_map('subject' => 'matchheading' => 'authorityserver' => { '1' => 'Match-heading' } ); - $self->add_bib1_field_map('subject' => 'seefrom' => 'authorityserver' => { '1' => 'Match-heading-see-from' } ); - $self->add_bib1_field_map('subject' => '' => 'authorityserver' => { '1' => 'Match-heading' } ); - $self->add_bib1_field_map('keyword' => 'alwaysmatch' => 'authorityserver' => { '1' => '_ALLRECORDS', '2' => '103' } ); - $self->add_bib1_field_map('keyword' => 'match' => 'authorityserver' => { '1' => 'Match' } ); - $self->add_bib1_field_map('keyword' => 'thesaurus' => 'authorityserver' => { '1' => 'Subject-heading-thesaurus' } ); - $self->add_bib1_field_map('keyword' => 'authtype' => 'authorityserver' => { '1' => 'authtype', '5' => '100' } ); - $self->add_bib1_field_map('keyword' => '' => 'authorityserver' => { '1' => 'Any' } ); - $self->add_search_field_alias( 'subject' => 'headingmain' => 'mainmainentry' ); - $self->add_search_field_alias( 'subject' => 'heading' => 'mainentry' ); - $self->add_search_field_alias( 'subject' => 'heading' => 'he' ); - $self->add_search_field_alias( 'subject' => 'matchheading' => 'match-heading' ); - $self->add_search_field_alias( 'keyword' => '' => 'any' ); - $self->add_search_field_alias( 'keyword' => 'match' => 'match' ); - $self->add_search_field_alias( 'subject' => 'seefrom' => 'see-from' ); - $self->add_search_field_alias( 'keyword' => 'thesaurus' => 'thesaurus' ); - $self->add_search_field_alias( 'keyword' => 'alwaysmatch' => 'all' ); - $self->add_search_field_alias( 'keyword' => 'authtype' => 'authtype' ); - $self->add_search_field_alias( 'keyword' => 'authtype' => 'at' ); - - $self->add_bib1_field_map('subject' => 'start' => 'authorityserver' => { '3' => '2', '4' => '1', '5' => '1' } ); - $self->add_bib1_field_map('subject' => 'exact' => 'authorityserver' => { '4' => '1', '5' => '100', '6' => '3' } ); - - $self->add_bib1_modifier_map('HeadingAsc' => 'authorityserver' => { '7' => '1', '1' => 'Heading', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('HeadingDsc' => 'authorityserver' => { '7' => '2', '1' => 'Heading', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('AuthidAsc' => 'authorityserver' => { '7' => '1', '1' => 'Local-Number', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('AuthidDsc' => 'authorityserver' => { '7' => '2', '1' => 'Local-Number', '' => '0', 'op' => '@or' } ); - $self->add_bib1_modifier_map('Relevance' => 'authorityserver' => { '2' => '102' } ); - - return $self; -} - -1; --- a/Koha/QueryParser/Driver/PQF/Util.pm +++ a/Koha/QueryParser/Driver/PQF/Util.pm @@ -1,54 +0,0 @@ -package Koha::QueryParser::Driver::PQF::Util; - -# This file is part of Koha. -# -# Copyright 2012 C & P Bibliography Services -# -# Koha is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Koha 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Koha; if not, see . - -use Scalar::Util qw(looks_like_number); - -use strict; -use warnings; - -=head1 NAME - - Koha::QueryParser::Driver::PQF::Util - Utility module for PQF QueryParser driver - -=head1 FUNCTIONS - -=head2 attributes_to_attr_string - - Koha::QueryParser::Driver::PQF::Util(%attributes); - - Koha::QueryParser::Driver::PQF::Util({ '1' => '1003', '4' => '6' }); - -Convert a hashref with a Bib-1 mapping into its PQF string representation. - -=cut - -sub attributes_to_attr_string { - my ($attributes) = @_; - my $attr_string = ''; - foreach my $key ( sort keys %{$attributes} ) { - next unless looks_like_number($key); - $attr_string .= ' @attr ' . $key . '=' . $attributes->{ $key } . ' '; - } - $attr_string =~ s/^\s*//; - $attr_string =~ s/\s*$//; - $attr_string .= ' ' . $attributes->{''} if defined $attributes->{''}; - return $attr_string; -} - -1; --- a/Koha/QueryParser/Driver/PQF/query_plan.pm +++ a/Koha/QueryParser/Driver/PQF/query_plan.pm @@ -1,70 +0,0 @@ -package Koha::QueryParser::Driver::PQF::query_plan; - -# This file is part of Koha. -# -# Copyright 2012 C & P Bibliography Services -# -# Koha is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Koha 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Koha; if not, see . - -use base 'OpenILS::QueryParser::query_plan'; - -use strict; -use warnings; - -=head1 NAME - -Koha::QueryParser::Driver::PQF::query_plan - query_plan subclass for PQF driver - -=head1 FUNCTIONS - -=head2 Koha::QueryParser::Driver::PQF::query_plan::target_syntax - - my $pqf = $query_plan->target_syntax($server); - -Transforms an OpenILS::QueryParser::query_plan object into PQF. Do not use directly. - -=cut - -sub target_syntax { - my ($self, $server) = @_; - my $pqf = ''; - my $node_pqf; - my $node_count = 0; - - for my $node ( @{$self->query_nodes} ) { - - if (ref($node)) { - $node_pqf = $node->target_syntax($server); - $node_count++ if $node_pqf; - $pqf .= $node_pqf; - } - } - $pqf = ($self->joiner eq '|' ? ' @or ' : ' @and ') x ($node_count - 1) . $pqf if $node_count > 1; - $node_count = ($node_count ? '1' : '0'); - for my $node ( @{$self->filters} ) { - if (ref($node)) { - $node_pqf = $node->target_syntax($server); - $node_count++ if $node_pqf; - $pqf .= $node_pqf; - } - } - $pqf = ($self->joiner eq '|' ? ' @or ' : ' @and ') x ($node_count - 1) . $pqf if $node_count > 1; - foreach my $modifier ( @{$self->modifiers} ) { - my $modifierpqf = $modifier->target_syntax($server, $self); - $pqf = $modifierpqf . ' ' . $pqf if $modifierpqf; - } - return ($self->negate ? '@not @attr 1=_ALLRECORDS @attr 2=103 "" ' : '') . $pqf; -} - -1; --- a/Koha/QueryParser/Driver/PQF/query_plan/facet.pm +++ a/Koha/QueryParser/Driver/PQF/query_plan/facet.pm @@ -1,46 +0,0 @@ -package Koha::QueryParser::Driver::PQF::query_plan::facet; - -# This file is part of Koha. -# -# Copyright 2012 C & P Bibliography Services -# -# Koha is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Koha 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Koha; if not, see . - -use base 'OpenILS::QueryParser::query_plan::facet'; - -use strict; -use warnings; - -=head1 NAME - -Koha::QueryParser::Driver::PQF::query_plan::facet - facet subclass for PQF driver - -=head1 FUNCTIONS - -=head2 Koha::QueryParser::Driver::PQF::query_plan::facet::target_syntax - - my $pqf = $facet->target_syntax($server); - -Transforms an OpenILS::QueryParser::query_plan::facet object into PQF. Do not use -directly. - -=cut - -sub target_syntax { - my ($self, $server) = @_; - - return ''; -} - -1; --- a/Koha/QueryParser/Driver/PQF/query_plan/filter.pm +++ a/Koha/QueryParser/Driver/PQF/query_plan/filter.pm @@ -1,51 +0,0 @@ -package Koha::QueryParser::Driver::PQF::query_plan::filter; - -# This file is part of Koha. -# -# Copyright 2012 C & P Bibliography Services -# -# Koha is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Koha 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Koha; if not, see . - -use base 'OpenILS::QueryParser::query_plan::filter'; - -use strict; -use warnings; - -=head1 NAME - -Koha::QueryParser::Driver::PQF::query_plan::filter - filter subclass for PQF driver - -=head1 FUNCTIONS - -=head2 Koha::QueryParser::Driver::PQF::query_plan::filter::target_syntax - - my $pqf = $filter->target_syntax($server); - -Transforms an OpenILS::QueryParser::query_plan::filter object into PQF. Do not use -directly. - -=cut - -sub target_syntax { - my ($self, $server) = @_; - my $attributes = $self->plan->QueryParser->bib1_mapping_by_name( 'filter', $self->name, $server ); - - if ($attributes->{'target_syntax_callback'}) { - return $attributes->{'target_syntax_callback'}->($self->plan->QueryParser, $self->name, $self->args, $self->negate, $server); - } else { - return ''; - } -} - -1; --- a/Koha/QueryParser/Driver/PQF/query_plan/modifier.pm +++ a/Koha/QueryParser/Driver/PQF/query_plan/modifier.pm @@ -1,50 +0,0 @@ -package Koha::QueryParser::Driver::PQF::query_plan::modifier; - -# This file is part of Koha. -# -# Copyright 2012 C & P Bibliography Services -# -# Koha is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Koha 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Koha; if not, see . - -use base 'OpenILS::QueryParser::query_plan::modifier'; - -use strict; -use warnings; - -=head1 NAME - -Koha::QueryParser::Driver::PQF::query_plan::modifer - modifier subclass for PQF driver - -=head1 FUNCTIONS - -=head2 Koha::QueryParser::Driver::PQF::query_plan::modifier::target_syntax - - my $pqf = $modifier->target_syntax($server, $query_plan); - -Transforms an OpenILS::QueryParser::query_plan::modifier object into PQF. Do not use -directly. The second argument points ot the query_plan, since modifiers do -not have a reference to their parent query_plan. - -=cut - -sub target_syntax { - my ($self, $server, $query_plan) = @_; - my $pqf = ''; - - my $attributes = $query_plan->QueryParser->bib1_mapping_by_name('modifier', $self->name, $server); - $pqf = ($attributes->{'op'} ? $attributes->{'op'} . ' ' : '') . ($self->negate ? '@not @attr 1=_ALLRECORDS @attr 2=103 "" ' : '') . $attributes->{'attr_string'}; - return $pqf; -} - -1; --- a/Koha/QueryParser/Driver/PQF/query_plan/node.pm +++ a/Koha/QueryParser/Driver/PQF/query_plan/node.pm @@ -1,106 +0,0 @@ -package Koha::QueryParser::Driver::PQF::query_plan::node; - -# This file is part of Koha. -# -# Copyright 2012 C & P Bibliography Services -# -# Koha is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Koha 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Koha; if not, see . - -use base 'OpenILS::QueryParser::query_plan::node'; - -use strict; -use warnings; - -=head1 NAME - -Koha::QueryParser::Driver::PQF::query_plan::node - node subclass for PQF driver - -=head1 FUNCTIONS - -=head2 Koha::QueryParser::Driver::PQF::query_plan::node::target_syntax - - my $pqf = $node->target_syntax($server); - -Transforms an OpenILS::QueryParser::query_plan::node object into PQF. Do not use directly. - -=cut - -sub target_syntax { - my ($self, $server) = @_; - my $pqf = ''; - my $atom_content; - my $atom_count = 0; - my @fields = (); - my $fieldobj; - my $relbump; - - if (scalar(@{$self->fields})) { - foreach my $field (@{$self->fields}) { - $fieldobj = $self->plan->QueryParser->bib1_mapping_by_name('field', $self->classname, $field, $server); - $relbump = $self->plan->QueryParser->bib1_mapping_by_name('relevance_bump', $self->classname, $field, $server); - if ($relbump && defined $relbump->{'attr_string'}) { - $fieldobj->{'attr_string'} .= ' ' . $relbump->{'attr_string'}; - } - push @fields, $fieldobj unless (!defined($fieldobj) || ($field eq $self->classname && @{$self->fields} > 1)); - } - } else { - $fieldobj = $self->plan->QueryParser->bib1_mapping_by_name('field', $self->classname, $self->classname, $server); - my $relbumps = $self->plan->QueryParser->bib1_mapping_by_name('relevance_bump', $self->classname, '', $server); - push @fields, $fieldobj; - if ($relbumps) { - foreach my $field (keys %$relbumps) { - $relbump = $relbumps->{$field}; - $fieldobj = $self->plan->QueryParser->bib1_mapping_by_name('field', $relbump->{'classname'}, $relbump->{'field'}, $server); - $fieldobj->{'attr_string'} ||= ''; - $fieldobj->{'attr_string'} .= ' ' . $relbump->{$server}{'attr_string'} if $relbump->{$server}{'attr_string'}; - push @fields, $fieldobj; - } - } - } - - if (@{$self->phrases}) { - foreach my $phrase (@{$self->phrases}) { - if ($phrase) { - $phrase =~ s/"/\\"/g; - $pqf .= ' @or ' x (scalar(@fields) - 1); - foreach my $attributes (@fields) { - $attributes->{'attr_string'} ||= ''; - $pqf .= $attributes->{'attr_string'} . ($attributes->{'4'} ? '' : ' @attr 4=1') . ' "' . $phrase . '" '; - } - $atom_count++; - } - } - } else { - foreach my $atom (@{$self->query_atoms}) { - if (ref($atom)) { - $atom_content = $atom->target_syntax($server); - if ($atom_content) { - $pqf .= ' @or ' x (scalar(@fields) - 1); - foreach my $attributes (@fields) { - $attributes->{'attr_string'} ||= ''; - if ($self->plan->QueryParser->custom_data->{'QueryAutoTruncate'} || $atom->suffix eq '*') { - $attributes->{'attr_string'} .= ($attributes->{'5'} ? '' : ' @attr 5=1 '); - } - $pqf .= $attributes->{'attr_string'} . ($attributes->{'4'} ? '' : ' @attr 4=6 ') . $atom_content . ' '; - } - $atom_count++; - } - } - } - } - $pqf = (OpenILS::QueryParser::_util::default_joiner eq '|' ? ' @or ' : ' @and ') x ($atom_count - 1) . $pqf if $atom_count > 1; - return ($self->negate ? '@not @attr 1=_ALLRECORDS @attr 2=103 "" ' : '') . $pqf; -} - -1; --- a/Koha/QueryParser/Driver/PQF/query_plan/node/atom.pm +++ a/Koha/QueryParser/Driver/PQF/query_plan/node/atom.pm @@ -1,49 +0,0 @@ -package Koha::QueryParser::Driver::PQF::query_plan::node::atom; - -# This file is part of Koha. -# -# Copyright 2012 C & P Bibliography Services -# -# Koha is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# Koha 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Koha; if not, see . - -use base 'OpenILS::QueryParser::query_plan::node::atom'; - -use strict; -use warnings; - -=head1 NAME - -Koha::QueryParser::Driver::PQF::query_plan::node::atom - atom subclass for PQF driver - -=head1 FUNCTIONS - -=head2 Koha::QueryParser::Driver::PQF::query_plan::node::atom::target_syntax - - my $pqf = $atom->target_syntax($server); - -Transforms an OpenILS::QueryParser::query_plan::node::atom object into PQF. Do not use -directly. - -=cut - -sub target_syntax { - my ($self, $server) = @_; - - my $content = $self->content; - $content =~ s/"/\\"/g; - - return ' "' . $content . '" '; -} - -1; --- a/OpenILS/QueryParser.pm +++ a/OpenILS/QueryParser.pm @@ -1,2242 +0,0 @@ -use strict; -use warnings; - -package OpenILS::QueryParser; -use JSON; - -=head1 NAME - -OpenILS::QueryParser - basic QueryParser class - -=head1 SYNOPSIS - -use OpenILS::QueryParser; -my $QParser = OpenILS::QueryParser->new(%args); - -=head1 DESCRIPTION - -Main entrypoint into the QueryParser functionality. - -=head1 FUNCTIONS - -=cut - -# Note that the first key must match the name of the package. -our %parser_config = ( - 'OpenILS::QueryParser' => { - filters => [], - modifiers => [], - operators => { - 'and' => '&&', - 'or' => '||', - float_start => '{{', - float_end => '}}', - group_start => '(', - group_end => ')', - required => '+', - disallowed => '-', - modifier => '#', - negated => '!' - } - } -); - -sub canonicalize { - my $self = shift; - return OpenILS::QueryParser::Canonicalize::abstract_query2str_impl( - $self->parse_tree->to_abstract_query(@_) - ); -} - - -=head2 facet_class_count - - $count = $QParser->facet_class_count(); -=cut - -sub facet_class_count { - my $self = shift; - return @{$self->facet_classes}; -} - -=head2 search_class_count - - $count = $QParser->search_class_count(); -=cut - -sub search_class_count { - my $self = shift; - return @{$self->search_classes}; -} - -=head2 filter_count - - $count = $QParser->filter_count(); -=cut - -sub filter_count { - my $self = shift; - return @{$self->filters}; -} - -=head2 modifier_count - - $count = $QParser->modifier_count(); -=cut - -sub modifier_count { - my $self = shift; - return @{$self->modifiers}; -} - -=head2 custom_data - - $data = $QParser->custom_data($class); -=cut - -sub custom_data { - my $class = shift; - $class = ref($class) || $class; - - $parser_config{$class}{custom_data} ||= {}; - return $parser_config{$class}{custom_data}; -} - -=head2 operators - - $operators = $QParser->operators(); - -Returns hashref of the configured operators. -=cut - -sub operators { - my $class = shift; - $class = ref($class) || $class; - - $parser_config{$class}{operators} ||= {}; - return $parser_config{$class}{operators}; -} - -sub allow_nested_modifiers { - my $class = shift; - my $v = shift; - $class = ref($class) || $class; - - $parser_config{$class}{allow_nested_modifiers} = $v if (defined $v); - return $parser_config{$class}{allow_nested_modifiers}; -} - -=head2 filters - - $filters = $QParser->filters(); - -Returns arrayref of the configured filters. -=cut - -sub filters { - my $class = shift; - $class = ref($class) || $class; - - $parser_config{$class}{filters} ||= []; - return $parser_config{$class}{filters}; -} - -=head2 filter_callbacks - - $filter_callbacks = $QParser->filter_callbacks(); - -Returns hashref of the configured filter callbacks. -=cut - -sub filter_callbacks { - my $class = shift; - $class = ref($class) || $class; - - $parser_config{$class}{filter_callbacks} ||= {}; - return $parser_config{$class}{filter_callbacks}; -} - -=head2 modifiers - - $modifiers = $QParser->modifiers(); - -Returns arrayref of the configured modifiers. -=cut - -sub modifiers { - my $class = shift; - $class = ref($class) || $class; - - $parser_config{$class}{modifiers} ||= []; - return $parser_config{$class}{modifiers}; -} - -=head2 new - - $QParser = OpenILS::QueryParser->new(%args); - -Creates a new QueryParser object. -=cut - -sub new { - my $class = shift; - $class = ref($class) || $class; - - my %opts = @_; - - my $self = bless {} => $class; - - for my $o (keys %{OpenILS::QueryParser->operators}) { - $class->operator($o => OpenILS::QueryParser->operator($o)) unless ($class->operator($o)); - } - - for my $opt ( keys %opts) { - $self->$opt( $opts{$opt} ) if ($self->can($opt)); - } - - return $self; -} - -=head2 new_plan - - $query_plan = $QParser->new_plan(); - -Create a new query plan. -=cut - -sub new_plan { - my $self = shift; - my $pkg = ref($self) || $self; - return do{$pkg.'::query_plan'}->new( QueryParser => $self, @_ ); -} - -=head2 add_search_filter - - $QParser->add_search_filter($filter, [$callback]); - -Adds a filter with the specified name and an optional callback to the -QueryParser configuration. -=cut - -sub add_search_filter { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $filter = shift; - my $callback = shift; - - return $filter if (grep { $_ eq $filter } @{$pkg->filters}); - push @{$pkg->filters}, $filter; - $pkg->filter_callbacks->{$filter} = $callback if ($callback); - return $filter; -} - -=head2 add_search_modifier - - $QParser->add_search_modifier($modifier); - -Adds a modifier with the specified name to the QueryParser configuration. -=cut - -sub add_search_modifier { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $modifier = shift; - - return $modifier if (grep { $_ eq $modifier } @{$pkg->modifiers}); - push @{$pkg->modifiers}, $modifier; - return $modifier; -} - -=head2 add_facet_class - - $QParser->add_facet_class($facet_class); - -Adds a facet class with the specified name to the QueryParser configuration. -=cut - -sub add_facet_class { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - - return $class if (grep { $_ eq $class } @{$pkg->facet_classes}); - - push @{$pkg->facet_classes}, $class; - $pkg->facet_fields->{$class} = []; - - return $class; -} - -=head2 add_search_class - - $QParser->add_search_class($class); - -Adds a search class with the specified name to the QueryParser configuration. -=cut - -sub add_search_class { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - - return $class if (grep { $_ eq $class } @{$pkg->search_classes}); - - push @{$pkg->search_classes}, $class; - $pkg->search_fields->{$class} = []; - $pkg->default_search_class( $pkg->search_classes->[0] ) if (@{$pkg->search_classes} == 1); - - return $class; -} - -=head2 add_search_modifier - - $op = $QParser->operator($operator, [$newvalue]); - -Retrieves or sets value for the specified operator. Valid operators and -their defaults are as follows: - -=over 4 - -=item * and => && - -=item * or => || - -=item * group_start => ( - -=item * group_end => ) - -=item * required => + - -=item * disallowed => - - -=item * modifier => # - -=back - -=cut - -sub operator { - my $class = shift; - $class = ref($class) || $class; - my $opname = shift; - my $op = shift; - - return unless ($opname); - - $parser_config{$class}{operators} ||= {}; - $parser_config{$class}{operators}{$opname} = $op if ($op); - - return $parser_config{$class}{operators}{$opname}; -} - -=head2 facet_classes - - $classes = $QParser->facet_classes([\@newclasses]); - -Returns arrayref of all configured facet classes after optionally -replacing configuration. -=cut - -sub facet_classes { - my $class = shift; - $class = ref($class) || $class; - my $classes = shift; - - $parser_config{$class}{facet_classes} ||= []; - $parser_config{$class}{facet_classes} = $classes if (ref($classes) && @$classes); - return $parser_config{$class}{facet_classes}; -} - -=head2 search_classes - - $classes = $QParser->search_classes([\@newclasses]); - -Returns arrayref of all configured search classes after optionally -replacing the previous configuration. -=cut - -sub search_classes { - my $class = shift; - $class = ref($class) || $class; - my $classes = shift; - - $parser_config{$class}{classes} ||= []; - $parser_config{$class}{classes} = $classes if (ref($classes) && @$classes); - return $parser_config{$class}{classes}; -} - -=head2 add_query_normalizer - - $function = $QParser->add_query_normalizer($class, $field, $func, [\@params]); - -=cut - -sub add_query_normalizer { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - my $field = shift; - my $func = shift; - my $params = shift || []; - - # do not add if function AND params are identical to existing member - return $func if (grep { - $_->{function} eq $func and - to_json($_->{params}) eq to_json($params) - } @{$pkg->query_normalizers->{$class}->{$field}}); - - push(@{$pkg->query_normalizers->{$class}->{$field}}, { function => $func, params => $params }); - - return $func; -} - -=head2 query_normalizers - - $normalizers = $QParser->query_normalizers($class, $field); - -Returns a list of normalizers associated with the specified search class -and field -=cut - -sub query_normalizers { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - - my $class = shift; - my $field = shift; - - $parser_config{$pkg}{normalizers} ||= {}; - if ($class) { - if ($field) { - $parser_config{$pkg}{normalizers}{$class}{$field} ||= []; - return $parser_config{$pkg}{normalizers}{$class}{$field}; - } else { - return $parser_config{$pkg}{normalizers}{$class}; - } - } - - return $parser_config{$pkg}{normalizers}; -} - -=head2 add_filter_normalizer - - $normalizer = $QParser->add_filter_normalizer($filter, $func, [\@params]); - -Adds a normalizer function to the specified filter. -=cut - -sub add_filter_normalizer { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $filter = shift; - my $func = shift; - my $params = shift || []; - - return $func if (grep { $_ eq $func } @{$pkg->filter_normalizers->{$filter}}); - - push(@{$pkg->filter_normalizers->{$filter}}, { function => $func, params => $params }); - - return $func; -} - -=head2 filter_normalizers - - $normalizers = $QParser->filter_normalizers($filter); - -Return arrayref of normalizer functions associated with the specified filter. -=cut - -sub filter_normalizers { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - - my $filter = shift; - - $parser_config{$pkg}{filter_normalizers} ||= {}; - if ($filter) { - $parser_config{$pkg}{filter_normalizers}{$filter} ||= []; - return $parser_config{$pkg}{filter_normalizers}{$filter}; - } - - return $parser_config{$pkg}{filter_normalizers}; -} - -=head2 default_search_class - - $default_class = $QParser->default_search_class([$class]); - -Set or return the default search class. -=cut - -sub default_search_class { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - $OpenILS::QueryParser::parser_config{$pkg}{default_class} = $pkg->add_search_class( $class ) if $class; - - return $OpenILS::QueryParser::parser_config{$pkg}{default_class}; -} - -=head2 remove_facet_class - - $QParser->remove_facet_class($class); - -Remove the specified facet class from the configuration. -=cut - -sub remove_facet_class { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - - return $class if (!grep { $_ eq $class } @{$pkg->facet_classes}); - - $pkg->facet_classes( [ grep { $_ ne $class } @{$pkg->facet_classes} ] ); - delete $OpenILS::QueryParser::parser_config{$pkg}{facet_fields}{$class}; - - return $class; -} - -=head2 remove_search_class - - $QParser->remove_search_class($class); - -Remove the specified search class from the configuration. -=cut - -sub remove_search_class { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - - return $class if (!grep { $_ eq $class } @{$pkg->search_classes}); - - $pkg->search_classes( [ grep { $_ ne $class } @{$pkg->search_classes} ] ); - delete $OpenILS::QueryParser::parser_config{$pkg}{fields}{$class}; - - return $class; -} - -=head2 add_facet_field - - $QParser->add_facet_field($class, $field); - -Adds the specified field (and facet class if it doesn't already exist) -to the configuration. -=cut - -sub add_facet_field { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - my $field = shift; - - $pkg->add_facet_class( $class ); - - return { $class => $field } if (grep { $_ eq $field } @{$pkg->facet_fields->{$class}}); - - push @{$pkg->facet_fields->{$class}}, $field; - - return { $class => $field }; -} - -=head2 facet_fields - - $fields = $QParser->facet_fields($class); - -Returns arrayref with list of fields for specified facet class. -=cut - -sub facet_fields { - my $class = shift; - $class = ref($class) || $class; - - $parser_config{$class}{facet_fields} ||= {}; - return $parser_config{$class}{facet_fields}; -} - -=head2 add_search_field - - $QParser->add_search_field($class, $field); - -Adds the specified field (and facet class if it doesn't already exist) -to the configuration. -=cut - -sub add_search_field { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - my $field = shift; - - $pkg->add_search_class( $class ); - - return { $class => $field } if (grep { $_ eq $field } @{$pkg->search_fields->{$class}}); - - push @{$pkg->search_fields->{$class}}, $field; - - return { $class => $field }; -} - -=head2 search_fields - - $fields = $QParser->search_fields(); - -Returns arrayref with list of configured search fields. -=cut - -sub search_fields { - my $class = shift; - $class = ref($class) || $class; - - $parser_config{$class}{fields} ||= {}; - return $parser_config{$class}{fields}; -} - -=head2 add_search_class_alias - - $QParser->add_search_class_alias($class, $alias); -=cut - -sub add_search_class_alias { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - my $alias = shift; - - $pkg->add_search_class( $class ); - - return { $class => $alias } if (grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}}); - - push @{$pkg->search_class_aliases->{$class}}, $alias; - - return { $class => $alias }; -} - -=head2 search_class_aliases - - $aliases = $QParser->search_class_aliases($class); -=cut - -sub search_class_aliases { - my $class = shift; - $class = ref($class) || $class; - - $parser_config{$class}{class_map} ||= {}; - return $parser_config{$class}{class_map}; -} - -=head2 add_search_field_alias - - $QParser->add_search_field_alias($class, $field, $alias); -=cut - -sub add_search_field_alias { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - my $field = shift; - my $alias = shift; - - return { $class => { $field => $alias } } if (grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}}); - - push @{$pkg->search_field_aliases->{$class}{$field}}, $alias; - - return { $class => { $field => $alias } }; -} - -=head2 search_field_aliases - - $aliases = $QParser->search_field_aliases(); -=cut - -sub search_field_aliases { - my $class = shift; - $class = ref($class) || $class; - - $parser_config{$class}{field_alias_map} ||= {}; - return $parser_config{$class}{field_alias_map}; -} - -=head2 remove_facet_field - - $QParser->remove_facet_field($class, $field); -=cut - -sub remove_facet_field { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - my $field = shift; - - return { $class => $field } if (!$pkg->facet_fields->{$class} || !grep { $_ eq $field } @{$pkg->facet_fields->{$class}}); - - $pkg->facet_fields->{$class} = [ grep { $_ ne $field } @{$pkg->facet_fields->{$class}} ]; - - return { $class => $field }; -} - -=head2 remove_search_field - - $QParser->remove_search_field($class, $field); -=cut - -sub remove_search_field { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - my $field = shift; - - return { $class => $field } if (!$pkg->search_fields->{$class} || !grep { $_ eq $field } @{$pkg->search_fields->{$class}}); - - $pkg->search_fields->{$class} = [ grep { $_ ne $field } @{$pkg->search_fields->{$class}} ]; - - return { $class => $field }; -} - -=head2 remove_search_field_alias - - $QParser->remove_search_field_alias($class, $field, $alias); -=cut - -sub remove_search_field_alias { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - my $field = shift; - my $alias = shift; - - return { $class => { $field => $alias } } if (!$pkg->search_field_aliases->{$class}{$field} || !grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}}); - - $pkg->search_field_aliases->{$class}{$field} = [ grep { $_ ne $alias } @{$pkg->search_field_aliases->{$class}{$field}} ]; - - return { $class => { $field => $alias } }; -} - -=head2 remove_search_class_alias - - $QParser->remove_search_class_alias($class, $alias); -=cut - -sub remove_search_class_alias { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $class = shift; - my $alias = shift; - - return { $class => $alias } if (!$pkg->search_class_aliases->{$class} || !grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}}); - - $pkg->search_class_aliases->{$class} = [ grep { $_ ne $alias } @{$pkg->search_class_aliases->{$class}} ]; - - return { $class => $alias }; -} - -=head2 debug - - $debug = $QParser->debug([$debug]); - -Return or set whether debugging output is enabled. -=cut - -sub debug { - my $self = shift; - my $q = shift; - $self->{_debug} = $q if (defined $q); - return $self->{_debug}; -} - -=head2 query - - $query = $QParser->query([$query]); - -Return or set the query. -=cut - -sub query { - my $self = shift; - my $q = shift; - $self->{_query} = " $q " if (defined $q); - return $self->{_query}; -} - -=head2 parse_tree - - $parse_tree = $QParser->parse_tree([$parse_tree]); - -Return or set the parse tree associated with the QueryParser. -=cut - -sub parse_tree { - my $self = shift; - my $q = shift; - $self->{_parse_tree} = $q if (defined $q); - return $self->{_parse_tree}; -} - -sub floating_plan { - my $self = shift; - my $q = shift; - $self->{_top} = $q if (defined $q); - return $self->{_top}; -} - -=head2 parse - - $QParser->parse([$query]); - -Parse the specified query, or the query already associated with the QueryParser -object. -=cut - -sub parse { - my $self = shift; - my $pkg = ref($self) || $self; - warn " ** parse package is $pkg\n" if $self->debug; -# $self->parse_tree( -# $self->decompose( -# $self->query( shift() ) -# ) -# ); - - undef $self->{_parse_tree}; - - $self->decompose( $self->query( shift() ) ); - - if ($self->floating_plan) { - $self->floating_plan->add_node( $self->parse_tree ); - $self->parse_tree( $self->floating_plan ); - } - - $self->parse_tree->plan_level(0); - - return $self; -} - -=head2 decompose - - ($struct, $remainder) = $QParser->decompose($querystring, [$current_class], [$recursing], [$phrase_helper]); - -This routine does the heavy work of parsing the query string recursively. -Returns the top level query plan, or the query plan from a lower level plus -the portion of the query string that needs to be processed at a higher level. -=cut - -our $last_class = ''; -our $last_type = ''; -our $floating = 0; -our $fstart; - -sub decompose { - my $self = shift; - my $pkg = ref($self) || $self; - - - $_ = shift; - my $current_class = shift || $self->default_search_class; - - my $recursing = shift || 0; - my $phrase_helper = shift || 0; - - # Build the search class+field uber-regexp - my $search_class_re = '^\s*('; - my $first_class = 1; - - warn ' 'x$recursing." ** decompose package is $pkg\n" if $self->debug; - - my %seen_classes; - for my $class ( keys %{$pkg->search_field_aliases} ) { - warn ' 'x$recursing." *** ... Looking for search fields in $class\n" if $self->debug; - - for my $field ( keys %{$pkg->search_field_aliases->{$class}} ) { - warn ' 'x$recursing." *** ... Looking for aliases of $field\n" if $self->debug; - - for my $alias ( @{$pkg->search_field_aliases->{$class}{$field}} ) { - next unless ($alias); - my $aliasr = qr/$alias/; - s/(^|\s+)$aliasr\|/$1$class\|$field#$alias\|/g; - s/(^|\s+)$aliasr[:=]/$1$class\|$field#$alias:/g; - warn ' 'x$recursing." *** Rewriting: $alias ($aliasr) as $class\|$field\n" if $self->debug; - } - } - - $search_class_re .= '|' unless ($first_class); - $first_class = 0; - $search_class_re .= $class . '(?:[|#][^:|]+)*'; - $seen_classes{$class} = 1; - } - - for my $class ( keys %{$pkg->search_class_aliases} ) { - - for my $alias ( @{$pkg->search_class_aliases->{$class}} ) { - next unless ($alias); - my $aliasr = qr/$alias/; - s/(^|[^|])\b$aliasr\|/$1$class#$alias\|/g; - s/(^|[^|])\b$aliasr[:=]/$1$class#$alias:/g; - warn ' 'x$recursing." *** Rewriting: $alias ($aliasr) as $class\n" if $self->debug; - } - - if (!$seen_classes{$class}) { - $search_class_re .= '|' unless ($first_class); - $first_class = 0; - - $search_class_re .= $class . '(?:[|#][^:|]+)*'; - $seen_classes{$class} = 1; - } - } - $search_class_re .= '):'; - - warn ' 'x$recursing." ** Rewritten query: $_\n" if $self->debug; - warn ' 'x$recursing." ** Search class RE: $search_class_re\n" if $self->debug; - - my $required_op = $pkg->operator('required'); - my $required_re = qr/\Q$required_op\E/; - - my $disallowed_op = $pkg->operator('disallowed'); - my $disallowed_re = qr/\Q$disallowed_op\E/; - - my $negated_op = $pkg->operator('negated'); - my $negated_re = qr/\Q$negated_op\E/; - - my $and_op = $pkg->operator('and'); - my $and_re = qr/^\s*\Q$and_op\E/; - - my $or_op = $pkg->operator('or'); - my $or_re = qr/^\s*\Q$or_op\E/; - - my $group_start = $pkg->operator('group_start'); - my $group_start_re = qr/^\s*($negated_re|$disallowed_re)?\Q$group_start\E/; - - my $group_end = $pkg->operator('group_end'); - my $group_end_re = qr/^\s*\Q$group_end\E/; - - my $float_start = $pkg->operator('float_start'); - my $float_start_re = qr/^\s*\Q$float_start\E/; - - my $float_end = $pkg->operator('float_end'); - my $float_end_re = qr/^\s*\Q$float_end\E/; - - my $modifier_tag = $pkg->operator('modifier'); - my $modifier_tag_re = qr/^\s*\Q$modifier_tag\E/; - - # Group start/end normally are ( and ), but can be overridden. - # We thus include ( and ) specifically due to filters, as well as : for classes. - my $phrase_cleanup_re = qr/\s*(\Q$required_op\E|\Q$disallowed_op\E|\Q$and_op\E|\Q$or_op\E|\Q$group_start\E|\Q$group_end\E|\Q$float_start\E|\Q$float_end\E|\Q$modifier_tag\E|\Q$negated_op\E|:|\(|\))/; - - # Build the filter and modifier uber-regexps - my $facet_re = '^\s*(-?)((?:' . join( '|', @{$pkg->facet_classes}) . ')(?:\|\w+)*)\[(.+?)\]'; - warn ' 'x$recursing." ** Facet RE: $facet_re\n" if $self->debug; - - my $filter_re = '^\s*(-?)(' . join( '|', @{$pkg->filters}) . ')\(([^()]+)\)'; - my $filter_as_class_re = '^\s*(-?)(' . join( '|', @{$pkg->filters}) . '):\s*(\S+)'; - - my $modifier_re = '^\s*'.$modifier_tag_re.'(' . join( '|', @{$pkg->modifiers}) . ')\b'; - my $modifier_as_class_re = '^\s*(' . join( '|', @{$pkg->modifiers}) . '):\s*(\S+)'; - - my $struct = shift || $self->new_plan( level => $recursing ); - $self->parse_tree( $struct ) if (!$self->parse_tree); - - my $remainder = ''; - - while (!$remainder) { - warn ' 'x$recursing."Start of the loop. last_type: $last_type, joiner: ".$struct->joiner.", struct: $struct\n" if $self->debug; - if ($last_type eq 'FEND' and $fstart and $fstart != $struct) { # fall back further - $remainder = $_; - last; - } elsif ($last_type eq 'FEND') { - $fstart = undef; - $last_type = ''; - } - - if (/^\s*$/) { # end of an explicit group - local $last_type = ''; - last; - } elsif (/$float_end_re/) { # end of an explicit group - warn ' 'x$recursing."Encountered explicit float end, remainder: $'\n" if $self->debug; - - $remainder = $'; - $_ = ''; - - $floating = 0; - $last_type = 'FEND'; - last; - } elsif (/$group_end_re/) { # end of an explicit group - warn ' 'x$recursing."Encountered explicit group end, remainder: $'\n" if $self->debug; - - $remainder = $'; - $_ = ''; - - local $last_type = ''; - } elsif ($self->filter_count && /$filter_re/) { # found a filter - warn ' 'x$recursing."Encountered search filter: $1$2 set to $3\n" if $self->debug; - - my $negate = ($1 eq $pkg->operator('disallowed')) ? 1 : 0; - $_ = $'; - - my $filter = $2; - my $params = [ split '[,]+', $3 ]; - - if ($pkg->filter_callbacks->{$filter}) { - my $replacement = $pkg->filter_callbacks->{$filter}->($self, $struct, $filter, $params, $negate); - $_ = "$replacement $_" if ($replacement); - } else { - $struct->new_filter( $filter => $params, $negate ); - } - - - local $last_type = ''; - } elsif ($self->filter_count && /$filter_as_class_re/) { # found a filter - warn ' 'x$recursing."Encountered search filter: $1$2 set to $3\n" if $self->debug; - - my $negate = ($1 eq $pkg->operator('disallowed')) ? 1 : 0; - $_ = $'; - - my $filter = $2; - my $params = [ split '[,]+', $3 ]; - - if ($pkg->filter_callbacks->{$filter}) { - my $replacement = $pkg->filter_callbacks->{$filter}->($self, $struct, $filter, $params, $negate); - $_ = "$replacement $_" if ($replacement); - } else { - $struct->new_filter( $filter => $params, $negate ); - } - - local $last_type = ''; - } elsif ($self->modifier_count && /$modifier_re/) { # found a modifier - warn ' 'x$recursing."Encountered search modifier: $1\n" if $self->debug; - - $_ = $'; - if (!($struct->top_plan || $parser_config{$pkg}->{allow_nested_modifiers})) { - warn ' 'x$recursing." Search modifiers only allowed at the top level of the query\n" if $self->debug; - } else { - $struct->new_modifier($1); - } - - local $last_type = ''; - } elsif ($self->modifier_count && /$modifier_as_class_re/) { # found a modifier - warn ' 'x$recursing."Encountered search modifier: $1\n" if $self->debug; - - my $mod = $1; - - $_ = $'; - if (!($struct->top_plan || $parser_config{$pkg}->{allow_nested_modifiers})) { - warn ' 'x$recursing." Search modifiers only allowed at the top level of the query\n" if $self->debug; - } elsif ($2 =~ /^[ty1]/i) { - $struct->new_modifier($mod); - } - - local $last_type = ''; - } elsif (/$float_start_re/) { # start of an explicit float - warn ' 'x$recursing."Encountered explicit float start\n" if $self->debug; - $floating = 1; - $fstart = $struct; - - $last_class = $current_class; - $current_class = undef; - - $self->floating_plan( $self->new_plan( floating => 1 ) ) if (!$self->floating_plan); - - # pass the floating_plan struct to be modified by the float'ed chunk - my ($floating_plan, $subremainder) = $self->new( debug => $self->debug )->decompose( $', undef, undef, undef, $self->floating_plan); - $_ = $subremainder; - warn ' 'x$recursing."Remainder after explicit float: $_\n" if $self->debug; - - $current_class = $last_class; - - $last_type = ''; - } elsif (/$group_start_re/) { # start of an explicit group - warn ' 'x$recursing."Encountered explicit group start\n" if $self->debug; - my $negate = $1; - my ($substruct, $subremainder) = $self->decompose( $', $current_class, $recursing + 1 ); - $substruct->negate(1) if ($substruct && $negate); - $struct->add_node( $substruct ) if ($substruct); - $_ = $subremainder; - warn ' 'x$recursing."Query remainder after bool group: $_\n" if $self->debug; - - local $last_type = ''; - - } elsif (/$and_re/) { # ANDed expression - $_ = $'; - warn ' 'x$recursing."Encountered AND\n" if $self->debug; - do {warn ' 'x$recursing."!!! Already doing the bool dance for AND\n" if $self->debug; next} if ($last_type eq 'AND'); - do {warn ' 'x$recursing."!!! Already doing the bool dance for OR\n" if $self->debug; next} if ($last_type eq 'OR'); - local $last_type = 'AND'; - - warn ' 'x$recursing."Saving LHS, building RHS\n" if $self->debug; - my $LHS = $struct; - #my ($RHS, $subremainder) = $self->decompose( "$group_start $_ $group_end", $current_class, $recursing + 1 ); - my ($RHS, $subremainder) = $self->decompose( $_, $current_class, $recursing + 1 ); - $_ = $subremainder; - - warn ' 'x$recursing."RHS built\n" if $self->debug; - warn ' 'x$recursing."Post-AND remainder: $subremainder\n" if $self->debug; - - my $wrapper = $self->new_plan( level => $recursing + 1 ); - - if ($LHS->floating) { - $wrapper->{query} = $LHS->{query}; - my $outer_wrapper = $self->new_plan( level => $recursing + 1 ); - $outer_wrapper->add_node($_) for ($wrapper,$RHS); - $LHS->{query} = [$outer_wrapper]; - $struct = $LHS; - } else { - $wrapper->add_node($_) for ($LHS, $RHS); - $wrapper->plan_level($wrapper->plan_level); # reset levels all the way down - $struct = $self->new_plan( level => $recursing ); - $struct->add_node($wrapper); - } - - $self->parse_tree( $struct ) if ($self->parse_tree == $LHS); - - local $last_type = ''; - } elsif (/$or_re/) { # ORed expression - $_ = $'; - warn ' 'x$recursing."Encountered OR\n" if $self->debug; - do {warn ' 'x$recursing."!!! Already doing the bool dance for AND\n" if $self->debug; next} if ($last_type eq 'AND'); - do {warn ' 'x$recursing."!!! Already doing the bool dance for OR\n" if $self->debug; next} if ($last_type eq 'OR'); - local $last_type = 'OR'; - - warn ' 'x$recursing."Saving LHS, building RHS\n" if $self->debug; - my $LHS = $struct; - #my ($RHS, $subremainder) = $self->decompose( "$group_start $_ $group_end", $current_class, $recursing + 1 ); - my ($RHS, $subremainder) = $self->decompose( $_, $current_class, $recursing + 2 ); - $_ = $subremainder; - - warn ' 'x$recursing."RHS built\n" if $self->debug; - warn ' 'x$recursing."Post-OR remainder: $subremainder\n" if $self->debug; - - my $wrapper = $self->new_plan( level => $recursing + 1, joiner => '|' ); - - if ($LHS->floating) { - $wrapper->{query} = $LHS->{query}; - my $outer_wrapper = $self->new_plan( level => $recursing + 1, joiner => '|' ); - $outer_wrapper->add_node($_) for ($wrapper,$RHS); - $LHS->{query} = [$outer_wrapper]; - $struct = $LHS; - } else { - $wrapper->add_node($_) for ($LHS, $RHS); - $wrapper->plan_level($wrapper->plan_level); # reset levels all the way down - $struct = $self->new_plan( level => $recursing ); - $struct->add_node($wrapper); - } - - $self->parse_tree( $struct ) if ($self->parse_tree == $LHS); - - local $last_type = ''; - } elsif ($self->facet_class_count && /$facet_re/) { # changing current class - warn ' 'x$recursing."Encountered facet: $1$2 => $3\n" if $self->debug; - - my $negate = ($1 eq $pkg->operator('disallowed')) ? 1 : 0; - my $facet = $2; - my $facet_value = [ split '\s*#\s*', $3 ]; - $struct->new_facet( $facet => $facet_value, $negate ); - $_ = $'; - - local $last_type = ''; - } elsif ($self->search_class_count && /$search_class_re/) { # changing current class - - if ($last_type eq 'CLASS') { - $struct->remove_last_node( $current_class ); - warn ' 'x$recursing."Encountered class change with no searches!\n" if $self->debug; - } - - warn ' 'x$recursing."Encountered class change: $1\n" if $self->debug; - - $current_class = $struct->classed_node( $1 )->requested_class(); - $_ = $'; - - local $last_type = 'CLASS'; - } elsif (/^\s*($required_re|$disallowed_re|$negated_re)?"([^"]+)"/) { # phrase, always anded - warn ' 'x$recursing.'Encountered' . ($1 ? " ['$1' modified]" : '') . " phrase: $2\n" if $self->debug; - - my $req_ness = $1 || ''; - $req_ness = $disallowed_op if ($req_ness eq $negated_op); - my $phrase = $2; - - if (!$phrase_helper) { - warn ' 'x$recursing."Recursing into decompose with the phrase as a subquery\n" if $self->debug; - my $after = $'; - my ($substruct, $subremainder) = $self->decompose( qq/$req_ness"$phrase"/, $current_class, $recursing + 1, 1 ); - $struct->add_node( $substruct ) if ($substruct); - $_ = $after; - } else { - warn ' 'x$recursing."Directly parsing the phrase subquery\n" if $self->debug; - $struct->joiner( '&' ); - - my $class_node = $struct->classed_node($current_class); - - if ($req_ness eq $disallowed_op) { - $class_node->negate(1); - } - $class_node->add_phrase( $phrase ); - - # Save $' before we clean up $phrase - my $temp_val = $'; - - # Cleanup the phrase to make it so that we don't parse things in it as anything other than atoms - $phrase =~ s/$phrase_cleanup_re/ /g; - - $_ = $temp_val; - - } - - local $last_type = ''; - - } elsif (/^\s*($required_re|$disallowed_re)([^${group_end}${float_end}\s"]+)/) { # convert require/disallow word to {un}phrase - warn ' 'x$recursing."Encountered required atom (mini phrase), transforming for phrase parse: $1\n" if $self->debug; - - $_ = $1 . '"' . $2 . '"' . $'; - - local $last_type = ''; - } elsif (/^\s*([^${group_end}${float_end}\s]+)/o) { # atom - warn ' 'x$recursing."Encountered atom: $1\n" if $self->debug; - warn ' 'x$recursing."Remainder: $'\n" if $self->debug; - - my $atom = $1; - my $after = $'; - - $_ = $after; - local $last_type = ''; - - my $class_node = $struct->classed_node($current_class); - - my $prefix = ($atom =~ s/^$negated_re//o) ? '!' : ''; - my $truncate = ($atom =~ s/\*$//o) ? '*' : ''; - - if ($atom ne '' and !grep { $atom =~ /^\Q$_\E+$/ } ('&','|')) { # throw away & and |, not allowed in tsquery, and not really useful anyway -# $class_node->add_phrase( $atom ) if ($atom =~ s/^$required_re//o); - - $class_node->add_fts_atom( $atom, suffix => $truncate, prefix => $prefix, node => $class_node ); - $struct->joiner( '&' ); - } - - local $last_type = ''; - } - - last unless ($_); - - } - - $struct = undef if - scalar(@{$struct->query_nodes}) == 0 && - scalar(@{$struct->filters}) == 0 && - !$struct->top_plan; - - return $struct if !wantarray; - return ($struct, $remainder); -} - -=head2 find_class_index - - $index = $QParser->find_class_index($class, $query); -=cut - -sub find_class_index { - my $class = shift; - my $query = shift; - - my ($class_part, @field_parts) = split '\|', $class; - $class_part ||= $class; - - for my $idx ( 0 .. scalar(@$query) - 1 ) { - next unless ref($$query[$idx]); - return $idx if ( $$query[$idx]{requested_class} && $class eq $$query[$idx]{requested_class} ); - } - - push(@$query, { classname => $class_part, (@field_parts ? (fields => \@field_parts) : ()), requested_class => $class, ftsquery => [], phrases => [] }); - return -1; -} - -=head2 core_limit - - $limit = $QParser->core_limit([$limit]); - -Return and/or set the core_limit. -=cut - -sub core_limit { - my $self = shift; - my $l = shift; - $self->{core_limit} = $l if ($l); - return $self->{core_limit}; -} - -=head2 superpage - - $superpage = $QParser->superpage([$superpage]); - -Return and/or set the superpage. -=cut - -sub superpage { - my $self = shift; - my $l = shift; - $self->{superpage} = $l if ($l); - return $self->{superpage}; -} - -=head2 superpage_size - - $size = $QParser->superpage_size([$size]); - -Return and/or set the superpage size. -=cut - -sub superpage_size { - my $self = shift; - my $l = shift; - $self->{superpage_size} = $l if ($l); - return $self->{superpage_size}; -} - - -#------------------------------- -package OpenILS::QueryParser::_util; - -# At this level, joiners are always & or |. This is not -# the external, configurable representation of joiners that -# defaults to # && and ||. -sub is_joiner { - my $str = shift; - - return (not ref $str and ($str eq '&' or $str eq '|')); -} - -sub default_joiner { '&' } - -# 0 for different, 1 for the same. -sub compare_abstract_atoms { - my ($left, $right) = @_; - - foreach (qw/prefix suffix content/) { - no warnings; # undef can stand in for '' here - return 0 unless $left->{$_} eq $right->{$_}; - } - - return 1; -} - -sub fake_abstract_atom_from_phrase { - my $phrase = shift; - my $neg = shift; - my $qp_class = shift || 'OpenILS::QueryParser'; - - my $prefix = '"'; - if ($neg) { - $prefix = - $OpenILS::QueryParser::parser_config{$qp_class}{operators}{disallowed} . - $prefix; - } - - return { - "type" => "atom", "prefix" => $prefix, "suffix" => '"', - "content" => $phrase - } -} - -sub find_arrays_in_abstract { - my ($hash) = @_; - - my @arrays; - foreach my $key (keys %$hash) { - if (ref $hash->{$key} eq "ARRAY") { - push @arrays, $hash->{$key}; - foreach (@{$hash->{$key}}) { - push @arrays, find_arrays_in_abstract($_); - } - } - } - - return @arrays; -} - -#------------------------------- -package OpenILS::QueryParser::Canonicalize; # not OO -use Data::Dumper; - -sub _abstract_query2str_filter { - my $f = shift; - my $qp_class = shift || 'OpenILS::QueryParser'; - my $qpconfig = $OpenILS::QueryParser::parser_config{$qp_class}; - - return sprintf( - '%s%s(%s)', - $f->{negate} ? $qpconfig->{operators}{disallowed} : "", - $f->{name}, - join(",", @{$f->{args}}) - ); -} - -sub _abstract_query2str_modifier { - my $f = shift; - my $qp_class = shift || 'OpenILS::QueryParser'; - my $qpconfig = $OpenILS::QueryParser::parser_config{$qp_class}; - - return $qpconfig->{operators}{modifier} . $f; -} - -sub _kid_list { - my $children = shift; - my $op = (keys %$children)[0]; - return @{$$children{$op}}; -} - - -# This should produce an equivalent query to the original, given an -# abstract_query. -sub abstract_query2str_impl { - my $abstract_query = shift; - my $depth = shift || 0; - - my $qp_class ||= shift || 'OpenILS::QueryParser'; - my $force_qp_node = shift || 0; - my $qpconfig = $OpenILS::QueryParser::parser_config{$qp_class}; - - my $fs = $qpconfig->{operators}{float_start}; - my $fe = $qpconfig->{operators}{float_end}; - my $gs = $qpconfig->{operators}{group_start}; - my $ge = $qpconfig->{operators}{group_end}; - my $and = $qpconfig->{operators}{and}; - my $or = $qpconfig->{operators}{or}; - my $ng = $qpconfig->{operators}{negated}; - - my $isnode = 0; - my $negate = ''; - my $size = 0; - my $q = ""; - - if (exists $abstract_query->{type}) { - if ($abstract_query->{type} eq 'query_plan') { - $q .= join(" ", map { _abstract_query2str_filter($_, $qp_class) } @{$abstract_query->{filters}}) if - exists $abstract_query->{filters}; - - $q .= ($q ? ' ' : '') . join(" ", map { _abstract_query2str_modifier($_, $qp_class) } @{$abstract_query->{modifiers}}) if - exists $abstract_query->{modifiers}; - - $size = _kid_list($abstract_query->{children}); - if ($abstract_query->{negate}) { - $isnode = 1; - $negate = $ng; - } - $isnode = 1 if ($size > 1 and ($force_qp_node or $depth)); - #warn "size: $size, depth: $depth, isnode: $isnode, AQ: ".Dumper($abstract_query); - } elsif ($abstract_query->{type} eq 'node') { - if ($abstract_query->{alias}) { - $q .= ($q ? ' ' : '') . $abstract_query->{alias}; - $q .= "|$_" foreach @{$abstract_query->{alias_fields}}; - } else { - $q .= ($q ? ' ' : '') . $abstract_query->{class}; - $q .= "|$_" foreach @{$abstract_query->{fields}}; - } - $q .= ":"; - $isnode = 1; - } elsif ($abstract_query->{type} eq 'atom') { - my $prefix = $abstract_query->{prefix} || ''; - $prefix = $qpconfig->{operators}{negated} if $prefix eq '!'; - $q .= ($q ? ' ' : '') . $prefix . - ($abstract_query->{content} || '') . - ($abstract_query->{suffix} || ''); - } elsif ($abstract_query->{type} eq 'facet') { - # facet syntax [ # ] is hardcoded I guess? - my $prefix = $abstract_query->{negate} ? $qpconfig->{operators}{disallowed} : ''; - $q .= ($q ? ' ' : '') . $prefix . $abstract_query->{name} . "[" . - join(" # ", @{$abstract_query->{values}}) . "]"; - } - } - - my $next_depth = int($size > 1); - - if (exists $abstract_query->{children}) { - - my $op = (keys(%{$abstract_query->{children}}))[0]; - - if ($abstract_query->{floating}) { # always the top node! - my $sub_node = pop @{$abstract_query->{children}{$op}}; - - $abstract_query->{floating} = 0; - $q = $fs . " " . abstract_query2str_impl($abstract_query,0,$qp_class, 1) . $fe. " "; - - $abstract_query = $sub_node; - } - - if ($abstract_query && exists $abstract_query->{children}) { - $op = (keys(%{$abstract_query->{children}}))[0]; - $q .= ($q ? ' ' : '') . join( - ($op eq '&' ? ' ' : " $or "), - map { - my $x = abstract_query2str_impl($_, $depth + $next_depth, $qp_class, $force_qp_node); $x =~ s/^\s+//; $x =~ s/\s+$//; $x; - } @{$abstract_query->{children}{$op}} - ); - } - } elsif ($abstract_query->{'&'} or $abstract_query->{'|'}) { - my $op = (keys(%{$abstract_query}))[0]; - $q .= ($q ? ' ' : '') . join( - ($op eq '&' ? ' ' : " $or "), - map { - my $x = abstract_query2str_impl($_, $depth + $next_depth, $qp_class, $force_qp_node); $x =~ s/^\s+//; $x =~ s/\s+$//; $x; - } @{$abstract_query->{$op}} - ); - } - - $q = "$gs$q$ge" if ($isnode); - $q = $negate . $q if ($q);; - - return $q; -} - -#------------------------------- -package OpenILS::QueryParser::query_plan; - -sub QueryParser { - my $self = shift; - return unless ref($self); - return $self->{QueryParser}; -} - -sub new { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my %args = (query => [], joiner => '&', @_); - - return bless \%args => $pkg; -} - -sub new_node { - my $self = shift; - my $pkg = ref($self) || $self; - my $node = do{$pkg.'::node'}->new( plan => $self, @_ ); - $self->add_node( $node ); - return $node; -} - -sub new_facet { - my $self = shift; - my $pkg = ref($self) || $self; - my $name = shift; - my $args = shift; - my $negate = shift; - - my $node = do{$pkg.'::facet'}->new( plan => $self, name => $name, 'values' => $args, negate => $negate ); - $self->add_node( $node ); - - return $node; -} - -sub new_filter { - my $self = shift; - my $pkg = ref($self) || $self; - my $name = shift; - my $args = shift; - my $negate = shift; - - my $node = do{$pkg.'::filter'}->new( plan => $self, name => $name, args => $args, negate => $negate ); - $self->add_filter( $node ); - - return $node; -} - - -sub _merge_filters { - my $left_filter = shift; - my $right_filter = shift; - my $join = shift; - - return unless $left_filter or $right_filter; - return $right_filter unless $left_filter; - return $left_filter unless $right_filter; - - my $args = $left_filter->{args} || []; - - if ($join eq '|') { - push(@$args, @{$right_filter->{args}}); - - } else { - # find the intersect values - my %new_vals; - map { $new_vals{$_} = 1 } @{$right_filter->{args} || []}; - $args = [ grep { $new_vals{$_} } @$args ]; - } - - $left_filter->{args} = $args; - return $left_filter; -} - -sub collapse_filters { - my $self = shift; - my $name = shift; - - # start by merging any filters at this level. - # like-level filters are always ORed together - - my $cur_filter; - my @cur_filters = grep {$_->name eq $name } @{ $self->filters }; - if (@cur_filters) { - $cur_filter = shift @cur_filters; - my $args = $cur_filter->{args} || []; - $cur_filter = _merge_filters($cur_filter, $_, '|') for @cur_filters; - } - - # next gather the collapsed filters from sub-plans and - # merge them with our own - - my @subquery = @{$self->{query}}; - - while (@subquery) { - my $blob = shift @subquery; - shift @subquery; # joiner - next unless $blob->isa('OpenILS::QueryParser::query_plan'); - my $sub_filter = $blob->collapse_filters($name); - $cur_filter = _merge_filters($cur_filter, $sub_filter, $self->joiner); - } - - if ($self->QueryParser->debug) { - my @args = ($cur_filter and $cur_filter->{args}) ? @{$cur_filter->{args}} : (); - warn "collapse_filters($name) => [@args]\n"; - } - - return $cur_filter; -} - -sub find_filter { - my $self = shift; - my $needle = shift;; - return unless ($needle); - - my $filter = $self->collapse_filters($needle); - - warn "find_filter($needle) => " . - (($filter and $filter->{args}) ? "@{$filter->{args}}" : '[]') . "\n" - if $self->QueryParser->debug; - - return $filter ? ($filter) : (); -} - -sub find_modifier { - my $self = shift; - my $needle = shift;; - return unless ($needle); - return grep { $_->name eq $needle } @{ $self->modifiers }; -} - -sub new_modifier { - my $self = shift; - my $pkg = ref($self) || $self; - my $name = shift; - - my $node = do{$pkg.'::modifier'}->new( $name ); - $self->add_modifier( $node ); - - return $node; -} - -sub classed_node { - my $self = shift; - my $requested_class = shift; - - my $node; - for my $n (@{$self->{query}}) { - next unless (ref($n) && $n->isa( 'OpenILS::QueryParser::query_plan::node' )); - if ($n->requested_class eq $requested_class) { - $node = $n; - last; - } - } - - if (!$node) { - $node = $self->new_node; - $node->requested_class( $requested_class ); - } - - return $node; -} - -sub remove_last_node { - my $self = shift; - my $requested_class = shift; - - my $old = pop(@{$self->query_nodes}); - pop(@{$self->query_nodes}) if (@{$self->query_nodes}); - - return $old; -} - -sub query_nodes { - my $self = shift; - return $self->{query}; -} - -sub floating { - my $self = shift; - my $f = shift; - $self->{floating} = $f if (defined $f); - return $self->{floating}; -} - -sub add_node { - my $self = shift; - my $node = shift; - - $self->{query} ||= []; - push(@{$self->{query}}, $self->joiner) if (@{$self->{query}}); - push(@{$self->{query}}, $node); - - return $self; -} - -sub top_plan { - my $self = shift; - - return $self->{level} ? 0 : 1; -} - -sub plan_level { - my $self = shift; - my $level = shift; - - if (defined $level) { - $self->{level} = $level; - for (@{$self->query_nodes}) { - $_->plan_level($level + 1) if (ref and $_->isa('OpenILS::QueryParser::query_plan')); - } - } - - return $self->{level}; -} - -sub joiner { - my $self = shift; - my $joiner = shift; - - $self->{joiner} = $joiner if ($joiner); - return $self->{joiner}; -} - -sub modifiers { - my $self = shift; - $self->{modifiers} ||= []; - return $self->{modifiers}; -} - -sub add_modifier { - my $self = shift; - my $modifier = shift; - - $self->{modifiers} ||= []; - $self->{modifiers} = [ grep {$_->name ne $modifier->name} @{$self->{modifiers}} ]; - - push(@{$self->{modifiers}}, $modifier); - - return $self; -} - -sub facets { - my $self = shift; - $self->{facets} ||= []; - return $self->{facets}; -} - -sub add_facet { - my $self = shift; - my $facet = shift; - - $self->{facets} ||= []; - $self->{facets} = [ grep {$_->name ne $facet->name} @{$self->{facets}} ]; - - push(@{$self->{facets}}, $facet); - - return $self; -} - -sub filters { - my $self = shift; - $self->{filters} ||= []; - return $self->{filters}; -} - -sub add_filter { - my $self = shift; - my $filter = shift; - - $self->{filters} ||= []; - - push(@{$self->{filters}}, $filter); - - return $self; -} - -sub negate { - my $self = shift; - my $negate = shift; - - $self->{negate} = $negate if (defined $negate); - - return $self->{negate}; -} - -# %opts supports two options at this time: -# no_phrases : -# If true, do not do anything to the phrases -# fields on any discovered nodes. -# with_config : -# If true, also return the query parser config as part of the blob. -# This will get set back to 0 before recursion to avoid repetition. -sub to_abstract_query { - my $self = shift; - my %opts = @_; - - my $pkg = ref $self->QueryParser || $self->QueryParser; - - my $abstract_query = { - type => "query_plan", - floating => $self->floating, - level => $self->plan_level, - filters => [map { $_->to_abstract_query } @{$self->filters}], - modifiers => [map { $_->to_abstract_query } @{$self->modifiers}], - negate => $self->negate - }; - - if ($opts{with_config}) { - $opts{with_config} = 0; - $abstract_query->{config} = $OpenILS::QueryParser::parser_config{$pkg}; - } - - my $kids = []; - - for my $qnode (@{$self->query_nodes}) { - # Remember: qnode can be a joiner string, a node, or another query_plan - - if (OpenILS::QueryParser::_util::is_joiner($qnode)) { - if ($abstract_query->{children}) { - my $open_joiner = (keys(%{$abstract_query->{children}}))[0]; - next if $open_joiner eq $qnode; - - my $oldroot = $abstract_query->{children}; - $kids = [$oldroot]; - $abstract_query->{children} = {$qnode => $kids}; - } else { - $abstract_query->{children} = {$qnode => $kids}; - } - } else { - push @$kids, $qnode->to_abstract_query(%opts); - } - } - - $abstract_query->{children} ||= { OpenILS::QueryParser::_util::default_joiner() => $kids }; - return $abstract_query; -} - - -#------------------------------- -package OpenILS::QueryParser::query_plan::node; -use Data::Dumper; -$Data::Dumper::Indent = 0; - -sub new { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my %args = @_; - - return bless \%args => $pkg; -} - -sub new_atom { - my $self = shift; - my $pkg = ref($self) || $self; - return do{$pkg.'::atom'}->new( @_ ); -} - -sub requested_class { # also split into classname, fields and alias - my $self = shift; - my $class = shift; - - if ($class) { - my @afields; - my (undef, $alias) = split '#', $class; - if ($alias) { - $class =~ s/#[^|]+//; - ($alias, @afields) = split '\|', $alias; - } - - my @fields = @afields; - my ($class_part, @field_parts) = split '\|', $class; - for my $f (@field_parts) { - push(@fields, $f) unless (grep { $f eq $_ } @fields); - } - - $class_part ||= $class; - - $self->{requested_class} = $class; - $self->{alias} = $alias if $alias; - $self->{alias_fields} = \@afields if $alias; - $self->{classname} = $class_part; - $self->{fields} = \@fields; - } - - return $self->{requested_class}; -} - -sub plan { - my $self = shift; - my $plan = shift; - - $self->{plan} = $plan if ($plan); - return $self->{plan}; -} - -sub alias { - my $self = shift; - my $alias = shift; - - $self->{alias} = $alias if ($alias); - return $self->{alias}; -} - -sub alias_fields { - my $self = shift; - my $alias = shift; - - $self->{alias_fields} = $alias if ($alias); - return $self->{alias_fields}; -} - -sub classname { - my $self = shift; - my $class = shift; - - $self->{classname} = $class if ($class); - return $self->{classname}; -} - -sub fields { - my $self = shift; - my @fields = @_; - - $self->{fields} ||= []; - $self->{fields} = \@fields if (@fields); - return $self->{fields}; -} - -sub phrases { - my $self = shift; - my @phrases = @_; - - $self->{phrases} ||= []; - $self->{phrases} = \@phrases if (@phrases); - return $self->{phrases}; -} - -sub add_phrase { - my $self = shift; - my $phrase = shift; - - push(@{$self->phrases}, $phrase); - - return $self; -} - -sub negate { - my $self = shift; - my $negate = shift; - - $self->{negate} = $negate if (defined $negate); - - return $self->{negate}; -} - -sub query_atoms { - my $self = shift; - my @query_atoms = @_; - - $self->{query_atoms} ||= []; - $self->{query_atoms} = \@query_atoms if (@query_atoms); - return $self->{query_atoms}; -} - -sub add_fts_atom { - my $self = shift; - my $atom = shift; - - if (!ref($atom)) { - my $content = $atom; - my @parts = @_; - - $atom = $self->new_atom( content => $content, @parts ); - } - - push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms}); - push(@{$self->query_atoms}, $atom); - - return $self; -} - -sub add_dummy_atom { - my $self = shift; - my @parts = @_; - - my $atom = $self->new_atom( @parts, dummy => 1 ); - - push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms}); - push(@{$self->query_atoms}, $atom); - - return $self; -} - -# This will find up to one occurence of @$short_list within @$long_list, and -# replace it with the single atom $replacement. -sub replace_phrase_in_abstract_query { - my ($self, $short_list, $long_list, $replacement) = @_; - - my $success = 0; - my @already = (); - my $goal = scalar @$short_list; - - for (my $i = 0; $i < scalar (@$long_list); $i++) { - my $right = $long_list->[$i]; - - if (OpenILS::QueryParser::_util::compare_abstract_atoms( - $short_list->[scalar @already], $right - )) { - push @already, $i; - } elsif (scalar @already) { - @already = (); - next; - } - - if (scalar @already == $goal) { - splice @$long_list, $already[0], scalar(@already), $replacement; - $success = 1; - last; - } - } - - return $success; -} - -sub to_abstract_query { - my $self = shift; - my %opts = @_; - - my $pkg = ref $self->plan->QueryParser || $self->plan->QueryParser; - - my $abstract_query = { - "type" => "node", - "alias" => $self->alias, - "alias_fields" => $self->alias_fields, - "class" => $self->classname, - "fields" => $self->fields - }; - - my $kids = []; - - for my $qatom (@{$self->query_atoms}) { - if (OpenILS::QueryParser::_util::is_joiner($qatom)) { - if ($abstract_query->{children}) { - my $open_joiner = (keys(%{$abstract_query->{children}}))[0]; - next if $open_joiner eq $qatom; - - my $oldroot = $abstract_query->{children}; - $kids = [$oldroot]; - $abstract_query->{children} = {$qatom => $kids}; - } else { - $abstract_query->{children} = {$qatom => $kids}; - } - } else { - push @$kids, $qatom->to_abstract_query; - } - } - - $abstract_query->{children} ||= { OpenILS::QueryParser::_util::default_joiner() => $kids }; - - if ($self->{phrases} and not $opts{no_phrases}) { - for my $phrase (@{$self->{phrases}}) { - # Phrases appear duplication in a real QP tree, and we don't want - # that duplication in our abstract query. So for all our phrases, - # break them into atoms as QP would, and remove any matching - # sequences of atoms from our abstract query. - - my $tmp_prefix = ''; - $tmp_prefix = $OpenILS::QueryParser::parser_config{$pkg}{operators}{disallowed} if ($self->{negate}); - - my $tmptree = $self->{plan}->{QueryParser}->new(query => $tmp_prefix.'"'.$phrase.'"')->parse->parse_tree; - if ($tmptree) { - # For a well-behaved phrase, we should now have only one node - # in the $tmptree query plan, and that node should have an - # orderly list of atoms and joiners. - - if ($tmptree->{query} and scalar(@{$tmptree->{query}}) == 1) { - my $tmplist; - - eval { - $tmplist = $tmptree->{query}->[0]->to_abstract_query( - no_phrases => 1 - )->{children}->{'&'}->[0]->{children}->{'&'}; - }; - next if $@; - - foreach ( - OpenILS::QueryParser::_util::find_arrays_in_abstract($abstract_query->{children}) - ) { - last if $self->replace_phrase_in_abstract_query( - $tmplist, - $_, - OpenILS::QueryParser::_util::fake_abstract_atom_from_phrase($phrase, $self->{negate}, $pkg) - ); - } - } - } - } - } - - $abstract_query->{children} ||= { OpenILS::QueryParser::_util::default_joiner() => $kids }; - return $abstract_query; -} - -#------------------------------- -package OpenILS::QueryParser::query_plan::node::atom; - -sub new { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my %args = @_; - - return bless \%args => $pkg; -} - -sub node { - my $self = shift; - return unless (ref $self); - return $self->{node}; -} - -sub content { - my $self = shift; - return unless (ref $self); - return $self->{content}; -} - -sub prefix { - my $self = shift; - return unless (ref $self); - return $self->{prefix}; -} - -sub suffix { - my $self = shift; - return unless (ref $self); - return $self->{suffix}; -} - -sub to_abstract_query { - my ($self) = @_; - - return { - (map { $_ => $self->$_ } qw/prefix suffix content/), - "type" => "atom" - }; -} -#------------------------------- -package OpenILS::QueryParser::query_plan::filter; - -sub new { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my %args = @_; - - return bless \%args => $pkg; -} - -sub plan { - my $self = shift; - return $self->{plan}; -} - -sub name { - my $self = shift; - return $self->{name}; -} - -sub negate { - my $self = shift; - return $self->{negate}; -} - -sub args { - my $self = shift; - return $self->{args}; -} - -sub to_abstract_query { - my ($self) = @_; - - return { - map { $_ => $self->$_ } qw/name negate args/ - }; -} - -#------------------------------- -package OpenILS::QueryParser::query_plan::facet; - -sub new { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my %args = @_; - - return bless \%args => $pkg; -} - -sub plan { - my $self = shift; - return $self->{plan}; -} - -sub name { - my $self = shift; - return $self->{name}; -} - -sub negate { - my $self = shift; - return $self->{negate}; -} - -sub values { - my $self = shift; - return $self->{'values'}; -} - -sub to_abstract_query { - my ($self) = @_; - - return { - (map { $_ => $self->$_ } qw/name negate values/), - "type" => "facet" - }; -} - -#------------------------------- -package OpenILS::QueryParser::query_plan::modifier; - -sub new { - my $pkg = shift; - $pkg = ref($pkg) || $pkg; - my $modifier = shift; - my $negate = shift; - - return bless { name => $modifier, negate => $negate } => $pkg; -} - -sub name { - my $self = shift; - return $self->{name}; -} - -sub negate { - my $self = shift; - return $self->{negate}; -} - -sub to_abstract_query { - my ($self) = @_; - - return $self->name; -} -1; --- a/about.pl +++ a/about.pl @@ -230,35 +230,6 @@ if ( ! C4::Context->config('tmp_path') ) { } } -# Test QueryParser configuration sanity -if ( C4::Context->preference( 'UseQueryParser' ) ) { - # Get the QueryParser configuration file name - my $queryparser_file = C4::Context->config( 'queryparser_config' ); - my $queryparser_fallback_file = '/etc/koha/searchengine/queryparser.yaml'; - # Check QueryParser is functional - my $QParser = C4::Context->queryparser(); - my $queryparser_error = {}; - if ( ! defined $QParser || ref($QParser) ne 'Koha::QueryParser::Driver::PQF' ) { - # Error initializing the QueryParser object - # Get the used queryparser.yaml file path to report the user - $queryparser_error->{ fallback } = ( defined $queryparser_file ) ? 0 : 1; - $queryparser_error->{ file } = ( defined $queryparser_file ) - ? $queryparser_file - : $queryparser_fallback_file; - # Report error data to the template - $template->param( QueryParserError => $queryparser_error ); - } else { - # Check for an absent queryparser_config entry in koha-conf.xml - if ( ! defined $queryparser_file ) { - # Not an error but a warning for the missing entry in koha-conf-xml - push @xml_config_warnings, { - error => 'queryparser_entry_missing', - file => $queryparser_fallback_file - }; - } - } -} - # Test Zebra facets configuration if ( !defined C4::Context->config('use_zebra_facets') ) { push @xml_config_warnings, { error => 'use_zebra_facets_entry_missing' }; --- a/cataloguing/addbooks.pl +++ a/cataloguing/addbooks.pl @@ -68,21 +68,14 @@ if ($query) { # build query my @operands = $query; - my $QParser; - $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser')); my $builtquery; my $query_cgi; my $builder = Koha::SearchEngine::QueryBuilder->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } ); my $searcher = Koha::SearchEngine::Search->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } ); - if ($QParser) { - $builtquery = $query; - $query_cgi = "q=".uri_escape_utf8($query); - } else { - ( undef, $builtquery, undef, $query_cgi, undef, undef, undef, undef, undef, undef ) = - $builder->build_query_compat( undef, \@operands, undef, undef, undef, 0, $lang ); - } + ( undef, $builtquery, undef, $query_cgi, undef, undef, undef, undef, undef, undef ) = + $builder->build_query_compat( undef, \@operands, undef, undef, undef, 0, $lang ); $template->param( search_query => $builtquery ) if C4::Context->preference('DumpSearchQueryTemplate'); --- a/cataloguing/value_builder/marc21_linking_section.pl +++ a/cataloguing/value_builder/marc21_linking_section.pl @@ -172,15 +172,8 @@ my $launcher = sub { my $startfrom = $query->param('startfrom'); my $resultsperpage = $query->param('resultsperpage') || 20; my $orderby; - my $QParser; - $QParser = C4::Context->queryparser if ( C4::Context->preference('UseQueryParser') ); - my $op; + my $op = 'and'; - if ($QParser) { - $op = '&&'; - } else { - $op = 'and'; - } my $searcher = Koha::SearchEngine::Search->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } ); $search = 'kw:' . $search . " $op mc-itemtype:" . $itype if $itype; --- a/cataloguing/value_builder/unimarc_field_4XX.pl +++ a/cataloguing/value_builder/unimarc_field_4XX.pl @@ -359,14 +359,7 @@ sub plugin { my $startfrom = $query->param('startfrom'); my $resultsperpage = $query->param('resultsperpage') || 20; my $orderby; - my $QParser; - $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser')); - my $op; - if ($QParser) { - $op = '&&'; - } else { - $op = 'and'; - } + my $op = 'and'; $search = 'kw:'.$search." $op mc-itemtype:".$itype if $itype; my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX}); my ( $errors, $results, $total_hits ) = $searcher->simple_search_compat($search, $startfrom * $resultsperpage, $resultsperpage ); --- a/debian/templates/koha-conf-site.xml.in +++ a/debian/templates/koha-conf-site.xml.in @@ -304,7 +304,6 @@ __END_SRU_PUBLICSERVER__ /var/lock/koha/__KOHASITE__ 1 1024 - /etc/koha/searchengine/queryparser.yaml __KOHA_CONF_DIR__/log4perl.conf