4 package OpenILS::Application::Storage::Publisher::authority;
5 use base qw/OpenILS::Application::Storage::Publisher/;
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;
14 use Digest::MD5 qw/md5_hex/;
16 use Time::HiRes qw/time sleep/;
17 use Unicode::Normalize;
19 my $log = 'OpenSRF::Utils::Logger';
23 my $parser = XML::LibXML->new;
30 my @tags = @{$args{tags}};
31 my @searches = @{$args{searches}};
33 my $search_table = authority::full_rec->table;
34 my $rec_table = authority::record_entry->table;
39 for my $search ( @searches ) {
40 my $sf = $$search{subfield};
41 my $term = naco_normalize($$search{term}, $sf);
43 push @values, $t, $sf, $term;
46 "SELECT record FROM $search_table ".
47 "WHERE tag = ? AND subfield = ? AND value = ?";
51 if ($self->api_name =~ /id_list/) {
52 $sql = 'SELECT DISTINCT record FROM (';
54 $sql = 'SELECT COUNT(DISTINCT record) FROM (';
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;";
61 if ($self->api_name =~ /id_list/) {
62 my $id_list = authority::full_rec->db_Main->selectcol_arrayref( $sql, {}, @values, $t, scalar(@searches) );
65 my $count = authority::full_rec->db_Main->selectcol_arrayref( $sql, {}, @values, $t, scalar(@searches) )->[0];
66 return $count if ($count > 0);
72 __PACKAGE__->register_method(
73 api_name => "open-ils.storage.authority.validate.tag",
74 method => 'validate_tag',
78 __PACKAGE__->register_method(
79 api_name => "open-ils.storage.authority.validate.tag.id_list",
80 method => 'validate_tag',
85 sub find_authority_marc {
90 my $term = NFD(lc($args{term}));
92 my $subfield = $args{subfield};
93 my $limit = $args{limit} || 100;
94 my $offset = $args{offset} || 0;
97 $limit = "LIMIT $limit";
103 $offset = "OFFSET $offset";
108 my $tag_where = "AND f.tag LIKE '$tag'";
110 $tag_where = "AND f.tag IN ('".join("','",@$tag)."')";
113 my $sf_where = "AND f.subfield = '$subfield'";
115 $sf_where = "AND f.subfield IN ('".join("','",@$subfield)."')";
118 my $search_table = authority::full_rec->table;
119 my $marc_table = authority::record_entry->table;
121 my ($index_col) = authority::full_rec->columns('FTS');
122 $index_col ||= 'value';
124 my $fts = OpenILS::Application::Storage::FTS->compile(default => $term, 'f.value', "f.$index_col");
126 $term =~ s/\W+$//gso;
130 my $fts_where = $fts->sql_where_clause;
131 my $fts_words = join '%', $fts->words;
133 return undef unless ($fts_words);
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'";
139 my $fts_rank = join '+', $fts->fts_rank;
141 my $select = <<" SQL";
142 SELECT a.marc, sum($fts_rank), count(f.record), first(f.value)
143 FROM $search_table f,
145 WHERE $fts_start_where
150 ORDER BY 2 desc, 3 desc, 4
156 $log->debug("Authority Search SQL :: [$select]",DEBUG);
158 my $recs = authority::full_rec->db_Main->selectcol_arrayref( $select );
160 $log->debug("Search yielded ".scalar(@$recs)." results.",DEBUG);
162 $client->respond($_) for (@$recs);
165 __PACKAGE__->register_method(
166 api_name => "open-ils.storage.authority.search.marc",
167 method => 'find_authority_marc',
175 my $class = shift || 'metabib::full_rec';
177 my $table = $class->table;
179 my ($index_col) = $class->columns('FTS');
180 $index_col ||= 'value';
182 my $fts = OpenILS::Application::Storage::FTS->compile(default => $term, 'm.value', "m.$index_col");
183 my $fts_where = $fts->sql_where_clause;
192 return $class->db_Main->selectcol_arrayref($sql)->[0];
197 sub find_see_from_controlled {
206 (my $class = $self->api_name) =~ s/^.+authority.([^\.]+)\.see.+$/$1/o;
208 $sf = 't' if ($class eq 'title');
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 );
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"));
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',
232 sub find_see_also_from_controlled {
239 (my $class = $self->api_name) =~ s/^.+authority.([^\.]+)\.see.+$/$1/o;
241 $sf = 't' if ($class eq 'title');
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"));
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',
262 __PACKAGE__->register_method(
263 api_name => "open-ils.storage.authority.in_db.browse_or_search",
264 method => "authority_in_db_browse_or_search",
268 desc => q/Use stored procedures to perform authorities-based
269 browses or searches/,
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
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"}
285 desc => "A list of authority record IDs",
291 sub authority_in_db_browse_or_search {
292 my ($self, $shift, $method, @args) = @_;
294 return unless $method =~ /^\w+$/;
296 my $db = authority::full_rec->db_Main;
297 my $list = $db->selectcol_arrayref(
300 (SELECT record FROM authority.simple_heading WHERE id = func.heading)
301 FROM authority.$method(?, ?, ?, ?) func(heading)