]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/authority.pm
QueryParser Driver: Much work
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Storage / Publisher / authority.pm
1 use strict;
2 use warnings;
3
4 package OpenILS::Application::Storage::Publisher::authority;
5 use base qw/OpenILS::Application::Storage::Publisher/;
6 use vars qw/$VERSION/;
7 use OpenSRF::EX qw/:try/;
8 use OpenILS::Application::Storage::FTS;
9 use OpenILS::Utils::Fieldmapper;
10 use OpenILS::Utils::Normalize qw( naco_normalize );
11 use OpenSRF::Utils::Logger qw/:level/;
12 use OpenSRF::Utils::Cache;
13 use Data::Dumper;
14 use Digest::MD5 qw/md5_hex/;
15 use XML::LibXML;
16 use Time::HiRes qw/time sleep/;
17 use Unicode::Normalize;
18
19 my $log = 'OpenSRF::Utils::Logger';
20
21 $VERSION = 1;
22
23 my $parser = XML::LibXML->new;
24
25 sub validate_tag {
26         my $self = shift;
27         my $client = shift;
28         my %args = @_;
29         
30         my @tags = @{$args{tags}};
31         my @searches = @{$args{searches}};
32
33         my $search_table = authority::full_rec->table;
34         my $rec_table = authority::record_entry->table;
35
36         my @values;
37         my @selects;
38         for my $t ( @tags ) {
39                 for my $search ( @searches ) {
40                         my $sf = $$search{subfield};
41                         my $term = naco_normalize($$search{term}, $sf);
42
43                         push @values, $t, $sf, $term;
44
45                         push @selects,
46                                 "SELECT record FROM $search_table ".
47                                 "WHERE tag = ? AND subfield = ? AND value = ?";
48                 }
49
50                 my $sql;
51                 if ($self->api_name =~ /id_list/) {
52                         $sql = 'SELECT DISTINCT record FROM (';
53                 } else {
54                         $sql = 'SELECT COUNT(DISTINCT record) FROM (';
55                 }
56                 $sql .= 'SELECT record FROM (('.join(') INTERSECT (', @selects).')) AS x ';
57                 $sql .= "JOIN $search_table recheck USING (record) ";
58                 $sql .= "JOIN $rec_table delcheck ON (recheck.record = delcheck.id and delcheck.deleted = 'f') ";
59                 $sql .= "WHERE recheck.tag = ? GROUP BY 1 HAVING (COUNT(recheck.id) - ?) = 0) AS foo;";
60
61                 if ($self->api_name =~ /id_list/) {
62                         my $id_list = authority::full_rec->db_Main->selectcol_arrayref( $sql, {}, @values, $t, scalar(@searches) );
63                         return $id_list;
64                 } else {
65                         my $count = authority::full_rec->db_Main->selectcol_arrayref( $sql, {}, @values, $t, scalar(@searches) )->[0];
66                         return $count if ($count > 0);
67                 }
68         }
69
70         return 0;
71 }
72 __PACKAGE__->register_method(
73         api_name        => "open-ils.storage.authority.validate.tag",
74         method          => 'validate_tag',
75         api_level       => 1,
76 );
77
78 __PACKAGE__->register_method(
79         api_name        => "open-ils.storage.authority.validate.tag.id_list",
80         method          => 'validate_tag',
81         api_level       => 1,
82 );
83
84
85 sub find_authority_marc {
86         my $self = shift;
87         my $client = shift;
88         my %args = @_;
89         
90         my $term = NFD(lc($args{term}));
91         my $tag = $args{tag};
92         my $subfield = $args{subfield};
93         my $limit = $args{limit} || 100;
94         my $offset = $args{offset} || 0;
95
96         if ($limit) {
97                 $limit = "LIMIT $limit";
98         } else {
99                 $limit = '';
100         }
101
102         if ($offset) {
103                 $offset = "OFFSET $offset";
104         } else {
105                 $offset = '';
106         }
107
108         my $tag_where = "AND f.tag LIKE '$tag'";
109         if (ref $tag) {
110                 $tag_where = "AND f.tag IN ('".join("','",@$tag)."')";
111         }
112
113         my $sf_where = "AND f.subfield = '$subfield'";
114         if (ref $subfield) {
115                 $sf_where = "AND f.subfield IN ('".join("','",@$subfield)."')";
116         }
117
118         my $search_table = authority::full_rec->table;
119         my $marc_table = authority::record_entry->table;
120
121         my ($index_col) = authority::full_rec->columns('FTS');
122         $index_col ||= 'value';
123
124         my $fts = OpenILS::Application::Storage::FTS->compile(default => $term, 'f.value', "f.$index_col");
125
126         $term =~ s/\W+$//gso;
127         $term =~ s/'/''/gso;
128         $term =~ s/\pM//gso;
129
130         my $fts_where = $fts->sql_where_clause;
131         my $fts_words = join '%', $fts->words;
132
133     return undef unless ($fts_words);
134
135         my $fts_words_where = "f.value LIKE '$fts_words\%'";
136         my $fts_start_where = "f.value LIKE '$term\%'";
137         my $fts_eq_where = "f.value = '$term'";
138
139         my $fts_rank = join '+', $fts->fts_rank;
140
141         my $select = <<"        SQL";
142                 SELECT  a.marc, sum($fts_rank), count(f.record), first(f.value)
143                 FROM    $search_table f,
144                         $marc_table a
145                 WHERE   $fts_start_where
146                         $tag_where
147                         $sf_where
148                         AND a.id = f.record
149                         GROUP BY 1
150                         ORDER BY 2 desc, 3 desc, 4
151                         $limit
152                         $offset
153                         
154         SQL
155
156         $log->debug("Authority Search SQL :: [$select]",DEBUG);
157
158         my $recs = authority::full_rec->db_Main->selectcol_arrayref( $select );
159         
160         $log->debug("Search yielded ".scalar(@$recs)." results.",DEBUG);
161
162         $client->respond($_) for (@$recs);
163         return undef;
164 }
165 __PACKAGE__->register_method(
166         api_name        => "open-ils.storage.authority.search.marc",
167         method          => 'find_authority_marc',
168         api_level       => 1,
169         stream          => 1,
170         cachable        => 1,
171 );
172
173 sub _empty_check {
174         my $term = shift;
175         my $class = shift || 'metabib::full_rec';
176
177         my $table = $class->table;
178
179         my ($index_col) = $class->columns('FTS');
180         $index_col ||= 'value';
181
182         my $fts = OpenILS::Application::Storage::FTS->compile(default => $term, 'm.value', "m.$index_col");
183         my $fts_where = $fts->sql_where_clause;
184
185         my $sql = <<"   SQL";
186                 SELECT  TRUE
187                 FROM    $table m
188                 WHERE   $fts_where
189                 LIMIT 1
190         SQL
191
192         return $class->db_Main->selectcol_arrayref($sql)->[0];
193 }
194
195 my $prevtime;
196
197 sub find_see_from_controlled {
198         my $self = shift;
199         my $client = shift;
200         my $term = shift;
201         my $limit = shift;
202         my $offset = shift;
203
204         $prevtime = time;
205
206         (my $class = $self->api_name) =~ s/^.+authority.([^\.]+)\.see.+$/$1/o;
207         my $sf = 'a';
208         $sf = 't' if ($class eq 'title');
209
210         my @marc = $self->method_lookup('open-ils.storage.authority.search.marc')
211                         ->run( term => $term, tag => [400,410,411,430,450,455], subfield => $sf, limit => $limit, offset => $offset );
212
213         
214         for my $m ( @marc ) {
215                 my $doc = $parser->parse_string($m);
216                 my @nodes = $doc->documentElement->findnodes('//*[substring(@tag,1,1)="1"]/*[@code="a" or @code="d" or @code="x"]');
217                 my $list = [ map { $_->textContent } @nodes ];
218                 $client->respond( $list ) if (_empty_check(join(' ',@$list), "metabib::${class}_field_entry"));
219         }
220         return undef;
221 }
222 for my $class ( qw/title author subject keyword series identifier/ ) {
223         __PACKAGE__->register_method(
224                 api_name        => "open-ils.storage.authority.$class.see_from.controlled",
225                 method          => 'find_see_from_controlled',
226                 api_level       => 1,
227                 stream          => 1,
228                 cachable        => 1,
229         );
230 }
231
232 sub find_see_also_from_controlled {
233         my $self = shift;
234         my $client = shift;
235         my $term = shift;
236         my $limit = shift;
237         my $offset = shift;
238
239         (my $class = $self->api_name) =~ s/^.+authority.([^\.]+)\.see.+$/$1/o;
240         my $sf = 'a';
241         $sf = 't' if ($class eq 'title');
242
243         my @marc = $self->method_lookup('open-ils.storage.authority.search.marc')
244                         ->run( term => $term, tag => [500,510,511,530,550,555], subfield => $sf, limit => $limit, offset => $offset );
245         for my $m ( @marc ) {
246                 my $doc = $parser->parse_string($m);
247                 my @nodes = $doc->documentElement->findnodes('//*[substring(@tag,1,1)="1"]/*[@code="a" or @code="d" or @code="x"]');
248                 my $list = [ map { $_->textContent } @nodes ];
249                 $client->respond( $list ) if (_empty_check(join(' ',@$list), "metabib::${class}_field_entry"));
250         }
251         return undef;
252 }
253 for my $class ( qw/title author subject keyword series identifier/ ) {
254         __PACKAGE__->register_method(
255                 api_name        => "open-ils.storage.authority.$class.see_also_from.controlled",
256                 method          => 'find_see_also_from_controlled',
257                 api_level       => 1,
258                 stream          => 1,
259                 cachable        => 1,
260         );
261 }
262 __PACKAGE__->register_method(
263     api_name    => "open-ils.storage.authority.in_db.browse_or_search",
264     method              => "authority_in_db_browse_or_search",
265     api_level   => 1,
266     argc        => 5,
267     signature   => {
268         desc => q/Use stored procedures to perform authorities-based
269         browses or searches/,
270         params => [
271             {name => "method", type => "string", desc => q/
272                 The name of a method within the authority schema to call.  This
273                 is an API call on a private service for a reason.  Do not pass
274                 unfiltered user input into this API call, especially in this
275                 parameter./},
276             {name => "what", type => "string", desc => q/
277                 What to search. Could be an axis name, an authority tag
278                 number, or a bib tag number/},
279             {name => "term", type => "string", desc => "Search term"},
280             {name => "page", type => "number", desc => "Zero-based page number"},
281             {name => "page_size", type => "number",
282                 desc => "Number of records per page"}
283         ],
284         return => {
285             desc => "A list of authority record IDs",
286             type => "array"
287         }
288     }
289 );
290
291 sub authority_in_db_browse_or_search {
292     my ($self, $shift, $method, @args) = @_;
293
294     return unless $method =~ /^\w+$/;
295
296     my $db = authority::full_rec->db_Main;
297         my $list = $db->selectcol_arrayref(
298         qq/
299             SELECT
300                 (SELECT record FROM authority.simple_heading WHERE id = func.heading)
301             FROM authority.$method(?, ?, ?, ?) func(heading)
302         /,
303         {}, @args
304     );
305
306     return $list;
307 }
308
309 1;