ffzg/recall_notices.pl: added --interval and --dedup
[koha.git] / misc / search_tools / rebuild_elastic_search.pl
1 #!/usr/bin/perl
2
3 # This inserts records from a Koha database into elastic search
4
5 # Copyright 2014 Catalyst IT
6 #
7 # This file is part of Koha.
8 #
9 # Koha is free software; you can redistribute it and/or modify it under the
10 # terms of the GNU General Public License as published by the Free Software
11 # Foundation; either version 3 of the License, or (at your option) any later
12 # version.
13 #
14 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License along
19 # with Koha; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22 =head1 NAME
23
24 rebuild_elastic_search.pl - inserts records from a Koha database into Elasticsearch
25
26 =head1 SYNOPSIS
27
28 B<rebuild_elastic_search.pl>
29 [B<-c|--commit>=C<count>]
30 [B<-v|--verbose>]
31 [B<-h|--help>]
32 [B<--man>]
33
34 =head1 DESCRIPTION
35
36 Inserts records from a Koha database into Elasticsearch.
37
38 =head1 OPTIONS
39
40 =over
41
42 =item B<-c|--commit>=C<count>
43
44 Specify how many records will be batched up before they're added to Elasticsearch.
45 Higher should be faster, but will cause more RAM usage. Default is 5000.
46
47 =item B<-d|--delete>
48
49 Delete the index and recreate it before indexing.
50
51 =item B<-a|--authorities>
52
53 Index the authorities only. Combining this with B<-b> is the same as
54 specifying neither and so both get indexed.
55
56 =item B<-b|--biblios>
57
58 Index the biblios only. Combining this with B<-a> is the same as
59 specifying neither and so both get indexed.
60
61 =item B<-bn|--bnumber>
62
63 Only index the supplied biblionumber, mostly for testing purposes. May be
64 repeated. This also applies to authorities via authid, so if you're using it,
65 you probably only want to do one or the other at a time.
66
67 =item B<-v|--verbose>
68
69 By default, this program only emits warnings and errors. This makes it talk
70 more. Add more to make it even more wordy, in particular when debugging.
71
72 =item B<-h|--help>
73
74 Help!
75
76 =item B<--man>
77
78 Full documentation.
79
80 =back
81
82 =cut
83
84 use autodie;
85 use Getopt::Long;
86 use C4::Context;
87 use Koha::MetadataRecord::Authority;
88 use Koha::BiblioUtils;
89 use Koha::SearchEngine::Elasticsearch::Indexer;
90 use MARC::Field;
91 use MARC::Record;
92 use Modern::Perl;
93 use Pod::Usage;
94
95 my $verbose = 0;
96 my $commit = 5000;
97 my ($delete, $help, $man);
98 my ($index_biblios, $index_authorities);
99 my (@biblionumbers);
100
101 $|=1; # flushes output
102
103 GetOptions(
104     'c|commit=i'       => \$commit,
105     'd|delete'         => \$delete,
106     'a|authorities' => \$index_authorities,
107     'b|biblios' => \$index_biblios,
108     'bn|bnumber=i' => \@biblionumbers,
109     'v|verbose+'       => \$verbose,
110     'h|help'           => \$help,
111     'man'              => \$man,
112 );
113
114 # Default is to do both
115 unless ($index_authorities || $index_biblios) {
116     $index_authorities = $index_biblios = 1;
117 }
118
119 pod2usage(1) if $help;
120 pod2usage( -exitstatus => 0, -verbose => 2 ) if $man;
121
122 sanity_check();
123
124 my $next;
125 if ($index_biblios) {
126     _log(1, "Indexing biblios\n");
127     if (@biblionumbers) {
128         $next = sub {
129             my $r = shift @biblionumbers;
130             return () unless defined $r;
131             return ($r, Koha::BiblioUtils->get_from_biblionumber($r, item_data => 1 ));
132         };
133     } else {
134         my $records = Koha::BiblioUtils->get_all_biblios_iterator();
135         $next = sub {
136             $records->next();
137         }
138     }
139     do_reindex($next, $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX);
140 }
141 if ($index_authorities) {
142     _log(1, "Indexing authorities\n");
143     if (@biblionumbers) {
144         $next = sub {
145             my $r = shift @biblionumbers;
146             return () unless defined $r;
147             my $a = Koha::MetadataRecord::Authority->get_from_authid($r);
148             return ($r, $a->record);
149         };
150     } else {
151         my $records = Koha::MetadataRecord::Authority->get_all_authorities_iterator();
152         $next = sub {
153             $records->next();
154         }
155     }
156     do_reindex($next, $Koha::SearchEngine::Elasticsearch::AUTHORITIES_INDEX);
157 }
158
159 sub do_reindex {
160     my ( $next, $index_name ) = @_;
161
162     my $indexer = Koha::SearchEngine::Elasticsearch::Indexer->new( { index => $index_name } );
163
164     if ($delete) {
165         $indexer->drop_index() if $indexer->index_exists();
166         $indexer->create_index();
167     }
168     elsif (!$indexer->index_exists) {
169         # Create index if does not exist
170         $indexer->create_index();
171     } elsif ($indexer->is_index_status_ok) {
172         # Update mapping unless index is some kind of problematic state
173         $indexer->update_mappings();
174     } elsif ($indexer->is_index_status_recreate_required) {
175         warn qq/Index "$index_name" has status "recreate required", suggesting it should be recreated/;
176     }
177
178     my $count        = 0;
179     my $commit_count = $commit;
180     my ( @id_buffer, @commit_buffer );
181     while ( my $record = $next->() ) {
182         my $id     = $record->id;
183         my $record = $record->record;
184         $count++;
185         if ( $verbose == 1 ) {
186             _log( 1, "$count records processed\n" ) if ( $count % 1000 == 0);
187         } else {
188             _log( 2, "$id\n" );
189         }
190
191         push @id_buffer,     $id;
192         push @commit_buffer, $record;
193         if ( !( --$commit_count ) ) {
194             _log( 1, "Committing $commit records..." );
195             $indexer->update_index( \@id_buffer, \@commit_buffer );
196             $commit_count  = $commit;
197             @id_buffer     = ();
198             @commit_buffer = ();
199             _log( 1, " done\n" );
200         }
201     }
202
203     # There are probably uncommitted records
204     _log( 1, "Committing final records...\n" );
205     $indexer->update_index( \@id_buffer, \@commit_buffer );
206     _log( 1, "Total $count records indexed\n" );
207 }
208
209 # Checks some basic stuff to ensure that it's sane before we start.
210 sub sanity_check {
211     # Do we have an elasticsearch block defined?
212     my $conf = C4::Context->config('elasticsearch');
213     die "No 'elasticsearch' block is defined in koha-conf.xml.\n" if ( !$conf );
214 }
215
216 # Output progress information.
217 #
218 #   _log($level, $msg);
219 #
220 # Will output $msg if the verbosity setting is set to $level or more. Will
221 # not include a trailing newline.
222 sub _log {
223     my ($level, $msg) = @_;
224
225     print $msg if ($verbose >= $level);
226 }