Skip tests segfaulting with ancient DBD::Sybase versions
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Oracle / Generic.pm
CommitLineData
18360aed 1package DBIx::Class::Storage::DBI::Oracle::Generic;
2
3use strict;
4use warnings;
b7b18f32 5use Scope::Guard ();
6use Context::Preserve ();
ed7ab0f4 7use Try::Tiny;
18360aed 8
7137528d 9=head1 NAME
10
7a84c41b 11DBIx::Class::Storage::DBI::Oracle::Generic - Oracle Support for DBIx::Class
7137528d 12
13=head1 SYNOPSIS
14
d88ecca6 15 # In your result (table) classes
16 use base 'DBIx::Class::Core';
2e46b6eb 17 __PACKAGE__->add_columns({ id => { sequence => 'mysequence', auto_nextval => 1 } });
7137528d 18 __PACKAGE__->set_primary_key('id');
19 __PACKAGE__->sequence('mysequence');
20
21=head1 DESCRIPTION
22
6c0230de 23This class implements base Oracle support. The subclass
24L<DBIx::Class::Storage::DBI::Oracle::WhereJoins> is for C<(+)> joins in Oracle
25versions before 9.
7137528d 26
27=head1 METHODS
28
29=cut
30
db56cf3d 31use base qw/DBIx::Class::Storage::DBI/;
2ad62d97 32use mro 'c3';
18360aed 33
dd2600c6 34sub deployment_statements {
35 my $self = shift;;
36 my ($schema, $type, $version, $dir, $sqltargs, @rest) = @_;
37
38 $sqltargs ||= {};
032b2366 39 my $quote_char = $self->schema->storage->sql_maker->quote_char;
40 $sqltargs->{quote_table_names} = $quote_char ? 1 : 0;
41 $sqltargs->{quote_field_names} = $quote_char ? 1 : 0;
dd2600c6 42
9780718f 43 my $oracle_version = try { $self->_get_dbh->get_info(18) };
a4433d8e 44
45 $sqltargs->{producer_args}{oracle_version} = $oracle_version;
46
38aead8e 47 $self->next::method($schema, $type, $version, $dir, $sqltargs, @rest);
dd2600c6 48}
49
18360aed 50sub _dbh_last_insert_id {
2e46b6eb 51 my ($self, $dbh, $source, @columns) = @_;
52 my @ids = ();
53 foreach my $col (@columns) {
54 my $seq = ($source->column_info($col)->{sequence} ||= $self->get_autoinc_seq($source,$col));
55 my $id = $self->_sequence_fetch( 'currval', $seq );
56 push @ids, $id;
57 }
58 return @ids;
18360aed 59}
60
61sub _dbh_get_autoinc_seq {
62 my ($self, $dbh, $source, $col) = @_;
63
032b2366 64 my $sql_maker = $self->sql_maker;
cb464582 65
e6dd7b42 66 my $source_name;
032b2366 67 if ( ref $source->name eq 'SCALAR' ) {
68 $source_name = ${$source->name};
e6dd7b42 69 }
70 else {
032b2366 71 $source_name = $source->name;
e6dd7b42 72 }
3963bf4c 73 $source_name = uc($source_name) unless $sql_maker->quote_char;
38aead8e 74
032b2366 75 # trigger_body is a LONG
76 local $dbh->{LongReadLen} = 64 * 1024 if ($dbh->{LongReadLen} < 64 * 1024);
77
78 # disable default bindtype
79 local $sql_maker->{bindtype} = 'normal';
80
81 # look up the correct sequence automatically
82 my ( $schema, $table ) = $source_name =~ /(\w+)\.(\w+)/;
83 my ($sql, @bind) = $sql_maker->select (
84 'ALL_TRIGGERS',
85 ['trigger_body'],
86 {
87 $schema ? (owner => $schema) : (),
88 table_name => $table || $source_name,
cc42fa9a 89 triggering_event => { -like => '%INSERT%' },
032b2366 90 status => 'ENABLED',
91 },
92 );
93 my $sth = $dbh->prepare($sql);
94 $sth->execute (@bind);
e6dd7b42 95
18360aed 96 while (my ($insert_trigger) = $sth->fetchrow_array) {
852a66f6 97 return $1 if $insert_trigger =~ m!("?\w+"?)\.nextval!i; # col name goes here???
18360aed 98 }
4f2b3017 99 $self->throw_exception("Unable to find a sequence INSERT trigger on table '$source_name'.");
18360aed 100}
101
2e46b6eb 102sub _sequence_fetch {
103 my ( $self, $type, $seq ) = @_;
9ae966b9 104 my ($id) = $self->_get_dbh->selectrow_array("SELECT ${seq}.${type} FROM DUAL");
2e46b6eb 105 return $id;
106}
107
6dc4be0f 108sub _ping {
c2481821 109 my $self = shift;
7ba7a57d 110
6dc4be0f 111 my $dbh = $self->_dbh or return 0;
7ba7a57d 112
6dc4be0f 113 local $dbh->{RaiseError} = 1;
ecdf1ac8 114 local $dbh->{PrintError} = 0;
c2d7baef 115
52b420dd 116 return try {
ecdf1ac8 117 $dbh->do('select 1 from dual');
52b420dd 118 1;
ed7ab0f4 119 } catch {
52b420dd 120 0;
6dc4be0f 121 };
c2481821 122}
123
d789fa99 124sub _dbh_execute {
125 my $self = shift;
126 my ($dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_;
127
87560ef9 128 my (@res, $tried);
dd415de8 129 my $wantarray = wantarray();
4f661051 130 my $next = $self->next::can;
87560ef9 131 do {
52b420dd 132 try {
dd415de8 133 my $exec = sub { $self->$next($dbh, $op, $extra_bind, $ident, $bind_attributes, @args) };
134
135 if (!defined $wantarray) {
136 $exec->();
137 }
138 elsif (! $wantarray) {
139 $res[0] = $exec->();
140 }
141 else {
142 @res = $exec->();
143 }
87560ef9 144
145 $tried++;
52b420dd 146 }
147 catch {
87560ef9 148 if (! $tried and $_ =~ /ORA-01003/) {
0f0abc97 149 # ORA-01003: no statement parsed (someone changed the table somehow,
150 # invalidating your cursor.)
151 my ($sql, $bind) = $self->_prep_for_execute($op, $extra_bind, $ident, \@args);
152 delete $dbh->{CachedKids}{$sql};
d789fa99 153 }
52b420dd 154 else {
155 $self->throw_exception($_);
156 }
157 };
87560ef9 158 } while (! $tried++);
dd415de8 159
160 return $wantarray ? @res : $res[0];
d789fa99 161}
162
7137528d 163=head2 get_autoinc_seq
164
165Returns the sequence name for an autoincrement column
166
167=cut
168
18360aed 169sub get_autoinc_seq {
170 my ($self, $source, $col) = @_;
d4daee7b 171
373940e1 172 $self->dbh_do('_dbh_get_autoinc_seq', $source, $col);
18360aed 173}
174
8f7e044c 175=head2 datetime_parser_type
176
177This sets the proper DateTime::Format module for use with
178L<DBIx::Class::InflateColumn::DateTime>.
179
180=cut
181
182sub datetime_parser_type { return "DateTime::Format::Oracle"; }
183
9900b569 184=head2 connect_call_datetime_setup
d2a3958e 185
186Used as:
187
9900b569 188 on_connect_call => 'datetime_setup'
d2a3958e 189
8384a713 190In L<connect_info|DBIx::Class::Storage::DBI/connect_info> to set the session nls
191date, and timestamp values for use with L<DBIx::Class::InflateColumn::DateTime>
192and the necessary environment variables for L<DateTime::Format::Oracle>, which
193is used by it.
d2a3958e 194
82f6f45f 195Maximum allowable precision is used, unless the environment variables have
196already been set.
d2a3958e 197
9900b569 198These are the defaults used:
199
200 $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
201 $ENV{NLS_TIMESTAMP_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF';
202 $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
203
d9e53b85 204To get more than second precision with L<DBIx::Class::InflateColumn::DateTime>
205for your timestamps, use something like this:
206
207 use Time::HiRes 'time';
208 my $ts = DateTime->from_epoch(epoch => time);
209
d2a3958e 210=cut
211
9900b569 212sub connect_call_datetime_setup {
d2a3958e 213 my $self = shift;
d2a3958e 214
215 my $date_format = $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
216 my $timestamp_format = $ENV{NLS_TIMESTAMP_FORMAT} ||=
217 'YYYY-MM-DD HH24:MI:SS.FF';
218 my $timestamp_tz_format = $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||=
219 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
220
7a84c41b 221 $self->_do_query(
d7a58a29 222 "alter session set nls_date_format = '$date_format'"
223 );
7a84c41b 224 $self->_do_query(
d7a58a29 225 "alter session set nls_timestamp_format = '$timestamp_format'"
226 );
227 $self->_do_query(
228 "alter session set nls_timestamp_tz_format='$timestamp_tz_format'"
229 );
d2a3958e 230}
231
5db2758d 232=head2 source_bind_attributes
233
234Handle LOB types in Oracle. Under a certain size (4k?), you can get away
235with the driver assuming your input is the deprecated LONG type if you
236encode it as a hex string. That ain't gonna fly at larger values, where
237you'll discover you have to do what this does.
238
239This method had to be overridden because we need to set ora_field to the
240actual column, and that isn't passed to the call (provided by Storage) to
241bind_attribute_by_data_type.
242
243According to L<DBD::Oracle>, the ora_field isn't always necessary, but
244adding it doesn't hurt, and will save your bacon if you're modifying a
245table with more than one LOB column.
246
247=cut
248
e6dd7b42 249sub source_bind_attributes
5db2758d 250{
d7a58a29 251 require DBD::Oracle;
252 my $self = shift;
253 my($source) = @_;
5db2758d 254
d7a58a29 255 my %bind_attributes;
5db2758d 256
d7a58a29 257 foreach my $column ($source->columns) {
258 my $data_type = $source->column_info($column)->{data_type} || '';
259 next unless $data_type;
5db2758d 260
d7a58a29 261 my %column_bind_attrs = $self->bind_attribute_by_data_type($data_type);
5db2758d 262
d7a58a29 263 if ($data_type =~ /^[BC]LOB$/i) {
931e5d43 264 if ($DBD::Oracle::VERSION eq '1.23') {
265 $self->throw_exception(
266"BLOB/CLOB support in DBD::Oracle == 1.23 is broken, use an earlier or later ".
555cc3f4 267"version.\n\nSee: https://rt.cpan.org/Public/Bug/Display.html?id=46016\n"
931e5d43 268 );
269 }
270
d7a58a29 271 $column_bind_attrs{'ora_type'} = uc($data_type) eq 'CLOB'
272 ? DBD::Oracle::ORA_CLOB()
273 : DBD::Oracle::ORA_BLOB()
274 ;
275 $column_bind_attrs{'ora_field'} = $column;
276 }
5db2758d 277
d7a58a29 278 $bind_attributes{$column} = \%column_bind_attrs;
279 }
5db2758d 280
d7a58a29 281 return \%bind_attributes;
5db2758d 282}
283
1816be4f 284sub _svp_begin {
d7a58a29 285 my ($self, $name) = @_;
286 $self->_get_dbh->do("SAVEPOINT $name");
1816be4f 287}
288
281719d2 289# Oracle automatically releases a savepoint when you start another one with the
290# same name.
291sub _svp_release { 1 }
292
293sub _svp_rollback {
d7a58a29 294 my ($self, $name) = @_;
295 $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
281719d2 296}
297
6c0230de 298=head2 relname_to_table_alias
299
300L<DBIx::Class> uses L<DBIx::Class::Relationship> names as table aliases in
301queries.
302
303Unfortunately, Oracle doesn't support identifiers over 30 chars in length, so
af0edca1 304the L<DBIx::Class::Relationship> name is shortened and appended with half of an
305MD5 hash.
6c0230de 306
307See L<DBIx::Class::Storage/"relname_to_table_alias">.
308
309=cut
310
311sub relname_to_table_alias {
312 my $self = shift;
313 my ($relname, $join_count) = @_;
314
315 my $alias = $self->next::method(@_);
316
317 return $alias if length($alias) <= 30;
318
af0edca1 319 # get a base64 md5 of the alias with join_count
320 require Digest::MD5;
321 my $ctx = Digest::MD5->new;
322 $ctx->add($alias);
323 my $md5 = $ctx->b64digest;
6c0230de 324
f098ade6 325 # remove alignment mark just in case
326 $md5 =~ s/=*\z//;
327
af0edca1 328 # truncate and prepend to truncated relname without vowels
329 (my $devoweled = $relname) =~ s/[aeiou]//g;
909668fe 330 my $shortened = substr($devoweled, 0, 18);
6c0230de 331
909668fe 332 my $new_alias =
333 $shortened . '_' . substr($md5, 0, 30 - length($shortened) - 1);
334
335 return $new_alias;
6c0230de 336}
337
6c0bb6a7 338=head2 with_deferred_fk_checks
339
340Runs a coderef between:
341
342 alter session set constraints = deferred
343 ...
344 alter session set constraints = immediate
345
b7b18f32 346to defer foreign key checks.
347
348Constraints must be declared C<DEFERRABLE> for this to work.
6c0bb6a7 349
350=cut
351
352sub with_deferred_fk_checks {
353 my ($self, $sub) = @_;
b7b18f32 354
355 my $txn_scope_guard = $self->txn_scope_guard;
356
6c0bb6a7 357 $self->_do_query('alter session set constraints = deferred');
b7b18f32 358
359 my $sg = Scope::Guard->new(sub {
360 $self->_do_query('alter session set constraints = immediate');
361 });
362
363 return Context::Preserve::preserve_context(sub { $sub->() },
364 after => sub { $txn_scope_guard->commit });
6c0bb6a7 365}
366
7a84c41b 367=head1 AUTHOR
18360aed 368
7a84c41b 369See L<DBIx::Class/CONTRIBUTORS>.
18360aed 370
371=head1 LICENSE
372
373You may distribute this code under the same terms as Perl itself.
374
375=cut
7137528d 376
3771;