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