append half of a base64 MD5 to shortened table aliases for Oracle
[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("alter session set nls_date_format = '$date_format'");
213 $self->_do_query(
214"alter session set nls_timestamp_format = '$timestamp_format'");
215 $self->_do_query(
216"alter session set nls_timestamp_tz_format='$timestamp_tz_format'");
d2a3958e 217}
218
5db2758d 219=head2 source_bind_attributes
220
221Handle LOB types in Oracle. Under a certain size (4k?), you can get away
222with the driver assuming your input is the deprecated LONG type if you
223encode it as a hex string. That ain't gonna fly at larger values, where
224you'll discover you have to do what this does.
225
226This method had to be overridden because we need to set ora_field to the
227actual column, and that isn't passed to the call (provided by Storage) to
228bind_attribute_by_data_type.
229
230According to L<DBD::Oracle>, the ora_field isn't always necessary, but
231adding it doesn't hurt, and will save your bacon if you're modifying a
232table with more than one LOB column.
233
234=cut
235
e6dd7b42 236sub source_bind_attributes
5db2758d 237{
efc5bf40 238 require DBD::Oracle;
5db2758d 239 my $self = shift;
240 my($source) = @_;
241
242 my %bind_attributes;
243
244 foreach my $column ($source->columns) {
245 my $data_type = $source->column_info($column)->{data_type} || '';
246 next unless $data_type;
247
248 my %column_bind_attrs = $self->bind_attribute_by_data_type($data_type);
249
250 if ($data_type =~ /^[BC]LOB$/i) {
0d1207e8 251 $column_bind_attrs{'ora_type'} = uc($data_type) eq 'CLOB' ?
252 DBD::Oracle::ORA_CLOB() :
253 DBD::Oracle::ORA_BLOB();
5db2758d 254 $column_bind_attrs{'ora_field'} = $column;
255 }
256
257 $bind_attributes{$column} = \%column_bind_attrs;
258 }
259
260 return \%bind_attributes;
261}
262
1816be4f 263sub _svp_begin {
264 my ($self, $name) = @_;
265
e33b954c 266 $self->_get_dbh->do("SAVEPOINT $name");
1816be4f 267}
268
281719d2 269# Oracle automatically releases a savepoint when you start another one with the
270# same name.
271sub _svp_release { 1 }
272
273sub _svp_rollback {
274 my ($self, $name) = @_;
275
9ae966b9 276 $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
281719d2 277}
278
6c0230de 279=head2 relname_to_table_alias
280
281L<DBIx::Class> uses L<DBIx::Class::Relationship> names as table aliases in
282queries.
283
284Unfortunately, Oracle doesn't support identifiers over 30 chars in length, so
af0edca1 285the L<DBIx::Class::Relationship> name is shortened and appended with half of an
286MD5 hash.
6c0230de 287
288See L<DBIx::Class::Storage/"relname_to_table_alias">.
289
290=cut
291
292sub relname_to_table_alias {
293 my $self = shift;
294 my ($relname, $join_count) = @_;
295
296 my $alias = $self->next::method(@_);
297
298 return $alias if length($alias) <= 30;
299
af0edca1 300 # get a base64 md5 of the alias with join_count
301 require Digest::MD5;
302 my $ctx = Digest::MD5->new;
303 $ctx->add($alias);
304 my $md5 = $ctx->b64digest;
6c0230de 305
af0edca1 306 # truncate and prepend to truncated relname without vowels
307 (my $devoweled = $relname) =~ s/[aeiou]//g;
308 my $res = substr($devoweled, 0, 18) . '_' . substr($md5, 0, 11);
6c0230de 309
af0edca1 310 return $res;
6c0230de 311}
312
7a84c41b 313=head1 AUTHOR
18360aed 314
7a84c41b 315See L<DBIx::Class/CONTRIBUTORS>.
18360aed 316
317=head1 LICENSE
318
319You may distribute this code under the same terms as Perl itself.
320
321=cut
7137528d 322
3231;