]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher.pm
using main DB outside transactions based on a flag
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Storage / Publisher.pm
1 package OpenILS::Application::Storage::Publisher;
2 use base qw/OpenILS::Application::Storage/;
3 our $VERSION = 1;
4
5 use Digest::MD5 qw/md5_hex/;
6 use OpenSRF::EX qw/:try/;
7 use OpenSRF::Utils;
8 use OpenSRF::Utils::Logger qw/:level/;
9 use OpenILS::Utils::Fieldmapper;
10
11 my $log = 'OpenSRF::Utils::Logger';
12
13
14 sub register_method {
15         my $class = shift;
16         my %args = @_;
17         my %dup_args = %args;
18
19         $class = ref($class) || $class;
20
21         $args{package} ||= $class;
22         __PACKAGE__->SUPER::register_method( %args );
23
24         if (exists($dup_args{cachable}) and $dup_args{cachable}) {
25                 (my $name = $dup_args{api_name}) =~ s/^open-ils\.storage/open-ils.storage.cachable/o;
26                 if ($name ne $dup_args{api_name}) {
27                         $dup_args{real_api_name} = $dup_args{api_name};
28                         $dup_args{method} = 'cachable_wrapper';
29                         $dup_args{api_name} = $name;
30                         $dup_args{package} = __PACKAGE__;
31                         __PACKAGE__->SUPER::register_method( %dup_args );
32                 }
33         }
34
35         if ($dup_args{real_api_name} =~ /^open-ils\.storage\.direct\..+\.search.+/o ||
36             $dup_args{api_name} =~ /^open-ils\.storage\.direct\..+\.search.+/o) {
37                 $dup_args{api_name} = $dup_args{real_api_name} if ($dup_args{real_api_name});
38
39                 (my $name = $dup_args{api_name}) =~ s/\.direct\./.id_list./o;
40
41                 $dup_args{notes} = $dup_args{real_api_name};
42                 $dup_args{real_api_name} = $dup_args{api_name};
43                 $dup_args{method} = 'search_ids';
44                 $dup_args{api_name} = $name;
45                 $dup_args{package} = __PACKAGE__;
46
47                 __PACKAGE__->SUPER::register_method( %dup_args );
48         }
49 }
50
51 sub cachable_wrapper {
52         my $self = shift;
53         my $client = shift;
54         my @args = @_;
55
56         my %cache_args = (
57                 limit           => 100,
58                 offset          => 0,
59                 timeout         => 7200,
60                 cache_page_size => 1000,
61         );
62
63         my @real_args;
64         my $key_string = $self->api_name;
65         for (my $ind = 0; $ind < scalar(@args); $ind++) {
66                 if (    $args[$ind] eq 'limit' ||
67                         $args[$ind] eq 'offset' ||
68                         $args[$ind] eq 'cache_page_size' ||
69                         $args[$ind] eq 'timeout' ) {
70
71                         my $key_ind = $ind;
72                         $ind++;
73                         my $value_ind = $ind;
74                         $cache_args{$args[$key_ind]} = $args[$value_ind];
75                         $log->debug("Cache limiter value for $args[$key_ind] is $args[$value_ind]", INTERNAL);
76                         next;
77                 }
78                 $key_string .= $args[$ind];
79                 $log->debug("Partial cache key value is $args[$ind]", INTERNAL);
80                 push @real_args, $args[$ind];
81         }
82
83         my $cache_page = int($cache_args{offset} / $cache_args{cache_page_size});
84         my $cache_key;
85         {       use bytes;
86                 $cache_key = md5_hex($key_string.$cache_page);
87         }
88
89         $log->debug("Key string for cache lookup is $key_string -> $cache_key", DEBUG);
90         $log->debug("Cache page is $cache_page", DEBUG);
91
92         my $cached_res = OpenSRF::Utils::Cache->new->get_cache( $cache_key );
93         if (defined $cached_res) {
94                 $log->debug("Found ".scalar(@$cached_res)." records in the cache", INFO);
95                 $log->debug("Values from cache: ".join(', ', @$cached_res), INTERNAL);
96                 my $start = int($cache_args{offset} - ($cache_page * $cache_args{cache_page_size}));
97                 my $end = int($start + $cache_args{limit} - 1);
98                 $log->debug("Responding with values from ".$start.' to '.$end,DEBUG);
99                 $client->respond( $_ ) for ( grep { defined } @$cached_res[ $start .. $end ]);
100                 return undef;
101         }
102
103         my $method = $self->method_lookup($self->{real_api_name});
104         my @res = $method->run(@real_args);
105
106
107         $client->respond( $_ ) for ( grep { defined } @res[$cache_args{offset} .. int($cache_args{offset} + $cache_args{limit} - 1)] );
108
109         $log->debug("Saving values from ".int($cache_page * $cache_args{cache_page_size})." to ".
110                 int(($cache_page + 1) * $cache_args{cache_page_size}). "to the cache", INTERNAL);
111         try {
112                 OpenSRF::Utils::Cache->new->put_cache(
113                         $cache_key =>
114                         [@res[int($cache_page * $cache_args{cache_page_size}) .. int(($cache_page + 1) * $cache_args{cache_page_size}) ]] =>
115                         OpenSRF::Utils->interval_to_seconds( $cache_args{timeout} )
116                 );
117         } catch Error with {
118                 my $e = shift;
119                 $log->error("Cache seems to be down, $e");
120         };
121
122         return undef;
123 }
124
125 sub retrieve_node {
126         my $self = shift;
127         my $client = shift;
128         my @ids = @_;
129
130         my $cdbi = $self->{cdbi};
131
132         for my $id ( @ids ) {
133                 next unless ($id);
134
135                 my ($rec) = $cdbi->fast_fieldmapper($id);
136                 if ($self->api_name !~ /batch/o) {
137                         return $rec if ($rec);
138                 }
139                 $client->respond($rec);
140         }
141         return undef;
142 }
143
144 sub search_ids {
145         my $self = shift;
146         my $client = shift;
147         my @args = @_;
148
149         my @res = $self->method_lookup($self->{real_api_name})->run(@args);
150
151         if (ref($res[0]) eq 'ARRAY') {
152                 return [ map { $_->id } @{ $res[0] } ];
153         }
154
155         $client->respond($_) for ( map { $_->id } @res );
156         return undef;
157 }
158
159 sub search_where {
160         my $self = shift;
161         my $client = shift;
162         my @args = @_;
163
164         if (ref($args[0]) eq 'HASH') {
165                 if ($args[1]) {
166                         $args[1]{limit_dialect} = $self->{cdbi}->db_Main;
167                 } else {
168                         $args[1] = {limit_dialect => $self->{cdbi}->db_Main };
169                 }
170         } else {
171                 $args[0] = { @args };
172                 $args[1] = {limit_dialect => $self->{cdbi} };
173         }
174
175         my $cdbi = $self->{cdbi};
176
177         for my $obj ($cdbi->search_where(@args)) {
178                 next unless ref($obj);
179                 $client->respond( $obj->to_fieldmapper );
180         }
181         return undef;
182 }
183
184 sub search {
185         my $self = shift;
186         my $client = shift;
187         my @args = @_;
188
189         my $cdbi = $self->{cdbi};
190
191         (my $search_type = $self->api_name) =~ s/.*\.(search[^.]*).*/$1/o;
192
193         for my $obj ($cdbi->$search_type(@args)) {
194                 next unless ref($obj);
195                 $client->respond( $obj->to_fieldmapper );
196         }
197         return undef;
198 }
199
200 sub search_one_field {
201         my $self = shift;
202         my $client = shift;
203         my @args = @_;
204
205         (my $field = $self->api_name) =~ s/.*\.([^\.]+)$/$1/o;
206
207         return search( $self, $client, $field, @args );
208 }
209
210 sub old_search_one_field {
211         my $self = shift;
212         my $client = shift;
213         my @terms = @_;
214
215         (my $search_type = $self->api_name) =~ s/.*\.(search[^.]*).*/$1/o;
216         (my $col = $self->api_name) =~ s/.*\.$search_type\.([^.]+).*/$1/;
217         my $cdbi = $self->{cdbi};
218
219         my $like = 0;
220         $like = 1 if ($search_type =~ /like$/o);
221         $like = 2 if ($search_type =~ /fts$/o);
222         $like = 3 if ($search_type =~ /regex$/o);
223
224         for my $term (@terms) {
225                 $log->debug("Searching $cdbi for $col using type $search_type, value '$term'",DEBUG);
226                 if (@terms == 1) {
227                         return [ $cdbi->fast_fieldmapper($term,$col,$like) ];
228                 }
229                 $client->respond( [ $cdbi->fast_fieldmapper($term,$col,$like) ] );
230         }
231         return undef;
232 }
233
234
235 sub create_node {
236         my $self = shift;
237         my $client = shift;
238         my $node = shift;
239
240         local $OpenILS::Application::Storage::WRITE = 1;
241
242         my $cdbi = $self->{cdbi};
243
244         my $success;
245         try {
246                 my $rec = $cdbi->create($node);
247                 $success = $rec->id if ($rec);
248         } catch Error with {
249                 $success = 0;
250         };
251
252         return $success;
253 }
254
255 sub update_node {
256         my $self = shift;
257         my $client = shift;
258         my $node = shift;
259
260         local $OpenILS::Application::Storage::WRITE = 1;
261
262         my $cdbi = $self->{cdbi};
263
264         return $cdbi->update($node);
265 }
266
267 sub mass_delete {
268         my $self = shift;
269         my $client = shift;
270         my $search = shift;
271
272         local $OpenILS::Application::Storage::WRITE = 1;
273
274         my $where = 'WHERE ';
275
276         my $cdbi = $self->{cdbi};
277         my $table = $cdbi->table;
278
279         my @keys = sort keys %$search;
280         
281         my @binds;
282         my @wheres;
283         for my $col ( @keys ) {
284                 if (ref($$search{$col}) and ref($$search{$col}) =~ /ARRAY/o) {
285                         push @wheres, "$col IN (" . join(',', map { '?' } @{ $$search{$col} }) . ')';
286                         push @binds, map { "$_" } @{ $$search{$col} };
287                 } else {
288                         push @wheres, "$col = ?";
289                         push @binds, $$search{$col};
290                 }
291         }
292         $where .= join ' AND ', @wheres;
293
294         my $delete = "DELETE FROM $table $where";
295
296         $log->debug("Performing MASS deletion : $delete",DEBUG);
297
298         my $dbh = $cdbi->db_Main;
299         my $success = 1;
300         try {
301                 my $sth = $dbh->prepare($delete);
302                 $sth->execute( @binds );
303                 $sth->finish;
304                 $log->debug("MASS Delete succeeded",DEBUG);
305         } catch Error with {
306                 $log->debug("MASS Delete FAILED : ".shift(),DEBUG);
307                 $success = 0;
308         };
309         return $success;
310 }
311
312 sub remote_update_node {
313         my $self = shift;
314         my $client = shift;
315         my $keys = shift;
316         my $vals = shift;
317
318         local $OpenILS::Application::Storage::WRITE = 1;
319
320         my $cdbi = $self->{cdbi};
321
322         my $success = 1;
323         try {
324                 $success = $cdbi->remote_update($keys,$vals);
325         } catch Error with {
326                 $success = 0;
327         };
328         return $success;
329 }
330
331 sub merge_node {
332         my $self = shift;
333         my $client = shift;
334         my $keys = shift;
335         my $vals = shift;
336
337         local $OpenILS::Application::Storage::WRITE = 1;
338
339         my $cdbi = $self->{cdbi};
340
341         my $success = 1;
342         try {
343                 $success = $cdbi->merge($keys,$vals)->id;
344         } catch Error with {
345                 $success = 0;
346         };
347         return $success;
348 }
349
350 sub delete_node {
351         my $self = shift;
352         my $client = shift;
353         my $node = shift;
354
355         local $OpenILS::Application::Storage::WRITE = 1;
356
357         my $cdbi = $self->{cdbi};
358
359         my $success = 1;
360         try {
361                 $success = $cdbi->delete($node);
362         } catch Error with {
363                 $success = 0;
364         };
365         return $success;
366 }
367
368 sub batch_call {
369         my $self = shift;
370         my $client = shift;
371         my @nodes = @_;
372
373         my $unwrap = $self->{unwrap};
374
375         my $cdbi = $self->{cdbi};
376         my $api_name = $self->api_name;
377         (my $single_call_api_name = $api_name) =~ s/batch\.//o;
378
379         $log->debug("Default $api_name looking up $single_call_api_name...",INTERNAL);
380         my $method = $self->method_lookup($single_call_api_name);
381
382         my @success;
383         while ( my $node = shift(@nodes) ) {
384                 my ($res) = $method->run( ($unwrap ? (@$node) : ($node)) ); 
385                 push(@success, 1) if ($res >= 0);
386         }
387
388         my $insert_total = 0;
389         $insert_total += $_ for (@success);
390
391         return $insert_total;
392 }
393
394
395 # --------------------- End of generic methods -----------------------
396
397
398 for my $pkg ( qw/actor action asset biblio config metabib authority money permission container/ ) {
399         "OpenILS::Application::Storage::Publisher::$pkg"->use;
400         if ($@) {
401                 $log->debug("ARG! Couldn't load $pkg class Publisher: $@", ERROR);
402                 throw OpenSRF::EX::ERROR ("ARG! Couldn't load $pkg class Publisher: $@");
403         }
404 }
405
406 for my $fmclass ( (Fieldmapper->classes) ) {
407
408         $log->debug("Generating methods for Fieldmapper class $fmclass", DEBUG);
409
410         next if ($fmclass->is_virtual);
411
412         (my $cdbi = $fmclass) =~ s/^Fieldmapper:://o;
413         (my $class = $cdbi) =~ s/::.*//o;
414         (my $api_class = $cdbi) =~ s/::/./go;
415         my $registration_class = __PACKAGE__ . "::$class";
416         my $api_prefix = 'open-ils.storage.direct.'.$api_class;
417
418         # Create the search methods
419         unless ( __PACKAGE__->is_registered( $api_prefix.'.search' ) ) {
420                 __PACKAGE__->register_method(
421                         api_name        => $api_prefix.'.search',
422                         method          => 'search',
423                         api_level       => 1,
424                         argc            => 2,
425                         stream          => 1,
426                         cdbi            => $cdbi,
427                         cachable        => 1,
428                 );
429         }
430
431         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_where' ) ) {
432                 __PACKAGE__->register_method(
433                         api_name        => $api_prefix.'.search_where',
434                         method          => 'search_where',
435                         api_level       => 1,
436                         stream          => 1,
437                         argc            => 1,
438                         cdbi            => $cdbi,
439                         cachable        => 1,
440                 );
441         }
442
443         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_like' ) ) {
444                 __PACKAGE__->register_method(
445                         api_name        => $api_prefix.'.search_like',
446                         method          => 'search',
447                         api_level       => 1,
448                         stream          => 1,
449                         cdbi            => $cdbi,
450                         cachable        => 1,
451                         argc            => 2,
452                 );
453         }
454
455         if (\&Class::DBI::search_fts and $cdbi->columns('FTS')) {
456                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_fts' ) ) {
457                         __PACKAGE__->register_method(
458                                 api_name        => $api_prefix.'.search_fts',
459                                 method          => 'search',
460                                 api_level       => 1,
461                                 stream          => 1,
462                                 cdbi            => $cdbi,
463                                 cachable        => 1,
464                                 argc            => 2,
465                         );
466                 }
467         }
468
469         if (\&Class::DBI::search_regex) {
470                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_regex' ) ) {
471                         __PACKAGE__->register_method(
472                                 api_name        => $api_prefix.'.search_regex',
473                                 method          => 'search',
474                                 api_level       => 1,
475                                 stream          => 1,
476                                 cdbi            => $cdbi,
477                                 cachable        => 1,
478                                 argc            => 2,
479                         );
480                 }
481         }
482
483         if (\&Class::DBI::search_ilike) {
484                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_ilike' ) ) {
485                         __PACKAGE__->register_method(
486                                 api_name        => $api_prefix.'.search_ilike',
487                                 method          => 'search',
488                                 api_level       => 1,
489                                 stream          => 1,
490                                 cdbi            => $cdbi,
491                                 cachable        => 1,
492                                 argc            => 2,
493                         );
494                 }
495         }
496
497         # Create the retrieve method
498         unless ( __PACKAGE__->is_registered( $api_prefix.'.retrieve' ) ) {
499                 __PACKAGE__->register_method(
500                         api_name        => $api_prefix.'.retrieve',
501                         method          => 'retrieve_node',
502                         api_level       => 1,
503                         cdbi            => $cdbi,
504                         cachable        => 1,
505                         argc            => 1,
506                 );
507         }
508
509         # Create the batch retrieve method
510         unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.retrieve' ) ) {
511                 __PACKAGE__->register_method(
512                         api_name        => $api_prefix.'.batch.retrieve',
513                         method          => 'retrieve_node',
514                         api_level       => 1,
515                         stream          => 1,
516                         cdbi            => $cdbi,
517                         cachable        => 1,
518                         argc            => 1,
519                 );
520         }
521
522         for my $field ($fmclass->real_fields) {
523                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search.'.$field ) ) {
524                         __PACKAGE__->register_method(
525                                 api_name        => $api_prefix.'.search.'.$field,
526                                 method          => 'search_one_field',
527                                 api_level       => 1,
528                                 cdbi            => $cdbi,
529                                 cachable        => 1,
530                                 stream          => 1,
531                                 argc            => 1,
532                         );
533                 }
534                 unless ( __PACKAGE__->is_registered( $api_prefix.'.search_like.'.$field ) ) {
535                         __PACKAGE__->register_method(
536                                 api_name        => $api_prefix.'.search_like.'.$field,
537                                 method          => 'search_one_field',
538                                 api_level       => 1,
539                                 cdbi            => $cdbi,
540                                 cachable        => 1,
541                                 stream          => 1,
542                                 argc            => 1,
543                         );
544                 }
545                 if (\&Class::DBI::search_fts and grep { $field eq $_ } $cdbi->columns('FTS')) {
546                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_fts.'.$field ) ) {
547                                 __PACKAGE__->register_method(
548                                         api_name        => $api_prefix.'.search_fts.'.$field,
549                                         method          => 'search_one_field',
550                                         api_level       => 1,
551                                         cdbi            => $cdbi,
552                                         cachable        => 1,
553                                         stream          => 1,
554                                         argc            => 1,
555                                 );
556                         }
557                 }
558                 if (\&Class::DBI::search_regex) {
559                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_regex.'.$field ) ) {
560                                 __PACKAGE__->register_method(
561                                         api_name        => $api_prefix.'.search_regex.'.$field,
562                                         method          => 'search_one_field',
563                                         api_level       => 1,
564                                         cdbi            => $cdbi,
565                                         cachable        => 1,
566                                         stream          => 1,
567                                         argc            => 1,
568                                 );
569                         }
570                 }
571                 if (\&Class::DBI::search_ilike) {
572                         unless ( __PACKAGE__->is_registered( $api_prefix.'.search_ilike.'.$field ) ) {
573                                 __PACKAGE__->register_method(
574                                         api_name        => $api_prefix.'.search_ilike.'.$field,
575                                         method          => 'search_one_field',
576                                         api_level       => 1,
577                                         cdbi            => $cdbi,
578                                         cachable        => 1,
579                                         stream          => 1,
580                                         argc            => 1,
581                                 );
582                         }
583                 }
584         }
585
586
587         unless ($fmclass->is_readonly) {
588                 # Create the create method
589                 unless ( __PACKAGE__->is_registered( $api_prefix.'.create' ) ) {
590                         __PACKAGE__->register_method(
591                                 api_name        => $api_prefix.'.create',
592                                 method          => 'create_node',
593                                 api_level       => 1,
594                                 cdbi            => $cdbi,
595                                 argc            => 1,
596                         );
597                 }
598
599                 # Create the batch create method
600                 unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.create' ) ) {
601                         __PACKAGE__->register_method(
602                                 api_name        => $api_prefix.'.batch.create',
603                                 method          => 'batch_call',
604                                 api_level       => 1,
605                                 cdbi            => $cdbi,
606                                 argc            => 1,
607                         );
608                 }
609
610                 # Create the update method
611                 unless ( __PACKAGE__->is_registered( $api_prefix.'.update' ) ) {
612                         __PACKAGE__->register_method(
613                                 api_name        => $api_prefix.'.update',
614                                 method          => 'update_node',
615                                 api_level       => 1,
616                                 cdbi            => $cdbi,
617                                 argc            => 1,
618                         );
619                 }
620
621                 # Create the batch update method
622                 unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.update' ) ) {
623                         __PACKAGE__->register_method(
624                                 api_name        => $api_prefix.'.batch.update',
625                                 method          => 'batch_call',
626                                 api_level       => 1,
627                                 cdbi            => $cdbi,
628                                 argc            => 1,
629                         );
630                 }
631
632                 # Create the delete method
633                 unless ( __PACKAGE__->is_registered( $api_prefix.'.delete' ) ) {
634                         __PACKAGE__->register_method(
635                                 api_name        => $api_prefix.'.delete',
636                                 method          => 'delete_node',
637                                 api_level       => 1,
638                                 cdbi            => $cdbi,
639                                 argc            => 1,
640                         );
641                 }
642
643                 # Create the batch delete method
644                 unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.delete' ) ) {
645                         __PACKAGE__->register_method(
646                                 api_name        => $api_prefix.'.batch.delete',
647                                 method          => 'batch_call',
648                                 api_level       => 1,
649                                 cdbi            => $cdbi,
650                                 argc            => 1,
651                         );
652                 }
653
654                 # Create the merge method
655                 unless ( __PACKAGE__->is_registered( $api_prefix.'.merge' ) ) {
656                         __PACKAGE__->register_method(
657                                 api_name        => $api_prefix.'.merge',
658                                 method          => 'merge_node',
659                                 api_level       => 1,
660                                 cdbi            => $cdbi,
661                                 argc            => 1,
662                         );
663                 }
664
665                 # Create the batch merge method
666                 unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.merge' ) ) {
667                         __PACKAGE__->register_method(
668                                 api_name        => $api_prefix.'.batch.merge',
669                                 method          => 'batch_call',
670                                 unwrap          => 1,
671                                 api_level       => 1,
672                                 cdbi            => $cdbi,
673                                 argc            => 1,
674                         );
675                 }
676
677                 # Create the remote_update method
678                 unless ( __PACKAGE__->is_registered( $api_prefix.'.remote_update' ) ) {
679                         __PACKAGE__->register_method(
680                                 api_name        => $api_prefix.'.remote_update',
681                                 method          => 'remote_update_node',
682                                 api_level       => 1,
683                                 cdbi            => $cdbi,
684                                 argc            => 1,
685                         );
686                 }
687
688                 # Create the batch remote_update method
689                 unless ( __PACKAGE__->is_registered( $api_prefix.'.batch.remote_update' ) ) {
690                         __PACKAGE__->register_method(
691                                 api_name        => $api_prefix.'.batch.remote_update',
692                                 method          => 'batch_call',
693                                 api_level       => 1,
694                                 unwrap          => 1,
695                                 cdbi            => $cdbi,
696                                 argc            => 1,
697                         );
698                 }
699
700                 # Create the search-based mass delete method
701                 unless ( __PACKAGE__->is_registered( $api_prefix.'.mass_delete' ) ) {
702                         __PACKAGE__->register_method(
703                                 api_name        => $api_prefix.'.mass_delete',
704                                 method          => 'mass_delete',
705                                 api_level       => 1,
706                                 cdbi            => $cdbi,
707                                 argc            => 1,
708                         );
709                 }
710         }
711 }
712
713 1;