File Coverage

blib/lib/Search/InvertedIndex/DB/Pg.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Search::InvertedIndex::DB::Pg;
2              
3 1     1   907 use strict;
  1         2  
  1         36  
4 1     1   4 use vars qw( $VERSION );
  1         1  
  1         48  
5             $VERSION = '0.02';
6              
7 1     1   5 use Carp "croak";
  1         1  
  1         56  
8 1     1   2223 use DBI;
  1         17819  
  1         72  
9 1     1   449 use DBD::Pg qw(:pg_types);
  0            
  0            
10              
11             =head1 NAME
12              
13             Search::InvertedIndex::DB::Pg - A Postgres backend for Search::InvertedIndex.
14              
15             =head1 SYNOPSIS
16              
17             use Search::InvertedIndex;
18             use Search::InvertedIndex::DB::Pg;
19              
20             my $db = Search::InvertedIndex::DB::Pg->new(
21             -db_name => "testdb",
22             -hostname => "test.example.com",
23             -port => 5432,
24             -username => "testuser",
25             -password => "testpass",
26             -table_name => "siindex",
27             -lock_mode => "EX",
28             );
29              
30             my $map = Search::InvertedIndex->new( -database => $db );
31              
32             =head1 DESCRIPTION
33              
34             An interface allowing L to store and retrieve
35             data from a PostgreSQL database. All the data is stored in a single
36             table, which will be created automatically if it does not exist when
37             C is called.
38              
39             =head1 METHODS
40              
41             =over 4
42              
43             =item B
44              
45             my $db = Search::InvertedIndex::DB::Pg->new(
46             -db_name => "testdb",
47             -hostname => "test.example.com",
48             -port => 5432,
49             -username => "testuser",
50             -password => "testpass",
51             -table_name => "siindex",
52             -lock_mode => "EX",
53             );
54              
55             C<-db_name> and C<-table_name> are mandatory. C<-lock_mode> defaults to C.
56             C<-port is optional> and defaults to not being specified..
57              
58             =cut
59              
60             sub new {
61             my ($class, %args) = @_;
62              
63             my $self = bless {}, $class;
64              
65             foreach my $required ( qw( -db_name -table_name ) ) {
66             croak "No $required supplied" unless $args{$required};
67             }
68             $args{-lock_mode} ||= "EX";
69              
70             foreach my $param ( qw( -db_name -hostname -port -username -password
71             -table_name -lock_mode ) ) {
72             $self->{$param} = $args{$param};
73             }
74              
75             return $self;
76             }
77              
78             =item B
79              
80             $db->open;
81              
82             Opens the database in the mode specified when C was called.
83             Croaks on error, returns true otherwise. Trying to open a nonexistent
84             database/table combination in C mode is considered to be an error.
85             Opening an already-open database/table combination isn't.
86              
87             =cut
88              
89             sub open {
90             my $self = shift;
91             my $db_name = $self->{-db_name};
92             my $hostname = $self->{-hostname};
93             my $port = $self->{-port};
94             my $username = $self->{-username};
95             my $password = $self->{-password};
96             my $table_name = $self->{-table_name};
97             my $lock_mode = $self->{-lock_mode};
98              
99             my $dsn = "dbi:Pg:dbname=$db_name";
100             $dsn .= ";host=$hostname" if $hostname;
101             $dsn .= ";port=$port" if $port;
102              
103             my $dbh = DBI->connect( $dsn, $username, $password,
104             { AutoCommit => 0 } )#turn off autocommit for speed
105             or croak "Couldn't connect to $db_name: $DBI::errstr";
106              
107             my $sth = $dbh->prepare(
108             "SELECT tablename FROM pg_tables WHERE tablename=?"
109             );
110             $sth->execute( $table_name );
111             my ($exists) = $sth->fetchrow_array;
112             $sth->finish;
113              
114             # If the table doesn't already exist, create it if we're in a suitable
115             # mode, and croak otherwise.
116             unless ( $exists ) {
117             if ( $lock_mode eq "EX" or $lock_mode eq "UN" ) {
118             $dbh->do(
119             "CREATE TABLE $table_name (
120             ii_key character (128),
121             ii_val bytea
122             )"
123             ) or croak $dbh->errstr;
124             $dbh->do(
125             "CREATE UNIQUE INDEX ${table_name}_pkey
126             ON $table_name (ii_key)"
127             ) or croak $dbh->errstr;
128             } else {
129             croak "Tried to open with a lock mode other than 'EX' or 'UN'"
130             . " and table $table_name doesn't exist in $db_name";
131              
132             }
133             }
134              
135             $self->{-db_handle} = $dbh;
136             $self->{-lock_status} = "UN";
137             $self->{-open_status} = 1;
138              
139             $self->lock( -lock_mode => $lock_mode );
140              
141             return 1;
142             }
143              
144             =item B
145              
146             $db->lock( -lock_mode => "EX" );
147              
148             The C<-lock_mode> parameter is required; allowed values are C,
149             C and C. Returns true on success; croaks on error.
150              
151             =cut
152              
153             sub lock {
154             my ($self, %args) = @_;
155              
156             my $db_name = $self->{-db_name};
157             my $dbh = $self->{-db_handle};
158             my $table_name = $self->{-table_name};
159             my $lock_status = $self->{-lock_status};
160              
161             croak "lock() called but database $db_name/table $table_name isn't open"
162             unless $self->status( "-open" );
163              
164             my $new_lock_mode = $args{-lock_mode};
165             return 1 if $new_lock_mode eq $lock_status;
166              
167             if ( $lock_status eq "EX" and $new_lock_mode ne "EX" ) {
168             $dbh->commit; # force a sync when changing to lower lock mode
169             }
170              
171             if ( $new_lock_mode eq "UN" or $new_lock_mode eq "SH"
172             or $new_lock_mode eq "EX" ) {
173             $self->{-lock_status} = $new_lock_mode;
174             } else {
175             croak "Unknown lock_mode '$new_lock_mode' requested";
176             }
177              
178             return 1;
179             }
180              
181             =item B
182              
183             my $opened = $db->status( "-open" );
184             my $lock_mode = $db->status( "-lock_mode" );
185              
186             Allowed requests are C<-open> and C<-lock_mode>. C<-lock_mode> can
187             only be called on an open database. C<-lock> is a synonym for
188             C<-lock_mode>. Croaks if sent an invalid request, or on error.
189              
190             =cut
191              
192             sub status {
193             my ($self, $request) = @_;
194             $request = lc($request);
195              
196             if ( $request eq '-open' ) {
197             return $self->{-open_status};
198             }
199              
200             if ( $request eq '-lock_mode' or $request eq '-lock' ) {
201             if ( $self->{-open_status} ) {
202             return uc($self->{-lock_status});
203             } else {
204             croak "Can't request 'lock_mode' status on an unopened db";
205             }
206             }
207              
208             croak "Invalid status request '$request'";
209             }
210              
211             =item B
212              
213             $db->put( -key => "foo", -value => "bar" );
214              
215             Both parameters are mandatory. Any others will be silently ignored.
216             Returns true on success and false on error.
217              
218             =cut
219              
220             sub put {
221             my $self = shift;
222             my %args = ref $_[0] ? %{ $_[0] } : @_ ;
223             %args = map { lc($_) => $args{$_} } keys %args;
224             $args{-value} = "$args{-value}"; # stringify so can store in a bytea
225              
226             unless ( defined $args{-key} and defined $args{-value} ) {
227             croak "Must supply both a -key and a -value";
228             }
229              
230             my $dbh = $self->{-db_handle};
231             my $old_ac = $dbh->{AutoCommit};
232             $dbh->{AutoCommit} = 0;
233             $dbh->commit;
234             $dbh->do( "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE" );
235             my $key_exists = $self->get( -key => $args{-key} );
236              
237             my $sth;
238             if ( defined $key_exists ) { # 'defined' as 0 is a legal value
239             $sth = $self->{-put_handle_update};
240             unless ($sth) {
241             my $table = $self->{-table_name};
242             $sth = $dbh->prepare(
243             "UPDATE $table SET ii_val=? WHERE ii_key=?"
244             );
245             $self->{-put_handle_update} = $sth;
246             }
247             } else {
248             $sth = $self->{-put_handle_insert};
249             unless ($sth) {
250             my $table = $self->{-table_name};
251             $sth = $dbh->prepare(
252             "INSERT INTO $table (ii_val, ii_key) VALUES(?, ?)"
253             );
254             $self->{-put_handle_insert} = $sth;
255             }
256             }
257              
258             # Use bind_param so nulls etc will be escaped properly.
259             $sth->bind_param( 1, $args{-value}, { pg_type => DBD::Pg::PG_BYTEA } );
260             $sth->bind_param( 2, $args{-key} );
261              
262             my $ok = $sth->execute;
263             $sth->finish;
264             if ( $ok ) {
265             $dbh->commit;
266             $dbh->{AutoCommit} = $old_ac;
267             return 1;
268             } else {
269             $dbh->rollback;
270             $dbh->{AutoCommit} = $old_ac;
271             return 0;
272             }
273             }
274              
275             =item B
276              
277             my $value = $db->get( -key => "foo" );
278              
279             Croaks if no C<-key> supplied.
280              
281             =cut
282              
283             sub get {
284             my $self = shift;
285             my %args = ref $_[0] ? %{ $_[0] } : @_ ;
286             %args = map { lc($_) => $args{$_} } keys %args;
287             croak "Must supply a -key" unless defined $args{-key};
288              
289             my $dbh = $self->{-db_handle};
290             my $sth = $self->{-get_handle};
291              
292             unless ( $sth ) {
293             my $table = $self->{-table_name};
294             $sth = $dbh->prepare("SELECT ii_val FROM $table WHERE ii_key = ?")
295             or return 0;
296             $self->{-get_handle} = $sth;
297             }
298              
299             $sth->execute( $args{-key} );
300             my $value = $sth->fetchrow_array;
301             $sth->finish;
302              
303             return $value;
304             }
305              
306             =item B
307              
308             $db->delete( -key => "foo" );
309              
310             =cut
311              
312             sub delete {
313             my $self = shift;
314             my %args = ref $_[0] ? %{ $_[0] } : @_ ;
315             %args = map { lc($_) => $args{$_} } keys %args;
316             croak "Must supply a -key" unless defined $args{-key};
317              
318             my $dbh = $self->{-db_handle};
319             my $sth = $self->{-del_handle};
320              
321             unless ( $sth ) {
322             my $table = $self->{-table_name};
323             $sth = $dbh->prepare("DELETE FROM $table WHERE ii_key = ?")
324             or return 0;
325             $self->{-del_handle} = $sth;
326             }
327              
328             $sth->execute( $args{-key} ) or return 0;
329             $sth->finish;
330             return 1;
331             }
332              
333             =item B
334              
335             $db->close;
336              
337             =cut
338              
339             sub close {
340             my $self = shift;
341              
342             $self->lock( -lock_mode => 'UN' );
343              
344             my $dbh = $self->{-db_handle};
345             $dbh->disconnect;
346              
347             $self->{-open_status} = 0;
348             $self->{-db_handle} = undef;
349             }
350              
351             =item B
352              
353             $db->clear;
354              
355             Clears out I indexing data.
356              
357             =cut
358              
359             sub clear {
360             my $self = shift;
361             my $dbh = $self->{-db_handle};
362             my $table = $self->{-table_name};
363             $dbh->do("DELETE FROM $table") or return 0;
364             return 1;
365             }
366              
367             sub DESTROY {
368             my $self = shift;
369             $self->close if $self->status( "open" );
370             }
371              
372             =back
373              
374             =head1 AUTHOR
375              
376             Kate L Pugh , based on
377             L by Michael Cramer and
378             L by Benjamin Franz.
379              
380             =head1 COPYRIGHT
381              
382             Copyright (C) 2003-4 Kake Pugh. All Rights Reserved.
383              
384             This module is free software; you can redistribute it and/or modify it
385             under the same terms as Perl itself.
386              
387             =head1 CREDITS
388              
389             Module based on work by Michael Cramer and Benjamin Franz. Patch from
390             Cees Hek.
391              
392             =head1 SEE ALSO
393              
394             L
395              
396             =cut
397              
398             1;