|
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 ) { |