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