From 2900bd14dcd02bf9ac9912c7e620c8e67d62c01c Mon Sep 17 00:00:00 2001 From: Jared Camins-Esakov Date: Tue, 2 Oct 2012 17:06:47 -0400 Subject: [PATCH] Bug 9239: Introduce QueryParser driver for PQF Since the most expressive query language supported by Zebra is PQF, this patch adds a PQF driver for QueryParser which will translate QueryParser queries into standard PQF (guided by mappings which have been written to match Koha's existing Zebra configuration) which can then be sent to Zebra. This driver, Koha::QueryParser::Driver::PQF(::*) extends the OpenILS::QueryParser(::*) class(es), so as to preserve maximum interoperability between the various users of the QueryParser driver. Initially, search syntax is as follows: * AND operator: && * OR operator: || * GROUPING operators: ( ) Fields can mostly be searched using the ccl prefixes they have now. The exception is the various date limits which are searched with a syntax like this: pubdate(2008) For sorting, you can simply add #title-sort-az (etc.) to your query. Signed-off-by: Chris Cormack Signed-off-by: Elliott Davis Test Passed successfully after installing missing dep for Test::Deep Signed-off-by: Katrin Fischer Signed-off-by: Jared Camins-Esakov --- Koha/QueryParser/Driver/PQF.pm | 913 ++++++++++ Koha/QueryParser/Driver/PQF/Util.pm | 22 + Koha/QueryParser/Driver/PQF/query_plan.pm | 46 + .../Driver/PQF/query_plan/facet.pm | 22 + .../Driver/PQF/query_plan/filter.pm | 27 + .../Driver/PQF/query_plan/modifier.pm | 27 + .../QueryParser/Driver/PQF/query_plan/node.pm | 76 + .../Driver/PQF/query_plan/node/atom.pm | 22 + etc/searchengine/queryparser.yaml | 1487 +++++++++++++++++ t/QueryParser.t | 95 ++ 10 files changed, 2737 insertions(+) create mode 100644 Koha/QueryParser/Driver/PQF.pm create mode 100644 Koha/QueryParser/Driver/PQF/Util.pm create mode 100644 Koha/QueryParser/Driver/PQF/query_plan.pm create mode 100644 Koha/QueryParser/Driver/PQF/query_plan/facet.pm create mode 100644 Koha/QueryParser/Driver/PQF/query_plan/filter.pm create mode 100644 Koha/QueryParser/Driver/PQF/query_plan/modifier.pm create mode 100644 Koha/QueryParser/Driver/PQF/query_plan/node.pm create mode 100644 Koha/QueryParser/Driver/PQF/query_plan/node/atom.pm create mode 100644 etc/searchengine/queryparser.yaml create mode 100644 t/QueryParser.t diff --git a/Koha/QueryParser/Driver/PQF.pm b/Koha/QueryParser/Driver/PQF.pm new file mode 100644 index 0000000000..3007333692 --- /dev/null +++ b/Koha/QueryParser/Driver/PQF.pm @@ -0,0 +1,913 @@ +package Koha::QueryParser::Driver::PQF; +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 ); + $self->add_search_field_alias( $class => $field => $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 ); + } + $self->add_search_field_alias( $class => $field => + $field_mappings->{$class}->{$field}->{'index'} ); + foreach my $alias ( + @{ $field_mappings->{$class}->{$field}->{'aliases'} } ) + { + next + if ( $alias eq + $field_mappings->{$class}->{$field}->{'index'} ); + $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' => '9013 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; diff --git a/Koha/QueryParser/Driver/PQF/Util.pm b/Koha/QueryParser/Driver/PQF/Util.pm new file mode 100644 index 0000000000..f2e018bb35 --- /dev/null +++ b/Koha/QueryParser/Driver/PQF/Util.pm @@ -0,0 +1,22 @@ +package Koha::QueryParser::Driver::PQF::Util; +use Scalar::Util qw(looks_like_number); + +use strict; +use warnings; + +sub attributes_to_attr_string { + my ($attributes) = @_; + my $attr_string = ''; + my $key; + my $value; + while (($key, $value) = each(%$attributes)) { + next unless looks_like_number($key); + $attr_string .= ' @attr ' . $key . '=' . $value . ' '; + } + $attr_string =~ s/^\s*//; + $attr_string =~ s/\s*$//; + $attr_string .= ' ' . $attributes->{''} if defined $attributes->{''}; + return $attr_string; +} + +1; diff --git a/Koha/QueryParser/Driver/PQF/query_plan.pm b/Koha/QueryParser/Driver/PQF/query_plan.pm new file mode 100644 index 0000000000..5435fe6ae3 --- /dev/null +++ b/Koha/QueryParser/Driver/PQF/query_plan.pm @@ -0,0 +1,46 @@ +package Koha::QueryParser::Driver::PQF::query_plan; +use base 'OpenILS::QueryParser::query_plan'; + +use strict; +use warnings; + +=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; + $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; + 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; diff --git a/Koha/QueryParser/Driver/PQF/query_plan/facet.pm b/Koha/QueryParser/Driver/PQF/query_plan/facet.pm new file mode 100644 index 0000000000..0407e5807c --- /dev/null +++ b/Koha/QueryParser/Driver/PQF/query_plan/facet.pm @@ -0,0 +1,22 @@ +package Koha::QueryParser::Driver::PQF::query_plan::facet; +use base 'OpenILS::QueryParser::query_plan::facet'; + +use strict; +use warnings; + +=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; diff --git a/Koha/QueryParser/Driver/PQF/query_plan/filter.pm b/Koha/QueryParser/Driver/PQF/query_plan/filter.pm new file mode 100644 index 0000000000..c632eeec64 --- /dev/null +++ b/Koha/QueryParser/Driver/PQF/query_plan/filter.pm @@ -0,0 +1,27 @@ +package Koha::QueryParser::Driver::PQF::query_plan::filter; +use base 'OpenILS::QueryParser::query_plan::filter'; + +use strict; +use warnings; + +=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; diff --git a/Koha/QueryParser/Driver/PQF/query_plan/modifier.pm b/Koha/QueryParser/Driver/PQF/query_plan/modifier.pm new file mode 100644 index 0000000000..c3a66e4c12 --- /dev/null +++ b/Koha/QueryParser/Driver/PQF/query_plan/modifier.pm @@ -0,0 +1,27 @@ +package Koha::QueryParser::Driver::PQF::query_plan::modifier; +use base 'OpenILS::QueryParser::query_plan::modifier'; + +use strict; +use warnings; + +=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 @fields; + + 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; diff --git a/Koha/QueryParser/Driver/PQF/query_plan/node.pm b/Koha/QueryParser/Driver/PQF/query_plan/node.pm new file mode 100644 index 0000000000..62417a17f2 --- /dev/null +++ b/Koha/QueryParser/Driver/PQF/query_plan/node.pm @@ -0,0 +1,76 @@ +package Koha::QueryParser::Driver::PQF::query_plan::node; +use base 'OpenILS::QueryParser::query_plan::node'; + +use strict; +use warnings; + +=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) { + $fieldobj->{'attr_string'} .= ' ' . $relbump->{'attr_string'}; + } + push @fields, $fieldobj unless ($field eq $self->classname && scalar @fields); + } + } 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) { + $pqf .= ' @or ' x (scalar(@fields) - 1); + foreach my $attributes (@fields) { + $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) { + $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; + return ($self->negate ? '@not @attr 1=_ALLRECORDS @attr 2=103 "" ' : '') . $pqf; +} + +1; diff --git a/Koha/QueryParser/Driver/PQF/query_plan/node/atom.pm b/Koha/QueryParser/Driver/PQF/query_plan/node/atom.pm new file mode 100644 index 0000000000..afdb4c433a --- /dev/null +++ b/Koha/QueryParser/Driver/PQF/query_plan/node/atom.pm @@ -0,0 +1,22 @@ +package Koha::QueryParser::Driver::PQF::query_plan::node::atom; +use base 'OpenILS::QueryParser::query_plan::node::atom'; + +use strict; +use warnings; + +=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) = @_; + + return ' "' . $self->content . '" '; +} + +1; diff --git a/etc/searchengine/queryparser.yaml b/etc/searchengine/queryparser.yaml new file mode 100644 index 0000000000..94f9d8ea87 --- /dev/null +++ b/etc/searchengine/queryparser.yaml @@ -0,0 +1,1487 @@ +--- +field_mappings: + author: + 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 + corporate: + aliases: + - corporate + - cpn + bib1_mapping: + biblioserver: + 1: 1005 + enabled: 1 + index: corporate + label: Corporate + exactauthor: + aliases: + - exactauthor + - au,ext + bib1_mapping: + biblioserver: + 1: 1003 + 4: 1 + 6: 3 + enabled: 1 + index: exactauthor + label: Exactauthor + personal: + aliases: + - personal + bib1_mapping: + biblioserver: + 1: 1004 + enabled: 1 + index: personal + label: Personal + keyword: + keyword: + aliases: + - any + - kw + bib1_mapping: + authorityserver: + 1: Any + biblioserver: + 1: 1016 + enabled: 1 + index: 'keyword' + label: 'Keyword' + abstract: + aliases: + - abstract + - ab + bib1_mapping: + biblioserver: + 1: 62 + enabled: 1 + index: abstract + label: Abstract + acqsource: + aliases: + - acqsource + bib1_mapping: + biblioserver: + 1: 8015 + enabled: 1 + index: acqsource + label: Acqsource + alwaysmatch: + aliases: + - alwaysmatch + - all + bib1_mapping: + authorityserver: + 1: _ALLRECORDS + 2: 103 + biblioserver: + 1: _ALLRECORDS + 2: 103 + enabled: 1 + index: alwaysmatch + label: Alwaysmatch + arl: + aliases: + - arl + bib1_mapping: + biblioserver: + 1: 9904 r=r + enabled: 1 + index: arl + label: Arl + arp: + aliases: + - arp + bib1_mapping: + biblioserver: + 1: 9013 r=r + enabled: 1 + index: arp + label: Arp + audience: + aliases: + - audience + - aud + bib1_mapping: + biblioserver: + 1: 8822 + enabled: 1 + index: audience + label: Audience + authtype: + aliases: + - authtype + - at + bib1_mapping: + authorityserver: + 1: authtype + 5: 100 + enabled: 1 + index: authtype + label: Authtype + authorext: + alias: [] + bib1_mapping: + biblioserver: + 1: 1003 + 4: 1 + 6: 2 + enabled: 1 + index: author + label: Authorkw + authorkw: + alias: [] + bib1_mapping: + biblioserver: + 1: 1003 + enabled: 1 + index: author + label: Authorkw + barcode: + aliases: + - barcode + - bc + bib1_mapping: + biblioserver: + 1: 8023 + enabled: 1 + index: barcode + label: Barcode + bib-level: + bib1_mapping: + biblioserver: + 1: 1021 + enabled: 1 + index: bib-level + aliases: + - bib-level + label: Bib-level + biblionumber: + bib1_mapping: + biblioserver: + 1: 9002 + 5: 100 + enabled: 1 + index: biblionumber + aliases: + - biblionumber + label: Biblionumber + biography: + bib1_mapping: + biblioserver: + 1: 8834 + enabled: 1 + index: biography + aliases: + - biography + - bio + label: Biography + biography-code: + bib1_mapping: + biblioserver: + 1: Biography-code + enabled: 1 + index: biography-code + aliases: + - biography-code + label: Biography-code + ccode: + bib1_mapping: + biblioserver: + 1: 8009 + enabled: 1 + index: ccode + aliases: + - ccode + - mc-ccode + label: Ccode + classification-source: + bib1_mapping: + biblioserver: + 1: 8003 + enabled: 1 + index: classification-source + aliases: + - classification-source + label: Classification-source + cn-bib-sort: + bib1_mapping: + biblioserver: + 1: 9005 + enabled: 1 + index: cn-bib-sort + aliases: + - cn-bib-sort + label: Cn-bib-sort + cn-bib-source: + bib1_mapping: + biblioserver: + 1: 9004 + enabled: 1 + index: cn-bib-source + aliases: + - cn-bib-source + label: Cn-bib-source + cn-class: + bib1_mapping: + biblioserver: + 1: 9007 + enabled: 1 + index: cn-class + aliases: + - cn-class + label: Cn-class + cn-item: + bib1_mapping: + biblioserver: + 1: 9008 + enabled: 1 + index: cn-item + aliases: + - cn-item + label: Cn-item + cn-prefix: + bib1_mapping: + biblioserver: + 1: 9009 + enabled: 1 + index: cn-prefix + aliases: + - cn-prefix + label: Cn-prefix + cn-sort: + bib1_mapping: + biblioserver: + 1: 8007 + enabled: 1 + index: cn-sort + aliases: + - cn-sort + label: Cn-sort + cn-suffix: + bib1_mapping: + biblioserver: + 1: 9010 + enabled: 1 + index: cn-suffix + aliases: + - cn-suffix + label: Cn-suffix + code-institution: + bib1_mapping: + biblioserver: + 1: 56 + enabled: 1 + index: code-institution + aliases: + - code-institution + label: Code-institution + coded-location-qualifier: + bib1_mapping: + biblioserver: + 1: 8016 + enabled: 1 + index: coded-location-qualifier + aliases: + - coded-location-qualifier + label: Coded-location-qualifier + content-type: + bib1_mapping: + biblioserver: + 1: 1034 + enabled: 1 + index: content-type + aliases: + - content-type + - ctype + label: Content-type + control-number: + bib1_mapping: + biblioserver: + 1: 9001 + enabled: 1 + index: control-number + aliases: + - control-number + label: Control-number + copynumber: + bib1_mapping: + biblioserver: + 1: 8027 + enabled: 1 + index: copynumber + aliases: + - copynumber + label: Copynumber + curriculum: + bib1_mapping: + biblioserver: + 1: 9658 + enabled: 1 + index: curriculum + aliases: + - curriculum + label: Curriculum + damaged: + bib1_mapping: + biblioserver: + 1: 8005 + enabled: 1 + index: damaged + aliases: + - damaged + label: Damaged + date-entered-on-file: + bib1_mapping: + biblioserver: + 1: date-entered-on-file + enabled: 1 + index: date-entered-on-file + aliases: + - date-entered-on-file + label: Date-entered-on-file + datelastborrowed: + bib1_mapping: + biblioserver: + 1: 8026 + enabled: 1 + index: datelastborrowed + aliases: + - datelastborrowed + label: Datelastborrowed + datelastseen: + bib1_mapping: + biblioserver: + 1: 8025 + enabled: 1 + index: datelastseen + aliases: + - datelastseen + label: Datelastseen + dt-bks: + bib1_mapping: + biblioserver: + 1: 8700 + enabled: 1 + index: dt-bks + aliases: + - dt-bks + label: Dt-bks + dt-cf: + bib1_mapping: + biblioserver: + 1: 8700 + enabled: 1 + index: dt-cf + aliases: + - dt-cf + label: Dt-cf + dt-map: + bib1_mapping: + biblioserver: + 1: 8700 + enabled: 1 + index: dt-map + aliases: + - dt-map + label: Dt-map + dt-sr: + bib1_mapping: + biblioserver: + 1: 8700 + enabled: 1 + index: dt-sr + aliases: + - dt-sr + label: Dt-sr + dt-vis: + bib1_mapping: + biblioserver: + 1: 8700 + enabled: 1 + index: dt-vis + aliases: + - dt-vis + label: Dt-vis + ean: + bib1_mapping: + biblioserver: + 1: EAN + enabled: 1 + index: ean + aliases: + - ean + label: Ean + extent: + bib1_mapping: + biblioserver: + 1: Extent + enabled: 1 + index: extent + aliases: + - extent + label: Extent + ff8-22: + bib1_mapping: + biblioserver: + 1: 8822 + enabled: 1 + index: ff8-22 + aliases: + - ff8-22 + label: Ff8-22 + ff8-23: + bib1_mapping: + biblioserver: + 1: 8823 + enabled: 1 + index: ff8-23 + aliases: + - ff8-23 + label: Ff8-23 + ff8-34: + bib1_mapping: + biblioserver: + 1: 8834 + enabled: 1 + index: ff8-34 + aliases: + - ff8-34 + label: Ff8-34 + fiction: + bib1_mapping: + biblioserver: + 1: 8833 + enabled: 1 + index: fiction + aliases: + - fiction + - fic + label: Fiction + format: + bib1_mapping: + biblioserver: + 1: 8823 + enabled: 1 + index: format + aliases: + - format + label: Format + graphics-support: + bib1_mapping: + biblioserver: + 1: Graphic-support + enabled: 1 + index: graphics-support + aliases: + - graphics-support + label: Graphics-support + graphics-type: + bib1_mapping: + biblioserver: + 1: Graphic-type + enabled: 1 + index: graphics-type + aliases: + - graphics-type + label: Graphics-type + holdingbranch: + bib1_mapping: + biblioserver: + 1: 8012 + enabled: 1 + index: holdingbranch + aliases: + - holdingbranch + label: Holdingbranch + homebranch: + bib1_mapping: + biblioserver: + 1: homebranch + enabled: 1 + index: homebranch + aliases: + - homebranch + - branch + label: Homebranch + host-item: + bib1_mapping: + biblioserver: + 1: 1033 + enabled: 1 + index: host-item + aliases: + - host-item + label: Host-item + host-item-number: + bib1_mapping: + biblioserver: + 1: 8911 + enabled: 1 + index: host-item-number + aliases: + - host-item-number + - hi + label: Host-item-number + id-other: + bib1_mapping: + biblioserver: + 1: 9012 + enabled: 1 + index: id-other + aliases: + - id-other + label: Id-other + identifier-standard: + bib1_mapping: + biblioserver: + 1: 1007 + enabled: 1 + index: identifier-standard + aliases: + - identifier-standard + - ident + label: Identifier-standard + illustration-code: + bib1_mapping: + biblioserver: + 1: "Illustration-code " + enabled: 1 + index: illustration-code + aliases: + - illustration-code + label: Illustration-code + isbn: + bib1_mapping: + biblioserver: + 1: 7 + enabled: 1 + index: isbn + aliases: + - isbn + - nb + label: Isbn + issn: + bib1_mapping: + biblioserver: + 1: 8 + enabled: 1 + index: issn + aliases: + - issn + - ns + label: Issn + issues: + bib1_mapping: + biblioserver: + 1: 8019 + enabled: 1 + index: issues + aliases: + - issues + label: Issues + item: + bib1_mapping: + biblioserver: + 1: 9520 + enabled: 1 + index: item + aliases: + - item + label: Item + itemnumber: + bib1_mapping: + biblioserver: + 1: 8010 + enabled: 1 + index: itemnumber + aliases: + - itemnumber + label: Itemnumber + itemtype: + bib1_mapping: + biblioserver: + 1: 9006 + enabled: 1 + index: itemtype + aliases: + - itemtype + - mc-itemtype + label: Itemtype + itype: + bib1_mapping: + biblioserver: + 1: 8031 + enabled: 1 + index: itype + aliases: + - itype + - mc-itype + label: Itype + l-format: + bib1_mapping: + biblioserver: + 1: 8703 + enabled: 1 + index: l-format + aliases: + - l-format + label: L-format + language: + bib1_mapping: + biblioserver: + 1: 54 + enabled: 1 + index: language + aliases: + - language + - ln + label: Language + lc-card-number: + bib1_mapping: + biblioserver: + 1: 9 + enabled: 1 + index: lc-card-number + aliases: + - lc-card-number + - lc-card + label: Lc-card-number + lex: + bib1_mapping: + biblioserver: + 1: 9903 r=r + enabled: 1 + index: lex + aliases: + - lex + label: Lex + literature-code: + bib1_mapping: + biblioserver: + 1: Literature-Code + enabled: 1 + index: literature-code + aliases: + - literature-code + label: Literature-code + llength: + bib1_mapping: + biblioserver: + 1: llength + enabled: 1 + index: llength + aliases: + - llength + label: Llength + local-classification: + bib1_mapping: + biblioserver: + 1: 8022 + enabled: 1 + index: local-classification + aliases: + - local-classification + - lcn + - callnum + label: Local-classification + local-number: + bib1_mapping: + biblioserver: + 1: 12 + enabled: 1 + index: local-number + aliases: + - local-number + - sn + label: Local-number + location: + bib1_mapping: + biblioserver: + 1: 8013 + enabled: 1 + index: location + aliases: + - location + - mc-loc + label: Location + lost: + bib1_mapping: + biblioserver: + 1: 8002 + enabled: 1 + index: lost + aliases: + - lost + label: Lost + match: + bib1_mapping: + authorityserver: + 1: Match + enabled: 1 + index: match + aliases: + - match + label: Match + material-type: + bib1_mapping: + biblioserver: + 1: Material-type + enabled: 1 + index: material-type + aliases: + - material-type + label: Material-type + materials-specified: + bib1_mapping: + biblioserver: + 1: 8004 + enabled: 1 + index: materials-specified + aliases: + - materials-specified + label: Materials-specified + music: + bib1_mapping: + biblioserver: + 1: Music-number + enabled: 1 + index: music + aliases: + - music + label: Music + name: + bib1_mapping: + biblioserver: + 1: 1002 + enabled: 1 + index: name + aliases: + - name + label: Name + note: + bib1_mapping: + biblioserver: + 1: 63 + enabled: 1 + index: note + aliases: + - note + - nt + label: Note + notforloan: + bib1_mapping: + biblioserver: + 1: 8008 + 4: 109 + enabled: 1 + index: notforloan + aliases: + - notforloan + label: Notforloan + onloan: + bib1_mapping: + biblioserver: + 1: 8024 + enabled: 1 + index: onloan + aliases: + - onloan + label: Onloan + personal-name: + bib1_mapping: + biblioserver: + 1: Personal-name + enabled: 1 + index: personal-name + aliases: + - personal-name + - pn + label: Personal-name + place-publication: + bib1_mapping: + biblioserver: + 1: 59 + enabled: 1 + index: place-publication + aliases: + - place-publication + - pl + label: Place-publication + popularity: + bib1_mapping: + biblioserver: + 1: issues + enabled: 1 + index: popularity + aliases: + - popularity + label: Popularity + price: + bib1_mapping: + biblioserver: + 1: 8017 + enabled: 1 + index: price + aliases: + - price + label: Price + publisher: + bib1_mapping: + biblioserver: + 1: 1018 + enabled: 1 + index: publisher + aliases: + - publisher + - pb + label: Publisher + record-control-number: + bib1_mapping: + biblioserver: + 1: 1045 + enabled: 1 + index: record-control-number + aliases: + - record-control-number + - rcn + label: Record-control-number + record-type: + bib1_mapping: + biblioserver: + 1: 1001 + enabled: 1 + index: record-type + aliases: + - record-type + - rtype + - mc-rtype + - mus + label: Record-type + regularity-code: + bib1_mapping: + biblioserver: + 1: Regularity-code + enabled: 1 + index: regularity-code + aliases: + - regularity-code + label: Regularity-code + renewals: + bib1_mapping: + biblioserver: + 1: 8020 + enabled: 1 + index: renewals + aliases: + - renewals + label: Renewals + replacementprice: + bib1_mapping: + biblioserver: + 1: 8029 + enabled: 1 + index: replacementprice + aliases: + - replacementprice + label: Replacementprice + replacementpricedate: + bib1_mapping: + biblioserver: + 1: 8030 + enabled: 1 + index: replacementpricedate + aliases: + - replacementpricedate + label: Replacementpricedate + reserves: + bib1_mapping: + biblioserver: + 1: 8021 + enabled: 1 + index: reserves + aliases: + - reserves + label: Reserves + restricted: + bib1_mapping: + biblioserver: + 1: 8006 + enabled: 1 + index: restricted + aliases: + - restricted + label: Restricted + stack: + bib1_mapping: + biblioserver: + 1: 8018 + enabled: 1 + index: stack + aliases: + - stack + label: Stack + stock-number: + bib1_mapping: + biblioserver: + 1: 1028 + enabled: 1 + index: stock-number + aliases: + - stock-number + label: Stock-number + stocknumber: + bib1_mapping: + biblioserver: + 1: 1062 + enabled: 1 + index: stocknumber + aliases: + - stocknumber + - inv + label: Stocknumber + summary: + bib1_mapping: + biblioserver: + 1: Summary + enabled: 1 + index: summary + aliases: + - summary + label: Summary + suppress: + bib1_mapping: + biblioserver: + 1: 9011 + enabled: 1 + index: suppress + aliases: + - suppress + label: Suppress + titleext: + bib1_mapping: + biblioserver: + 1: 4 + 4: 1 + 6: 2 + enabled: 1 + index: title + aliases: [] + label: Title ext + titlekw: + bib1_mapping: + biblioserver: + 1: 4 + enabled: 1 + index: title + aliases: [] + label: Title kw + thesaurus: + bib1_mapping: + authorityserver: + 1: Subject-heading-thesaurus + enabled: 1 + index: thesaurus + aliases: + - thesaurus + label: Thesaurus + totalissues: + bib1_mapping: + biblioserver: + 1: 9003 + enabled: 1 + index: totalissues + aliases: + - totalissues + label: Totalissues + type-of-serial: + bib1_mapping: + biblioserver: + 1: Type-Of-Serial + enabled: 1 + index: type-of-serial + aliases: + - type-of-serial + label: Type-of-serial + upc: + bib1_mapping: + biblioserver: + 1: UPC + enabled: 1 + index: upc + aliases: + - upc + label: Upc + uri: + bib1_mapping: + biblioserver: + 1: 8028 + enabled: 1 + index: uri + aliases: + - uri + label: Uri + video-mt: + bib1_mapping: + biblioserver: + 1: Video-mt + enabled: 1 + index: video-mt + aliases: + - video-mt + label: Video-mt + withdrawn: + bib1_mapping: + biblioserver: + 1: 8001 + enabled: 1 + index: withdrawn + aliases: + - withdrawn + label: Withdrawn + subject: + subject: + bib1_mapping: + authorityserver: + 1: Match-heading + biblioserver: + 1: 21 + enabled: 1 + index: '' + label: '' + aliases: + - su + - su-to + - su-geo + - su-ut + authority-number: + bib1_mapping: + biblioserver: + 1: Koha-Auth-Number + enabled: 1 + index: authority-number + label: Authority-number + aliases: + - authority-number + - an + complete: + bib1_mapping: + biblioserver: + 1: 21 + 3: 1 + 4: 1 + 5: 100 + 6: 3 + enabled: 1 + index: complete + label: Complete + aliases: + - complete + exact: + bib1_mapping: + authorityserver: + 4: 1 + 5: 100 + 6: 3 + enabled: 1 + index: exact + label: Exact + aliases: + - exact + heading: + bib1_mapping: + authorityserver: + 1: Heading + enabled: 1 + index: heading + label: Heading + aliases: + - heading + - mainentry + - he + headingmain: + bib1_mapping: + authorityserver: + 1: Heading-Main + enabled: 1 + index: headingmain + label: Headingmain + aliases: + - headingmain + - mainmainentry + matchheading: + bib1_mapping: + authorityserver: + 1: Match-heading + enabled: 1 + index: matchheading + label: Matchheading + aliases: + - matchheading + - match-heading + name-personal: + bib1_mapping: + biblioserver: + 1: 1009 + enabled: 1 + index: name-personal + label: Name-personal + aliases: + - name-personal + - su-na + seefrom: + bib1_mapping: + authorityserver: + 1: Match-heading-see-from + enabled: 1 + index: seefrom + label: Seefrom + aliases: + - seefrom + - see-from + start: + bib1_mapping: + authorityserver: + 3: 2 + 4: 1 + 5: 1 + enabled: 1 + index: start + label: Start + aliases: + - start + title: + title: + bib1_mapping: + biblioserver: + 1: 4 + enabled: 1 + index: '' + label: '' + aliases: + - ti + cover: + bib1_mapping: + biblioserver: + 1: 36 + enabled: 1 + index: cover + label: Cover + aliases: + - cover + - title-cover + exacttitle: + bib1_mapping: + biblioserver: + 1: 4 + 4: 1 + 6: 3 + enabled: 1 + index: exacttitle + label: Exacttitle + aliases: + - exacttitle + - ti,ext + series: + bib1_mapping: + biblioserver: + 1: 5 + enabled: 1 + index: series + label: Series + aliases: + - series + - title-series + - se + uniform: + bib1_mapping: + biblioserver: + 1: Title-uniform + enabled: 1 + index: uniform + label: Uniform + aliases: + - uniform + - title-uniform +filter_mappings: + acqdate: + bib1_mapping: + biblioserver: + 1: Date-of-acquisition + 4: 4 + target_syntax_callback: date_filter_target_callback + enabled: 1 + label: Acqdate + copydate: + bib1_mapping: + biblioserver: + 1: 30 + 4: 4 + target_syntax_callback: date_filter_target_callback + + enabled: 1 + label: Copydate + pubdate: + bib1_mapping: + biblioserver: + 1: pubdate + 4: 4 + target_syntax_callback: date_filter_target_callback + enabled: 1 + label: Pubdate +modifier_mappings: + AuthidAsc: + bib1_mapping: + authorityserver: + "": 0 + 1: Local-Number + 7: 1 + op: "@or" + enabled: 1 + label: AuthidAsc + AuthidDsc: + bib1_mapping: + authorityserver: + "": 0 + 1: Local-Number + 7: 2 + op: "@or" + enabled: 1 + label: AuthidDsc + HeadingAsc: + bib1_mapping: + authorityserver: + "": 0 + 1: Heading + 7: 1 + op: "@or" + enabled: 1 + label: HeadingAsc + HeadingDsc: + bib1_mapping: + authorityserver: + "": 0 + 1: Heading + 7: 2 + op: "@or" + enabled: 1 + label: HeadingDsc + Relevance: + bib1_mapping: + authorityserver: + 2: 102 + enabled: 1 + label: Relevance + acqdate_asc: + bib1_mapping: + biblioserver: + "": 0 + 1: 32 + 7: 2 + op: "@or" + enabled: 1 + label: Acqdate_asc + acqdate_dsc: + bib1_mapping: + biblioserver: + "": 0 + 1: 32 + 7: 1 + op: "@or" + enabled: 1 + label: Acqdate_dsc + ascending: + bib1_mapping: + biblioserver: + 7: 1 + enabled: 1 + label: Ascending + author_asc: + bib1_mapping: + biblioserver: + "": 0 + 1: 1003 + 7: 2 + op: "@or" + enabled: 1 + label: Author_asc + author_az: + bib1_mapping: + biblioserver: + "": 0 + 1: 1003 + 7: 1 + op: "@or" + enabled: 1 + label: Author_az + author_dsc: + bib1_mapping: + biblioserver: + "": 0 + 1: 1003 + 7: 1 + op: "@or" + enabled: 1 + label: Author_dsc + author_za: + bib1_mapping: + biblioserver: + "": 0 + 1: 1003 + 7: 2 + op: "@or" + enabled: 1 + label: Author_za + call_number_asc: + bib1_mapping: + biblioserver: + "": 0 + 1: 8007 + 7: 2 + op: "@or" + enabled: 1 + label: Call_number_asc + call_number_dsc: + bib1_mapping: + biblioserver: + "": 0 + 1: 8007 + 7: 1 + op: "@or" + enabled: 1 + label: Call_number_dsc + descending: + bib1_mapping: + biblioserver: + 7: 2 + enabled: 1 + label: Descending + popularity_asc: + bib1_mapping: + biblioserver: + "": 0 + 1: 9003 + 7: 2 + op: "@or" + enabled: 1 + label: Popularity_asc + popularity_dsc: + bib1_mapping: + biblioserver: + "": 0 + 1: 9003 + 7: 1 + op: "@or" + enabled: 1 + label: Popularity_dsc + pubdate_asc: + bib1_mapping: + biblioserver: + "": 0 + 1: 31 + 7: 2 + op: "@or" + enabled: 1 + label: Pubdate_asc + pubdate_dsc: + bib1_mapping: + biblioserver: + "": 0 + 1: 31 + 7: 1 + op: "@or" + enabled: 1 + label: Pubdate_dsc + relevance: + bib1_mapping: + biblioserver: + 2: 102 + enabled: 1 + label: Relevance + relevance_dsc: + bib1_mapping: + biblioserver: + 2: 102 + enabled: 1 + label: Relevance_dsc + title-sort-az: + bib1_mapping: + biblioserver: + "": 0 + 1: 36 + 7: 1 + op: "@or" + enabled: 1 + label: Title-sort-az + title-sort-za: + bib1_mapping: + biblioserver: + "": 0 + 1: 36 + 7: 2 + op: "@or" + enabled: 1 + label: Title-sort-za + title_asc: + bib1_mapping: + biblioserver: + "": 0 + 1: 4 + 7: 1 + op: "@or" + enabled: 1 + label: Title_asc + title_az: + bib1_mapping: + biblioserver: + "": 0 + 1: 4 + 7: 1 + op: "@or" + enabled: 1 + label: Title_az + title_dsc: + bib1_mapping: + biblioserver: + "": 0 + 1: 4 + 7: 2 + op: "@or" + enabled: 1 + label: Title_dsc + title_za: + bib1_mapping: + biblioserver: + "": 0 + 1: 4 + 7: 2 + op: "@or" + enabled: 1 + label: Title_za +relevance_bumps: + keyword: + titleext: + enabled: 1 + bib1_mapping: + biblioserver: 34 + titlekw: + enabled: 1 + bib1_mapping: + biblioserver: 20 diff --git a/t/QueryParser.t b/t/QueryParser.t new file mode 100644 index 0000000000..01e898a64e --- /dev/null +++ b/t/QueryParser.t @@ -0,0 +1,95 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; +use Test::Deep; +use Module::Load::Conditional qw(can_load); + +BEGIN { + use_ok( 'Koha::QueryParser::Driver::PQF' ); +} + +my $QParser = Koha::QueryParser::Driver::PQF->new(); + +ok(defined $QParser, 'Successfully created empty QP object'); +ok($QParser->load_config('./etc/searchengine/queryparser.yaml'), 'Loaded QP config'); + +is($QParser->search_class_count, 4, 'Initialized 4 search classes'); +is (scalar(@{$QParser->search_fields()->{'keyword'}}), 107, "Correct number of search fields for 'keyword' class"); + +is($QParser->target_syntax('biblioserver', 'smith'), '@or @or @attr 1=1016 @attr 4=6 "smith" @attr 9=20 @attr 2=102 @attr 4=6 "smith" @attr 9=34 @attr 2=102 @attr 4=6 "smith"', 'super simple keyword query'); +is($QParser->target_syntax('biblioserver', 'au:smith'), '@attr 1=1003 @attr 4=6 "smith"', 'simple author query'); +is($QParser->target_syntax('biblioserver', 'keyword|publisher:smith'), '@attr 1=1018 @attr 4=6 "smith"', 'fielded publisher query'); +is($QParser->target_syntax('biblioserver', 'ti:"little engine that could"'), '@attr 1=4 @attr 4=1 "little engine that could"', 'phrase query'); +is($QParser->target_syntax('biblioserver', 'keyword|titlekw:smith'), '@attr 1=4 @attr 9=20 @attr 2=102 @attr 4=6 "smith"', 'relevance-bumped query'); +is($QParser->target_syntax('biblioserver', 'au:smith && johnson'), '@and @attr 1=1003 @attr 4=6 "smith" @attr 1=1003 @attr 4=6 "johnson"', 'query with boolean &&'); +is($QParser->target_syntax('biblioserver', 'au:smith pubdate(-2008)'), '@and @attr 1=1003 @attr 4=6 "smith" @attr 4=4 @attr 1=pubdate @attr 2=2 "2008"', 'keyword search with pubdate limited to -2008'); +is($QParser->target_syntax('biblioserver', 'au:smith pubdate(2008-)'), '@and @attr 1=1003 @attr 4=6 "smith" @attr 4=4 @attr 1=pubdate @attr 2=4 "2008"', 'keyword search with pubdate limited to 2008-'); +is($QParser->target_syntax('biblioserver', 'au:smith pubdate(2008)'), '@and @attr 1=1003 @attr 4=6 "smith" @attr 4=4 @attr 1=pubdate "2008"', 'keyword search with pubdate limited to 2008'); +is($QParser->target_syntax('biblioserver', 'au:smith pubdate(1980,2008)'), '@and @attr 1=1003 @attr 4=6 "smith" @or @attr 4=4 @attr 1=pubdate "1980" @attr 4=4 @attr 1=pubdate "2008"', 'keyword search with pubdate limited to 1980, 2008'); +is($QParser->target_syntax('biblioserver', 'au:smith #acqdate_dsc'), '@or @attr 1=32 @attr 7=1 0 @attr 1=1003 @attr 4=6 "smith"', 'keyword search sorted by acqdate descending'); +is($QParser->bib1_mapping_by_attr('field', 'biblioserver', {'1' => '1004'})->{'field'}, 'personal', 'retrieve field by attr'); +is($QParser->bib1_mapping_by_attr_string('field', 'biblioserver', '@attr 1=1004')->{'field'}, 'personal', 'retrieve field by attrstring'); + +is ($QParser->clear_all_mappings, $QParser, 'clear all mappings returns self'); +is ($QParser->clear_all_configuration, $QParser, 'clear all configuration returns self'); +is (scalar(keys(%{$QParser->search_fields})), 0, "All mapping erased"); + +$QParser->add_bib1_field_map('author' => 'personal' => 'biblioserver' => { '1' => '1004' } ); +$QParser->add_bib1_modifier_map('relevance' => 'biblioserver' => { '2' => '102' } ); +my $desired_config = { + 'field_mappings' => { + 'author' => { + 'personal' => { + 'aliases' => [ + 'personal' + ], + 'bib1_mapping' => { + 'biblioserver' => { + '1' => '1004' + } + }, + 'enabled' => '1', + 'index' => 'personal', + 'label' => 'Personal' + } + } + }, + 'filter_mappings' => {}, + 'modifier_mappings' => { + 'relevance' => { + 'bib1_mapping' => { + 'biblioserver' => { + '2' => '102' + } + }, + 'enabled' => 1, + 'label' => 'Relevance' + } + }, + 'relevance_bumps' => {} +}; + +SKIP: { + my $got_config; + skip 'YAML is unavailable', 2 unless can_load('modules' => { 'YAML::Any' => undef }); + $got_config = YAML::Any::Load($QParser->serialize_mappings()); + ok(ref $got_config, 'serialized YAML valid'); + is_deeply($got_config, $desired_config, 'Mappings serialized correctly to YAML'); + + skip 'JSON is unavailable', 2 unless can_load('modules' => { 'JSON' => undef }); + undef $got_config; + eval { + $got_config = JSON::from_json($QParser->serialize_mappings('json')); + }; + is($@, '', 'serialized JSON valid'); + is_deeply($got_config, $desired_config, 'Mappings serialized correctly to JSON'); +} + +$QParser->clear_all_mappings; +is($QParser->TEST_SETUP, $QParser, 'TEST_SETUP returns self'); +is($QParser->search_class_count, 4, 'Initialized 4 search classes in test setup'); + +done_testing(); -- 2.39.5