use strict;
use warnings;
+use base qw/DBIx::Class::Storage::DBI/;
+use mro 'c3';
+use DBIx::Class::Carp;
use Scope::Guard ();
use Context::Preserve 'preserve_context';
use Try::Tiny;
+use List::Util 'first';
use namespace::clean;
__PACKAGE__->sql_limit_dialect ('RowNum');
__PACKAGE__->sql_quote_char ('"');
+__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker::Oracle');
+__PACKAGE__->datetime_parser_type('DateTime::Format::Oracle');
+
+sub __cache_queries_with_max_lob_parts { 2 }
=head1 NAME
This class implements base Oracle support. The subclass
L<DBIx::Class::Storage::DBI::Oracle::WhereJoins> is for C<(+)> joins in Oracle
-versions before 9.
+versions before 9.0.
=head1 METHODS
=cut
-use base qw/DBIx::Class::Storage::DBI/;
-use mro 'c3';
-
-__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker::Oracle');
-__PACKAGE__->datetime_parser_type('DateTime::Format::Oracle');
-
sub _determine_supports_insert_returning {
my $self = shift;
my ( $schema, $table ) = $source_name =~ /( (?:${ql})? \w+ (?:${qr})? ) \. ( (?:${ql})? \w+ (?:${qr})? )/x;
# if no explicit schema was requested - use the default schema (which in the case of Oracle is the db user)
- $schema ||= uc( ($self->_dbi_connect_info||[])->[1] || '');
+ $schema ||= \'= USER';
my ($sql, @bind) = $sql_maker->select (
'ALL_TRIGGERS',
my $next = $self->next::can;
do {
try {
- my $exec = sub { $self->$next($dbh, $sql, @args) };
+ my $exec = sub {
+ # Turn off sth caching for multi-part LOBs. See _prep_for_execute above.
+ local $self->{disable_sth_caching} = 1
+ if first {
+ ($_->[0]{_ora_lob_autosplit_part}||0)
+ > (__cache_queries_with_max_lob_parts-1)
+ } @{ $args[0] };
+
+ $self->$next($dbh, $sql, @args)
+ };
if (!defined $want) {
$exec->();
#
sub _dbi_attrs_for_bind {
my ($self, $ident, $bind) = @_;
+
my $attrs = $self->next::method($ident, $bind);
for my $i (0 .. $#$attrs) {
}
}
-sub _svp_begin {
+# Handle blob columns in WHERE.
+#
+# For equality comparisons:
+#
+# We split data intended for comparing to a LOB into 2000 character chunks and
+# compare them using dbms_lob.substr on the LOB column.
+#
+# We turn off DBD::Oracle LOB binds for these partial LOB comparisons by passing
+# dbd_attrs => undef, because these are regular varchar2 comparisons and
+# otherwise the query will fail.
+#
+# Since the most common comparison size is likely to be under 4000 characters
+# (TEXT comparisons previously deployed to other RDBMSes) we disable
+# prepare_cached for queries with more than two part comparisons to a LOB
+# column. This is done in _dbh_execute (above) which was previously overridden
+# to gracefully recover from an Oracle error. This is to be careful to not
+# exhaust your application's open cursor limit.
+#
+# See:
+# http://itcareershift.com/blog1/2011/02/21/oracle-max-number-of-open-cursors-complete-reference-for-the-new-oracle-dba/
+# on the open_cursor limit.
+#
+# For everything else:
+#
+# We assume that everything that is not a LOB comparison, will most likely be a
+# LIKE query or some sort of function invocation. This may prove to be a naive
+# assumption in the future, but for now it should cover the two most likely
+# things users would want to do with a BLOB or CLOB, an equality test or a LIKE
+# query (on a CLOB.)
+#
+# For these expressions, the bind must NOT have the attributes of a LOB bind for
+# DBD::Oracle, otherwise the query will fail. This is done by passing
+# dbd_attrs => undef.
+
+sub _prep_for_execute {
+ my $self = shift;
+ my ($op) = @_;
+
+ my ($sql, $bind) = $self->next::method(@_);
+
+ return ($sql, $bind) if $op eq 'insert';
+
+ my $blob_bind_index;
+ for (0 .. $#$bind) {
+ $blob_bind_index->{$_} = 1 if $self->_is_lob_type(
+ $bind->[$_][0]{sqlt_datatype}
+ );
+ }
+
+ return ($sql, $bind) unless $blob_bind_index;
+
+ my (@sql_parts, $new_sql, @new_binds);
+
+ if ($op eq 'select' || $op eq 'delete') {
+ @sql_parts = split /\?/, $sql;
+ }
+ elsif ($op eq 'update') {
+ $self->throw_exception('Update with complex WHERE clauses currently not supported')
+ if $sql =~ /\bWHERE\b .+ \bWHERE\b/xs;
+
+ my ($set_part, $where_part) = $sql =~ /^ (.+?) ( \bWHERE\b .+) /xs;
+
+ my $set_bind_count = $set_part =~ y/?//;
+ @new_binds = splice @$bind, 0, $set_bind_count;
+
+ @sql_parts = split /\?/, $where_part;
+ $new_sql = $set_part;
+ }
+ else {
+ $self->throw_exception("Unsupported \$op: $op");
+ }
+
+ my $col_equality_re = qr/ (?<=\s) ([\w."]+) (\s*=\s*) $/x;
+
+ for my $b_idx (0 .. $#$bind) {
+ my $bound = $bind->[$b_idx];
+
+ if ($blob_bind_index->{$b_idx}) {
+ if (my ($col, $eq) = $sql_parts[0] =~ $col_equality_re) {
+ my $data = $bound->[1];
+
+ $data = "$data" if ref $data;
+
+ my @parts = unpack '(a2000)*', $data;
+
+ my @sql_frag;
+
+ for my $idx (0..$#parts) {
+ push @sql_frag, sprintf (
+ 'UTL_RAW.CAST_TO_VARCHAR2(RAWTOHEX(DBMS_LOB.SUBSTR(%s, 2000, %d))) = ?',
+ $col, ($idx*2000 + 1),
+ );
+ }
+
+ my $sql_frag = '( ' . (join ' AND ', @sql_frag) . ' )';
+
+ $sql_parts[0] =~ s/$col_equality_re/$sql_frag/;
+
+ $new_sql .= shift @sql_parts;
+
+ for my $idx (0..$#parts) {
+ push @new_binds, [
+ {
+ %{ $bound->[0] },
+ _ora_lob_autosplit_part => $idx,
+ dbd_attrs => undef,
+ },
+ $parts[$idx]
+ ];
+ }
+ }
+ else {
+ $new_sql .= shift(@sql_parts) . '?';
+
+ push @new_binds, [
+ {
+ %{ $bound->[0] },
+ dbd_attrs => undef,
+ },
+ $bound->[1],
+ ];
+ }
+ }
+ else {
+ $new_sql .= shift(@sql_parts) . '?';
+ push @new_binds, $bound;
+ }
+ }
+
+ if (@sql_parts > 1) {
+ carp "There are more placeholders than binds, this should not happen!";
+ @sql_parts = join ('?', @sql_parts);
+ }
+
+ $new_sql .= $sql_parts[0];
+
+ return ($new_sql, \@new_binds);
+}
+
+# Savepoints stuff.
+
+sub _exec_svp_begin {
my ($self, $name) = @_;
- $self->_get_dbh->do("SAVEPOINT $name");
+ $self->_dbh->do("SAVEPOINT $name");
}
# Oracle automatically releases a savepoint when you start another one with the
# same name.
-sub _svp_release { 1 }
+sub _exec_svp_release { 1 }
-sub _svp_rollback {
+sub _exec_svp_rollback {
my ($self, $name) = @_;
- $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
+ $self->_dbh->do("ROLLBACK TO SAVEPOINT $name")
}
=head2 relname_to_table_alias
=head1 AUTHOR
-See L<DBIx::Class/CONTRIBUTORS>.
+See L<DBIx::Class/AUTHOR> and L<DBIx::Class/CONTRIBUTORS>.
=head1 LICENSE
=cut
1;
+# vim:sts=2 sw=2: