use Carp::Clan qw/^DBIx::Class|^Try::Tiny/;
use DBI;
use DBIx::Class::Storage::DBI::Cursor;
-use DBIx::Class::Storage::Statistics;
use Scalar::Util qw/refaddr weaken reftype blessed/;
+use List::Util qw/first/;
use Data::Dumper::Concise 'Dumper';
use Sub::Name 'subname';
use Try::Tiny;
sub _arm_global_destructor {
my $self = shift;
- my $key = Scalar::Util::refaddr ($self);
+ my $key = refaddr ($self);
$seek_and_destroy{$key} = $self;
- Scalar::Util::weaken ($seek_and_destroy{$key});
+ weaken ($seek_and_destroy{$key});
}
END {
$self->dbh_do('_dbh_execute', @_); # retry over disconnects
}
-sub _prefetch_insert_auto_nextvals {
+sub insert {
my ($self, $source, $to_insert) = @_;
- my $upd = {};
-
- foreach my $col ( $source->columns ) {
- if ( !defined $to_insert->{$col} ) {
- my $col_info = $source->column_info($col);
+ my $colinfo = $source->columns_info;
- if ( $col_info->{auto_nextval} ) {
- $upd->{$col} = $to_insert->{$col} = $self->_sequence_fetch(
- 'nextval',
- $col_info->{sequence} ||=
+ # mix with auto-nextval marked values (a bit of a speed hit, but
+ # no saner way to handle this yet)
+ my $auto_nextvals = {} ;
+ for my $col (keys %$colinfo) {
+ if (
+ $colinfo->{$col}{auto_nextval}
+ and
+ (
+ ! exists $to_insert->{$col}
+ or
+ ref $to_insert->{$col} eq 'SCALAR'
+ )
+ ) {
+ $auto_nextvals->{$col} = $self->_sequence_fetch(
+ 'nextval',
+ ( $colinfo->{$col}{sequence} ||=
$self->_dbh_get_autoinc_seq($self->_get_dbh, $source, $col)
- );
- }
+ ),
+ );
}
}
- return $upd;
-}
+ # fuse the values
+ $to_insert = { %$to_insert, %$auto_nextvals };
-sub insert {
- my $self = shift;
- my ($source, $to_insert, $opts) = @_;
+ # list of primary keys we try to fetch from the database
+ # both not-exsists and scalarrefs are considered
+ my %fetch_pks;
+ %fetch_pks = ( map
+ { $_ => scalar keys %fetch_pks } # so we can preserve order for prettyness
+ grep
+ { ! exists $to_insert->{$_} or ref $to_insert->{$_} eq 'SCALAR' }
+ $source->primary_columns
+ );
+
+ my $sqla_opts;
+ if ($self->_use_insert_returning) {
- my $updated_cols = $self->_prefetch_insert_auto_nextvals (@_);
+ # retain order as declared in the resultsource
+ for (sort { $fetch_pks{$a} <=> $fetch_pks{$b} } keys %fetch_pks ) {
+ push @{$sqla_opts->{returning}}, $_;
+ }
+ }
my $bind_attributes = $self->source_bind_attributes($source);
- my ($rv, $sth) = $self->_execute('insert' => [], $source, $bind_attributes, $to_insert, $opts);
+ my ($rv, $sth) = $self->_execute('insert' => [], $source, $bind_attributes, $to_insert, $sqla_opts);
- if ($opts->{returning}) {
- my @ret_cols = @{$opts->{returning}};
+ my %returned_cols = %$auto_nextvals;
+ if (my $retlist = $sqla_opts->{returning}) {
my @ret_vals = try {
local $SIG{__WARN__} = sub {};
my @r = $sth->fetchrow_array;
@r;
};
- my %ret;
- @ret{@ret_cols} = @ret_vals if (@ret_vals);
-
- $updated_cols = {
- %$updated_cols,
- %ret,
- };
+ @returned_cols{@$retlist} = @ret_vals if @ret_vals;
}
- return $updated_cols;
+ return \%returned_cols;
}
+
## Currently it is assumed that all values passed will be "normal", i.e. not
## scalar refs, or at least, all the same type as the first set, the statement is
## only prepped once.
my ($sql, $bind) = $self->_prep_for_execute (
'insert', undef, $source, [\%colvalues]
);
- my @bind = @$bind;
- my $empty_bind = 1 if (not @bind) &&
- (grep { ref $_ eq 'SCALAR' } values %colvalues) == @$cols;
+ if (! @$bind) {
+ # if the bindlist is empty - make sure all "values" are in fact
+ # literal scalarrefs. If not the case this means the storage ate
+ # them away (e.g. the NoBindVars component) and interpolated them
+ # directly into the SQL. This obviosly can't be good for multi-inserts
- if ((not @bind) && (not $empty_bind)) {
- $self->throw_exception(
- 'Cannot insert_bulk without support for placeholders'
- );
+ $self->throw_exception('Cannot insert_bulk without support for placeholders')
+ if first { ref $_ ne 'SCALAR' } values %colvalues;
}
# neither _execute_array, nor _execute_inserts_with_no_binds are
# scope guard
my $guard = $self->txn_scope_guard;
- $self->_query_start( $sql, [ dummy => '__BULK_INSERT__' ] );
+ $self->_query_start( $sql, @$bind ? [ dummy => '__BULK_INSERT__' ] : () );
my $sth = $self->sth($sql);
my $rv = do {
- if ($empty_bind) {
- # bind_param_array doesn't work if there are no binds
- $self->_dbh_execute_inserts_with_no_binds( $sth, scalar @$data );
+ if (@$bind) {
+ #@bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args
+ $self->_execute_array( $source, $sth, $bind, $cols, $data );
}
else {
-# @bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args
- $self->_execute_array( $source, $sth, \@bind, $cols, $data );
+ # bind_param_array doesn't work if there are no binds
+ $self->_dbh_execute_inserts_with_no_binds( $sth, scalar @$data );
}
};
- $self->_query_end( $sql, [ dummy => '__BULK_INSERT__' ] );
+ $self->_query_end( $sql, @$bind ? [ dummy => '__BULK_INSERT__' ] : () );
$guard->commit;
- return (wantarray ? ($rv, $sth, @bind) : $rv);
+ return (wantarray ? ($rv, $sth, @$bind) : $rv);
}
sub _execute_array {
}
catch {
$err = shift;
+ };
+
+ # Make sure statement is finished even if there was an exception.
+ try {
+ $sth->finish
}
- finally {
- # Make sure statement is finished even if there was an exception.
- try {
- $sth->finish
- }
- catch {
- $err = shift unless defined $err;
- };
+ catch {
+ $err = shift unless defined $err;
};
$self->throw_exception($err) if defined $err;