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