Lines 34-41
use Search::Elasticsearch;
Link Here
|
34 |
use Try::Tiny; |
34 |
use Try::Tiny; |
35 |
use YAML::Syck; |
35 |
use YAML::Syck; |
36 |
|
36 |
|
37 |
use Data::Dumper; # TODO remove |
|
|
38 |
|
39 |
__PACKAGE__->mk_ro_accessors(qw( index )); |
37 |
__PACKAGE__->mk_ro_accessors(qw( index )); |
40 |
__PACKAGE__->mk_accessors(qw( sort_fields )); |
38 |
__PACKAGE__->mk_accessors(qw( sort_fields )); |
41 |
|
39 |
|
Lines 137-160
A hashref containing the settings is returned.
Link Here
|
137 |
sub get_elasticsearch_settings { |
135 |
sub get_elasticsearch_settings { |
138 |
my ($self) = @_; |
136 |
my ($self) = @_; |
139 |
|
137 |
|
140 |
# Ultimately this should come from a file or something, and not be |
138 |
# Use state to speed up repeated calls |
141 |
# hardcoded. |
139 |
state $settings = undef; |
142 |
my $settings = { |
140 |
if (!defined $settings) { |
143 |
index => { |
141 |
my $config_file = C4::Context->config('elasticsearch_index_config'); |
144 |
analysis => { |
142 |
$config_file ||= C4::Context->config('intranetdir') . '/etc/searchengine/elasticsearch/index_config.yaml'; |
145 |
analyzer => { |
143 |
$settings = LoadFile( $config_file ); |
146 |
analyser_phrase => { |
144 |
} |
147 |
tokenizer => 'icu_tokenizer', |
145 |
|
148 |
filter => ['icu_folding'], |
|
|
149 |
}, |
150 |
analyser_standard => { |
151 |
tokenizer => 'icu_tokenizer', |
152 |
filter => ['icu_folding'], |
153 |
}, |
154 |
}, |
155 |
} |
156 |
} |
157 |
}; |
158 |
return $settings; |
146 |
return $settings; |
159 |
} |
147 |
} |
160 |
|
148 |
|
Lines 170-285
created.
Link Here
|
170 |
sub get_elasticsearch_mappings { |
158 |
sub get_elasticsearch_mappings { |
171 |
my ($self) = @_; |
159 |
my ($self) = @_; |
172 |
|
160 |
|
173 |
# TODO cache in the object? |
161 |
# Use state to speed up repeated calls |
174 |
my $mappings = { |
162 |
state %all_mappings; |
175 |
data => { |
163 |
state %sort_fields; |
176 |
_all => {type => "string", analyzer => "analyser_standard"}, |
164 |
|
177 |
properties => { |
165 |
if (!defined $all_mappings{$self->index}) { |
178 |
record => { |
166 |
$sort_fields{$self->index} = {}; |
179 |
store => "true", |
167 |
my $mappings = { |
180 |
include_in_all => JSON::false, |
168 |
data => _get_elasticsearch_mapping('general', '') |
181 |
type => "text", |
169 |
}; |
182 |
}, |
170 |
my $marcflavour = lc C4::Context->preference('marcflavour'); |
183 |
} |
171 |
$self->_foreach_mapping( |
184 |
} |
172 |
sub { |
185 |
}; |
173 |
my ( $name, $type, $facet, $suggestible, $sort, $marc_type ) = @_; |
186 |
my %sort_fields; |
174 |
return if $marc_type ne $marcflavour; |
187 |
my $marcflavour = lc C4::Context->preference('marcflavour'); |
175 |
# TODO if this gets any sort of complexity to it, it should |
188 |
$self->_foreach_mapping( |
176 |
# be broken out into its own function. |
189 |
sub { |
177 |
|
190 |
my ( $name, $type, $facet, $suggestible, $sort, $marc_type ) = @_; |
178 |
# TODO be aware of date formats, but this requires pre-parsing |
191 |
return if $marc_type ne $marcflavour; |
179 |
# as ES will simply reject anything with an invalid date. |
192 |
# TODO if this gets any sort of complexity to it, it should |
180 |
my $es_type = 'text'; |
193 |
# be broken out into its own function. |
181 |
if ($type eq 'boolean') { |
194 |
|
182 |
$es_type = 'boolean'; |
195 |
# TODO be aware of date formats, but this requires pre-parsing |
183 |
} elsif ($type eq 'number' || $type eq 'sum') { |
196 |
# as ES will simply reject anything with an invalid date. |
184 |
$es_type = 'integer'; |
197 |
my $es_type = |
185 |
} elsif ($type eq 'isbn' || $type eq 'stdno') { |
198 |
$type eq 'boolean' |
186 |
$es_type = 'stdno'; |
199 |
? 'boolean' |
187 |
} |
200 |
: 'text'; |
|
|
201 |
|
202 |
if ($es_type eq 'boolean') { |
203 |
$mappings->{data}{properties}{$name} = _elasticsearch_mapping_for_boolean( $name, $es_type, $facet, $suggestible, $sort, $marc_type ); |
204 |
return; #Boolean cannot have facets nor sorting nor suggestions |
205 |
} else { |
206 |
$mappings->{data}{properties}{$name} = _elasticsearch_mapping_for_default( $name, $es_type, $facet, $suggestible, $sort, $marc_type ); |
207 |
} |
208 |
|
188 |
|
209 |
if ($facet) { |
189 |
$mappings->{data}{properties}{$name} = _get_elasticsearch_mapping('search', $es_type); |
210 |
$mappings->{data}{properties}{ $name . '__facet' } = { |
190 |
|
211 |
type => "keyword", |
191 |
if ($facet) { |
212 |
}; |
192 |
$mappings->{data}{properties}{ $name . '__facet' } = _get_elasticsearch_mapping('facet', $es_type); |
213 |
} |
193 |
} |
214 |
if ($suggestible) { |
194 |
if ($suggestible) { |
215 |
$mappings->{data}{properties}{ $name . '__suggestion' } = { |
195 |
$mappings->{data}{properties}{ $name . '__suggestion' } = _get_elasticsearch_mapping('suggestible', $es_type); |
216 |
type => 'completion', |
196 |
} |
217 |
analyzer => 'simple', |
197 |
# Sort is a bit special as it can be true, false, undef. |
218 |
search_analyzer => 'simple', |
198 |
# We care about "true" or "undef", |
219 |
}; |
199 |
# "undef" means to do the default thing, which is make it sortable. |
220 |
} |
200 |
if (!defined $sort || $sort) { |
221 |
# Sort is a bit special as it can be true, false, undef. |
201 |
$mappings->{data}{properties}{ $name . '__sort' } = _get_elasticsearch_mapping('sort', $es_type); |
222 |
# We care about "true" or "undef", |
202 |
$sort_fields{$self->index}{$name} = 1; |
223 |
# "undef" means to do the default thing, which is make it sortable. |
203 |
} |
224 |
if ($sort || !defined $sort) { |
|
|
225 |
$mappings->{data}{properties}{ $name . '__sort' } = { |
226 |
search_analyzer => "analyser_phrase", |
227 |
analyzer => "analyser_phrase", |
228 |
type => "text", |
229 |
include_in_all => JSON::false, |
230 |
fields => { |
231 |
phrase => { |
232 |
type => "keyword", |
233 |
}, |
234 |
}, |
235 |
}; |
236 |
$sort_fields{$name} = 1; |
237 |
} |
204 |
} |
238 |
} |
205 |
); |
239 |
); |
206 |
$all_mappings{$self->index} = $mappings; |
240 |
$self->sort_fields(\%sort_fields); |
207 |
} |
241 |
return $mappings; |
208 |
$self->sort_fields(\%{$sort_fields{$self->index}}); |
|
|
209 |
|
210 |
return $all_mappings{$self->index}; |
242 |
} |
211 |
} |
243 |
|
212 |
|
244 |
=head2 _elasticsearch_mapping_for_* |
213 |
=head2 _get_elasticsearch_mapping |
245 |
|
214 |
|
246 |
Get the ES mappings for the given data type or a special mapping case |
215 |
Get the ES mappings for the given purpose and data type |
247 |
|
216 |
|
248 |
Receives the same parameters from the $self->_foreach_mapping() dispatcher |
217 |
$mapping = _get_elasticsearch_mapping('search', 'text'); |
249 |
|
218 |
|
250 |
=cut |
219 |
=cut |
251 |
|
220 |
|
252 |
sub _elasticsearch_mapping_for_boolean { |
221 |
sub _get_elasticsearch_mapping { |
253 |
my ( $name, $type, $facet, $suggestible, $sort, $marc_type ) = @_; |
|
|
254 |
|
222 |
|
255 |
return { |
223 |
my ( $purpose, $type ) = @_; |
256 |
type => $type, |
|
|
257 |
null_value => 0, |
258 |
}; |
259 |
} |
260 |
|
224 |
|
261 |
sub _elasticsearch_mapping_for_default { |
225 |
# Use state to speed up repeated calls |
262 |
my ( $name, $type, $facet, $suggestible, $sort, $marc_type ) = @_; |
226 |
state $settings = undef; |
263 |
|
227 |
if (!defined $settings) { |
264 |
return { |
228 |
my $config_file = C4::Context->config('elasticsearch_field_config'); |
265 |
search_analyzer => "analyser_standard", |
229 |
$config_file ||= C4::Context->config('intranetdir') . '/etc/searchengine/elasticsearch/field_config.yaml'; |
266 |
analyzer => "analyser_standard", |
230 |
$settings = LoadFile( $config_file ); |
267 |
type => $type, |
231 |
} |
268 |
fields => { |
232 |
|
269 |
phrase => { |
233 |
if (!defined $settings->{$purpose}) { |
270 |
search_analyzer => "analyser_phrase", |
234 |
die "Field purpose $purpose not defined in field config"; |
271 |
analyzer => "analyser_phrase", |
235 |
} |
272 |
type => "text", |
236 |
if ($type eq '') { |
273 |
}, |
237 |
return $settings->{$purpose}; |
274 |
raw => { |
238 |
} |
275 |
type => "keyword", |
239 |
if (defined $settings->{$purpose}{$type}) { |
276 |
} |
240 |
return $settings->{$purpose}{$type}; |
277 |
}, |
241 |
} |
278 |
}; |
242 |
if (defined $settings->{$purpose}{'default'}) { |
|
|
243 |
return $settings->{$purpose}{'default'}; |
244 |
} |
245 |
return undef; |
279 |
} |
246 |
} |
280 |
|
247 |
|
281 |
sub reset_elasticsearch_mappings { |
248 |
sub reset_elasticsearch_mappings { |
282 |
my $mappings_yaml = C4::Context->config('intranetdir') . '/admin/searchengine/elasticsearch/mappings.yaml'; |
249 |
my ( $reset_fields ) = @_; |
|
|
250 |
my $mappings_yaml = C4::Context->config('elasticsearch_index_mappings'); |
251 |
$mappings_yaml ||= C4::Context->config('intranetdir') . '/etc/searchengine/elasticsearch/mappings.yaml'; |
283 |
my $indexes = LoadFile( $mappings_yaml ); |
252 |
my $indexes = LoadFile( $mappings_yaml ); |
284 |
|
253 |
|
285 |
while ( my ( $index_name, $fields ) = each %$indexes ) { |
254 |
while ( my ( $index_name, $fields ) = each %$indexes ) { |
Lines 349-358
sub get_fixer_rules {
Link Here
|
349 |
if ($type eq 'sum' ) { |
318 |
if ($type eq 'sum' ) { |
350 |
push @rules, "sum('$name')"; |
319 |
push @rules, "sum('$name')"; |
351 |
} |
320 |
} |
352 |
if ($self->sort_fields()->{$name}) { |
321 |
my $sort_fields = $self->sort_fields(); |
353 |
if ($sort || !defined $sort) { |
322 |
if (defined $sort_fields->{$name} && $sort_fields->{$name}) { |
354 |
push @rules, "marc_map('$marc_field','${name}__sort.\$append', $options)"; |
323 |
push @rules, "marc_map('$marc_field','${name}__sort.\$append')"; |
355 |
} |
324 |
push @rules, "join_field('${name}__sort',' ')"; |
356 |
} |
325 |
} |
357 |
} |
326 |
} |
358 |
); |
327 |
); |