use DBIx::Class::Storage::Statistics;
use Scalar::Util();
use List::Util();
+use Data::Dumper::Concise();
# what version of sqlt do we require if deploy() without a ddl_dir is invoked
# when changing also adjust the corresponding author_require in Makefile.PL
my ($self) = @_;
if ((not $self->_driver_determined) && (not $self->{_in_determine_driver})) {
- my $started_unconnected = 0;
+ my $started_connected = 0;
local $self->{_in_determine_driver} = 1;
if (ref($self) eq __PACKAGE__) {
my $driver;
if ($self->_dbh) { # we are connected
$driver = $self->_dbh->{Driver}{Name};
+ $started_connected = 1;
} else {
# if connect_info is a CODEREF, we have no choice but to connect
if (ref $self->_dbi_connect_info->[0] &&
# try to use dsn to not require being connected, the driver may still
# force a connection in _rebless to determine version
($driver) = $self->_dbi_connect_info->[0] =~ /dbi:([^:]+):/i;
- $started_unconnected = 1;
}
}
$self->_init; # run driver-specific initializations
$self->_run_connection_actions
- if $started_unconnected && defined $self->_dbh;
+ if !$started_connected && defined $self->_dbh;
}
}
my %colvalues;
@colvalues{@$cols} = (0..$#$cols);
- # pass scalarref to SQLA for literal sql if it's the same in all slices
for my $i (0..$#$cols) {
my $first_val = $data->[0][$i];
- next unless (Scalar::Util::reftype($first_val)||'') eq 'SCALAR';
+ next unless ref $first_val eq 'SCALAR';
+
+ $colvalues{ $cols->[$i] } = $first_val;
+## This is probably unnecessary since $rs->populate only looks at the first
+## slice anyway.
+# if (grep {
+# ref $_ eq 'SCALAR' && $$_ eq $$first_val
+# } map $data->[$_][$i], (1..$#$data)) == (@$data - 1);
+ }
+
+ # check for bad data
+ my $bad_slice = sub {
+ my ($msg, $col_idx, $slice_idx) = @_;
+ $self->throw_exception(sprintf "%s for column '%s' in populate slice:\n%s",
+ $msg,
+ $cols->[$col_idx],
+ Data::Dumper::Concise::Dumper({
+ map { $cols->[$_] => $data->[$slice_idx][$_] } (0 .. $#$cols)
+ }),
+ );
+ };
+
+ for my $datum_idx (0..$#$data) {
+ my $datum = $data->[$datum_idx];
- $colvalues{ $cols->[$i] } = $first_val
- if (grep {
- (Scalar::Util::reftype($_)||'') eq 'SCALAR' &&
- $$_ eq $$first_val
- } map $data->[$_][$i], (1..$#$data)) == (@$data - 1);
+ for my $col_idx (0..$#$cols) {
+ my $val = $datum->[$col_idx];
+ my $sqla_bind = $colvalues{ $cols->[$col_idx] };
+ my $is_literal_sql = (ref $sqla_bind) eq 'SCALAR';
+
+ if ($is_literal_sql) {
+ if (not ref $val) {
+ $bad_slice->('bind found where literal SQL expected', $col_idx, $datum_idx);
+ }
+ elsif ((my $reftype = ref $val) ne 'SCALAR') {
+ $bad_slice->("$reftype reference found where literal SQL expected",
+ $col_idx, $datum_idx);
+ }
+ elsif ($$val ne $$sqla_bind){
+ $bad_slice->("inconsistent literal SQL value, expecting: '$$sqla_bind'",
+ $col_idx, $datum_idx);
+ }
+ }
+ elsif (my $reftype = ref $val) {
+ $bad_slice->("$reftype reference found where bind expected",
+ $col_idx, $datum_idx);
+ }
+ }
}
my ($sql, $bind) = $self->_prep_for_execute (
my @bind = @$bind;
my $empty_bind = 1 if (not @bind) &&
- (grep { (Scalar::Util::reftype($_)||'') eq 'SCALAR' } values %colvalues)
- == @$cols;
+ (grep { ref $_ eq 'SCALAR' } values %colvalues) == @$cols;
if ((not @bind) && (not $empty_bind)) {
$self->throw_exception(
);
}
- $self->_query_start( $sql, @bind );
+ $self->_query_start( $sql, ['__BULK__'] );
my $sth = $self->sth($sql);
my $rv = do {
if ($empty_bind) {
# bind_param_array doesn't work if there are no binds
- $self->_execute_array_empty( $sth, scalar @$data );
+ $self->_dbh_execute_inserts_with_no_binds( $sth, scalar @$data );
}
else {
# @bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args
}
};
- $self->_query_end( $sql, @bind );
+ $self->_query_end( $sql, ['__BULK__'] );
return (wantarray ? ($rv, $sth, @bind) : $rv);
}
sub _execute_array {
- my ($self, $source, $sth, $bind, $cols, $data, $guard) = @_;
+ my ($self, $source, $sth, $bind, $cols, $data, @extra) = @_;
+
+ my $guard = $self->txn_scope_guard unless $self->{transaction_depth} != 0;
## This must be an arrayref, else nothing works!
my $tuple_status = [];
$placeholder_index++;
}
- my $rv = eval { $sth->execute_array({ArrayTupleStatus => $tuple_status}) };
-
-# only needed for Sybase, it requires a commit before the $sth->finish
- $guard->commit if $guard;
+ my $rv = eval {
+ $self->_dbh_execute_array($sth, $tuple_status, @extra);
+ };
+ my $err = $@ || $sth->errstr;
- $sth->finish;
+# Statement must finish even if there was an exception.
+ eval { $sth->finish };
+ $err = $@ unless $err;
- if (my $err = $@ || $sth->errstr) {
+ if ($err) {
my $i = 0;
++$i while $i <= $#$tuple_status && !ref $tuple_status->[$i];
$self->throw_exception(sprintf "%s for populate slice:\n%s",
($tuple_status->[$i][1] || $err),
- $self->_pretty_print ({
+ Data::Dumper::Concise::Dumper({
map { $cols->[$_] => $data->[$i][$_] } (0 .. $#$cols)
}),
);
}
+ $guard->commit if $guard;
+
return $rv;
}
-sub _execute_array_empty {
+sub _dbh_execute_array {
+ my ($self, $sth, $tuple_status, @extra) = @_;
+
+ return $sth->execute_array({ArrayTupleStatus => $tuple_status});
+}
+
+sub _dbh_execute_inserts_with_no_binds {
my ($self, $sth, $count) = @_;
+
+ my $guard = $self->txn_scope_guard unless $self->{transaction_depth} != 0;
+
eval {
my $dbh = $self->_get_dbh;
local $dbh->{RaiseError} = 1;
local $dbh->{PrintError} = 0;
- foreach (1..$count) {
- $sth->execute;
-# In case of a multi-statement with a select, some DBDs (namely Sybase) require
-# the cursor to be exhausted.
- $sth->fetchall_arrayref;
- }
+
+ $sth->execute foreach 1..$count;
};
my $exception = $@;
- $sth->finish;
+
+# Make sure statement is finished even if there was an exception.
+ eval { $sth->finish };
+ $exception = $@ unless $exception;
+
$self->throw_exception($exception) if $exception;
+ $guard->commit if $guard;
+
return $count;
}
return;
}
-# SQLT version handling
+# SQLT version handling
{
- my $_sqlt_version_ok; # private
- my $_sqlt_version_error; # private
+ my $_sqlt_version_ok; # private
+ my $_sqlt_version_error; # private
sub _sqlt_version_ok {
if (!defined $_sqlt_version_ok) {