Bug 23396: (QA follow-up) Add atomic update
[koha.git] / misc / cronjobs / update_totalissues.pl
blobeb11861f8dc7f83f17529607b1fe876a14c9118f
1 #!/usr/bin/perl
3 # Copyright 2012 C & P Bibliography Services
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20 use strict;
21 use warnings;
23 BEGIN {
25 # find Koha's Perl modules
26 # test carefully before changing this
27 use FindBin;
28 eval { require "$FindBin::Bin/../kohalib.pl" };
31 use Getopt::Long;
32 use Pod::Usage;
34 use Koha::Script -cron;
35 use C4::Context;
36 use C4::Biblio;
37 use C4::Log;
38 use DateTime;
39 use DateTime::Format::MySQL;
40 use Time::HiRes qw/time/;
41 use POSIX qw/strftime ceil/;
43 sub usage {
44 pod2usage( -verbose => 2 );
45 exit;
48 $| = 1;
50 # command-line parameters
51 my $verbose = 0;
52 my $test_only = 0;
53 my $want_help = 0;
54 my $since;
55 my $interval;
56 my $usestats = 0;
57 my $useitems = 0;
58 my $incremental = 0;
59 my $commit = 100;
60 my $unit;
62 my $result = GetOptions(
63 'v|verbose' => \$verbose,
64 't|test' => \$test_only,
65 's|since=s' => \$since,
66 'i|interval=s' => \$interval,
67 'use-stats' => \$usestats,
68 'use-items' => \$useitems,
69 'incremental' => \$incremental,
70 'c|commit=i' => \$commit,
71 'h|help' => \$want_help
74 binmode( STDOUT, ":utf8" );
76 if ( defined $since && defined $interval ) {
77 print "The --since and --interval options are mutually exclusive.\n\n";
78 $want_help = 1;
81 if ( $useitems && $incremental ) {
82 print
83 "The --use-items and --incremental options are mutually exclusive.\n\n";
84 $want_help = 1;
87 if ( $incremental && !( defined $since || defined $interval ) ) {
88 $interval = '24h';
91 unless ( $usestats || $useitems ) {
92 print "You must specify either --use-stats and/or --use-items.\n\n";
93 $want_help = 1;
96 if ( not $result or $want_help ) {
97 usage();
100 cronlogaction();
102 my $dbh = C4::Context->dbh;
103 $dbh->{AutoCommit} = 0;
105 my $num_bibs_processed = 0;
106 my $num_bibs_error = 0;
108 my $starttime = time();
110 process_items() if $useitems;
111 process_stats() if $usestats;
113 report();
115 exit 0;
117 sub process_items {
118 my $query =
119 "SELECT items.biblionumber, SUM(items.issues) FROM items GROUP BY items.biblionumber;";
120 process_query($query);
123 sub process_stats {
124 if ($interval) {
125 my $dt = DateTime->now;
127 my %units = (
128 h => 'hours',
129 d => 'days',
130 w => 'weeks',
131 m => 'months',
132 y => 'years'
135 $interval =~ m/([0-9]*)([hdwmy]?)$/;
136 $unit = $2 || 'd';
137 $since = DateTime::Format::MySQL->format_datetime(
138 $dt->subtract( $units{$unit} => $1 ) );
140 my $limit = '';
141 $limit = " AND statistics.datetime >= ?" if ( $interval || $since );
143 my $query =
144 "SELECT biblio.biblionumber, COUNT(statistics.itemnumber) FROM biblio LEFT JOIN items ON (biblio.biblionumber=items.biblionumber) LEFT JOIN statistics ON (items.itemnumber=statistics.itemnumber) WHERE statistics.type = 'issue' $limit GROUP BY biblio.biblionumber;";
145 process_query( $query, $limit );
147 unless ($incremental) {
148 $query =
149 "SELECT biblio.biblionumber, 0 FROM biblio LEFT JOIN items ON (biblio.biblionumber=items.biblionumber) LEFT JOIN statistics ON (items.itemnumber=statistics.itemnumber) WHERE statistics.itemnumber IS NULL GROUP BY biblio.biblionumber;";
150 process_query( $query, '' );
152 $query =
153 "SELECT biblio.biblionumber, 0 FROM biblio LEFT JOIN items ON (biblio.biblionumber=items.biblionumber) WHERE items.itemnumber IS NULL GROUP BY biblio.biblionumber;";
154 process_query( $query, '' );
157 $dbh->commit();
160 sub process_query {
161 my $query = shift;
162 my $uselimit = shift;
163 my $sth = $dbh->prepare($query);
165 if ( $since && $uselimit ) {
166 $sth->execute($since);
168 else {
169 $sth->execute();
172 while ( my ( $biblionumber, $totalissues ) = $sth->fetchrow_array() ) {
173 $num_bibs_processed++;
174 $totalissues = 0 unless $totalissues;
175 print "Processing bib $biblionumber ($totalissues issues)\n"
176 if $verbose;
177 if ( not $test_only ) {
178 my $ret;
179 if ( $incremental && $totalissues > 0 ) {
180 $ret = UpdateTotalIssues( $biblionumber, $totalissues );
182 else {
183 $ret = UpdateTotalIssues( $biblionumber, 0, $totalissues );
185 unless ($ret) {
186 print "Error while processing bib $biblionumber\n" if $verbose;
187 $num_bibs_error++;
190 if ( not $test_only and ( $num_bibs_processed % $commit ) == 0 ) {
191 print_progress_and_commit($num_bibs_processed);
195 $dbh->commit();
198 sub report {
199 my $endtime = time();
200 my $totaltime = ceil( ( $endtime - $starttime ) * 1000 );
201 $starttime = strftime( '%D %T', localtime($starttime) );
202 $endtime = strftime( '%D %T', localtime($endtime) );
204 my $summary = <<_SUMMARY_;
206 Update total issues count script report
207 =======================================================
208 Run started at: $starttime
209 Run ended at: $endtime
210 Total run time: $totaltime ms
211 Number of bibs modified: $num_bibs_processed
212 Number of bibs with error: $num_bibs_error
213 _SUMMARY_
214 $summary .= "\n**** Ran in test mode only ****\n" if $test_only;
215 print $summary;
218 sub print_progress_and_commit {
219 my $recs = shift;
220 $dbh->commit();
221 print "... processed $recs records\n";
224 =head1 NAME
226 update_totalissues.pl
228 =head1 SYNOPSIS
230 update_totalissues.pl --use-stats
231 update_totalissues.pl --use-items
232 update_totalissues.pl --commit=1000
233 update_totalissues.pl --since='2012-01-01'
234 update_totalissues.pl --interval=30d
236 =head1 DESCRIPTION
238 This batch job populates bibliographic records' total issues count based
239 on historical issue statistics.
241 =over 8
243 =item B<--help>
245 Prints this help
247 =item B<-v|--verbose>
249 Provide verbose log information (list every bib modified).
251 =item B<--use-stats>
253 Use the data in the statistics table for populating total issues.
255 =item B<--use-items>
257 Use items.issues data for populating total issues. Note that issues
258 data from the items table does not respect the --since or --interval
259 options, by definition. Also note that if both --use-stats and
260 --use-items are specified, the count of biblios processed will be
261 misleading.
263 =item B<-s|--since=DATE>
265 Only process issues recorded in the statistics table since DATE.
267 =item B<-i|--interval=S>
269 Only process issues recorded in the statistics table in the last N
270 units of time. The interval should consist of a number with a one-letter
271 unit suffix. The valid suffixes are h (hours), d (days), w (weeks),
272 m (months), and y (years). The default unit is days.
274 =item B<--incremental>
276 Add the number of issues found in the statistics table to the existing
277 total issues count. Intended so that this script can be used as a cron
278 job to update popularity information during low-usage periods. If neither
279 --since or --interval are specified, incremental mode will default to
280 processing the last twenty-four hours.
282 =item B<--commit=N>
284 Commit the results to the database after every N records are processed.
286 =item B<--test>
288 Only test the popularity population script.
290 =back
292 =head1 WARNING
294 If the time on your database server does not match the time on your Koha
295 server you will need to take that into account, and probably use the
296 --since argument instead of the --interval argument for incremental
297 updating.
299 =head1 CREDITS
301 This patch to Koha was sponsored by the Arcadia Public Library and the
302 Arcadia Public Library Foundation in honor of Jackie Faust-Moreno, late
303 director of the Arcadia Public Library.
305 =head1 AUTHOR
307 Jared Camins-Esakov <jcamins AT cpbibliography DOT com>
309 =cut