View | Details | Raw Unified | Return to bug 20589
Collapse All | Expand All

(-)a/Koha/SearchEngine/Elasticsearch.pm (-14 / +39 lines)
Lines 200-207 sub get_elasticsearch_mappings { Link Here
200
        my $marcflavour = lc C4::Context->preference('marcflavour');
200
        my $marcflavour = lc C4::Context->preference('marcflavour');
201
        $self->_foreach_mapping(
201
        $self->_foreach_mapping(
202
            sub {
202
            sub {
203
                my ( $name, $type, $facet, $suggestible, $sort, $marc_type ) = @_;
203
                my ( $name, $type, $facet, $suggestible, $sort, $search, $marc_type ) = @_;
204
205
                return if $marc_type ne $marcflavour;
204
                return if $marc_type ne $marcflavour;
206
                # TODO if this gets any sort of complexity to it, it should
205
                # TODO if this gets any sort of complexity to it, it should
207
                # be broken out into its own function.
206
                # be broken out into its own function.
Lines 217-223 sub get_elasticsearch_mappings { Link Here
217
                    $es_type = 'stdno';
216
                    $es_type = 'stdno';
218
                }
217
                }
219
218
220
                $mappings->{data}{properties}{$name} = _get_elasticsearch_field_config('search', $es_type);
219
                if ($search) {
220
                    $mappings->{data}{properties}{$name} = _get_elasticsearch_field_config('search', $es_type);
221
                }
221
222
222
                if ($facet) {
223
                if ($facet) {
223
                    $mappings->{data}{properties}{ $name . '__facet' } = _get_elasticsearch_field_config('facet', $es_type);
224
                    $mappings->{data}{properties}{ $name . '__facet' } = _get_elasticsearch_field_config('facet', $es_type);
Lines 284-298 sub reset_elasticsearch_mappings { Link Here
284
285
285
    while ( my ( $index_name, $fields ) = each %$indexes ) {
286
    while ( my ( $index_name, $fields ) = each %$indexes ) {
286
        while ( my ( $field_name, $data ) = each %$fields ) {
287
        while ( my ( $field_name, $data ) = each %$fields ) {
287
            my %sf_params = map { $_ => $data->{$_} } grep { exists $data->{$_} } qw/ type label weight facet_order /;
288
289
            my %sf_params = map { $_ => $data->{$_} } grep { exists $data->{$_} } qw/ type label weight staff_client opac facet_order /;
290
291
            # Set default values
292
            $sf_params{staff_client} //= 1;
293
            $sf_params{opac} //= 1;
294
288
            $sf_params{name} = $field_name;
295
            $sf_params{name} = $field_name;
289
296
290
            my $search_field = Koha::SearchFields->find_or_create( \%sf_params, { key => 'name' } );
297
            my $search_field = Koha::SearchFields->find_or_create( \%sf_params, { key => 'name' } );
291
298
292
            my $mappings = $data->{mappings};
299
            my $mappings = $data->{mappings};
293
            for my $mapping ( @$mappings ) {
300
            for my $mapping ( @$mappings ) {
294
                my $marc_field = Koha::SearchMarcMaps->find_or_create({ index_name => $index_name, marc_type => $mapping->{marc_type}, marc_field => $mapping->{marc_field} });
301
                my $marc_field = Koha::SearchMarcMaps->find_or_create({
295
                $search_field->add_to_search_marc_maps($marc_field, { facet => $mapping->{facet} || 0, suggestible => $mapping->{suggestible} || 0, sort => $mapping->{sort} } );
302
                    index_name => $index_name,
303
                    marc_type => $mapping->{marc_type},
304
                    marc_field => $mapping->{marc_field}
305
                });
306
                $search_field->add_to_search_marc_maps($marc_field, {
307
                    facet => $mapping->{facet} || 0,
308
                    suggestible => $mapping->{suggestible} || 0,
309
                    sort => $mapping->{sort},
310
                    search => $mapping->{search} || 1
311
                });
296
            }
312
            }
297
        }
313
        }
298
    }
314
    }
Lines 650-658 sub _array_to_marc { Link Here
650
    return $record;
666
    return $record;
651
}
667
}
652
668
653
=head2 _field_mappings($facet, $suggestible, $sort, $target_name, $target_type, $range)
669
=head2 _field_mappings($facet, $suggestible, $sort, $search, $target_name, $target_type, $range)
654
670
655
    my @mappings = _field_mappings($facet, $suggestible, $sort, $target_name, $target_type, $range)
671
    my @mappings = _field_mappings($facet, $suggestible, $sort, $search, $target_name, $target_type, $range)
656
672
657
Get mappings, an internal data structure later used by
673
Get mappings, an internal data structure later used by
658
L<_process_mappings($mappings, $data, $record_document, $altscript)> to process MARC target
674
L<_process_mappings($mappings, $data, $record_document, $altscript)> to process MARC target
Lines 681-686 Boolean indicating whether to create a suggestion field for this mapping. Link Here
681
697
682
Boolean indicating whether to create a sort field for this mapping.
698
Boolean indicating whether to create a sort field for this mapping.
683
699
700
=item C<$search>
701
702
Boolean indicating whether to create a search field for this mapping.
703
684
=item C<$target_name>
704
=item C<$target_name>
685
705
686
Elasticsearch document target field name.
706
Elasticsearch document target field name.
Lines 706-712 be extracted. Link Here
706
=cut
726
=cut
707
727
708
sub _field_mappings {
728
sub _field_mappings {
709
    my ($_self, $facet, $suggestible, $sort, $target_name, $target_type, $range) = @_;
729
    my ($_self, $facet, $suggestible, $sort, $search, $target_name, $target_type, $range) = @_;
710
    my %mapping_defaults = ();
730
    my %mapping_defaults = ();
711
    my @mappings;
731
    my @mappings;
712
732
Lines 734-741 sub _field_mappings { Link Here
734
        };
754
        };
735
    }
755
    }
736
756
737
    my $mapping = [$target_name, $default_options];
757
    if ($search) {
738
    push @mappings, $mapping;
758
        my $mapping = [$target_name, $default_options];
759
        push @mappings, $mapping;
760
    }
739
761
740
    my @suffixes = ();
762
    my @suffixes = ();
741
    push @suffixes, 'facet' if $facet;
763
    push @suffixes, 'facet' if $facet;
Lines 792-798 sub _get_marc_mapping_rules { Link Here
792
    };
814
    };
793
815
794
    $self->_foreach_mapping(sub {
816
    $self->_foreach_mapping(sub {
795
        my ($name, $type, $facet, $suggestible, $sort, $marc_type, $marc_field) = @_;
817
        my ($name, $type, $facet, $suggestible, $sort, $search, $marc_type, $marc_field) = @_;
796
        return if $marc_type ne $marcflavour;
818
        return if $marc_type ne $marcflavour;
797
819
798
        if ($type eq 'sum') {
820
        if ($type eq 'sum') {
Lines 855-861 sub _get_marc_mapping_rules { Link Here
855
            }
877
            }
856
878
857
            my $range = defined $3 ? $3 : undef;
879
            my $range = defined $3 ? $3 : undef;
858
            my @mappings = $self->_field_mappings($facet, $suggestible, $sort, $name, $type, $range);
880
            my @mappings = $self->_field_mappings($facet, $suggestible, $sort, $search, $name, $type, $range);
859
881
860
            if ($field_tag < 10) {
882
            if ($field_tag < 10) {
861
                $rules->{control_fields}->{$field_tag} //= [];
883
                $rules->{control_fields}->{$field_tag} //= [];
Lines 875-881 sub _get_marc_mapping_rules { Link Here
875
        }
897
        }
876
        elsif ($marc_field =~ $leader_regexp) {
898
        elsif ($marc_field =~ $leader_regexp) {
877
            my $range = defined $1 ? $1 : undef;
899
            my $range = defined $1 ? $1 : undef;
878
            my @mappings = $self->_field_mappings($facet, $suggestible, $sort, $name, $type, $range);
900
            my @mappings = $self->_field_mappings($facet, $suggestible, $sort, $search, $name, $type, $range);
879
            push @{$rules->{leader}}, @mappings;
901
            push @{$rules->{leader}}, @mappings;
880
        }
902
        }
881
        else {
903
        else {
Lines 954-959 sub _foreach_mapping { Link Here
954
                'search_marc_to_fields.facet',
976
                'search_marc_to_fields.facet',
955
                'search_marc_to_fields.suggestible',
977
                'search_marc_to_fields.suggestible',
956
                'search_marc_to_fields.sort',
978
                'search_marc_to_fields.sort',
979
                'search_marc_to_fields.search',
957
                'search_marc_map.marc_type',
980
                'search_marc_map.marc_type',
958
                'search_marc_map.marc_field',
981
                'search_marc_map.marc_field',
959
            ],
982
            ],
Lines 961-966 sub _foreach_mapping { Link Here
961
                'facet',
984
                'facet',
962
                'suggestible',
985
                'suggestible',
963
                'sort',
986
                'sort',
987
                'search',
964
                'marc_type',
988
                'marc_type',
965
                'marc_field',
989
                'marc_field',
966
            ],
990
            ],
Lines 976-981 sub _foreach_mapping { Link Here
976
            $search_field->get_column('facet'),
1000
            $search_field->get_column('facet'),
977
            $search_field->get_column('suggestible'),
1001
            $search_field->get_column('suggestible'),
978
            $search_field->get_column('sort'),
1002
            $search_field->get_column('sort'),
1003
            $search_field->get_column('search'),
979
            $search_field->get_column('marc_type'),
1004
            $search_field->get_column('marc_type'),
980
            $search_field->get_column('marc_field'),
1005
            $search_field->get_column('marc_field'),
981
        );
1006
        );
(-)a/Koha/SearchEngine/Elasticsearch/QueryBuilder.pm (-47 / +184 lines)
Lines 48-53 use URI::Escape; Link Here
48
48
49
use C4::Context;
49
use C4::Context;
50
use Koha::Exceptions;
50
use Koha::Exceptions;
51
use Koha::Caches;
51
52
52
=head2 build_query
53
=head2 build_query
53
54
Lines 90-96 sub build_query { Link Here
90
            query            => $query,
91
            query            => $query,
91
            fuzziness        => $fuzzy_enabled ? 'auto' : '0',
92
            fuzziness        => $fuzzy_enabled ? 'auto' : '0',
92
            default_operator => 'AND',
93
            default_operator => 'AND',
93
            default_field    => '_all',
94
            fields          => $self->_search_fields({ is_opac => $options{is_opac}, weighted_fields => $options{weighted_fields} }),
94
            lenient          => JSON::true,
95
            lenient          => JSON::true,
95
            analyze_wildcard => JSON::true,
96
            analyze_wildcard => JSON::true,
96
            fields           => $options{fields} || [],
97
            fields           => $options{fields} || [],
Lines 231-246 sub build_query_compat { Link Here
231
        $search_param_query_str || (),
232
        $search_param_query_str || (),
232
        $self->_join_queries( $self->_convert_index_strings(@$limits) ) || () );
233
        $self->_join_queries( $self->_convert_index_strings(@$limits) ) || () );
233
234
234
    my @fields = '_all';
235
    if ( defined($params->{weighted_fields}) && $params->{weighted_fields} ) {
236
        push @fields, sprintf("%s^%s", $_->name, $_->weight) for Koha::SearchFields->weighted_fields;
237
    }
238
239
    # If there's no query on the left, let's remove the junk left behind
235
    # If there's no query on the left, let's remove the junk left behind
240
    $query_str =~ s/^ AND //;
236
    $query_str =~ s/^ AND //;
241
    my %options;
237
    my %options;
242
    $options{fields} = \@fields;
243
    $options{sort} = \@sort_params;
238
    $options{sort} = \@sort_params;
239
    $options{is_opac} = $params->{is_opac};
240
    $options{weighted_fields} = $params->{weighted_fields};
244
    my $query = $self->build_query( $query_str, %options );
241
    my $query = $self->build_query( $query_str, %options );
245
242
246
    # We roughly emulate the CGI parameters of the zebra query builder
243
    # We roughly emulate the CGI parameters of the zebra query builder
Lines 314-335 sub build_authorities_query { Link Here
314
311
315
    foreach my $s ( @{ $search->{searches} } ) {
312
    foreach my $s ( @{ $search->{searches} } ) {
316
        my ( $wh, $op, $val ) = @{$s}{qw(where operator value)};
313
        my ( $wh, $op, $val ) = @{$s}{qw(where operator value)};
317
        $wh = '_all' if $wh eq '';
314
        if ( $op eq 'is' || $op eq '=' || $op eq 'exact') {
318
        if ( $op eq 'is' || $op eq '='  || $op eq 'exact' ) {
315
            if ($wh) {
319
316
                # Match the whole field, case insensitive, UTF normalized.
320
            # look for something that matches a term completely
317
                push @query_parts, { term => { "$wh.ci_raw" => $val } };
321
            # note, '=' is about numerical vals. May need special handling.
318
            }
322
            # Also, we lowercase our search because the ES
319
            else {
323
            # index lowercases its values, and term searches don't get the
320
                # Match the whole field for all searchable fields, case insensitive,
324
            # search analyzer applied to them.
321
                # UTF normalized.
325
            push @query_parts, { match_phrase => {"$wh.phrase" => lc $val} };
322
                # Given that field data is "The quick brown fox"
323
                # "The quick brown fox" and "the quick brown fox" will match
324
                # but not "quick brown fox".
325
                push @query_parts, {
326
                    multi_match => {
327
                        query => $val,
328
                        fields => $self->_search_fields({ subfield => 'ci_raw' }),
329
                    }
330
                };
331
            }
326
        }
332
        }
327
        elsif ( $op eq 'start' ) {
333
        elsif ( $op eq 'start') {
328
            # startswith search, uses lowercase untokenized version of heading
334
            # Match the prefix within a field for all searchable fields.
329
            push @query_parts, { match_phrase_prefix => {"$wh.phrase" => lc $val} };
335
            # Given that field data is "The quick brown fox"
336
            # "The quick bro" will match, but not "quick bro"
337
338
            # Does not seems to be a multi prefix query
339
            # so we need to create one
340
            if ($wh) {
341
                # Match prefix of the field.
342
                push @query_parts, { prefix => {"$wh.ci_raw" => $val} };
343
            }
344
            else {
345
                my @prefix_queries;
346
                foreach my $field (@{$self->_search_fields()}) {
347
                    push @prefix_queries, {
348
                        prefix => { "$field.ci_raw" => $val }
349
                    };
350
                }
351
                push @query_parts, {
352
                    'bool' => {
353
                        'should' => \@prefix_queries,
354
                        'minimum_should_match' => 1
355
                    }
356
                };
357
            }
330
        }
358
        }
331
        else {
359
        else {
332
            # regular wordlist stuff
360
            # Query all searchable fields.
361
            # Given that field data is "The quick brown fox"
362
            # a search containing any of the words will match, regardless
363
            # of order.
364
333
            my @tokens = $self->_split_query( $val );
365
            my @tokens = $self->_split_query( $val );
334
            foreach my $token ( @tokens ) {
366
            foreach my $token ( @tokens ) {
335
                $token = $self->_truncate_terms(
367
                $token = $self->_truncate_terms(
Lines 337-375 sub build_authorities_query { Link Here
337
                );
369
                );
338
            }
370
            }
339
            my $query = $self->_join_queries( @tokens );
371
            my $query = $self->_join_queries( @tokens );
340
            push @query_parts, { query_string => { default_field => $wh, query => $query } };
372
373
            if ($wh) {
374
                push @query_parts, { query_string => { default_field => $wh, query => $query } };
375
            }
376
            else {
377
                push @query_parts, {
378
                    query_string => {
379
                        query => $query,
380
                        fields => $self->_search_fields(),
381
                    }
382
                };
383
            }
341
        }
384
        }
342
    }
385
    }
343
386
344
    # Merge the query parts appropriately
387
    # Merge the query parts appropriately
345
    # 'should' behaves like 'or'
388
    # 'should' behaves like 'or'
346
    # 'must' behaves like 'and'
389
    # 'must' behaves like 'and'
347
    # Zebra results seem to match must so using that here
390
    # Zebra behaviour seem to match must so using that here
348
    my $query = { query =>
391
    my $elastic_query = {};
349
                 { bool =>
392
    $elastic_query->{bool}->{must} = \@query_parts;
350
                     { must => \@query_parts  }
393
351
                 }
394
    # Filter by authtypecode if set
352
             };
395
    if ($search->{authtypecode}) {
353
    if ( $search->{authtypecode} ) {
396
        $elastic_query->{bool}->{filter} = {
354
        $query->{query}->{bool}->{filter} = { term => { 'authtype' => lc $search->{authtypecode} } };
397
            term => {
398
                "authtype.raw" => $search->{authtypecode}
399
            }
400
        };
355
    }
401
    }
356
402
357
    my %s;
403
    my $query = {
358
    if ( exists $search->{sort} ) {
404
        query => $elastic_query
359
        foreach my $k ( keys %{ $search->{sort} } ) {
405
    };
360
            my $f = $self->_sort_field($k);
361
            $s{$f} = $search->{sort}{$k};
362
        }
363
        $search->{sort} = \%s;
364
    }
365
406
366
    # add the sort stuff
407
    # Add the sort stuff
367
    $query->{sort} = [ $search->{sort} ]  if exists $search->{sort};
408
    $query->{sort} = [ $search->{sort} ] if exists $search->{sort};
368
409
369
    return $query;
410
    return $query;
370
}
411
}
371
412
372
373
=head2 build_authorities_query_compat
413
=head2 build_authorities_query_compat
374
414
375
    my ($query) =
415
    my ($query) =
Lines 467-474 sub build_authorities_query_compat { Link Here
467
507
468
    my %sort;
508
    my %sort;
469
    my $sort_field =
509
    my $sort_field =
470
        ( $orderby =~ /^heading/ ) ? 'heading'
510
        ( $orderby =~ /^heading/ ) ? 'heading__sort'
471
      : ( $orderby =~ /^auth/ )    ? 'local-number'
511
      : ( $orderby =~ /^auth/ )    ? 'local-number__sort'
472
      :                              undef;
512
      :                              undef;
473
    if ($sort_field) {
513
    if ($sort_field) {
474
        my $sort_order = ( $orderby =~ /asc$/ ) ? 'asc' : 'desc';
514
        my $sort_order = ( $orderby =~ /asc$/ ) ? 'asc' : 'desc';
Lines 535-541 types. Link Here
535
=cut
575
=cut
536
576
537
our %index_field_convert = (
577
our %index_field_convert = (
538
    'kw' => '_all',
578
    'kw' => '',
539
    'ab' => 'abstract',
579
    'ab' => 'abstract',
540
    'au' => 'author',
580
    'au' => 'author',
541
    'lcn' => 'local-classification',
581
    'lcn' => 'local-classification',
Lines 629-635 sub _convert_index_fields { Link Here
629
    # If a field starts with mc- we save it as it's used (and removed) later
669
    # If a field starts with mc- we save it as it's used (and removed) later
630
    # when joining things, to indicate we make it an 'OR' join.
670
    # when joining things, to indicate we make it an 'OR' join.
631
    # (Sorry, this got a bit ugly after special cases were found.)
671
    # (Sorry, this got a bit ugly after special cases were found.)
632
    grep { $_->{field} } map {
672
    map {
633
        # Lower case all field names
673
        # Lower case all field names
634
        my ( $f, $t ) = map(lc, split /,/);
674
        my ( $f, $t ) = map(lc, split /,/);
635
        my $mc = '';
675
        my $mc = '';
Lines 642-648 sub _convert_index_fields { Link Here
642
            type  => $index_type_convert{ $t // '__default' }
682
            type  => $index_type_convert{ $t // '__default' }
643
        };
683
        };
644
        $r->{field} = ($mc . $r->{field}) if $mc && $r->{field};
684
        $r->{field} = ($mc . $r->{field}) if $mc && $r->{field};
645
        $r;
685
        $r->{field} ? $r : undef;
646
    } @indexes;
686
    } @indexes;
647
}
687
}
648
688
Lines 671-678 sub _convert_index_strings { Link Here
671
            push @res, $s;
711
            push @res, $s;
672
            next;
712
            next;
673
        }
713
        }
674
        push @res, $conv->{field} . ":"
714
        push @res, ($conv->{field} ? $conv->{field} . ':' : '')
675
          . $self->_modify_string_by_type( %$conv, operand => $term );
715
            . $self->_modify_string_by_type( %$conv, operand => $term );
676
    }
716
    }
677
    return @res;
717
    return @res;
678
}
718
}
Lines 969-972 sub _split_query { Link Here
969
    return @tokens;
1009
    return @tokens;
970
}
1010
}
971
1011
1012
=head2 _search_fields
1013
    my $weighted_fields = $self->_search_fields({
1014
        is_opac => 0,
1015
        weighted_fields => 1,
1016
        subfield => 'raw'
1017
    });
1018
1019
Generate a list of searchable fields to be used for Elasticsearch queries
1020
applied to multiple fields.
1021
1022
Returns an arrayref of field names for either OPAC or Staff client, with
1023
possible weights and subfield appended to each field name depending on the
1024
options provided.
1025
1026
=over 4
1027
1028
=item C<$params>
1029
1030
Hashref with options. The parameter C<is_opac> indicates whether the searchable
1031
fields for OPAC or Staff client should be retrieved. If C<weighted_fields> is set
1032
fields weights will be applied on returned fields. C<subfield> can be used to
1033
provide a subfield that will be appended to fields as "C<field_name>.C<subfield>".
1034
1035
=back
1036
1037
=cut
1038
1039
sub _search_fields {
1040
    my ($self, $params) = @_;
1041
    $params //= {
1042
        is_opac => 0,
1043
        weighted_fields => 0,
1044
        # This is a hack for authorities build_authorities_query
1045
        # can hopefully be removed in the future
1046
        subfield => undef,
1047
    };
1048
1049
    my $cache = Koha::Caches->get_instance();
1050
    my $cache_key = 'elasticsearch_search_fields' . ($params->{is_opac} ? '_opac' : '_staff_client');
1051
    my $search_fields = $cache->get_from_cache($cache_key, { unsafe => 1 });
1052
1053
    if (!$search_fields) {
1054
        # The reason we don't use Koha::SearchFields->search here is we don't
1055
        # want or need resultset wrapped as Koha::SearchField object.
1056
        # It does not make any sense in this context and would cause
1057
        # unnecessary overhead sice we are only querying for data
1058
        # Also would not work, or produce strange results, with the "columns"
1059
        # option.
1060
        my $schema = Koha::Database->schema;
1061
        my $result = $schema->resultset('SearchField')->search(
1062
            {
1063
                $params->{is_opac} ? (
1064
                    'opac' => 1,
1065
                ) : (
1066
                    'staff_client' => 1
1067
                ),
1068
                'search_marc_map.index_name' => $self->index,
1069
                'search_marc_map.marc_type' => C4::Context->preference('marcflavour'),
1070
                'search_marc_to_fields.search' => 1,
1071
            },
1072
            {
1073
                columns => [qw/name weight/],
1074
                collapse => 1,
1075
                join => {search_marc_to_fields => 'search_marc_map'},
1076
            }
1077
        );
1078
        my @search_fields;
1079
        while (my $search_field = $result->next) {
1080
            push @search_fields, [
1081
                $search_field->name,
1082
                $search_field->weight ? $search_field->weight : ()
1083
            ];
1084
        }
1085
        $search_fields = \@search_fields;
1086
        $cache->set_in_cache($cache_key, $search_fields);
1087
    }
1088
    if ($params->{subfield}) {
1089
        my $subfield = $params->{subfield};
1090
        $search_fields = [
1091
            map {
1092
                # Copy values to avoid mutating cached
1093
                # data (since unsafe is used)
1094
                my ($field, $weight) = @{$_};
1095
                ["${field}.${subfield}", $weight];
1096
            } @{$search_fields}
1097
        ];
1098
    }
1099
1100
    if ($params->{weighted_fields}) {
1101
        return [map { join('^', @{$_}) } @{$search_fields}];
1102
    }
1103
    else {
1104
        # Exclude weight from field
1105
        return [map { $_->[0] } @{$search_fields}];
1106
    }
1107
}
1108
972
1;
1109
1;
(-)a/Koha/SearchField.pm (-1 lines)
Lines 20-26 use Modern::Perl; Link Here
20
use Carp;
20
use Carp;
21
21
22
use Koha::Database;
22
use Koha::Database;
23
use Koha::SearchMarcMaps;
24
23
25
use base qw(Koha::Object);
24
use base qw(Koha::Object);
26
25
(-)a/Koha/SearchFields.pm (-15 lines)
Lines 35-55 Koha::SearchFields - Koha SearchField Object set class Link Here
35
35
36
=cut
36
=cut
37
37
38
=head3 weighted_fields
39
40
my (@w_fields, @weight) = Koha::SearchFields->weighted_fields();
41
42
=cut
43
44
sub weighted_fields {
45
    my ($self) = @_;
46
47
    return $self->search(
48
        { weight => { '>' => 0, '!=' => undef } },
49
        { order_by => { -desc => 'weight' } }
50
    );
51
}
52
53
=head3 type
38
=head3 type
54
39
55
=cut
40
=cut
(-)a/admin/searchengine/elasticsearch/field_config.yaml (-21 / +13 lines)
Lines 1-9 Link Here
1
---
1
---
2
# General field configuration
2
# General field configuration
3
general:
3
general:
4
  _all:
5
    type: string
6
    analyzer: analyser_standard
7
  properties:
4
  properties:
8
    marc_data:
5
    marc_data:
9
      store: true
6
      store: true
Lines 29-59 search: Link Here
29
    null_value: 0
26
    null_value: 0
30
  stdno:
27
  stdno:
31
    type: text
28
    type: text
32
    analyzer: analyser_stdno
29
    analyzer: analyzer_stdno
33
    search_analyzer: analyser_stdno
30
    search_analyzer: analyzer_stdno
34
    fields:
31
    fields:
35
      phrase:
32
      phrase:
36
        type: text
33
        type: text
37
        analyzer: analyser_stdno
34
        analyzer: analyzer_phrase
38
        search_analyzer: analyser_stdno
35
        search_analyzer: analyzer_phrase
39
      raw:
36
      raw:
40
        type: keyword
37
        type: keyword
41
    copy_to: _all
42
  default:
38
  default:
43
    type: text
39
    type: text
44
    analyzer: analyser_standard
40
    analyzer: analyzer_standard
45
    search_analyzer: analyser_standard
41
    search_analyzer: analyzer_standard
46
    fields:
42
    fields:
47
      phrase:
43
      phrase:
48
        type: text
44
        type: text
49
        analyzer: analyser_phrase
45
        analyzer: analyzer_phrase
50
        search_analyzer: analyser_phrase
46
        search_analyzer: analyzer_phrase
51
      raw:
47
      raw:
52
        type: keyword
48
        type: keyword
53
      lc_raw:
49
        normalizer: nfkc_cf_normalizer
50
      ci_raw:
54
        type: keyword
51
        type: keyword
55
        normalizer: my_normalizer
52
        normalizer: icu_folding_normalizer
56
    copy_to: _all
57
# Facets
53
# Facets
58
facet:
54
facet:
59
  default:
55
  default:
Lines 67-75 suggestible: Link Here
67
# Sort
63
# Sort
68
sort:
64
sort:
69
  default:
65
  default:
70
    type: text
66
    type: icu_collation_keyword
71
    analyzer: analyser_phrase
67
    index: false
72
    search_analyzer: analyser_phrase
73
    fields:
74
      phrase:
75
        type: keyword
(-)a/admin/searchengine/elasticsearch/index_config.yaml (-6 / +6 lines)
Lines 3-31 Link Here
3
index:
3
index:
4
  analysis:
4
  analysis:
5
    analyzer:
5
    analyzer:
6
      # Phrase analyzer is used for phrases (phrase match, sorting)
6
      # Phrase analyzer is used for phrases (exact phrase match)
7
      analyser_phrase:
7
      analyzer_phrase:
8
        tokenizer: keyword
8
        tokenizer: keyword
9
        filter:
9
        filter:
10
          - icu_folding
10
          - icu_folding
11
        char_filter:
11
        char_filter:
12
          - punctuation
12
          - punctuation
13
      analyser_standard:
13
      analyzer_standard:
14
        tokenizer: icu_tokenizer
14
        tokenizer: icu_tokenizer
15
        filter:
15
        filter:
16
          - icu_folding
16
          - icu_folding
17
      analyser_stdno:
17
      analyzer_stdno:
18
        tokenizer: whitespace
18
        tokenizer: whitespace
19
        filter:
19
        filter:
20
          - icu_folding
20
          - icu_folding
21
        char_filter:
21
        char_filter:
22
          - punctuation
22
          - punctuation
23
    normalizer:
23
    normalizer:
24
      normalizer_keyword:
24
      icu_folding_normalizer:
25
        type: custom
25
        type: custom
26
        filter:
26
        filter:
27
          - icu_folding
27
          - icu_folding
28
      my_normalizer:
28
      nfkc_cf_normalizer:
29
        type: custom
29
        type: custom
30
        char_filter: icu_normalizer
30
        char_filter: icu_normalizer
31
    char_filter:
31
    char_filter:
(-)a/admin/searchengine/elasticsearch/mappings.pl (-18 / +58 lines)
Lines 27-32 use Koha::SearchEngine::Elasticsearch; Link Here
27
use Koha::SearchEngine::Elasticsearch::Indexer;
27
use Koha::SearchEngine::Elasticsearch::Indexer;
28
use Koha::SearchMarcMaps;
28
use Koha::SearchMarcMaps;
29
use Koha::SearchFields;
29
use Koha::SearchFields;
30
use Koha::Caches;
30
31
31
use Try::Tiny;
32
use Try::Tiny;
32
33
Lines 68-73 my $update_mappings = sub { Link Here
68
    }
69
    }
69
};
70
};
70
71
72
my $cache = Koha::Caches->get_instance();
73
my $clear_cache = sub {
74
    $cache->clear_from_cache('elasticsearch_search_fields_staff_client');
75
    $cache->clear_from_cache('elasticsearch_search_fields_opac');
76
};
77
71
if ( $op eq 'edit' ) {
78
if ( $op eq 'edit' ) {
72
79
73
    $schema->storage->txn_begin;
80
    $schema->storage->txn_begin;
Lines 76-87 if ( $op eq 'edit' ) { Link Here
76
    my @field_label = $input->multi_param('search_field_label');
83
    my @field_label = $input->multi_param('search_field_label');
77
    my @field_type = $input->multi_param('search_field_type');
84
    my @field_type = $input->multi_param('search_field_type');
78
    my @field_weight = $input->multi_param('search_field_weight');
85
    my @field_weight = $input->multi_param('search_field_weight');
86
    my @field_staff_client = $input->multi_param('search_field_staff_client');
87
    my @field_opac = $input->multi_param('search_field_opac');
79
88
80
    my @index_name          = $input->multi_param('mapping_index_name');
89
    my @index_name          = $input->multi_param('mapping_index_name');
81
    my @search_field_name  = $input->multi_param('mapping_search_field_name');
90
    my @search_field_name   = $input->multi_param('mapping_search_field_name');
82
    my @mapping_sort        = $input->multi_param('mapping_sort');
91
    my @mapping_sort        = $input->multi_param('mapping_sort');
83
    my @mapping_facet       = $input->multi_param('mapping_facet');
92
    my @mapping_facet       = $input->multi_param('mapping_facet');
84
    my @mapping_suggestible = $input->multi_param('mapping_suggestible');
93
    my @mapping_suggestible = $input->multi_param('mapping_suggestible');
94
    my @mapping_search      = $input->multi_param('mapping_search');
85
    my @mapping_marc_field  = $input->multi_param('mapping_marc_field');
95
    my @mapping_marc_field  = $input->multi_param('mapping_marc_field');
86
    my @faceted_field_names = $input->multi_param('display_facet');
96
    my @faceted_field_names = $input->multi_param('display_facet');
87
97
Lines 92-97 if ( $op eq 'edit' ) { Link Here
92
            my $field_label = $field_label[$i];
102
            my $field_label = $field_label[$i];
93
            my $field_type = $field_type[$i];
103
            my $field_type = $field_type[$i];
94
            my $field_weight = $field_weight[$i];
104
            my $field_weight = $field_weight[$i];
105
            my $field_staff_client = $field_staff_client[$i];
106
            my $field_opac = $field_opac[$i];
95
107
96
            my $search_field = Koha::SearchFields->find( { name => $field_name }, { key => 'name' } );
108
            my $search_field = Koha::SearchFields->find( { name => $field_name }, { key => 'name' } );
97
            $search_field->label($field_label);
109
            $search_field->label($field_label);
Lines 106-111 if ( $op eq 'edit' ) { Link Here
106
            else {
118
            else {
107
                $search_field->weight($field_weight);
119
                $search_field->weight($field_weight);
108
            }
120
            }
121
            $search_field->staff_client($field_staff_client ? 1 : 0);
122
            $search_field->opac($field_opac ? 1 : 0);
109
123
110
            my $facet_order = first { $faceted_field_names[$_] eq $field_name } 0 .. $#faceted_field_names;
124
            my $facet_order = first { $faceted_field_names[$_] eq $field_name } 0 .. $#faceted_field_names;
111
            $search_field->facet_order(defined $facet_order ? $facet_order + 1 : undef);
125
            $search_field->facet_order(defined $facet_order ? $facet_order + 1 : undef);
Lines 118-136 if ( $op eq 'edit' ) { Link Here
118
132
119
        for my $i ( 0 .. scalar(@index_name) - 1 ) {
133
        for my $i ( 0 .. scalar(@index_name) - 1 ) {
120
            my $index_name          = $index_name[$i];
134
            my $index_name          = $index_name[$i];
121
            my $search_field_name  = $search_field_name[$i];
135
            my $search_field_name   = $search_field_name[$i];
122
            my $mapping_marc_field  = $mapping_marc_field[$i];
136
            my $mapping_marc_field  = $mapping_marc_field[$i];
123
            my $mapping_facet       = $mapping_facet[$i];
137
            my $mapping_facet       = $mapping_facet[$i];
124
            my $mapping_suggestible = $mapping_suggestible[$i];
125
            my $mapping_sort        = $mapping_sort[$i];
126
            $mapping_sort = undef if $mapping_sort eq 'undef';
127
            $mapping_facet = ( grep {/^$search_field_name$/} @facetable_field_names ) ? $mapping_facet : 0;
138
            $mapping_facet = ( grep {/^$search_field_name$/} @facetable_field_names ) ? $mapping_facet : 0;
139
            my $mapping_suggestible = $mapping_suggestible[$i];
140
            my $mapping_sort        = $mapping_sort[$i] eq 'undef' ? undef : $mapping_sort[$i];
141
            my $mapping_search      = $mapping_search[$i];
128
142
129
            my $search_field = Koha::SearchFields->find({ name => $search_field_name }, { key => 'name' });
143
            my $search_field = Koha::SearchFields->find({ name => $search_field_name }, { key => 'name' });
130
            # TODO Check mapping format
144
            # TODO Check mapping format
131
            my $marc_field = Koha::SearchMarcMaps->find_or_create({ index_name => $index_name, marc_type => $marc_type, marc_field => $mapping_marc_field });
145
            my $marc_field = Koha::SearchMarcMaps->find_or_create({
132
            $search_field->add_to_search_marc_maps($marc_field, { facet => $mapping_facet, suggestible => $mapping_suggestible, sort => $mapping_sort } );
146
                index_name => $index_name,
133
147
                marc_type => $marc_type,
148
                marc_field => $mapping_marc_field
149
            });
150
            $search_field->add_to_search_marc_maps($marc_field, {
151
                facet => $mapping_facet,
152
                suggestible => $mapping_suggestible,
153
                sort => $mapping_sort,
154
                search => $mapping_search
155
            });
134
        }
156
        }
135
    };
157
    };
136
    if ($@) {
158
    if ($@) {
Lines 139-144 if ( $op eq 'edit' ) { Link Here
139
    } else {
161
    } else {
140
        push @messages, { type => 'message', code => 'success_on_update' };
162
        push @messages, { type => 'message', code => 'success_on_update' };
141
        $schema->storage->txn_commit;
163
        $schema->storage->txn_commit;
164
        $clear_cache->();
142
        $update_mappings->();
165
        $update_mappings->();
143
    }
166
    }
144
}
167
}
Lines 146-158 elsif( $op eq 'reset_confirmed' ) { Link Here
146
    Koha::SearchMarcMaps->delete;
169
    Koha::SearchMarcMaps->delete;
147
    Koha::SearchFields->delete;
170
    Koha::SearchFields->delete;
148
    Koha::SearchEngine::Elasticsearch->reset_elasticsearch_mappings;
171
    Koha::SearchEngine::Elasticsearch->reset_elasticsearch_mappings;
172
    $clear_cache->();
149
    push @messages, { type => 'message', code => 'success_on_reset' };
173
    push @messages, { type => 'message', code => 'success_on_reset' };
150
}
174
}
151
elsif( $op eq 'reset_confirm' ) {
175
elsif( $op eq 'reset_confirm' ) {
152
    $template->param( reset_confirm => 1 );
176
    $template->param( reset_confirm => 1 );
153
}
177
}
154
178
155
156
my @indexes;
179
my @indexes;
157
180
158
for my $index_name (@index_names) {
181
for my $index_name (@index_names) {
Lines 179-207 for my $index_name (@index_names) { Link Here
179
my @facetable_fields = Koha::SearchEngine::Elasticsearch->get_facetable_fields();
202
my @facetable_fields = Koha::SearchEngine::Elasticsearch->get_facetable_fields();
180
for my $index_name (@index_names) {
203
for my $index_name (@index_names) {
181
    my $search_fields = Koha::SearchFields->search(
204
    my $search_fields = Koha::SearchFields->search(
182
        { 'search_marc_map.index_name' => $index_name, 'search_marc_map.marc_type' => $marc_type, },
205
        {
183
        {   join => { search_marc_to_fields => 'search_marc_map' },
206
            'search_marc_map.index_name' => $index_name,
184
            '+select' => [ 'search_marc_to_fields.facet', 'search_marc_to_fields.suggestible', 'search_marc_to_fields.sort', 'search_marc_map.marc_field' ],
207
            'search_marc_map.marc_type' => $marc_type,
185
            '+as'     => [ 'facet',                       'suggestible',                       'sort',                       'marc_field' ],
208
        },
209
        {
210
            join => { search_marc_to_fields => 'search_marc_map' },
211
            '+select' => [
212
                'search_marc_to_fields.facet',
213
                'search_marc_to_fields.suggestible',
214
                'search_marc_to_fields.sort',
215
                'search_marc_to_fields.search',
216
                'search_marc_map.marc_field'
217
            ],
218
            '+as' => [
219
                'facet',
220
                'suggestible',
221
                'sort',
222
                'search',
223
                'marc_field'
224
            ],
186
            order_by => { -asc => [qw/name marc_field/] }
225
            order_by => { -asc => [qw/name marc_field/] }
187
        }
226
         }
188
    );
227
     );
189
228
190
    my @mappings;
229
    my @mappings;
191
    my @facetable_field_names = map { $_->name } @facetable_fields;
230
    my @facetable_field_names = map { $_->name } @facetable_fields;
192
231
193
    while ( my $s = $search_fields->next ) {
232
    while ( my $s = $search_fields->next ) {
194
        my $name = $s->name;
233
        my $name = $s->name;
195
        push @mappings,
234
        push @mappings, {
196
          { search_field_name  => $name,
235
            search_field_name  => $name,
197
            search_field_label => $s->label,
236
            search_field_label => $s->label,
198
            search_field_type  => $s->type,
237
            search_field_type  => $s->type,
199
            marc_field         => $s->get_column('marc_field'),
238
            marc_field         => $s->get_column('marc_field'),
200
            sort               => $s->get_column('sort') // 'undef', # To avoid warnings "Use of uninitialized value in lc"
239
            sort               => $s->get_column('sort') // 'undef', # To avoid warnings "Use of uninitialized value in lc"
201
            suggestible        => $s->get_column('suggestible'),
240
            suggestible        => $s->get_column('suggestible'),
241
            search             => $s->get_column('search'),
202
            facet              => $s->get_column('facet'),
242
            facet              => $s->get_column('facet'),
203
            is_facetable       => ( grep {/^$name$/} @facetable_field_names ) ? 1 : 0,
243
            is_facetable       => ( grep {/^$name$/} @facetable_field_names ) ? 1 : 0,
204
          };
244
        };
205
    }
245
    }
206
246
207
    push @indexes, { index_name => $index_name, mappings => \@mappings };
247
    push @indexes, { index_name => $index_name, mappings => \@mappings };
(-)a/catalogue/search.pl (-6 / +1 lines)
Lines 463-473 my $page = $cgi->param('page') || 1; Link Here
463
# Define some global variables
463
# Define some global variables
464
my ( $error,$query,$simple_query,$query_cgi,$query_desc,$limit,$limit_cgi,$limit_desc,$query_type);
464
my ( $error,$query,$simple_query,$query_cgi,$query_desc,$limit,$limit_cgi,$limit_desc,$query_type);
465
465
466
my $build_params;
467
unless ( $cgi->param('advsearch') ) {
468
    $build_params->{weighted_fields} = 1;
469
}
470
471
my $builder = Koha::SearchEngine::QueryBuilder->new(
466
my $builder = Koha::SearchEngine::QueryBuilder->new(
472
    { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
467
    { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
473
my $searcher = Koha::SearchEngine::Search->new(
468
my $searcher = Koha::SearchEngine::Search->new(
Lines 480-486 my $searcher = Koha::SearchEngine::Search->new( Link Here
480
    $query_type
475
    $query_type
481
  )
476
  )
482
  = $builder->build_query_compat( \@operators, \@operands, \@indexes, \@limits,
477
  = $builder->build_query_compat( \@operators, \@operands, \@indexes, \@limits,
483
    \@sort_by, $scan, $lang, $build_params );
478
    \@sort_by, $scan, $lang, { weighted_fields => !$cgi->param('advsearch') });
484
479
485
## parse the query_cgi string and put it into a form suitable for <input>s
480
## parse the query_cgi string and put it into a form suitable for <input>s
486
my @query_inputs;
481
my @query_inputs;
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/searchengine/elasticsearch/mappings.tt (-6 / +60 lines)
Lines 160-167 a.add, a.delete { Link Here
160
                    <th>Name</th>
160
                    <th>Name</th>
161
                    <th>Label</th>
161
                    <th>Label</th>
162
                    <th>Type</th>
162
                    <th>Type</th>
163
                    <th colspan="2">Searchable</th>
163
                    <th>Weight</th>
164
                    <th>Weight</th>
164
                  </tr>
165
                  </tr>
166
                  <tr>
167
                    <th colspan=3>&nbsp;</th>
168
                    <th>Staff client</th>
169
                    <th>OPAC</th>
170
                    <th>&nbsp;</th>
171
                  </tr>
165
                </thead>
172
                </thead>
166
                <tbody>
173
                <tbody>
167
                  [% FOREACH search_field IN all_search_fields %]
174
                  [% FOREACH search_field IN all_search_fields %]
Lines 169-175 a.add, a.delete { Link Here
169
                      <td>
176
                      <td>
170
                        <input type="text" name="search_field_name" value="[% search_field.name | html %]" />
177
                        <input type="text" name="search_field_name" value="[% search_field.name | html %]" />
171
                      </td>
178
                      </td>
172
                      <td><input type="text" name="search_field_label" value="[% search_field.label | html %]" />
179
                      <td>
180
                        <input type="text" name="search_field_label" value="[% search_field.label | html %]" />
181
                      </td>
173
                      <td>
182
                      <td>
174
                        <select name="search_field_type">
183
                        <select name="search_field_type">
175
                          <option value=""></option>
184
                          <option value=""></option>
Lines 211-221 a.add, a.delete { Link Here
211
                        </select>
220
                        </select>
212
                      </td>
221
                      </td>
213
                      <td>
222
                      <td>
214
                      [% IF search_field.mapped_biblios %]
223
                        <select name="search_field_staff_client">
215
                        <input type="number" step="0.01" min="0.01" max="999.99" name="search_field_weight" value="[% search_field.weight | html %]" />
224
                          [% IF search_field.staff_client %]
216
                      [% ELSE %]
225
                            <option value="1" selected="selected">Yes</option>
217
                        <input type="hidden" name="search_field_weight" value="">
226
                            <option value="0">No</option>
218
                      [% END %]
227
                          [% ELSE %]
228
                            <option value="1">Yes</option>
229
                            <option value="0" selected="selected">No</option>
230
                          [% END %]
231
                        </select>
232
                      </td>
233
                      <td>
234
                        <select name="search_field_opac">
235
                          [% IF search_field.opac %]
236
                            <option value="1" selected="selected">Yes</option>
237
                            <option value="0">No</option>
238
                          [% ELSE %]
239
                            <option value="1">Yes</option>
240
                            <option value="0" selected="selected">No</option>
241
                          [% END %]
242
                        </select>
243
                      </td>
244
                      <td>
245
                        [% IF search_field.mapped_biblios %]
246
                            <input type="number" step="0.01" min="0.01" max="999.99" name="search_field_weight" value="[% search_field.weight | html %]" />
247
                        [% ELSE %]
248
                            <input type="hidden" name="search_field_weight" value="">
249
                        [% END %]
219
                      </td>
250
                      </td>
220
                    </tr>
251
                    </tr>
221
                  [% END %]
252
                  [% END %]
Lines 231-236 a.add, a.delete { Link Here
231
                          <th>Sortable</th>
262
                          <th>Sortable</th>
232
                          <th>Facetable</th>
263
                          <th>Facetable</th>
233
                          <th>Suggestible</th>
264
                          <th>Suggestible</th>
265
                          <th>Searchable</th>
234
                          <th>Mapping</th>
266
                          <th>Mapping</th>
235
                          <th></th>
267
                          <th></th>
236
                        </tr>
268
                        </tr>
Lines 290-295 a.add, a.delete { Link Here
290
                              </select>
322
                              </select>
291
                            </td>
323
                            </td>
292
                            <td>
324
                            <td>
325
                              <select name="mapping_search">
326
                                [% IF mapping.search %]
327
                                  <option value="0">No</option>
328
                                  <option value="1" selected="selected">Yes</option>
329
                                [% ELSE %]
330
                                  <option value="0" selected="selected">No</option>
331
                                  <option value="1">Yes</option>
332
                                [% END %]
333
                              </select>
334
                            </td>
335
                            <td>
293
                                <input name="mapping_marc_field" type="text" value="[% mapping.marc_field | html %]" />
336
                                <input name="mapping_marc_field" type="text" value="[% mapping.marc_field | html %]" />
294
                            </td>
337
                            </td>
295
                            <td><a class="btn btn-default btn-xs delete" style="cursor: pointer;"><i class="fa fa-trash"></i> Delete</a></td>
338
                            <td><a class="btn btn-default btn-xs delete" style="cursor: pointer;"><i class="fa fa-trash"></i> Delete</a></td>
Lines 335-340 a.add, a.delete { Link Here
335
                              [% END %]
378
                              [% END %]
336
                            </select>
379
                            </select>
337
                          </td>
380
                          </td>
381
                          <td>
382
                            <select data-id="mapping_search">
383
                              [% IF mapping.search %]
384
                                <option value="0">No</option>
385
                                <option value="1" selected="selected">Yes</option>
386
                              [% ELSE %]
387
                                <option value="0" selected="selected">No</option>
388
                                <option value="1">Yes</option>
389
                              [% END %]
390
                            </select>
391
                          </td>
338
                          <td><input data-id="mapping_marc_field" type="text" /></td>
392
                          <td><input data-id="mapping_marc_field" type="text" /></td>
339
                          <td><a class="btn btn-default btn-xs add"><i class="fa fa-plus"></i> Add</a></td>
393
                          <td><a class="btn btn-default btn-xs add"><i class="fa fa-plus"></i> Add</a></td>
340
                        </tr>
394
                        </tr>
(-)a/opac/opac-search.pl (-10 / +15 lines)
Lines 554-571 if (C4::Context->preference('OpacSuppression')) { Link Here
554
    }
554
    }
555
}
555
}
556
556
557
my $build_params = {
558
    suppress => $suppress
559
};
560
561
unless ( $cgi->param('advsearch') ) {
562
    $build_params->{weighted_fields} = 1;
563
}
564
565
## I. BUILD THE QUERY
557
## I. BUILD THE QUERY
566
( $error,$query,$simple_query,$query_cgi,$query_desc,$limit,$limit_cgi,$limit_desc,$query_type)
558
( $error,$query,$simple_query,$query_cgi,$query_desc,$limit,$limit_cgi,$limit_desc,$query_type)
567
  = $builder->build_query_compat( \@operators, \@operands,
559
  = $builder->build_query_compat(
568
    \@indexes, \@limits, \@sort_by, 0, $lang, $build_params);
560
    \@operators,
561
    \@operands,
562
    \@indexes,
563
    \@limits,
564
    \@sort_by,
565
    0,
566
    $lang,
567
    {
568
        expanded_facet => $expanded_facet,
569
        suppress => $suppress,
570
        is_opac => 1,
571
        weighted_fields => !$cgi->param('advsearch')
572
    }
573
);
569
574
570
sub _input_cgi_parse {
575
sub _input_cgi_parse {
571
    my @elements;
576
    my @elements;
(-)a/t/db_dependent/Koha/SearchEngine/Elasticsearch/QueryBuilder.t (-22 / +41 lines)
Lines 23-28 use t::lib::Mocks; Link Here
23
use t::lib::TestBuilder;
23
use t::lib::TestBuilder;
24
use Test::More tests => 6;
24
use Test::More tests => 6;
25
25
26
use List::Util qw( all );
27
26
use Koha::Database;
28
use Koha::Database;
27
use Koha::SearchEngine::Elasticsearch::QueryBuilder;
29
use Koha::SearchEngine::Elasticsearch::QueryBuilder;
28
30
Lines 81-88 $se->mock( 'get_elasticsearch_mappings', sub { Link Here
81
    return $all_mappings{$self->index};
83
    return $all_mappings{$self->index};
82
});
84
});
83
85
86
my $cache = Koha::Caches->get_instance();
87
my $clear_search_fields_cache = sub {
88
    $cache->clear_from_cache('elasticsearch_search_fields_staff_client');
89
    $cache->clear_from_cache('elasticsearch_search_fields_opac');
90
};
91
84
subtest 'build_authorities_query_compat() tests' => sub {
92
subtest 'build_authorities_query_compat() tests' => sub {
85
    plan tests => 37;
93
    plan tests => 47;
86
94
87
    my $qb;
95
    my $qb;
88
96
Lines 107-140 subtest 'build_authorities_query_compat() tests' => sub { Link Here
107
    $search_term = 'Donald Duck';
115
    $search_term = 'Donald Duck';
108
    foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
116
    foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
109
        my $query = $qb->build_authorities_query_compat( [ $koha_name ],  undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
117
        my $query = $qb->build_authorities_query_compat( [ $koha_name ],  undef, undef, ['contains'], [$search_term], 'AUTH_TYPE', 'asc' );
118
        is( $query->{query}->{bool}->{must}[0]->{query_string}->{query}, "(Donald*) AND (Duck*)" );
110
        if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
119
        if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
111
            is( $query->{query}->{bool}->{must}[0]->{query_string}->{query},
120
            isa_ok( $query->{query}->{bool}->{must}[0]->{query_string}->{fields}, 'ARRAY')
112
                "(Donald*) AND (Duck*)");
113
        } else {
121
        } else {
114
            is( $query->{query}->{bool}->{must}[0]->{query_string}->{query},
122
            is( $query->{query}->{bool}->{must}[0]->{query_string}->{default_field}, $koha_to_index_name->{$koha_name} );
115
                "(Donald*) AND (Duck*)");
116
        }
123
        }
117
    }
124
    }
118
125
119
    foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
126
    foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
120
        my $query = $qb->build_authorities_query_compat( [ $koha_name ],  undef, undef, ['is'], [$search_term], 'AUTH_TYPE', 'asc' );
127
        my $query = $qb->build_authorities_query_compat( [ $koha_name ],  undef, undef, ['is'], [$search_term], 'AUTH_TYPE', 'asc' );
121
        if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
128
        if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
122
            is( $query->{query}->{bool}->{must}[0]->{match_phrase}->{"_all.phrase"},
129
            is(
123
                "donald duck");
130
                $query->{query}->{bool}->{must}[0]->{multi_match}->{query},
131
                "Donald Duck"
132
            );
133
            my $all_matches = all { /\.ci_raw$/ }
134
                @{$query->{query}->{bool}->{must}[0]->{multi_match}->{fields}};
135
            ok( $all_matches, 'Correct fields parameter for "is" query in "any" or "all"' );
124
        } else {
136
        } else {
125
            is( $query->{query}->{bool}->{must}[0]->{match_phrase}->{$koha_to_index_name->{$koha_name}.".phrase"},
137
            is(
126
                "donald duck");
138
                $query->{query}->{bool}->{must}[0]->{term}->{$koha_to_index_name->{$koha_name} . ".ci_raw"},
139
                "Donald Duck"
140
            );
127
        }
141
        }
128
    }
142
    }
129
143
130
    foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
144
    foreach my $koha_name ( keys %{ $koha_to_index_name } ) {
131
        my $query = $qb->build_authorities_query_compat( [ $koha_name ],  undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'asc' );
145
        my $query = $qb->build_authorities_query_compat( [ $koha_name ],  undef, undef, ['start'], [$search_term], 'AUTH_TYPE', 'asc' );
132
        if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
146
        if ( $koha_name eq 'all' || $koha_name eq 'any' ) {
133
            is( $query->{query}->{bool}->{must}[0]->{match_phrase_prefix}->{"_all.phrase"},
147
            my $all_matches = all { (%{$_->{prefix}})[0] =~ /\.ci_raw$/ && (%{$_->{prefix}})[1] eq "Donald Duck" }
134
                "donald duck");
148
                @{$query->{query}->{bool}->{must}[0]->{bool}->{should}};
149
            ok( $all_matches, "Correct multiple prefix query" );
135
        } else {
150
        } else {
136
            is( $query->{query}->{bool}->{must}[0]->{match_phrase_prefix}->{$koha_to_index_name->{$koha_name}.".phrase"},
151
            is( $query->{query}->{bool}->{must}[0]->{prefix}->{$koha_to_index_name->{$koha_name} . ".ci_raw"}, "Donald Duck" );
137
                "donald duck");
138
        }
152
        }
139
    }
153
    }
140
154
Lines 452-470 subtest 'build query from form subtests' => sub { Link Here
452
};
466
};
453
467
454
subtest 'build_query with weighted fields tests' => sub {
468
subtest 'build_query with weighted fields tests' => sub {
455
    plan tests => 4;
469
    plan tests => 2;
456
470
457
    my $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new( { index => 'mydb' } );
471
    my $qb = Koha::SearchEngine::Elasticsearch::QueryBuilder->new( { index => 'mydb' } );
458
    my $db_builder = t::lib::TestBuilder->new();
472
    my $db_builder = t::lib::TestBuilder->new();
459
473
460
    Koha::SearchFields->search({})->delete;
474
    Koha::SearchFields->search({})->delete;
475
    $clear_search_fields_cache->();
461
476
462
    $db_builder->build({
477
    $db_builder->build({
463
        source => 'SearchField',
478
        source => 'SearchField',
464
        value => {
479
        value => {
465
            name    => 'acqdate',
480
            name    => 'acqdate',
466
            label   => 'acqdate',
481
            label   => 'acqdate',
467
            weight  => undef
482
            weight  => undef,
483
            staff_client => 1
468
        }
484
        }
469
    });
485
    });
470
486
Lines 473-479 subtest 'build_query with weighted fields tests' => sub { Link Here
473
        value => {
489
        value => {
474
            name    => 'title',
490
            name    => 'title',
475
            label   => 'title',
491
            label   => 'title',
476
            weight  => 25
492
            weight  => 25,
493
            staff_client => 1
477
        }
494
        }
478
    });
495
    });
479
496
Lines 482-488 subtest 'build_query with weighted fields tests' => sub { Link Here
482
        value => {
499
        value => {
483
            name    => 'subject',
500
            name    => 'subject',
484
            label   => 'subject',
501
            label   => 'subject',
485
            weight  => 15
502
            weight  => 15,
503
            staff_client => 1
486
        }
504
        }
487
    });
505
    });
488
506
Lines 490-499 subtest 'build_query with weighted fields tests' => sub { Link Here
490
    undef, undef, undef, { weighted_fields => 1 });
508
    undef, undef, undef, { weighted_fields => 1 });
491
509
492
    my $fields = $query->{query}{query_string}{fields};
510
    my $fields = $query->{query}{query_string}{fields};
493
    is(scalar(@$fields), 3, 'Search is done on 3 fields');
511
494
    is($fields->[0], '_all', 'First search field is _all');
512
    my @found = grep { $_ eq 'title^25.00' } @{$fields};
495
    is($fields->[1], 'title^25.00', 'Second search field is title');
513
    is(@found, 1, 'Search field is title has correct weight'); # Fails
496
    is($fields->[2], 'subject^15.00', 'Third search field is subject');
514
515
    @found = grep { $_ eq 'subject^15.00' } @{$fields};
516
    is(@found, 1, 'Search field subject has correct weight'); # Fails
497
};
517
};
498
518
499
subtest "_convert_sort_fields() tests" => sub {
519
subtest "_convert_sort_fields() tests" => sub {
500
- 

Return to bug 20589