use namespace::clean w/ Try::Tiny
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / InterBase.pm
1 package DBIx::Class::Storage::DBI::InterBase;
2
3 use strict;
4 use warnings;
5 use base qw/DBIx::Class::Storage::DBI/;
6 use mro 'c3';
7 use List::Util();
8 use Try::Tiny;
9 use namespace::clean;
10
11 =head1 NAME
12
13 DBIx::Class::Storage::DBI::InterBase - Driver for the Firebird RDBMS
14
15 =head1 DESCRIPTION
16
17 This class implements autoincrements for Firebird using C<RETURNING> as well as
18 L<auto_nextval|DBIx::Class::ResultSource/auto_nextval> sets the limit dialect to
19 C<FIRST X SKIP X> and provides L<DBIx::Class::InflateColumn::DateTime> support.
20
21 You need to use either the
22 L<disable_sth_caching|DBIx::Class::Storage::DBI/disable_sth_caching> option or
23 L</connect_call_use_softcommit> (see L</CAVEATS>) for your code to function
24 correctly with this driver. Otherwise you will likely get bizarre error messages
25 such as C<no statement executing>. The alternative is to use the
26 L<ODBC|DBIx::Class::Storage::DBI::ODBC::Firebird> driver, which is more suitable
27 for long running processes such as under L<Catalyst>.
28
29 To turn on L<DBIx::Class::InflateColumn::DateTime> support, see
30 L</connect_call_datetime_setup>.
31
32 =cut
33
34 sub _supports_insert_returning { 1 }
35
36 sub _sequence_fetch {
37   my ($self, $nextval, $sequence) = @_;
38
39   if ($nextval ne 'nextval') {
40     $self->throw_exception("Can only fetch 'nextval' for a sequence");
41   }
42
43   $self->throw_exception('No sequence to fetch') unless $sequence;
44
45   my ($val) = $self->_get_dbh->selectrow_array(
46 'SELECT GEN_ID(' . $self->sql_maker->_quote($sequence) .
47 ', 1) FROM rdb$database');
48
49   return $val;
50 }
51
52 sub _dbh_get_autoinc_seq {
53   my ($self, $dbh, $source, $col) = @_;
54
55   my $table_name = $source->from;
56   $table_name    = $$table_name if ref $table_name;
57   $table_name    = $self->sql_maker->quote_char ? $table_name : uc($table_name);
58
59   local $dbh->{LongReadLen} = 100000;
60   local $dbh->{LongTruncOk} = 1;
61
62   my $sth = $dbh->prepare(<<'EOF');
63 SELECT t.rdb$trigger_source
64 FROM rdb$triggers t
65 WHERE t.rdb$relation_name = ?
66 AND t.rdb$system_flag = 0 -- user defined
67 AND t.rdb$trigger_type = 1 -- BEFORE INSERT
68 EOF
69   $sth->execute($table_name);
70
71   while (my ($trigger) = $sth->fetchrow_array) {
72     my @trig_cols = map {
73       /^"([^"]+)/ ? $1 : uc($1)
74     } $trigger =~ /new\.("?\w+"?)/ig;
75
76     my ($quoted, $generator) = $trigger =~
77 /(?:gen_id\s* \( \s* |next \s* value \s* for \s*)(")?(\w+)/ix;
78
79     if ($generator) {
80       $generator = uc $generator unless $quoted;
81
82       return $generator
83         if List::Util::first {
84           $self->sql_maker->quote_char ? ($_ eq $col) : (uc($_) eq uc($col))
85         } @trig_cols;
86     }
87   }
88
89   return undef;
90 }
91
92 # this sub stolen from DB2
93
94 sub _sql_maker_opts {
95   my ( $self, $opts ) = @_;
96
97   if ( $opts ) {
98     $self->{_sql_maker_opts} = { %$opts };
99   }
100
101   return { limit_dialect => 'FirstSkip', %{$self->{_sql_maker_opts}||{}} };
102 }
103
104 sub _svp_begin {
105     my ($self, $name) = @_;
106
107     $self->_get_dbh->do("SAVEPOINT $name");
108 }
109
110 sub _svp_release {
111     my ($self, $name) = @_;
112
113     $self->_get_dbh->do("RELEASE SAVEPOINT $name");
114 }
115
116 sub _svp_rollback {
117     my ($self, $name) = @_;
118
119     $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
120 }
121
122 sub _ping {
123   my $self = shift;
124
125   my $dbh = $self->_dbh or return 0;
126
127   local $dbh->{RaiseError} = 1;
128   local $dbh->{PrintError} = 0;
129
130   return try {
131     $dbh->do('select 1 from rdb$database');
132     1;
133   } catch {
134     0;
135   };
136 }
137
138 # We want dialect 3 for new features and quoting to work, DBD::InterBase uses
139 # dialect 1 (interbase compat) by default.
140 sub _init {
141   my $self = shift;
142   $self->_set_sql_dialect(3);
143 }
144
145 sub _set_sql_dialect {
146   my $self = shift;
147   my $val  = shift || 3;
148
149   my $dsn = $self->_dbi_connect_info->[0];
150
151   return if ref($dsn) eq 'CODE';
152
153   if ($dsn !~ /ib_dialect=/) {
154     $self->_dbi_connect_info->[0] = "$dsn;ib_dialect=$val";
155     my $connected = defined $self->_dbh;
156     $self->disconnect;
157     $self->ensure_connected if $connected;
158   }
159 }
160
161 sub _get_server_version {
162   my $self = shift;
163
164   return $self->next::method(@_) if ref $self ne __PACKAGE__;
165
166   local $SIG{__WARN__} = sub {}; # silence warning due to bug in DBD::InterBase
167
168   return $self->next::method(@_);
169 }
170
171 =head2 connect_call_use_softcommit
172
173 Used as:
174
175   on_connect_call => 'use_softcommit'
176
177 In L<connect_info|DBIx::Class::Storage::DBI/connect_info> to set the
178 L<DBD::InterBase> C<ib_softcommit> option.
179
180 You need either this option or C<< disable_sth_caching => 1 >> for
181 L<DBIx::Class> code to function correctly (otherwise you may get C<no statement
182 executing> errors.) Or use the L<ODBC|DBIx::Class::Storage::DBI::ODBC::Firebird>
183 driver.
184
185 The downside of using this option is that your process will B<NOT> see UPDATEs,
186 INSERTs and DELETEs from other processes for already open statements.
187
188 =cut
189
190 sub connect_call_use_softcommit {
191   my $self = shift;
192
193   $self->_dbh->{ib_softcommit} = 1;
194 }
195
196 =head2 connect_call_datetime_setup
197
198 Used as:
199
200   on_connect_call => 'datetime_setup'
201
202 In L<connect_info|DBIx::Class::Storage::DBI/connect_info> to set the date and
203 timestamp formats using:
204
205   $dbh->{ib_time_all} = 'ISO';
206
207 See L<DBD::InterBase> for more details.
208
209 The C<TIMESTAMP> data type supports up to 4 digits after the decimal point for
210 second precision. The full precision is used.
211
212 The C<DATE> data type stores the date portion only, and it B<MUST> be declared
213 with:
214
215   data_type => 'date'
216
217 in your Result class.
218
219 Timestamp columns can be declared with either C<datetime> or C<timestamp>.
220
221 You will need the L<DateTime::Format::Strptime> module for inflation to work.
222
223 For L<DBIx::Class::Storage::DBI::ODBC::Firebird>, this is a noop and sub-second
224 precision is not currently available.
225
226 =cut
227
228 sub connect_call_datetime_setup {
229   my $self = shift;
230
231   $self->_get_dbh->{ib_time_all} = 'ISO';
232 }
233
234 sub datetime_parser_type {
235   'DBIx::Class::Storage::DBI::InterBase::DateTime::Format'
236 }
237
238 package # hide from PAUSE
239   DBIx::Class::Storage::DBI::InterBase::DateTime::Format;
240
241 my $timestamp_format = '%Y-%m-%d %H:%M:%S.%4N'; # %F %T
242 my $date_format      = '%Y-%m-%d';
243
244 my ($timestamp_parser, $date_parser);
245
246 sub parse_datetime {
247   shift;
248   require DateTime::Format::Strptime;
249   $timestamp_parser ||= DateTime::Format::Strptime->new(
250     pattern  => $timestamp_format,
251     on_error => 'croak',
252   );
253   return $timestamp_parser->parse_datetime(shift);
254 }
255
256 sub format_datetime {
257   shift;
258   require DateTime::Format::Strptime;
259   $timestamp_parser ||= DateTime::Format::Strptime->new(
260     pattern  => $timestamp_format,
261     on_error => 'croak',
262   );
263   return $timestamp_parser->format_datetime(shift);
264 }
265
266 sub parse_date {
267   shift;
268   require DateTime::Format::Strptime;
269   $date_parser ||= DateTime::Format::Strptime->new(
270     pattern  => $date_format,
271     on_error => 'croak',
272   );
273   return $date_parser->parse_datetime(shift);
274 }
275
276 sub format_date {
277   shift;
278   require DateTime::Format::Strptime;
279   $date_parser ||= DateTime::Format::Strptime->new(
280     pattern  => $date_format,
281     on_error => 'croak',
282   );
283   return $date_parser->format_datetime(shift);
284 }
285
286 1;
287
288 =head1 CAVEATS
289
290 =over 4
291
292 =item *
293
294 with L</connect_call_use_softcommit>, you will not be able to see changes made
295 to data in other processes. If this is an issue, use
296 L<disable_sth_caching|DBIx::Class::Storage::DBI/disable_sth_caching> as a
297 workaround for the C<no statement executing> errors, this of course adversely
298 affects performance.
299
300 Alternately, use the L<ODBC|DBIx::Class::Storage::DBI::ODBC::Firebird> driver.
301
302 =item *
303
304 C<last_insert_id> support by default only works for Firebird versions 2 or
305 greater, L<auto_nextval|DBIx::Class::ResultSource/auto_nextval> however should
306 work with earlier versions.
307
308 =item *
309
310 Sub-second precision for TIMESTAMPs is not currently available when using the
311 L<ODBC|DBIx::Class::Storage::DBI::ODBC::Firebird> driver.
312
313 =back
314
315 =head1 AUTHOR
316
317 See L<DBIx::Class/AUTHOR> and L<DBIx::Class/CONTRIBUTORS>.
318
319 =head1 LICENSE
320
321 You may distribute this code under the same terms as Perl itself.
322
323 =cut