1 package Koha::OAI::Server::ListBase;
3 # Copyright The National Library of Finland, University of Helsinki 2016-2017
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>.
22 Koha::OAI::Server::ListBase - OAI ListIdentifiers/ListRecords shared functionality
26 Koha::OAI::Server::ListBase contains OAI-PMH functions shared by ListIdentifiers and ListRecords.
32 use Koha::OAI::Server::ResumptionToken;
33 use Koha::OAI::Server::Record;
34 use Koha::OAI::Server::DeletedRecord;
35 use C4::OAI::Sets qw( GetOAISetBySpec GetOAISetsBiblio );
39 my ($class, $self, $repository, $metadata, %args) = @_;
41 my $token = Koha::OAI::Server::ResumptionToken->new( %args );
42 my $dbh = C4::Context->dbh;
44 if ( defined $token->{'set'} ) {
45 $set = GetOAISetBySpec($token->{'set'});
47 my $deleted = defined $token->{deleted} ? $token->{deleted} : 0;
48 my $max = $repository->{koha_max_count};
50 my $format = $args{metadataPrefix} || $token->{metadata_prefix};
51 my $include_items = $repository->items_included( $format );
52 my $from = $token->{from_arg};
53 my $until = $token->{until_arg};
54 my $next_id = $token->{next_id};
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)
60 for ( ; $deleted >= 0; $deleted-- ) {
61 my $table = $deleted ? 'deletedbiblio_metadata' : 'biblio_metadata';
63 my @part_bind_params = ($next_id);
65 my $where = "biblionumber >= ?";
67 $where .= " AND timestamp >= ?";
68 push @part_bind_params, $from;
71 $where .= " AND timestamp <= ?";
72 push @part_bind_params, $until;
75 $where .= " AND biblionumber in (SELECT osb.biblionumber FROM oai_sets_biblios osb WHERE osb.set_id = ?)";
76 push @part_bind_params, $set->{'id'};
79 my @bind_params = @part_bind_params;
81 my $criteria = "WHERE $where ORDER BY biblionumber LIMIT " . ($max + 1);
89 # If items are included, fetch a set of potential biblionumbers from items tables as well.
90 # Then merge them, sort them and take the required number of them from the resulting list.
91 # This may seem counter-intuitive as in worst case we fetch 3 times the biblionumbers needed,
92 # but avoiding joins or subqueries makes this so much faster that it does not matter.
93 if ( $include_items ) {
94 $sql = "($sql) UNION (SELECT DISTINCT(biblionumber) FROM deleteditems $criteria)";
95 push @bind_params, @part_bind_params;
97 $sql .= " UNION (SELECT DISTINCT(biblionumber) FROM items $criteria)";
98 push @bind_params, @part_bind_params;
100 $sql = "SELECT biblionumber FROM ($sql) bibnos ORDER BY biblionumber LIMIT " . ($max + 1);
103 my $sth = $dbh->prepare( $sql ) || die( 'Could not prepare statement: ' . $dbh->errstr );
104 $sth->execute( @bind_params ) || die( 'Could not execute statement: ' . $sth->errstr );
107 if ( $include_items ) {
110 SELECT MAX(timestamp)
112 SELECT timestamp FROM deletedbiblio_metadata WHERE biblionumber = ?
114 SELECT timestamp FROM deleteditems WHERE biblionumber = ?
119 SELECT MAX(timestamp)
121 SELECT timestamp FROM biblio_metadata WHERE biblionumber = ?
123 SELECT timestamp FROM deleteditems WHERE biblionumber = ?
125 SELECT timestamp FROM items WHERE biblionumber = ?
130 $ts_sql = "SELECT timestamp FROM $table WHERE biblionumber = ?";
132 my $ts_sth = $dbh->prepare( $ts_sql ) || die( 'Could not prepare statement: ' . $dbh->errstr );
134 foreach my $row (@{ $sth->fetchall_arrayref() }) {
135 my $biblionumber = $row->[0];
137 if ( $count > $max ) {
138 $self->resumptionToken(
139 Koha::OAI::Server::ResumptionToken->new(
140 metadataPrefix => $token->{metadata_prefix},
141 from => $token->{from},
142 until => $token->{until},
143 cursor => $token->{cursor} + $max,
144 set => $token->{set},
146 next_id => $biblionumber
151 my @params = ($biblionumber);
152 if ( $include_items ) {
153 push @params, $deleted ? ( $biblionumber ) : ( $biblionumber, $biblionumber );
155 $ts_sth->execute( @params ) || die( 'Could not execute statement: ' . $ts_sth->errstr );
157 my ($timestamp) = $ts_sth->fetchrow;
159 my $oai_sets = GetOAISetsBiblio($biblionumber);
161 foreach ( @$oai_sets ) {
162 push @setSpecs, $_->{spec};
165 my $marcxml = !$deleted ? $repository->get_biblio_marcxml($biblionumber, $format) : undef;
167 $self->record( Koha::OAI::Server::Record->new(
168 $repository, $marcxml, $timestamp, \@setSpecs,
169 identifier => $repository->{ koha_identifier } . ':' . $biblionumber,
170 metadataPrefix => $token->{metadata_prefix}
173 $self->record( Koha::OAI::Server::DeletedRecord->new(
174 $timestamp, \@setSpecs, identifier => $repository->{ koha_identifier } . ':' . $biblionumber
178 $timestamp =~ s/ /T/;
180 $self->identifier( HTTP::OAI::Header->new(
181 identifier => $repository->{ koha_identifier} . ':' . $biblionumber,
182 datestamp => $timestamp,
183 status => $deleted ? 'deleted' : undef
187 # Reset $next_id for the next stage