Skip to content

Commit 1aca9ec

Browse files
kidclampkfischer
authored andcommitted
Bug 38694: Add ESBoostFieldMatch option to Elasticsearch
What this patch does is: 1 - Wraps the existing search code in a a "bool" compound query as a "must". This should not affect relevancy or results of the existing searches. 2 - Before we clean/truncate terms, loop through the passed in search terms and indexes to build a new 'should' query, using the 'match' on the specified index/field that is added to the 'bool' query from above. This means that if a result from the original query is also returned here, that item will be boosted in the result. For searches on 'keyword' or 'title', or if no index is set, we use 'title-cover' as the most narrow form of title This query isn't going to help when users enter CCL (i.e. ti:To die for) and it won't boost titles from 505, series, etc when doing a general search. Nor will it have a detrimental effect, it will only boost field matches To test: 1 - Add a record with 245 $a novel 2 - Add a record with 245 $a A novel : $b about things / $c by me 3 - Search for: novel - Above records are returned lower in the list (results #6 and #14 for me) 4 - Search for: a novel - Above records are returned lower in the list (results #6 and #14 for me) 5 - Apply patch, restart all, enable ESBoostFieldMatch option 6 - Repeat searches, note exact titles are boosted - For 'novel' record from #1 is #1 result and other is second - For 'a novel' record form #2 is #1 result and other is third 7 - Disable the new pref and repeat steps 3 and 4 and get results as before patch 8 - Experiment with other searches, turning pref on and off to verify relevant titles are boosted when enabled 9 - Search results when disabled should return as before the patch Signed-off-by: Matthias Meusburger <matthias.meusburger@biblibre.com> Signed-off-by: Thomas Klausner <domm@plix.at> Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
1 parent d7265dd commit 1aca9ec

4 files changed

Lines changed: 77 additions & 16 deletions

File tree

Koha/SearchEngine/Elasticsearch/QueryBuilder.pm

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -203,17 +203,19 @@ sub build_query {
203203
if ( $options{whole_record} ) {
204204
push @$fields, 'marc_data_array.*';
205205
}
206-
$res->{query} = {
207-
query_string => {
208-
query => $query,
209-
fuzziness => $fuzzy_enabled ? 'auto' : '0',
210-
default_operator => 'AND',
211-
fields => $fields,
212-
lenient => JSON::true,
213-
analyze_wildcard => JSON::true,
214-
}
206+
my $query_string = {
207+
query => $query,
208+
fuzziness => $fuzzy_enabled ? 'auto' : '0',
209+
default_operator => 'AND',
210+
fields => $fields,
211+
lenient => JSON::true,
212+
analyze_wildcard => JSON::true,
215213
};
216-
$res->{query}->{query_string}->{type} = 'cross_fields' if C4::Context->preference('ElasticsearchCrossFields');
214+
$query_string->{type} = 'cross_fields' if C4::Context->preference('ElasticsearchCrossFields');
215+
216+
$res->{query} = { bool => { must => [ { query_string => $query_string } ] } };
217+
218+
$res->{query}->{bool}->{should} = $options{field_match_boost_query} if $options{field_match_boost_query};
217219

218220
if ( $options{sort} ) {
219221
foreach my $sort ( @{ $options{sort} } ) {
@@ -288,6 +290,10 @@ sub build_query_compat {
288290
} else {
289291
my @sort_params = $self->_convert_sort_fields(@$sort_by);
290292
my @index_params = $self->_convert_index_fields(@$indexes);
293+
my $field_match_boost_query =
294+
C4::Context->preference('ESBoostFieldMatch')
295+
? $self->_build_field_match_boost_query( { operands => $operands, indexes => \@index_params } )
296+
: [];
291297
$limits = $self->_fix_limit_special_cases($orig_limits);
292298
if ( $params->{suppress} ) { push @$limits, "suppress:false"; }
293299

@@ -328,12 +334,13 @@ sub build_query_compat {
328334
# If there's no query on the left, let's remove the junk left behind
329335
$query_str =~ s/^ AND //;
330336
my %options;
331-
$options{sort} = \@sort_params;
332-
$options{is_opac} = $params->{is_opac};
333-
$options{weighted_fields} = $params->{weighted_fields};
334-
$options{whole_record} = $params->{whole_record};
335-
$options{skip_facets} = $params->{skip_facets};
336-
$query = $self->build_query( $query_str, %options );
337+
$options{sort} = \@sort_params;
338+
$options{is_opac} = $params->{is_opac};
339+
$options{weighted_fields} = $params->{weighted_fields};
340+
$options{whole_record} = $params->{whole_record};
341+
$options{skip_facets} = $params->{skip_facets};
342+
$options{field_match_boost_query} = $field_match_boost_query if @$field_match_boost_query;
343+
$query = $self->build_query( $query_str, %options );
337344
}
338345

339346
# We roughly emulate the CGI parameters of the zebra query builder
@@ -369,6 +376,30 @@ sub build_query_compat {
369376
);
370377
}
371378

379+
=head2 _build_field_match_boost_query
380+
381+
my ($query, $query_str) = $builder->_build_field_match_boost_query({ operands => \@operands, indexes => \@indexes)
382+
383+
This will build an array of match queries for terms and indexes passed in and return a reference to the array.
384+
385+
=cut
386+
387+
sub _build_field_match_boost_query {
388+
my ( $self, $params ) = @_;
389+
my $indexes = $params->{indexes};
390+
my $operands = $params->{operands};
391+
392+
my @boost_query;
393+
my $ea = each_array( @$operands, @$indexes );
394+
while ( my ( $operand, $index ) = $ea->() ) {
395+
next unless $operand;
396+
$index = $index->{field} if ref $index eq 'HASH';
397+
$index = 'title-cover' if ( !$index || $index eq 'kw' || $index eq 'ti' || $index eq 'title' );
398+
push @boost_query, { match => { $index => { query => $operand } } };
399+
}
400+
return \@boost_query;
401+
}
402+
372403
=head2 build_authorities_query
373404
374405
my $query = $builder->build_authorities_query(\%search);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use Modern::Perl;
2+
use Koha::Installer::Output qw(say_warning say_success say_info);
3+
4+
return {
5+
bug_number => "38694",
6+
description => "Add ESBoostFieldMatch system preference",
7+
up => sub {
8+
my ($args) = @_;
9+
my ( $dbh, $out ) = @$args{qw(dbh out)};
10+
11+
# Do you stuffs here
12+
$dbh->do(
13+
q{
14+
INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES
15+
('ESBoostFieldMatch', '0', NULL, 'Add a "match" query to es when searching, will follow indexes chosen in advanced search, or use title-cover for generic keyword or title index search', 'YesNo')
16+
}
17+
);
18+
19+
say_success( $out, "Added new system preference 'ESBoostFieldMatch'" );
20+
},
21+
};

installer/data/mysql/mandatory/sysprefs.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `
261261
('ERMProviderEbscoApiKey', '', '', 'API key for EBSCO', 'free'),
262262
('ERMProviderEbscoCustomerID', '', '', 'Customer ID for EBSCO', 'free'),
263263
('ERMProviders', 'local', 'local|ebsco', 'Set the providers for the ERM module', 'Choice'),
264+
('ESBoostFieldMatch', '0', NULL, 'Add a "match" query to es when searching, will follow indexes chosen in advanced search, or use title-cover for generic keyword or title index search', 'YesNo'),
264265
('ESPreventAutoTruncate', 'barcode|control-number|control-number-identifier|date-of-acquisition|date-of-publication|date-time-last-modified|identifier-standard|isbn|issn|itype|lc-card-number|number-local-acquisition|other-control-number|record-control-number', NULL, 'List of searchfields (separated by | or ,) that should not be autotruncated by Elasticsearch even if QueryAutoTruncate is set to Yes', 'free'),
265266
('ExcludeHolidaysFromMaxPickUpDelay', '0', NULL, 'If ON, reserves max pickup delay takes into accountthe closed days.', 'YesNo'),
266267
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),

koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/searching.pref

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ Searching:
9595
0: Disable
9696
- "the cross_fields option for Elasticsearch searches, supported in Elasticsearch 6.X and above."
9797
- See the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#type-cross-fields">Elasticsearch cross_fields documentation</a>.
98+
-
99+
- pref: ESBoostFieldMatch
100+
default: 0
101+
choices:
102+
1: Enable
103+
0: Disable
104+
- "adding a second 'match' field search to the ES query to boost relevancy. Keyword and title fields will be boosted with title-cover, other fields will boost directly."
105+
- "This will not boost CCL style searches, only standard or advanced searches"
98106
-
99107
- pref: SavedSearchFilters
100108
default: 0

0 commit comments

Comments
 (0)