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