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