0c7c56a13d7fcc977e5de5ff471059c9e32d7cfb
[koha.git] / Koha / OAI / Server / ListBase.pm
1 package Koha::OAI::Server::ListBase;
2
3 # Copyright The National Library of Finland, University of Helsinki 2016-2017
4 #
5 # This file is part of Koha.
6 #
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.
11 #
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.
16 #
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>.
19
20 =head1 NAME
21
22 Koha::OAI::Server::ListBase - OAI ListIdentifiers/ListRecords shared functionality
23
24 =head1 DESCRIPTION
25
26 Koha::OAI::Server::ListBase contains OAI-PMH functions shared by ListIdentifiers and ListRecords.
27
28 =cut
29
30 use Modern::Perl;
31 use C4::Biblio;
32 use HTTP::OAI;
33 use Koha::OAI::Server::ResumptionToken;
34 use Koha::OAI::Server::Record;
35 use Koha::OAI::Server::DeletedRecord;
36 use C4::OAI::Sets;
37 use MARC::File::XML;
38
39 sub GetRecords {
40     my ($class, $self, $repository, $metadata, %args) = @_;
41
42     my $token = new Koha::OAI::Server::ResumptionToken( %args );
43     my $dbh = C4::Context->dbh;
44     my $set;
45     if ( defined $token->{'set'} ) {
46         $set = GetOAISetBySpec($token->{'set'});
47     }
48     my $offset = $token->{offset};
49     my $deleted = defined $token->{deleted} ? $token->{deleted} : 0;
50     my $deleted_count = defined $token->{deleted_count} ? $token->{deleted_count} : 0;
51     my $max = $repository->{koha_max_count};
52     my $count = 0;
53     my $format = $args{metadataPrefix} || $token->{metadata_prefix};
54     my $include_items = $repository->items_included( $format );
55
56     # Since creating a union of normal and deleted record tables would be a heavy
57     # operation in a large database, build results in two stages:
58     # first deleted records ($deleted == 1), then normal records ($deleted == 0)
59     STAGELOOP:
60     for ( ; $deleted >= 0; $deleted-- ) {
61         my $table = $deleted ? 'deletedbiblioitems' : 'biblioitems';
62         my $sql = "
63             SELECT biblionumber
64             FROM $table
65             WHERE (timestamp >= ? AND timestamp <= ?)
66         ";
67         my @bind_params = ($token->{'from_arg'}, $token->{'until_arg'});
68
69         if ($include_items) {
70             $sql .= "
71                 OR biblionumber IN (SELECT biblionumber from deleteditems WHERE timestamp >= ? AND timestamp <= ?)
72             ";
73             push @bind_params, ($token->{'from_arg'}, $token->{'until_arg'});
74             if (!$deleted) {
75                 $sql .= "
76                     OR biblionumber IN (SELECT biblionumber from items WHERE timestamp >= ? AND timestamp <= ?)
77                 ";
78                 push @bind_params, ($token->{'from_arg'}, $token->{'until_arg'});
79             }
80         }
81
82         $sql .= "
83             ORDER BY biblionumber
84         ";
85
86         # Use a subquery for sets since it allows us to use an index in
87         # biblioitems table and is quite a bit faster than a join.
88         if (defined $set) {
89             $sql = "
90                 SELECT bi.* FROM ($sql) bi
91                   WHERE bi.biblionumber in (SELECT osb.biblionumber FROM oai_sets_biblios osb WHERE osb.set_id = ?)
92             ";
93             push @bind_params, $set->{'id'};
94         }
95
96         $sql .= "
97             LIMIT " . ($max + 1) . "
98             OFFSET " . ($offset - $deleted_count);
99
100         my $sth = $dbh->prepare( $sql ) || die( 'Could not prepare statement: ' . $dbh->errstr );
101
102         if ( $deleted ) {
103             $sql = "
104                 SELECT MAX(timestamp)
105                 FROM (
106                     SELECT timestamp FROM deletedbiblioitems WHERE biblionumber = ?
107                     UNION
108                     SELECT timestamp FROM deleteditems WHERE biblionumber = ?
109                 ) bis
110             ";
111         } else {
112             $sql = "
113                 SELECT MAX(timestamp)
114                 FROM (
115                     SELECT timestamp FROM biblioitems WHERE biblionumber = ?
116                     UNION
117                     SELECT timestamp FROM deleteditems WHERE biblionumber = ?
118                     UNION
119                     SELECT timestamp FROM items WHERE biblionumber = ?
120                 ) bi
121             ";
122         }
123         my $record_sth = $dbh->prepare( $sql ) || die( 'Could not prepare statement: ' . $dbh->errstr );
124
125         $sth->execute( @bind_params ) || die( 'Could not execute statement: ' . $sth->errstr );
126         while ( my ($biblionumber) = $sth->fetchrow ) {
127             $count++;
128             if ( $count > $max ) {
129                 $self->resumptionToken(
130                     new Koha::OAI::Server::ResumptionToken(
131                         metadataPrefix  => $token->{metadata_prefix},
132                         from            => $token->{from},
133                         until           => $token->{until},
134                         offset          => $token->{offset} + $max,
135                         set             => $token->{set},
136                         deleted         => $deleted,
137                         deleted_count   => $deleted_count
138                     )
139                 );
140                 last STAGELOOP;
141             }
142             my @params = $deleted ? ( $biblionumber, $biblionumber ) : ( $biblionumber, $biblionumber, $biblionumber );
143             $record_sth->execute( @params ) || die( 'Could not execute statement: ' . $sth->errstr );
144
145             my ($timestamp) = $record_sth->fetchrow;
146
147             my $oai_sets = GetOAISetsBiblio($biblionumber);
148             my @setSpecs;
149             foreach ( @$oai_sets ) {
150                 push @setSpecs, $_->{spec};
151             }
152             if ( $metadata ) {
153                 my $marcxml = !$deleted ? $repository->get_biblio_marcxml($biblionumber, $format) : undef;
154                 if ( $marcxml ) {
155                   $self->record( Koha::OAI::Server::Record->new(
156                       $repository, $marcxml, $timestamp, \@setSpecs,
157                       identifier      => $repository->{ koha_identifier } . ':' . $biblionumber,
158                       metadataPrefix  => $token->{metadata_prefix}
159                   ) );
160                 } else {
161                   $self->record( Koha::OAI::Server::DeletedRecord->new(
162                       $timestamp, \@setSpecs, identifier => $repository->{ koha_identifier } . ':' . $biblionumber
163                   ) );
164                 }
165             } else {
166                 $timestamp =~ s/ /T/;
167                 $timestamp .= 'Z';
168                 $self->identifier( new HTTP::OAI::Header(
169                     identifier => $repository->{ koha_identifier} . ':' . $biblionumber,
170                     datestamp  => $timestamp,
171                     status     => $deleted ? 'deleted' : undef
172                 ) );
173             }
174         }
175         # Store offset and deleted record count
176         $offset += $count;
177         $deleted_count = $offset if ($deleted);
178     }
179     return $count;
180 }
181
182 1;