5c2dec843dca3a9120b5a79975c189b340e6175e
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Storage / CDBI.pm
1 package OpenILS::Application::Storage::CDBI;
2 use base qw/Class::DBI/;
3 use Class::DBI;
4 use Class::DBI::AbstractSearch;
5
6 use OpenILS::Application::Storage::CDBI::actor;
7 use OpenILS::Application::Storage::CDBI::action;
8 use OpenILS::Application::Storage::CDBI::asset;
9 use OpenILS::Application::Storage::CDBI::authority;
10 use OpenILS::Application::Storage::CDBI::biblio;
11 use OpenILS::Application::Storage::CDBI::config;
12 use OpenILS::Application::Storage::CDBI::metabib;
13 use OpenILS::Application::Storage::CDBI::money;
14 use OpenILS::Application::Storage::CDBI::permission;
15
16 use JSON;
17 use OpenSRF::Utils::Logger;
18 use OpenSRF::EX qw/:try/;
19
20 our $VERSION = undef;
21 my $log = 'OpenSRF::Utils::Logger';
22
23 sub child_init {
24         my $self = shift;
25
26         $log->debug("Creating ImaDBI Querys", DEBUG);
27         __PACKAGE__->set_sql( 'OILSFastSearch', <<"     SQL", 'Main');
28                 SELECT  %s
29                   FROM  %s
30                   WHERE %s = ?
31         SQL
32
33         __PACKAGE__->set_sql( 'OILSFastOrderedSearchLike', <<"  SQL", 'Main');
34                 SELECT  %s
35                   FROM  %s
36                   WHERE %s LIKE ?
37                   ORDER BY %s
38         SQL
39
40         __PACKAGE__->set_sql( 'OILSFastOrderedSearch', <<"      SQL", 'Main');
41                 SELECT  %s
42                   FROM  %s
43                   WHERE %s = ?
44                   ORDER BY %s
45         SQL
46
47         $log->debug("Calling Driver child_init", DEBUG);
48         $self->SUPER::child_init(@_);
49
50 }
51
52 sub fast_flesh_sth {
53         my $class = shift;
54         $class = ref($class) || $class;
55
56         my $field = shift;
57         my $value = shift;
58         my $order = shift;
59         my $like = shift;
60
61
62         if (!(defined($order) and ref($order) and ref($order) eq 'HASH')) {
63                 if (defined($value) and ref($value) and ref($value) eq 'HASH') {
64                         $order = $value;
65                         $value = undef;
66                 } else {
67                         $order = { order_by => $class->columns('Primary') }
68                 }
69         }
70
71         unless (defined $value) {
72                 $value = $field;
73                 ($field) = $class->columns('Primary');
74         }
75
76         unless (defined $field) {
77                 ($field) = $class->columns('Primary');
78         }
79
80         unless ($order->{order_by}) {
81                 $order = { order_by => $class->columns('Primary') }
82         }
83
84         my $fm_class = 'Fieldmapper::'.$class;
85         my $field_list = join ',', $class->columns('All');
86         
87         my $sth;
88         if (!$like) {
89                 $sth = $class->sql_OILSFastOrderedSearch( $field_list, $class->table, $field, $order->{order_by});
90         } else {
91                 $sth = $class->sql_OILSFastOrderedSearchLike( $field_list, $class->table, $field, $order->{order_by});
92         }
93         $sth->execute($value);
94         return $sth;
95 }
96
97 sub fast_flesh {
98         my $self = shift;
99         return map $class->construct($_), $self->fast_flesh_sth(@_)->fetchall_hash;
100 }
101
102 sub fast_fieldmapper {
103         my $self = shift;
104         my $id = shift;
105         my $col = shift;
106         my $like = shift;
107         my $options = shift;
108         my $class = ref($self) || $self;
109         my $fm_class = 'Fieldmapper::'.$class;
110         my @fms;
111         $log->debug("fast_fieldmapper() ==> Retrieving $fm_class", INTERNAL);
112         if ($like < 2) {
113                 for my $hash ($self->fast_flesh_sth( $col, "$id", { order_by => $col }, $like )->fetchall_hash) {
114                         my $fm = $fm_class->new;
115                         for my $field ( $fm_class->real_fields ) {
116                                 $fm->$field( $$hash{$field} );
117                         }
118                         push @fms, $fm;
119                 }
120         } else {
121                 my $search_type = 'search';
122                 if ($like == 2) {
123                         $search_type = 'search_fts'
124                 } elsif ($like == 3) {
125                         $search_type = 'search_regex'
126                 }
127
128                 for my $obj ($class->$search_type({ $col => $id}, $options)) {
129                         push @fms, $obj->to_fieldmapper;
130                 }
131         }
132         return @fms;
133 }
134
135 sub retrieve {
136         my $self = shift;
137         my $arg = shift;
138         if (ref($arg) &&
139                 (UNIVERSAL::isa($arg => 'Fieldmapper') ||
140                  UNIVERSAL::isa($arg => 'Class::DBI')) ) {
141                 my ($col) = $self->primary_column;
142                 $log->debug("Using field $col as the primary key", INTERNAL);
143                 $arg = $arg->$col;
144         } elsif (ref $arg) {
145                 my ($col) = $self->primary_column;
146                 $log->debug("Using field $col as the primary key", INTERNAL);
147                 $arg = $arg->{$col};
148         }
149                 
150         $log->debug("Retrieving $self with $arg", INTERNAL);
151         my $rec;
152         try {
153                 $rec = $self->SUPER::retrieve("$arg");
154         } catch Error with {
155                 $log->debug("Could not retrieve $self with $arg! -- ".shift(), DEBUG);
156                 return undef;
157         };
158         return $rec;
159 }
160
161 sub to_fieldmapper {
162         my $obj = shift;
163         my $class = ref($obj) || $obj;
164
165         my $fm_class = 'Fieldmapper::'.$class;
166         my $fm = $fm_class->new;
167
168         if (ref($obj)) {
169                 for my $field ( $fm->real_fields ) {
170                         $fm->$field( $obj->$field );
171                 }
172         }
173
174         return $fm;
175 }
176
177 sub merge {
178         my $self = shift;
179         my $search = shift;
180         my $arg = shift;
181
182         delete $$arg{$_} for (keys %$search);
183
184         $log->debug("CDBI->merge: \$search is $search (".ref($search)." : ".join(',',map{"$_ => $$search{$_}"}keys(%$search)).")",DEBUG);
185         $log->debug("CDBI->merge: \$arg is $arg (".ref($arg)." : ".join(',',map{"$_ => $$arg{$_}"}keys(%$arg)).")",DEBUG);
186
187         my @objs = ($self);
188         @objs = $self->search_where($search) unless (ref $self);
189
190         if (@objs == 1) {
191                 return $objs[0]->update($arg);
192         } elsif (@objs == 0) {
193                 return $self->create({%$search,%$arg});
194         } else {
195                 throw OpenSRF::EX::WARN ("Non-unique search key for merge.  Perhaps you meant to use remote_update?");
196         }
197 }
198
199 sub remote_update {
200         my $self = shift;
201         my $search = shift;
202         my $arg = shift;
203
204         delete $$arg{$_} for (keys %$search);
205
206         $log->debug("CDBI->remote_update: \$search is $search (".ref($search)." : ".join(',',map{"$_ => $$search{$_}"}keys(%$search)).")",DEBUG);
207         $log->debug("CDBI->remote_update: \$arg is $arg (".ref($arg)." : ".join(',',map{"$_ => $$arg{$_}"}keys(%$arg)).")",DEBUG);
208
209 #       my @objs = $self->search_where($search);
210 #       throw OpenSRF::EX::WARN ("No objects found for remote_update.  Perhaps you meant to use merge?")
211 #               if (@objs == 0);
212
213 #       $_->update($arg) for (@objs);
214 #       return scalar(@objs);
215
216         my @finds = sort keys %$search;
217         my @sets = sort keys %$arg;
218
219         my @find_vals = @$search{@finds};
220         my @set_vals = @$arg{@sets};
221
222         my $sql = 'UPDATE %s SET %s WHERE %s';
223
224         my $table = $self->table;
225         my $set = join(', ', map { "$_=?" } @sets);
226         my $where = join(', ', map { "$_=?" } @finds);
227
228         my $sth = $self->db_Main->prepare(sprintf($sql, $table, $set, $where));
229         $sth->execute(@set_vals,@find_vals);
230         return $sth->rows;
231
232 }
233
234 sub create {
235         my $self = shift;
236         my $arg = shift;
237
238         $log->debug("CDBI->create: \$arg is $arg (".ref($arg)." : ".JSON->perl2JSON($arg).")",DEBUG);
239
240         if (ref($arg) && UNIVERSAL::isa($arg => 'Fieldmapper')) {
241                 return $self->create_from_fieldmapper($arg,@_);
242         }
243
244         return $self->SUPER::create($arg,@_);
245 }
246
247 sub create_from_fieldmapper {
248         my $obj = shift;
249         my $fm = shift;
250         my @params = @_;
251
252         $log->debug("Creating node of type ".ref($fm), DEBUG);
253
254         my $class = ref($obj) || $obj;
255         my ($primary) = $class->columns('Primary');
256
257         if (ref($fm) &&UNIVERSAL::isa($fm => 'Fieldmapper')) {
258                 my %hash = map { defined $fm->$_ ?
259                                         ($_ => $fm->$_) :
260                                         ()
261                                 } grep { $_ ne $primary } $class->columns('All');
262
263                 if ($class->find_column( 'last_xact_id' )) {
264                         my $xact_id = $class->current_xact_id;
265                         throw Error unless ($xact_id);
266                         $hash{last_xact_id} = $xact_id;
267                 }
268
269                 return $class->create( \%hash, @params );
270         } else {
271                 return undef;
272         }
273 }
274
275 sub delete {
276         my $self = shift;
277         my $arg = shift;
278         my $orig = $self;
279
280         my $class = ref($self) || $self;
281
282         $self = $self->retrieve($arg) if (!ref($self));
283         unless (defined $self) {
284                 $log->debug("ARG! Couldn't retrieve record ".$arg->id, DEBUG);
285                 throw OpenSRF::EX::WARN ("ARG! Couldn't retrieve record ");
286         }
287
288         if ($class->find_column( 'last_xact_id' )) {
289                 my $xact_id = $self->current_xact_id;
290                 
291                 throw Error ("Deleting from $class requires a transaction be established")
292                         unless ($xact_id);
293                 
294                 throw Error ("The row you are attempting to delete has been changed since you read it")
295                         unless ( $orig->last_xact_id eq $self->last_xact_id);
296
297                 $self->last_xact_id( $class->current_xact_id );
298                 $self->SUPER::update;
299         }
300
301         $self->SUPER::delete;
302
303         return 1;
304 }
305
306 sub debug_object {
307         my $obj = shift;
308         my $string = '';
309
310         $string .= "Object type:\t".ref($obj)."\n";
311         $string .= "Object string:\t$obj\n";
312
313         if (ref($obj) && UNIVERSAL::isa($obj => 'Fieldmapper')) {
314                 $string .= "Object fields:\n";
315                 for my $col ($obj->real_fields()) {
316                         $string .= "\t$col\t=> ".$obj->$col."\n";
317                 }
318         } elsif (ref($obj) && UNIVERSAL::isa($obj => 'Class::DBI')) {
319                 $string .= "Object cols:\n";
320                 for my $col ($obj->columns('All')) {
321                         $string .= "\t$col\t=> ".$obj->$col."\n";
322                 }
323         } elsif (ref($obj) && UNIVERSAL::isa($obj => 'HASH')) {
324                 $string .= "Object keys and vals:\n";
325                 for my $col (keys %$obj) {
326                         $string .= "\t$col\t=> $$obj{$col}\n";
327                 }
328         }
329
330         $string .= "\n";
331         
332         $log->debug($string,DEBUG);
333 }
334
335
336 sub update {
337         my $self = shift;
338         my $arg = shift;
339
340         $log->debug("Attempting to update using $arg", DEBUG) if ($arg);
341
342         if (ref($arg)) {
343                 $self = $self->modify_from_fieldmapper($arg);
344                 $log->debug("Modification of $self seems to have failed....", DEBUG);
345                 return undef unless (defined $self);
346         }
347
348         $log->debug("Calling Class::DBI->update on modified object $self", DEBUG);
349
350         debug_object($self);
351
352         return $self->SUPER::update if ($self->is_changed);
353         return 0;
354 }
355
356 sub modify_from_fieldmapper {
357         my $obj = shift;
358         my $fm = shift;
359         my $orig = $obj;
360
361         debug_object($obj);
362         debug_object($fm);
363
364         $log->debug("Modifying object using fieldmapper", DEBUG);
365
366         my $class = ref($obj) || $obj;
367         my ($primary) = $class->columns('Primary');
368
369
370         if (!ref($obj)) {
371                 $obj = $class->retrieve($fm);
372                 debug_object($obj);
373                 unless ($obj) {
374                         $log->debug("Retrieve of $class using $fm (".$fm->id.") failed! -- ".shift(), ERROR);
375                         throw OpenSRF::EX::WARN ("No $class with id of ".$fm->id."!!");
376                 }
377         }
378
379         my %hash;
380         
381         if (ref($fm) and UNIVERSAL::isa($fm => 'Fieldmapper')) {
382                 %hash = map { defined $fm->$_ ?
383                                 ($_ => ''.$fm->$_) :
384                                 ()
385                         } grep { $_ ne $primary } $class->columns('All');
386         } else {
387                 %hash = %{$fm};
388         }
389
390         my $au = $obj->autoupdate;
391         $obj->autoupdate(0);
392         
393         debug_object($obj);
394
395         for my $field ( keys %hash ) {
396                 $obj->$field( $hash{$field} ) if ($obj->$field ne $hash{$field});
397                 $log->debug("Setting field $field on $obj to $hash{$field}",INTERNAL);
398         }
399
400         if ($class->find_column( 'last_xact_id' ) and $obj->is_changed) {
401                 my ($xact_id) = OpenILS::Application::Storage->method_lookup('open-ils.storage.transaction.current')->run();
402                 throw Error ("Updating $class requires a transaction be established")
403                         unless ($xact_id);
404                 throw Error ("The row you are attempting to delete has been changed since you read it")
405                         unless ( $fm->last_xact_id eq $obj->last_xact_id);
406                 $obj->last_xact_id( $xact_id );
407         } else {
408                 $obj->autoupdate($au)
409         }
410
411         return $obj;
412 }
413
414
415
416         #-------------------------------------------------------------------------------
417         actor::user->has_a( home_ou => 'actor::org_unit' );
418         actor::user->has_a( card => 'actor::card' );
419         actor::user->has_a( standing => 'config::standing' );
420         actor::user->has_a( profile => 'actor::profile' );
421         actor::user->has_a( mailing_address => 'actor::user_address' );
422         actor::user->has_a( billing_address => 'actor::user_address' );
423         actor::user->has_a( ident_type => 'config::identification_type' );
424         actor::user->has_a( ident_type2 => 'config::identification_type' );
425         actor::user->has_a( net_access_level => 'config::net_access_level' );
426
427         actor::user_address->has_a( usr => 'actor::user' );
428         
429         actor::card->has_a( usr => 'actor::user' );
430         
431         actor::org_unit->has_a( parent_ou => 'actor::org_unit' );
432         actor::org_unit->has_a( ou_type => 'actor::org_unit_type' );
433         #actor::org_unit->has_a( address => 'actor::org_address' );
434
435         actor::stat_cat_entry->has_a( stat_cat => 'actor::stat_cat' );
436         actor::stat_cat->has_many( entries => 'actor::stat_cat_entry' );
437         actor::stat_cat_entry_user_map->has_a( stat_cat => 'actor::stat_cat' );
438         actor::stat_cat_entry_user_map->has_a( stat_cat_entry => 'actor::stat_cat_entry' );
439         actor::stat_cat_entry_user_map->has_a( target_usr => 'actor::user' );
440
441         asset::stat_cat_entry->has_a( stat_cat => 'asset::stat_cat' );
442         asset::stat_cat->has_many( entries => 'asset::stat_cat_entry' );
443         asset::stat_cat_entry_copy_map->has_a( stat_cat => 'asset::stat_cat' );
444         asset::stat_cat_entry_copy_map->has_a( stat_cat_entry => 'asset::stat_cat_entry' );
445         asset::stat_cat_entry_copy_map->has_a( owning_copy => 'asset::copy' );
446
447         action::survey_response->has_a( usr => 'actor::user' );
448         action::survey_response->has_a( survey => 'action::survey' );
449         action::survey_response->has_a( question => 'action::survey_question' );
450         action::survey_response->has_a( answer => 'action::survey_answer' );
451
452         action::survey_question->has_a( survey => 'action::survey' );
453
454         action::survey_answer->has_a( question => 'action::survey' );
455
456         asset::copy_note->has_a( owning_copy => 'asset::copy' );
457
458         actor::user->has_many( stat_cat_entries => [ 'actor::stat_cat_entry_user_map' => 'stat_cat_entry' ] );
459         actor::user->has_many( stat_cat_entry_user_maps => 'actor::stat_cat_entry_user_map' );
460
461         asset::copy->has_many( stat_cat_entries => [ 'asset::stat_cat_entry_copy_map' => 'stat_cat_entry' ] );
462         asset::copy->has_many( stat_cat_entry_copy_maps => 'asset::stat_cat_entry_copy_map' );
463
464         asset::copy->has_a( call_number => 'asset::call_number' );
465         asset::copy->has_a( creator => 'actor::user' );
466         asset::copy->has_a( editor => 'actor::user' );
467         asset::copy->has_a( status => 'config::copy_status' );
468         asset::copy->has_a( location => 'asset::copy_location' );
469         asset::copy->has_a( circ_lib => 'actor::org_unit' );
470
471         asset::call_number_note->has_a( call_number => 'asset::call_number' );
472
473         asset::call_number->has_a( record => 'biblio::record_entry' );
474         asset::call_number->has_a( creator => 'actor::user' );
475         asset::call_number->has_a( editor => 'actor::user' );
476
477         authority::record_note->has_a( record => 'authority::record_entry' );
478         biblio::record_note->has_a( record => 'biblio::record_entry' );
479         
480         authority::record_entry->has_a( creator => 'actor::user' );
481         authority::record_entry->has_a( editor => 'actor::user' );
482         biblio::record_entry->has_a( creator => 'actor::user' );
483         biblio::record_entry->has_a( editor => 'actor::user' );
484         
485         metabib::metarecord->has_a( master_record => 'biblio::record_entry' );
486         
487         authority::record_descriptor->has_a( record => 'authority::record_entry' );
488         metabib::record_descriptor->has_a( record => 'biblio::record_entry' );
489         
490         authority::full_rec->has_a( record => 'authority::record_entry' );
491         metabib::full_rec->has_a( record => 'biblio::record_entry' );
492         
493         metabib::title_field_entry->has_a( source => 'biblio::record_entry' );
494         metabib::title_field_entry->has_a( field => 'config::metabib_field' );
495         
496         metabib::author_field_entry->has_a( source => 'biblio::record_entry' );
497         metabib::author_field_entry->has_a( field => 'config::metabib_field' );
498         
499         metabib::subject_field_entry->has_a( source => 'biblio::record_entry' );
500         metabib::subject_field_entry->has_a( field => 'config::metabib_field' );
501         
502         metabib::keyword_field_entry->has_a( source => 'biblio::record_entry' );
503         metabib::keyword_field_entry->has_a( field => 'config::metabib_field' );
504         
505         metabib::series_field_entry->has_a( source => 'biblio::record_entry' );
506         metabib::series_field_entry->has_a( field => 'config::metabib_field' );
507         
508         metabib::metarecord_source_map->has_a( metarecord => 'metabib::metarecord' );
509         metabib::metarecord_source_map->has_a( source => 'biblio::record_entry' );
510
511         action::circulation->has_a( usr => 'actor::user' );
512         action::circulation->has_a( target_copy => 'asset::copy' );
513         action::circulation->has_a( circ_lib => 'actor::org_unit' );
514
515         money::billable_transaction->has_a( usr => 'actor::user' );
516         
517         
518         #-------------------------------------------------------------------------------
519         actor::user->has_many( survey_responses => 'action::survey_response' );
520         actor::user->has_many( addresses => 'actor::user_address' );
521         actor::user->has_many( cards => 'actor::card' );
522
523         actor::org_unit->has_many( users => 'actor::user' );
524         actor::profile->has_many( users => 'actor::user' );
525
526         action::survey->has_many( questions => 'action::survey_question' );
527         action::survey->has_many( responses => 'action::survey_response' );
528         
529         action::survey_question->has_many( answers => 'action::survey_answer' );
530         action::survey_question->has_many( responses => 'action::survey_response' );
531
532         action::survey_answer->has_many( responses => 'action::survey_response' );
533
534         asset::copy->has_many( notes => 'asset::copy_note' );
535         asset::call_number->has_many( copies => 'asset::copy' );
536         asset::call_number->has_many( notes => 'asset::call_number_note' );
537
538         authority::record_entry->has_many( record_descriptor => 'authority::record_descriptor' );
539         authority::record_entry->has_many( notes => 'authority::record_note' );
540
541         biblio::record_entry->has_many( record_descriptor => 'metabib::record_descriptor' );
542         biblio::record_entry->has_many( notes => 'biblio::record_note' );
543         biblio::record_entry->has_many( call_numbers => 'asset::call_number' );
544         biblio::record_entry->has_many( full_record_entries => 'metabib::full_rec' );
545         biblio::record_entry->has_many( title_field_entries => 'metabib::title_field_entry' );
546         biblio::record_entry->has_many( author_field_entries => 'metabib::author_field_entry' );
547         biblio::record_entry->has_many( subject_field_entries => 'metabib::subject_field_entry' );
548         biblio::record_entry->has_many( keyword_field_entries => 'metabib::keyword_field_entry' );
549         biblio::record_entry->has_many( series_field_entries => 'metabib::series_field_entry' );
550
551         metabib::metarecord->has_many( source_records => [ 'metabib::metarecord_source_map' => 'source'] );
552
553         money::billable_transaction->has_many( billings => 'money::billing' );
554         money::billable_transaction->has_many( payments => 'money::payment' );
555
556         money::grocery->has_many( billings => 'money::billing' );
557         money::grocery->has_many( payments => 'money::payment' );
558
559         money::billing->has_a( xact => 'money::billable_transaction' );
560         money::payment->has_a( xact => 'money::billable_transaction' );
561
562         money::cash_payment->has_a( xact => 'money::billable_transaction' );
563         money::cash_payment->has_a( accepting_usr => 'actor::user' );
564
565         money::check_payment->has_a( xact => 'money::billable_transaction' );
566         money::check_payment->has_a( accepting_usr => 'actor::user' );
567
568         money::credit_card_payment->has_a( xact => 'money::billable_transaction' );
569         money::credit_card_payment->has_a( accepting_usr => 'actor::user' );
570
571         money::forgive_payment->has_a( xact => 'money::billable_transaction' );
572         money::forgive_payment->has_a( accepting_usr => 'actor::user' );
573
574         money::work_payment->has_a( xact => 'money::billable_transaction' );
575         money::work_payment->has_a( accepting_usr => 'actor::user' );
576
577         money::credit_payment->has_a( xact => 'money::billable_transaction' );
578         money::credit_payment->has_a( accepting_usr => 'actor::user' );
579
580         permission::grp_tree->has_a( parent => 'permission::grp_tree' );
581
582         permission::grp_perm_map->has_a( grp => 'permission::grp_tree' );
583         permission::grp_perm_map->has_a(  perm => 'permission::perm_list' );
584         permission::grp_perm_map->has_a(  depth => 'actor::org_unit_type' );
585         
586         permission::usr_perm_map->has_a( usr => 'actor::user' );
587         permission::usr_perm_map->has_a(  perm => 'permission::perm_list' );
588         permission::usr_perm_map->has_a(  depth => 'actor::org_unit_type' );
589         
590         permission::usr_grp_map->has_a(  usr => 'actor::user' );
591         permission::usr_grp_map->has_a(  grp => 'permission::grp_tree' );
592
593         action::hold_notification->has_a(  hold => 'action::hold_request' );
594         
595         action::hold_copy_map->has_a(  hold => 'action::hold_request' );
596         action::hold_copy_map->has_a(  target_copy => 'asset::copy' );
597
598         action::hold_request->has_a(  current_copy => 'asset::copy' );
599         action::hold_request->has_a(  requestor => 'actor::user' );
600         action::hold_request->has_a(  usr => 'actor::user' );
601         action::hold_request->has_a(  pickup_lib => 'actor::org_unit' );
602
603         action::hold_request->has_many(  notifications => 'action::hold_notification' );
604         action::hold_request->has_many(  copy_maps => 'action::hold_copy_map' );
605
606         asset::copy->has_many(  hold_maps => 'action::hold_copy_map' );
607
608 1;