Reduce the number of heavy dbh_do calls
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Pg.pm
1 package DBIx::Class::Storage::DBI::Pg;
2
3 use strict;
4 use warnings;
5
6 use base qw/DBIx::Class::Storage::DBI::MultiColumnIn/;
7 use mro 'c3';
8
9 use DBD::Pg qw(:pg_types);
10
11 # Ask for a DBD::Pg with array support
12 warn "DBD::Pg 2.9.2 or greater is strongly recommended\n"
13   if ($DBD::Pg::VERSION < 2.009002);  # pg uses (used?) version::qv()
14
15 sub with_deferred_fk_checks {
16   my ($self, $sub) = @_;
17
18   $self->_get_dbh->do('SET CONSTRAINTS ALL DEFERRED');
19   $sub->();
20 }
21
22 sub last_insert_id {
23   my ($self,$source,$col) = @_;
24   my $seq = ($source->column_info($col)->{sequence} ||= $self->get_autoinc_seq($source,$col));
25   $self->throw_exception("could not fetch primary key for " . $source->name . ", could not "
26     . "get autoinc sequence for $col (check that table and column specifications are correct "
27     . "and in the correct case)") unless defined $seq;
28
29   $self->_dbh_last_insert_id ($self->_dbh, $seq);
30 }
31
32 # there seems to be absolutely no reason to have this as a separate method,
33 # but leaving intact in case someone is already overriding it
34 sub _dbh_last_insert_id {
35   my ($self, $dbh, $seq) = @_;
36   $dbh->last_insert_id(undef, undef, undef, undef, {sequence => $seq});
37 }
38
39
40 sub _get_pg_search_path {
41     my ($self,$dbh) = @_;
42     # cache the search path as ['schema','schema',...] in the storage
43     # obj
44     $self->{_pg_search_path} ||= do {
45         my @search_path;
46         my ($sp_string) = $dbh->selectrow_array('SHOW search_path');
47         while( $sp_string =~ s/("[^"]+"|[^,]+),?// ) {
48             unless( defined $1 and length $1 ) {
49                 $self->throw_exception("search path sanity check failed: '$1'")
50             }
51             push @search_path, $1;
52         }
53         \@search_path
54     };
55 }
56
57 sub _dbh_get_autoinc_seq {
58   my ($self, $dbh, $schema, $table, @pri) = @_;
59
60   # get the list of postgres schemas to search.  if we have a schema
61   # specified, use that.  otherwise, use the search path
62   my @search_path;
63   if( defined $schema and length $schema ) {
64       @search_path = ( $schema );
65   } else {
66       @search_path = @{ $self->_get_pg_search_path($dbh) };
67   }
68
69   foreach my $search_schema (@search_path) {
70       foreach my $col (@pri) {
71           my $info = $dbh->column_info(undef,$search_schema,$table,$col)->fetchrow_hashref;
72           if($info) {
73               # if we get here, we have definitely found the right
74               # column.
75               if( defined $info->{COLUMN_DEF} and
76                   $info->{COLUMN_DEF}
77                     =~ /^nextval\(+'([^']+)'::(?:text|regclass)\)/i
78                 ) {
79                   my $seq = $1;
80                   return $seq =~ /\./ ? $seq : $info->{TABLE_SCHEM} . "." . $seq;
81               } else {
82                   # we have found the column, but cannot figure out
83                   # the nextval seq
84                   return;
85               }
86           }
87       }
88   }
89   return;
90 }
91
92 sub get_autoinc_seq {
93   my ($self,$source,$col) = @_;
94
95   my @pri = $source->primary_columns;
96
97   my $schema;
98   my $table = $source->name;
99
100   if (ref $table eq 'SCALAR') {
101     $table = $$table;
102   }
103   elsif ($table =~ /^(.+)\.(.+)$/) {
104     ($schema, $table) = ($1, $2);
105   }
106
107   $self->dbh_do('_dbh_get_autoinc_seq', $schema, $table, @pri);
108 }
109
110 sub sqlt_type {
111   return 'PostgreSQL';
112 }
113
114 sub datetime_parser_type { return "DateTime::Format::Pg"; }
115
116 sub bind_attribute_by_data_type {
117   my ($self,$data_type) = @_;
118
119   my $bind_attributes = {
120     bytea => { pg_type => DBD::Pg::PG_BYTEA },
121     blob  => { pg_type => DBD::Pg::PG_BYTEA },
122   };
123
124   if( defined $bind_attributes->{$data_type} ) {
125     return $bind_attributes->{$data_type};
126   }
127   else {
128     return;
129   }
130 }
131
132 sub _sequence_fetch {
133   my ( $self, $type, $seq ) = @_;
134   my ($id) = $self->_get_dbh->selectrow_array("SELECT nextval('${seq}')");
135   return $id;
136 }
137
138 sub _svp_begin {
139     my ($self, $name) = @_;
140
141     $self->_get_dbh->pg_savepoint($name);
142 }
143
144 sub _svp_release {
145     my ($self, $name) = @_;
146
147     $self->_get_dbh->pg_release($name);
148 }
149
150 sub _svp_rollback {
151     my ($self, $name) = @_;
152
153     $self->_get_dbh->pg_rollback_to($name);
154 }
155
156 1;
157
158 =head1 NAME
159
160 DBIx::Class::Storage::DBI::Pg - Automatic primary key class for PostgreSQL
161
162 =head1 SYNOPSIS
163
164   # In your table classes
165   __PACKAGE__->load_components(qw/PK::Auto Core/);
166   __PACKAGE__->set_primary_key('id');
167   __PACKAGE__->sequence('mysequence');
168
169 =head1 DESCRIPTION
170
171 This class implements autoincrements for PostgreSQL.
172
173 =head1 POSTGRESQL SCHEMA SUPPORT
174
175 This supports multiple PostgreSQL schemas, with one caveat: for
176 performance reasons, the schema search path is queried the first time it is
177 needed and CACHED for subsequent uses.
178
179 For this reason, you should do any necessary manipulation of the
180 PostgreSQL search path BEFORE instantiating your schema object, or as
181 part of the on_connect_do option to connect(), for example:
182
183    my $schema = My::Schema->connect
184                   ( $dsn,$user,$pass,
185                     { on_connect_do =>
186                         [ 'SET search_path TO myschema, foo, public' ],
187                     },
188                   );
189
190 =head1 AUTHORS
191
192 See L<DBIx::Class/CONTRIBUTORS>
193
194 =head1 LICENSE
195
196 You may distribute this code under the same terms as Perl itself.
197
198 =cut