File Coverage

blib/lib/Jifty/DBI/Handle/Pg.pm
Criterion Covered Total %
statement 33 100 33.0
branch 11 34 32.3
condition 8 23 34.7
subroutine 5 13 38.4
pod 8 8 100.0
total 65 178 36.5


line stmt bran cond sub pod time code
1             package Jifty::DBI::Handle::Pg;
2 2     2   14256 use strict;
  2         3  
  2         70  
3              
4 2     2   9 use vars qw($VERSION @ISA $DBIHandle $DEBUG);
  2         2  
  2         119  
5 2     2   8 use base qw(Jifty::DBI::Handle);
  2         2  
  2         485  
6              
7 2     2   8 use strict;
  2         2  
  2         1771  
8              
9             =head1 NAME
10              
11             Jifty::DBI::Handle::Pg - A Postgres specific Handle object
12              
13             =head1 SYNOPSIS
14              
15              
16             =head1 DESCRIPTION
17              
18             This module provides a subclass of L that
19             compensates for some of the idiosyncrasies of Postgres.
20              
21             =head1 METHODS
22              
23             =cut
24              
25             =head2 connect
26              
27             connect takes a hashref and passes it off to SUPER::connect; Forces
28             the timezone to GMT, returns a database handle.
29              
30             =cut
31              
32             sub connect {
33 0     0 1 0 my $self = shift;
34              
35 0         0 $self->SUPER::connect(@_);
36 0         0 $self->simple_query("SET TIME ZONE 'GMT'");
37 0         0 $self->simple_query("SET DATESTYLE TO 'ISO'");
38 0         0 $self->auto_commit(1);
39 0         0 return ($DBIHandle);
40             }
41              
42             =head2 insert
43              
44             Takes a table name as the first argument and assumes that the rest of
45             the arguments are an array of key-value pairs to be inserted.
46              
47             In case of insert failure, returns a L object
48             preloaded with error info
49              
50             =cut
51              
52             sub insert {
53 0     0 1 0 my $self = shift;
54 0         0 my $table = shift;
55 0         0 my %args = (@_);
56 0         0 my $sth = $self->SUPER::insert( $table, %args );
57              
58 0 0       0 unless ($sth) {
59 0         0 return ($sth);
60             }
61              
62 0 0 0     0 if ( $args{'id'} || $args{'Id'} ) {
63 0   0     0 $self->{'id'} = $args{'id'} || $args{'Id'};
64 0         0 return ( $self->{'id'} );
65             }
66              
67 0         0 my $sequence_name = $self->id_sequence_name($table);
68 0 0       0 unless ($sequence_name) { return ($sequence_name) } # Class::ReturnValue
  0         0  
69 0         0 my $seqsth = $self->dbh->prepare(
70             qq{SELECT CURRVAL('} . $sequence_name . qq{')} );
71 0         0 $seqsth->execute;
72 0         0 $self->{'id'} = $seqsth->fetchrow_array();
73              
74 0         0 return ( $self->{'id'} );
75             }
76              
77             =head2 id_sequence_name TABLE
78              
79             Takes a TABLE name and returns the name of the sequence of the primary key for that table.
80              
81             =cut
82              
83             sub id_sequence_name {
84 0     0 1 0 my $self = shift;
85 0         0 my $table = shift;
86              
87 0 0       0 return $self->{'_sequences'}{$table}
88             if ( exists $self->{'_sequences'}{$table} );
89              
90             #Lets get the id of that row we just inserted
91 0         0 my $seq;
92 0         0 my $colinfosth = $self->dbh->column_info( undef, undef, lc($table), '%' );
93 0         0 while ( my $foo = $colinfosth->fetchrow_hashref ) {
94              
95             # Regexp from DBIx::Class's Pg handle. Thanks to Marcus Ramberg
96 0 0 0     0 if ( defined $foo->{'COLUMN_DEF'}
97             && $foo->{'COLUMN_DEF'}
98             =~ m!^nextval\(+'"?([^"']+)"?'(::(?:text|regclass)\))+!i )
99             {
100 0         0 return $self->{'_sequences'}{$table} = $1;
101             }
102              
103             }
104 0         0 my $ret = Class::ReturnValue->new();
105 0         0 $ret->as_error(
106             errno => '-1',
107             message => "Found no sequence for $table",
108             do_backtrace => undef
109             );
110 0         0 return ( $ret->return_value );
111              
112             }
113              
114             =head2 blob_params column_NAME column_type
115              
116             Returns a hash ref for the bind_param call to identify BLOB types used
117             by the current database for a particular column type. The current
118             Postgres implementation only supports BYTEA types.
119              
120             =cut
121              
122             sub blob_params {
123 0     0 1 0 my $self = shift;
124 0         0 my $name = shift;
125 0         0 my $type = shift;
126              
127             # Don't assign to key 'value' as it is defined later.
128 0 0       0 return ( { pg_type => DBD::Pg::PG_BYTEA() } )
129             if $type =~ /^(?:blob|bytea)$/;
130 0         0 return ( {} );
131             }
132              
133             =head2 apply_limits STATEMENTREF ROWS_PER_PAGE FIRST_ROW
134              
135             takes an SQL SELECT statement and massages it to return ROWS_PER_PAGE
136             starting with FIRST_ROW;
137              
138             =cut
139              
140             sub apply_limits {
141 0     0 1 0 my $self = shift;
142 0         0 my $statementref = shift;
143 0         0 my $per_page = shift;
144 0         0 my $first = shift;
145              
146 0         0 my $limit_clause = '';
147              
148 0 0       0 if ($per_page) {
149 0         0 $limit_clause = " LIMIT ";
150 0         0 $limit_clause .= $per_page;
151 0 0 0     0 if ( $first && $first != 0 ) {
152 0         0 $limit_clause .= " OFFSET $first";
153             }
154             }
155              
156 0         0 $$statementref .= $limit_clause;
157              
158             }
159              
160             =head2 _make_clause_case_insensitive column operator VALUE
161              
162             Takes a column, operator and value. performs the magic necessary to make
163             your database treat this clause as case insensitive.
164              
165             Returns a column operator value triple.
166              
167             =cut
168              
169             sub _make_clause_case_insensitive {
170 0     0   0 my $self = shift;
171 0         0 my $column = shift;
172 0         0 my $operator = shift;
173 0         0 my $value = shift;
174              
175 0 0       0 if ( $self->_case_insensitivity_valid( $column, $operator, $value ) ) {
176 0         0 $column = "LOWER($column)";
177 0 0 0     0 if ( $operator =~ /^(IN|=)$/i and ref($value) eq 'ARRAY' ) {
178 0         0 $value = [ map {"LOWER($_)"} @$value ];
  0         0  
179             } else {
180 0         0 $value = "LOWER($value)";
181             }
182             }
183 0         0 return ( $column, $operator, $value );
184             }
185              
186             =head2 distinct_query STATEMENTREF
187              
188             takes an incomplete SQL SELECT statement and massages it to return a DISTINCT result set.
189              
190             =cut
191              
192             sub distinct_query {
193 1     1 1 41 my $self = shift;
194 1         2 my $statementref = shift;
195 1         1 my $collection = shift;
196 1         4 my $table = $collection->table;
197              
198 1 100 100     3 if (grep {
  7 50       29  
199 1         7 ( defined $_->{'alias'} and $_->{'alias'} ne 'main' )
200             || defined $_->{'function'}
201             } @{ $collection->order_by }
202             )
203             {
204              
205             # If we are ordering by something not in 'main', we need to GROUP
206             # BY and adjust the ORDER_BY accordingly
207 1 50       7 local $collection->{group_by}
208 1         1 = [ @{ $collection->{group_by} || [] }, { column => 'id' } ];
209 7   100     14 local $collection->{order_by} = [
210             map {
211 1         2 my $alias = $_->{alias} || '';
212 7         7 my $column = $_->{column};
213 7         3 my $order = $_->{order};
214 7 50       15 if ($column =~ /\W/) {
215 0         0 warn "Possible SQL injection in column '$column' in order_by\n";
216 0         0 next;
217             }
218 7 100       9 $alias .= '.' if $alias;
219              
220 5         35 ( ( !$alias or $alias eq 'main.' ) and $column eq 'id' )
221             ? $_
222 7 100 100     24 : { %{$_}, column => undef, function => ($order =~ /^des/i ? 'MAX':'MIN'). "($alias$column)" }
    100          
223 1         2 } @{ $collection->{order_by} }
224             ];
225 1         7 my $group = $collection->_group_clause;
226 1         5 my $order = $collection->_order_clause;
227 1         3 $$statementref
228             = "SELECT "
229             . $collection->query_columns
230             . " FROM ( SELECT main.id FROM $$statementref $group $order ) distinctquery, $table main WHERE (main.id = distinctquery.id)";
231             } else {
232 0           $$statementref
233             = "SELECT DISTINCT "
234             . $collection->query_columns
235             . " FROM $$statementref";
236 0           $$statementref .= $collection->_group_clause;
237 0           $$statementref .= $collection->_order_clause;
238             }
239             }
240              
241             =head2 canonical_true
242              
243             The canonical true value in Postgres is 't'.
244              
245             =cut
246              
247 0     0 1   sub canonical_true { 't' }
248              
249             =head2 canonical_false
250              
251             The canonical false value in Postgres is 'f'.
252              
253             =cut
254              
255 0     0 1   sub canonical_false { 'f' }
256              
257             1;
258              
259             __END__