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