[Dbix-class] fast-path populate inserts incorrect data

Andrew Gregory andrew.gregory.8 at gmail.com
Thu May 11 14:04:13 GMT 2017


_dbh_execute_for_fetch handles some columns (e.g. MONEY columns with
MS SQL Server) incorrectly.  For columns that are casted during
insertion, the column data is not in the format _dbh_execute_for_fetch
expects.  It tries to extract the _bind_data_slice_idx field, but it
is located inside a second list element like so:

 [
   {
     'dbic_colname' => 'money_col',
     'sqlt_datatype' => 'MONEY'
   },
   \[
     '?',
     [
       {
         '_bind_data_slice_idx' => 0,
         'dbic_colname' => 'money_col'
       },
       '0.50'
     ]
   ]
 ],

Instead of the appropriate index, it gets undef, which it then uses as
an index into the row values, causing it to incorrectly insert the
first value in the row for all of these columns.

The following patch appears to fix the issue, but I'm not nearly
familiar enough with the codebase to know if it's the correct
solution:

diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm
index 9600389b..40d71eeb 100644
--- a/lib/DBIx/Class/Storage/DBI.pm
+++ b/lib/DBIx/Class/Storage/DBI.pm
@@ -2317,7 +2317,7 @@ sub _dbh_execute_for_fetch {
         ? "$v"
         : $v
       ;
-    } map { $_->[0] } @$proto_bind ];
+    } map { ref $_->[1] ? ${$_->[1]}->[1][0] : $_->[0] } @$proto_bind ];
   };
 
   my $tuple_status = [];

Finally, here is a test case to replicate the issue:

#!/usr/bin/perl

use strict;
use warnings;

package FPP::Schema::Result::Table;
use parent 'DBIx::Class::Core';

__PACKAGE__->table('dummy');
__PACKAGE__->add_columns(
    rowid  => { data_type => 'INT',   is_numeric => 1 },
    amount => { data_type => 'MONEY', is_numeric => 1 },
);
__PACKAGE__->set_primary_key('rowid');

package FPP::Schema;
use parent 'DBIx::Class::Schema';

# force MSSQL syntax which uses a CAST when inserting MONEY values
__PACKAGE__->storage_type('::DBI::MSSQL');
__PACKAGE__->register_source( 'Table',
    FPP::Schema::Result::Table->result_source_instance );

package main;

# SQLite's syntax is flexible enough to accommodate SQL Server statements
my $schema = FPP::Schema->connect('dbi:SQLite:dbname=:memory:');
$schema->deploy();

$schema->storage->debug(1);

# fast-path populate; note the warning about an uninitialized value
$schema->resultset('Table')->populate( [ [qw( rowid amount )], [ 1, 250 ] ] );

# slow-path populate
scalar $schema->resultset('Table')
  ->populate( [ [qw( rowid amount )], [ 2, 150 ] ] );

# table should look like:
#  rowid amount
#      1    250
#      2    150
#
# instead, we get:
#  rowid amount
#      1      1
#      2    150

foreach my $row ( $schema->resultset('Table')->all ) {
    print 'rowid: ',  $row->rowid,  "\n";
    print 'amount: ', $row->amount, "\n";
}



More information about the DBIx-Class mailing list