]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg.pm
3e29c671a0581edf33ccf84f10abb30a5ea14ca2
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Storage / Driver / Pg.pm
1 { # Based on the change to Class::DBI in OpenILS::Application::Storage.  This will
2   # allow us to use TSearch2 via a simple cdbi "search" interface.
3         #-------------------------------------------------------------------------------
4         use Class::DBI;
5         package Class::DBI;
6
7         sub search_fti {
8                 my $self = shift;
9                 my @args = @_;
10                 if (ref($args[-1]) eq 'HASH') {
11                         $args[-1]->{_placeholder} = "to_tsquery('default',?)";
12                 } else {
13                         push @args, {_placeholder => "to_tsquery('default',?)"};
14                 }
15                 $self->_do_search("@@"  => @args);
16         }
17 }
18
19 { # Every driver needs to provide a 'compile()' method to OpenILS::Application::Storage::FTS.
20   # If that driver wants to support FTI, that is...
21         #-------------------------------------------------------------------------------
22         package OpenILS::Application::Storage::FTS;
23         use OpenSRF::Utils::Logger qw/:level/;
24         my $log = 'OpenSRF::Utils::Logger';
25
26         sub compile {
27                 my $self = shift;
28                 my $term = shift;
29
30                 $self = ref($self) || $self;
31                 $self = bless {} => $self;
32
33                 $self->decompose($term);
34
35                 my $newterm = join('&', $self->words);
36
37                 if (@{$self->nots}) {
38                         $newterm = '('.$newterm.')&('. join('|', $self->nots) . ')';
39                 }
40
41                 $log->debug("Compiled term is [$newterm]", DEBUG);
42                 $newterm = OpenILS::Application::Storage::Driver::Pg->quote($newterm);
43                 $log->debug("Quoted term is [$newterm]", DEBUG);
44
45                 $self->{fts_query} = ["to_tsquery('default',$newterm)"];
46                 $self->{fts_query_nots} = [];
47                 $self->{fts_op} = '@@';
48                 $self->{text_col} = shift;
49                 $self->{fts_col} = shift;
50
51                 return $self;
52         }
53
54         sub sql_where_clause {
55                 my $self = shift;
56                 my $column = $self->fts_col;
57                 my @output;
58         
59                 my @ranks;
60                 for my $fts ( $self->fts_query ) {
61                         push @output, join(' ', $self->fts_col, $self->{fts_op}, $fts);
62                         push @ranks, "rank($column, $fts)";
63                 }
64                 $self->{fts_rank} = \@ranks;
65         
66                 my $phrase_match = $self->sql_exact_phrase_match();
67                 return join(' AND ', @output) . $phrase_match;
68         }
69
70 }
71
72
73 { # The driver package itself just needs a db_Main method (or db_Slaves if
74   #Class::DBI::Replication is in use) for Class::DBI to call.
75   #
76   # Any other fixups can go in here too... Also, the drivers should subclass the
77   # DBI driver that they are wrapping, or provide a 'quote()' method that calls
78   # the DBD::xxx::quote() method on FTI's behalf.
79   #
80   # The dirver MUST be a subclass of Class::DBI(::Replication) and
81   # OpenILS::Application::Storage.
82   #-------------------------------------------------------------------------------
83         package OpenILS::Application::Storage::Driver::Pg;
84         use Class::DBI;
85         use base qw/Class::DBI OpenILS::Application::Storage/;
86         use DBI;
87         use OpenSRF::EX qw/:try/;
88         use OpenSRF::DomainObject::oilsResponse;
89         use OpenSRF::Utils::Logger qw/:level/;
90         my $log = 'OpenSRF::Utils::Logger';
91
92         __PACKAGE__->set_sql( retrieve_limited => 'SELECT * FROM __TABLE__ ORDER BY id LIMIT ?' );
93
94         my $master_db;
95         my @slave_dbs;
96         my $_db_params;
97         sub child_init {
98                 my $self = shift;
99                 $_db_params = shift;
100
101                 $log->debug("Running child_init inside ".__PACKAGE__, INTERNAL);
102
103                 $_db_params = [ $_db_params ] unless (ref($_db_params) eq 'ARRAY');
104
105                 my %attrs = (   %{$self->_default_attributes},
106                                 RootClass => 'DBIx::ContextualFetch',
107                                 ShowErrorStatement => 1,
108                                 RaiseError => 1,
109                                 AutoCommit => 1,
110                                 PrintError => 1,
111                                 Taint => 1,
112                                 pg_enable_utf8 => 1,
113                                 FetchHashKeyName => 'NAME_lc',
114                                 ChopBlanks => 1,
115                 );
116
117                 my $master = shift @$_db_params;
118                 $master_db = DBI->connect("dbi:Pg:host=$$master{host};dbname=$$master{db}",$$master{user},$$master{pw}, \%attrs);
119                 $master_db->do("SET NAMES '$$master{client_encoding}';") if ($$master{client_encoding});
120
121                 $log->debug("Connected to MASTER db '$$master{db} at $$master{host}", INFO);
122                 
123                 for my $db (@$_db_params) {
124                         push @slave_dbs, DBI->connect("dbi:Pg:host=$$db{host};dbname=$$db{db}",$$db{user},$$db{pw}, \%attrs);
125                         $slave_dbs[-1]->do("SET NAMES '$$db{client_encoding}';") if ($$master{client_encoding});
126
127                         $log->debug("Connected to MASTER db '$$master{db} at $$master{host}", INFO);
128                 }
129
130                 $log->debug("All is well on the western front", INTERNAL);
131         }
132
133         sub db_Main {
134                 my $self = shift;
135                 return $master_db if ($self->current_xact_session);
136                 return $master_db unless (@slave_dbs);
137                 return ($master_db, @slave_dbs)[rand(scalar(@slave_dbs))];
138         }
139
140         sub quote {
141                 my $self = shift;
142                 return $self->db_Main->quote(@_)
143         }
144
145 #       sub tsearch2_trigger {
146 #               my $self = shift;
147 #               return unless ($self->value);
148 #               $self->index_vector(
149 #                       $self->db_Slaves->selectrow_array(
150 #                               "SELECT to_tsvector('default',?);",
151 #                               {},
152 #                               $self->value
153 #                       )
154 #               );
155 #       }
156
157         my $_xact_session;
158
159         sub current_xact_session {
160                 my $self = shift;
161                 if (defined($_xact_session)) {
162                         return $_xact_session;
163                 }
164                 return undef;
165         }
166
167         sub current_xact_is_auto {
168                 my $self = shift;
169                 my $auto = shift;
170                 if (defined($_xact_session) and ref($_xact_session)) {
171                         if (defined $auto) {
172                                 $_xact_session->session_data(autocommit => $auto);
173                         }
174                         return $_xact_session->session_data('autocommit'); 
175                 }
176         }
177
178         sub current_xact_id {
179                 my $self = shift;
180                 if (defined($_xact_session) and ref($_xact_session)) {
181                         return $_xact_session->session_id;
182                 }
183                 return undef;
184         }
185
186         sub set_xact_session {
187                 my $self = shift;
188                 my $ses = shift;
189                 if (!defined($ses)) {
190                         return undef;
191                 }
192                 $_xact_session = $ses;
193                 return $_xact_session;
194         }
195
196         sub unset_xact_session {
197                 my $self = shift;
198                 my $ses = $_xact_session;
199                 undef $_xact_session;
200                 return $ses;
201         }
202
203 }
204
205
206 {
207         package OpenILS::Application::Storage;
208         use OpenSRF::Utils::Logger;
209         my $log = 'OpenSRF::Utils::Logger';
210
211         my $pg = 'OpenILS::Application::Storage::Driver::Pg';
212
213         sub pg_begin_xaction {
214                 my $self = shift;
215                 my $client = shift;
216
217                 if (my $old_xact = $pg->current_xact_session) {
218                         if ($pg->current_xact_is_auto) {
219                                 $log->debug("Commiting old autocommit transaction with Open-ILS XACT-ID [$old_xact]", INFO);
220                                 $self->pg_commit_xaction($client);
221                         } else {
222                                 $log->debug("Rolling back old NON-autocommit transaction with Open-ILS XACT-ID [$old_xact]", INFO);
223                                 $self->pg_rollback_xaction($client);
224                                 return new OpenSRF::DomainObject::oilsException (
225                                                 statusCode => 500,
226                                                 status => "Previous transaction rolled back!",
227                                 );
228                         }
229                 }
230                 
231                 $pg->set_xact_session( $client->session );
232                 my $xact_id = $pg->current_xact_id;
233
234                 $log->debug("Beginning a new trasaction with Open-ILS XACT-ID [$xact_id]", INFO);
235
236                 my $dbh = OpenILS::Application::Storage::CDBI->db_Main;
237                 
238                 try {
239                         $dbh->begin_work;
240
241                 } catch Error with {
242                         my $e = shift;
243                         $log->debug("Failed to begin a new trasaction with Open-ILS XACT-ID [$xact_id]: ".$e, INFO);
244                         return $e;
245                 };
246
247
248                 my $death_cb = $client->session->register_callback(
249                         death => sub {
250                                 __PACKAGE__->pg_rollback_xaction;
251                         }
252                 );
253
254                 $log->debug("Registered 'death' callback [$death_cb] for new trasaction with Open-ILS XACT-ID [$xact_id]", DEBUG);
255
256                 $client->session->session_data( death_cb => $death_cb );
257
258                 if ($self->api_name =~ /autocommit$/o) {
259                         $pg->current_xact_is_auto(1);
260                         my $dc_cb = $client->session->register_callback(
261                                 disconnect => sub {
262                                         my $ses = shift;
263                                         $ses->unregister_callback(death => $death_cb);
264                                         __PACKAGE__->pg_commit_xaction;
265                                 }
266                         );
267                         $log->debug("Registered 'disconnect' callback [$dc_cb] for new trasaction with Open-ILS XACT-ID [$xact_id]", DEBUG);
268                         if ($client and $client->session) {
269                                 $client->session->session_data( disconnect_cb => $dc_cb );
270                         }
271                 }
272
273                 return 1;
274
275         }
276         __PACKAGE__->register_method(
277                 method          => 'pg_begin_xaction',
278                 api_name        => 'open-ils.storage.transaction.begin',
279                 api_level       => 1,
280                 argc            => 0,
281         );
282         __PACKAGE__->register_method(
283                 method          => 'pg_begin_xaction',
284                 api_name        => 'open-ils.storage.transaction.begin.autocommit',
285                 api_level       => 1,
286                 argc            => 0,
287         );
288
289         sub pg_commit_xaction {
290                 my $self = shift;
291
292
293                 try {
294                         $log->debug("Committing trasaction with Open-ILS XACT-ID [$xact_id]", INFO);
295                         my $dbh = OpenILS::Application::Storage::CDBI->db_Main;
296                         $dbh->commit;
297
298                 } catch Error with {
299                         my $e = shift;
300                         $log->debug("Failed to commit trasaction with Open-ILS XACT-ID [$xact_id]: ".$e, INFO);
301                         return 0;
302                 };
303                 
304                 $pg->current_xact_session->unregister_callback( death => 
305                         $pg->current_xact_session->session_data( 'death_cb' )
306                 ) if ($pg->current_xact_session);
307
308                 if ($pg->current_xact_is_auto) {
309                         $pg->current_xact_session->unregister_callback( disconnect => 
310                                 $pg->current_xact_session->session_data( 'disconnect_cb' )
311                         );
312                 }
313
314                 $pg->unset_xact_session;
315
316                 return 1;
317                 
318         }
319         __PACKAGE__->register_method(
320                 method          => 'pg_commit_xaction',
321                 api_name        => 'open-ils.storage.transaction.commit',
322                 api_level       => 1,
323                 argc            => 0,
324         );
325
326         sub pg_rollback_xaction {
327                 my $self = shift;
328
329                 my $xact_id = $pg->current_xact_id;
330                 try {
331                         my $dbh = OpenILS::Application::Storage::CDBI->db_Main;
332                         $log->debug("Rolling back a trasaction with Open-ILS XACT-ID [$xact_id]", INFO);
333                         $dbh->rollback;
334
335                 } catch Error with {
336                         my $e = shift;
337                         $log->debug("Failed to roll back trasaction with Open-ILS XACT-ID [$xact_id]: ".$e, INFO);
338                         return 0;
339                 };
340         
341                 $pg->current_xact_session->unregister_callback( death =>
342                         $pg->current_xact_session->session_data( 'death_cb' )
343                 ) if ($pg->current_xact_session);
344
345                 if ($pg->current_xact_is_auto) {
346                         $pg->current_xact_session->unregister_callback( disconnect =>
347                                 $pg->current_xact_session->session_data( 'disconnect_cb' )
348                         );
349                 }
350
351                 $pg->unset_xact_session;
352
353                 return 1;
354         }
355         __PACKAGE__->register_method(
356                 method          => 'pg_rollback_xaction',
357                 api_name        => 'open-ils.storage.transaction.rollback',
358                 api_level       => 1,
359                 argc            => 0,
360         );
361
362 }
363
364 {
365         #---------------------------------------------------------------------
366         package asset::call_number;
367         
368         asset::call_number->table( 'asset.call_number' );
369         asset::call_number->sequence( 'asset.call_number_id_seq' );
370         
371         #---------------------------------------------------------------------
372         package asset::copy;
373         
374         asset::copy->table( 'asset.copy' );
375         asset::copy->sequence( 'asset.copy_id_seq' );
376         
377         #---------------------------------------------------------------------
378         package biblio::record_entry;
379         
380         biblio::record_entry->table( 'biblio.record_entry' );
381         biblio::record_entry->sequence( 'biblio.record_entry_id_seq' );
382
383         #---------------------------------------------------------------------
384         package biblio::record_node;
385         
386         biblio::record_node->table( 'biblio.record_data' );
387         biblio::record_node->sequence( 'biblio.record_data_id_seq' );
388         
389         #---------------------------------------------------------------------
390         package biblio::record_marc;
391         
392         biblio::record_marc->table( 'biblio.record_marc' );
393         biblio::record_marc->sequence( 'biblio.record_marc_id_seq' );
394
395         #---------------------------------------------------------------------
396         package biblio::record_mods;
397         
398         biblio::record_mods->table( 'biblio.record_mods' );
399         biblio::record_mods->sequence( 'biblio.record_mods_id_seq' );
400
401         #---------------------------------------------------------------------
402         package biblio::record_note;
403         
404         biblio::record_note->table( 'biblio.record_note' );
405         biblio::record_note->sequence( 'biblio.record_note_id_seq' );
406         
407         #---------------------------------------------------------------------
408         package actor::user;
409         
410         actor::user->table( 'actor.usr' );
411         actor::user->sequence( 'actor.usr_id_seq' );
412         
413         #---------------------------------------------------------------------
414         package actor::org_unit_type;
415         
416         actor::org_unit_type->table( 'actor.org_unit_type' );
417         actor::org_unit_type->sequence( 'actor.org_unit_type_id_seq' );
418
419         #---------------------------------------------------------------------
420         package actor::org_unit;
421         
422         actor::org_unit_type->table( 'actor.org_unit' );
423         actor::org_unit_type->sequence( 'actor.org_unit_id_seq' );
424
425         #---------------------------------------------------------------------
426
427         #-------------------------------------------------------------------------------
428         package metabib::metarecord;
429
430         metabib::metarecord->table( 'metabib.metarecord' );
431         metabib::metarecord->sequence( 'metabib.metarecord_id_seq' );
432
433         #-------------------------------------------------------------------------------
434
435         #-------------------------------------------------------------------------------
436         package metabib::title_field_entry;
437
438         metabib::title_field_entry->table( 'metabib.title_field_entry' );
439         metabib::title_field_entry->sequence( 'metabib.title_field_entry_id_seq' );
440         metabib::title_field_entry->columns( 'FTS' => 'index_vector' );
441
442 #       metabib::title_field_entry->add_trigger(
443 #               before_create => \&OpenILS::Application::Storage::Driver::Pg::tsearch2_trigger
444 #       );
445 #       metabib::title_field_entry->add_trigger(
446 #               before_update => \&OpenILS::Application::Storage::Driver::Pg::tsearch2_trigger
447 #       );
448
449         #-------------------------------------------------------------------------------
450
451         #-------------------------------------------------------------------------------
452         package metabib::author_field_entry;
453
454         metabib::author_field_entry->table( 'metabib.author_field_entry' );
455         metabib::author_field_entry->sequence( 'metabib.author_field_entry_id_seq' );
456         metabib::author_field_entry->columns( 'FTS' => 'index_vector' );
457
458         #-------------------------------------------------------------------------------
459
460         #-------------------------------------------------------------------------------
461         package metabib::subject_field_entry;
462
463         metabib::subject_field_entry->table( 'metabib.subject_field_entry' );
464         metabib::subject_field_entry->sequence( 'metabib.subject_field_entry_id_seq' );
465         metabib::subject_field_entry->columns( 'FTS' => 'index_vector' );
466
467         #-------------------------------------------------------------------------------
468
469         #-------------------------------------------------------------------------------
470         package metabib::keyword_field_entry;
471
472         metabib::keyword_field_entry->table( 'metabib.keyword_field_entry' );
473         metabib::keyword_field_entry->sequence( 'metabib.keyword_field_entry_id_seq' );
474         metabib::keyword_field_entry->columns( 'FTS' => 'index_vector' );
475
476         #-------------------------------------------------------------------------------
477
478         #-------------------------------------------------------------------------------
479         package metabib::title_field_entry_source_map;
480
481         metabib::title_field_entry_source_map->table( 'metabib.title_field_entry_source_map' );
482
483         #-------------------------------------------------------------------------------
484
485         #-------------------------------------------------------------------------------
486         package metabib::author_field_entry_source_map;
487
488         metabib::author_field_entry_source_map->table( 'metabib.author_field_entry_source_map' );
489
490         #-------------------------------------------------------------------------------
491
492         #-------------------------------------------------------------------------------
493         package metabib::subject_field_entry_source_map;
494
495         metabib::subject_field_entry_source_map->table( 'metabib.subject_field_entry_source_map' );
496
497         #-------------------------------------------------------------------------------
498
499         #-------------------------------------------------------------------------------
500         package metabib::keyword_field_entry_source_map;
501
502         metabib::keyword_field_entry_source_map->table( 'metabib.keyword_field_entry_source_map' );
503
504         #-------------------------------------------------------------------------------
505
506         #-------------------------------------------------------------------------------
507         package metabib::metarecord_source_map;
508
509         metabib::metarecord_source_map->table( 'metabib.full_rec' );
510
511         #-------------------------------------------------------------------------------
512
513         #-------------------------------------------------------------------------------
514         package metabib::full_rec;
515
516         metabib::full_rec->table( 'metabib.full_rec' );
517         metabib::full_rec->sequence( 'metabib.full_rec_id_seq' );
518         metabib::full_rec->columns( 'FTS' => 'index_vector' );
519
520         #-------------------------------------------------------------------------------
521 }
522
523 1;