1 package OpenILS::Application::Storage::Publisher;
2 use base qw/OpenILS::Application::Storage/;
5 use Digest::MD5 qw/md5_hex/;
6 use OpenSRF::EX qw/:try/;
8 use OpenILS::Utils::DateTime;
9 use OpenSRF::Utils::Logger qw/:level/;
10 use OpenILS::Utils::Fieldmapper;
12 my $log = 'OpenSRF::Utils::Logger';
20 $class = ref($class) || $class;
22 $args{package} ||= $class;
23 __PACKAGE__->SUPER::register_method( %args );
25 if (exists($dup_args{cachable}) and $dup_args{cachable}) {
26 (my $name = $dup_args{api_name}) =~ s/^open-ils\.storage/open-ils.storage.cachable/o;
27 if ($name ne $dup_args{api_name}) {
28 $dup_args{real_api_name} = $dup_args{api_name};
29 $dup_args{method} = 'cachable_wrapper';
30 $dup_args{api_name} = $name;
31 $dup_args{package} = __PACKAGE__;
32 __PACKAGE__->SUPER::register_method( %dup_args );
36 if ($dup_args{real_api_name} =~ /^open-ils\.storage\.direct\..+\.search.+/o ||
37 $dup_args{api_name} =~ /^open-ils\.storage\.direct\..+\.search.+/o) {
38 $dup_args{api_name} = $dup_args{real_api_name} if ($dup_args{real_api_name});
40 (my $name = $dup_args{api_name}) =~ s/\.direct\./.id_list./o;
42 $dup_args{notes} = $dup_args{real_api_name};
43 $dup_args{real_api_name} = $dup_args{api_name};
44 $dup_args{method} = 'search_ids';
45 $dup_args{api_name} = $name;
46 $dup_args{package} = __PACKAGE__;
48 __PACKAGE__->SUPER::register_method( %dup_args );
52 sub cachable_wrapper {
61 cache_page_size => 1000,
65 my $key_string = $self->api_name;
66 for (my $ind = 0; $ind < scalar(@args); $ind++) {
67 if ( $args[$ind] eq 'limit' ||
68 $args[$ind] eq 'offset' ||
69 $args[$ind] eq 'cache_page_size' ||
70 $args[$ind] eq 'timeout' ) {
75 $cache_args{$args[$key_ind]} = $args[$value_ind];
76 $log->debug("Cache limiter value for $args[$key_ind] is $args[$value_ind]", INTERNAL);
79 $key_string .= $args[$ind];
80 $log->debug("Partial cache key value is $args[$ind]", INTERNAL);
81 push @real_args, $args[$ind];
84 my $cache_page = int($cache_args{offset} / $cache_args{cache_page_size});
87 $cache_key = md5_hex($key_string.$cache_page);
90 $log->debug("Key string for cache lookup is $key_string -> $cache_key", DEBUG);
91 $log->debug("Cache page is $cache_page", DEBUG);
93 my $cached_res = OpenSRF::Utils::Cache->new->get_cache( $cache_key );
94 if (defined $cached_res) {
95 $log->debug("Found ".scalar(@$cached_res)." records in the cache", INFO);
96 $log->debug("Values from cache: ".join(', ', @$cached_res), INTERNAL);
97 my $start = int($cache_args{offset} - ($cache_page * $cache_args{cache_page_size}));
98 my $end = int($start + $cache_args{limit} - 1);
99 $log->debug("Responding with values from ".$start.' to '.$end,DEBUG);
100 $client->respond( $_ ) for ( grep { defined } @$cached_res[ $start .. $end ]);
104 my $method = $self->method_lookup($self->{real_api_name});
105 my @res = $method->run(@real_args);
108 $client->respond( $_ ) for ( grep { defined } @res[$cache_args{offset} .. int($cache_args{offset} + $cache_args{limit} - 1)] );
110 $log->debug("Saving values from ".int($cache_page * $cache_args{cache_page_size})." to ".
111 int(($cache_page + 1) * $cache_args{cache_page_size}). "to the cache", INTERNAL);
113 OpenSRF::Utils::Cache->new->put_cache(
115 [@res[int($cache_page * $cache_args{cache_page_size}) .. int(($cache_page + 1) * $cache_args{cache_page_size}) ]] =>
116 OpenILS::Utils::DateTime->interval_to_seconds( $cache_args{timeout} )
120 $log->error("Cache seems to be down, $e");
130 my $cdbi = $self->{cdbi};
131 my $table = $cdbi->table;
135 WHERE id IN (( SELECT (RANDOM() * (SELECT MAX(id) FROM $table))::INT LIMIT 1 ));
141 my $id = $cdbi->db_Main->selectcol_arrayref($sql);
144 return ($cdbi->fast_fieldmapper(@$id))[0];
154 my $cdbi = $self->{cdbi};
156 for my $id ( @ids ) {
159 my ($rec) = $cdbi->fast_fieldmapper($id);
160 if ($self->api_name !~ /batch/o) {
161 return $rec if ($rec);
163 $client->respond($rec);
173 my @res = $self->method_lookup($self->{real_api_name})->run(@args);
175 if (ref($res[0]) eq 'ARRAY') {
176 return [ map { $_->id } @{ $res[0] } ];
179 $client->respond($_) for ( map { $_->id } @res );
188 if (ref($args[0]) eq 'HASH') {
190 $args[1]{limit_dialect} = $self->{cdbi}->db_Main;
192 $args[1] = {limit_dialect => $self->{cdbi}->db_Main };
195 $args[0] = { @args };
196 $args[1] = {limit_dialect => $self->{cdbi} };
199 my $cdbi = $self->{cdbi};
201 for my $obj ($cdbi->search_where(@args)) {
202 next unless ref($obj);
203 $client->respond( $obj->to_fieldmapper );
213 my $cdbi = $self->{cdbi};
215 (my $search_type = $self->api_name) =~ s/.*\.(search[^.]*).*/$1/o;
217 for my $obj ($cdbi->$search_type(@args)) {
218 next unless ref($obj);
219 $client->respond( $obj->to_fieldmapper );
224 sub search_one_field {
229 (my $field = $self->api_name) =~ s/.*\.([^\.]+)$/$1/o;
231 return search( $self, $client, $field, @args );
234 sub old_search_one_field {
239 (my $search_type = $self->api_name) =~ s/.*\.(search[^.]*).*/$1/o;
240 (my $col = $self->api_name) =~ s/.*\.$search_type\.([^.]+).*/$1/;
241 my $cdbi = $self->{cdbi};
244 $like = 1 if ($search_type =~ /like$/o);
245 $like = 2 if ($search_type =~ /fts$/o);
246 $like = 3 if ($search_type =~ /regex$/o);
248 for my $term (@terms) {
249 $log->debug("Searching $cdbi for $col using type $search_type, value '$term'",DEBUG);
251 return [ $cdbi->fast_fieldmapper($term,$col,$like) ];
253 $client->respond( [ $cdbi->fast_fieldmapper($term,$col,$like) ] );
264 local $OpenILS::Application::Storage::WRITE = 1;
266 my $cdbi = $self->{cdbi};
270 my $rec = $cdbi->create($node);
271 $success = $rec->id if ($rec);
284 local $OpenILS::Application::Storage::WRITE = 1;
286 my $cdbi = $self->{cdbi};
288 return $cdbi->update($node);
296 local $OpenILS::Application::Storage::WRITE = 1;
298 my $where = 'WHERE ';
300 my $cdbi = $self->{cdbi};
301 my $table = $cdbi->table;
303 my @keys = sort keys %$search;
307 for my $col ( @keys ) {
308 if (ref($$search{$col}) and ref($$search{$col}) =~ /ARRAY/o) {
309 push @wheres, "$col IN (" . join(',', map { '?' } @{ $$search{$col} }) . ')';
310 push @binds, map { "$_" } @{ $$search{$col} };
312 push @wheres, "$col = ?";
313 push @binds, $$search{$col};
316 $where .= join ' AND ', @wheres;
318 my $delete = "DELETE FROM $table $where";
320 $log->debug("Performing MASS deletion : $delete",DEBUG);
322 my $dbh = $cdbi->db_Main;
325 my $sth = $dbh->prepare($delete);
326 $sth->execute( @binds );
328 $log->debug("MASS Delete succeeded",DEBUG);
330 $log->debug("MASS Delete FAILED : ".shift(),DEBUG);
342 local $OpenILS::Application::Storage::WRITE = 1;
344 my $cdbi = $self->{cdbi};
348 $success = $cdbi->merge($keys,$vals)->id;
360 local $OpenILS::Application::Storage::WRITE = 1;
362 my $cdbi = $self->{cdbi};
366 $success = $cdbi->delete($node);
378 my $unwrap = $self->{unwrap};
380 my $cdbi = $self->{cdbi};
381 my $api_name = $self->api_name;
382 (my $single_call_api_name = $api_name) =~ s/batch\.//o;
384 $log->debug("Default $api_name looking up $single_call_api_name...",INTERNAL);
385 my $method = $self->method_lookup($single_call_api_name);
388 while ( my $node = shift(@nodes) ) {
389 my ($res) = $method->run( ($unwrap ? (@$node) : ($node)) );
390 push(@success, 1) if ($res >= 0);
393 my $insert_total = 0;
394 $insert_total += $_ for (@success);
396 return $insert_total;
400 # --------------------- End of generic methods -----------------------
403 for my $pkg ( qw/actor action asset biblio config metabib authority money permission container/ ) {
404 "OpenILS::Application::Storage::Publisher::$pkg"->use;
406 $log->debug("ARG! Couldn't load $pkg class Publisher: $@", ERROR);
407 throw OpenSRF::EX::ERROR ("ARG! Couldn't load $pkg class Publisher: $@");
411 for my $fmclass ( (Fieldmapper->classes) ) {
413 $log->debug("Generating methods for Fieldmapper class $fmclass", DEBUG);
415 next if ($fmclass->is_virtual);
417 (my $cdbi = $fmclass) =~ s/^Fieldmapper:://o;
418 (my $class = $cdbi) =~ s/::.*//o;
419 (my $api_class = $cdbi) =~ s/::/./go;
420 my $registration_class = __PACKAGE__ . "::$class";
421 my $api_prefix = 'open-ils.storage.direct.'.$api_class;
423 # Create the search methods
424 unless ( __PACKAGE__->is_registered( $api_prefix.'.search' ) ) {
425 __PACKAGE__->register_method(
426 api_name => $api_prefix.'.search',
436 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_where' ) ) {
437 __PACKAGE__->register_method(
438 api_name => $api_prefix.'.search_where',
439 method => 'search_where',
450 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_like' ) ) {
451 __PACKAGE__->register_method(
452 api_name => $api_prefix.'.search_like',
462 if (\&Class::DBI::search_fts and $cdbi->columns('FTS')) {
463 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_fts' ) ) {
464 __PACKAGE__->register_method(
465 api_name => $api_prefix.'.search_fts',
476 if (\&Class::DBI::search_regex) {
477 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_regex' ) ) {
478 __PACKAGE__->register_method(
479 api_name => $api_prefix.'.search_regex',
490 if (\&Class::DBI::search_ilike) {
491 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_ilike' ) ) {
492 __PACKAGE__->register_method(
493 api_name => $api_prefix.'.search_ilike',
506 # Create the random method
507 unless ( __PACKAGE__->is_registered( $api_prefix.'.random' ) ) {
508 __PACKAGE__->register_method(
509 api_name => $api_prefix.'.random',
510 method => 'random_object',
517 # Create the retrieve method
518 unless ( __PACKAGE__->is_registered( $api_prefix.'.retrieve' ) ) {
519 __PACKAGE__->register_method(
520 api_name => $api_prefix.'.retrieve',
521 method => 'retrieve_node',
529 # Create the batch retrieve method
530 unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.retrieve' ) ) {
531 __PACKAGE__->register_method(
532 api_name => $api_prefix.'.batch.retrieve',
533 method => 'retrieve_node',
542 for my $field ($fmclass->real_fields) {
543 unless ( __PACKAGE__->is_registered( $api_prefix.'.search.'.$field ) ) {
544 __PACKAGE__->register_method(
545 api_name => $api_prefix.'.search.'.$field,
546 method => 'search_one_field',
557 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_like.'.$field ) ) {
558 __PACKAGE__->register_method(
559 api_name => $api_prefix.'.search_like.'.$field,
560 method => 'search_one_field',
568 if (\&Class::DBI::search_fts and grep { $field eq $_ } $cdbi->columns('FTS')) {
569 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_fts.'.$field ) ) {
570 __PACKAGE__->register_method(
571 api_name => $api_prefix.'.search_fts.'.$field,
572 method => 'search_one_field',
581 if (\&Class::DBI::search_regex) {
582 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_regex.'.$field ) ) {
583 __PACKAGE__->register_method(
584 api_name => $api_prefix.'.search_regex.'.$field,
585 method => 'search_one_field',
594 if (\&Class::DBI::search_ilike) {
595 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_ilike.'.$field ) ) {
596 __PACKAGE__->register_method(
597 api_name => $api_prefix.'.search_ilike.'.$field,
598 method => 'search_one_field',
613 unless ($fmclass->is_readonly) {
614 # Create the create method
615 unless ( __PACKAGE__->is_registered( $api_prefix.'.create' ) ) {
616 __PACKAGE__->register_method(
617 api_name => $api_prefix.'.create',
618 method => 'create_node',
625 # Create the batch create method
626 unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.create' ) ) {
627 __PACKAGE__->register_method(
628 api_name => $api_prefix.'.batch.create',
629 method => 'batch_call',
636 # Create the update method
637 unless ( __PACKAGE__->is_registered( $api_prefix.'.update' ) ) {
638 __PACKAGE__->register_method(
639 api_name => $api_prefix.'.update',
640 method => 'update_node',
647 # Create the batch update method
648 unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.update' ) ) {
649 __PACKAGE__->register_method(
650 api_name => $api_prefix.'.batch.update',
651 method => 'batch_call',
658 # Create the delete method
659 unless ( __PACKAGE__->is_registered( $api_prefix.'.delete' ) ) {
660 __PACKAGE__->register_method(
661 api_name => $api_prefix.'.delete',
662 method => 'delete_node',
669 # Create the batch delete method
670 unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.delete' ) ) {
671 __PACKAGE__->register_method(
672 api_name => $api_prefix.'.batch.delete',
673 method => 'batch_call',
680 # Create the merge method
681 unless ( __PACKAGE__->is_registered( $api_prefix.'.merge' ) ) {
682 __PACKAGE__->register_method(
683 api_name => $api_prefix.'.merge',
684 method => 'merge_node',
691 # Create the batch merge method
692 unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.merge' ) ) {
693 __PACKAGE__->register_method(
694 api_name => $api_prefix.'.batch.merge',
695 method => 'batch_call',
703 # Create the search-based mass delete method
704 unless ( __PACKAGE__->is_registered( $api_prefix.'.mass_delete' ) ) {
705 __PACKAGE__->register_method(
706 api_name => $api_prefix.'.mass_delete',
707 method => 'mass_delete',