Bug 9239: Introduce QueryParser driver for PQF
authorJared Camins-Esakov <jcamins@cpbibliography.com>
Tue, 2 Oct 2012 21:06:47 +0000 (17:06 -0400)
committerJared Camins-Esakov <jcamins@cpbibliography.com>
Sun, 17 Mar 2013 01:32:32 +0000 (21:32 -0400)
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 <chris@bigballofwax.co.nz>
Signed-off-by: Elliott Davis <elliott@bywatersolions.com>
Test Passed successfully after installing missing dep for Test::Deep

Signed-off-by: Katrin Fischer <Katrin.Fischer.83@web.de>
Signed-off-by: Jared Camins-Esakov <jcamins@cpbibliography.com>
Koha/QueryParser/Driver/PQF.pm [new file with mode: 0644]
Koha/QueryParser/Driver/PQF/Util.pm [new file with mode: 0644]
Koha/QueryParser/Driver/PQF/query_plan.pm [new file with mode: 0644]
Koha/QueryParser/Driver/PQF/query_plan/facet.pm [new file with mode: 0644]
Koha/QueryParser/Driver/PQF/query_plan/filter.pm [new file with mode: 0644]
Koha/QueryParser/Driver/PQF/query_plan/modifier.pm [new file with mode: 0644]
Koha/QueryParser/Driver/PQF/query_plan/node.pm [new file with mode: 0644]
Koha/QueryParser/Driver/PQF/query_plan/node/atom.pm [new file with mode: 0644]
etc/searchengine/queryparser.yaml [new file with mode: 0644]
t/QueryParser.t [new file with mode: 0644]

diff --git a/Koha/QueryParser/Driver/PQF.pm b/Koha/QueryParser/Driver/PQF.pm
new file mode 100644 (file)
index 0000000..3007333
--- /dev/null
@@ -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<bib1_field_map> - search class/field Bib-1 mappings
+
+=item B<bib1_modifier_map> - search modifier mappings
+
+=item B<bib1_filter_map> - search filter mappings
+
+=item B<bib1_relevance_bump_map> - 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 (file)
index 0000000..f2e018b
--- /dev/null
@@ -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 (file)
index 0000000..5435fe6
--- /dev/null
@@ -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 (file)
index 0000000..0407e58
--- /dev/null
@@ -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 (file)
index 0000000..c632eee
--- /dev/null
@@ -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 (file)
index 0000000..c3a66e4
--- /dev/null
@@ -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 (file)
index 0000000..62417a1
--- /dev/null
@@ -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 (file)
index 0000000..afdb4c4
--- /dev/null
@@ -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 (file)
index 0000000..94f9d8e
--- /dev/null
@@ -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 (file)
index 0000000..01e898a
--- /dev/null
@@ -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();