Skip Menu |

This queue is for tickets about the DBD-Mock CPAN distribution.

Report information
The Basics
Id: 86294
Status: resolved
Priority: 0/
Queue: DBD-Mock

People
Owner: Nobody in particular
Requestors: pianohacker [...] gmail.com
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: 1.47



Subject: fetchall_hashref does not support nested keys
Date: Thu, 20 Jun 2013 15:19:23 -0600
To: bug-DBD-Mock [...] rt.cpan.org
From: Jesse <pianohacker [...] gmail.com>
From the DBI documentation: For queries returning more than one 'key' column, you can specify multiple column names by passing $key_field as a reference to an array containing one or more key column names (or index numbers). For example: $sth = $dbh->prepare("SELECT foo, bar, baz FROM table"); $sth->execute; $hash_ref = $sth->fetchall_hashref( [ qw(foo bar) ] ); print "For foo 42 and bar 38, baz is $hash_ref->{42}->{38}->{baz}\n" -- Jesse Weaver
From: zurom [...] mail.ru
The same with selectall_hashref my $res_has = $dbh->selectall_hashref(" SELECT col1, col2, col3 FROM Table1 T1 ", [ 'col1', 'col2' ] ); From the DBI documentation: The $key_field parameter defines which column, or columns, are used as keys in the returned hash. It can either be the name of a single field, or a reference to an ARRAY containing multiple field names. Using multiple names yields a tree of NESTED HASHES.
On Thu Jun 20 17:19:37 2013, pianohacker@gmail.com wrote: Show quoted text
> From the DBI documentation: > > For queries returning more than one 'key' column, you can specify > multiple column names by passing $key_field as a reference to an array > containing one or more key column names (or > index numbers). For example: > > $sth = $dbh->prepare("SELECT foo, bar, baz FROM table"); > $sth->execute; > $hash_ref = $sth->fetchall_hashref( [ qw(foo bar) ] ); > print "For foo 42 and bar 38, baz is > $hash_ref->{42}->{38}->{baz}\n" >
I've attached a patch that adds support for multiple key fields. It also fixes a bug when the key field is given as a number instead of a column name: numeric key fields are one-indexed, but the code was assuming they are zero-indexed. This patch is based on the implementation of fetchall_hashref in DBI v1.641 (https://metacpan.org/source/TIMB/DBI-1.641/DBI.pm#L2101). Support for multiple key fields was introduced in DBI v1.48; previous versions choke if you pass an arrayref. If DBD::Mock needs to mimic that behavior for older versions of DBI, this patch will need to be tweaked.
Subject: fetchall_hashref-support-multiple-key-fields.patch
diff -rupN DBD-Mock-1.45/lib/DBD/Mock/st.pm DBD-Mock-1.45-new/lib/DBD/Mock/st.pm --- DBD-Mock-1.45/lib/DBD/Mock/st.pm 2012-10-21 20:45:29.000000000 -0500 +++ DBD-Mock-1.45-new/lib/DBD/Mock/st.pm 2018-08-13 12:05:06.000000000 -0500 @@ -237,45 +237,59 @@ sub fetchall_hashref { } $dbh->{mock_can_fetch}++ if $dbh->{mock_can_fetch} < 0; - my $tracker = $sth->FETCH('mock_my_history'); - my $rethash = {}; - - # get the name set by - my $name = $sth->{Database}->FETCH('FetchHashKeyName') || 'NAME'; - my $fields = $sth->FETCH($name); - - # check if $keyfield is not an integer - if ( !( $keyfield =~ /^-?\d+$/ ) ) { - my $found = 0; - - # search for index of item that matches $keyfield - foreach my $index ( 0 .. scalar( @{$fields} ) ) { - if ( $fields->[$index] eq $keyfield ) { - $found++; + # get the case conversion to use for hash key names (NAME/NAME_lc/NAME_uc) + my $hash_key_name = $sth->{Database}->FETCH('FetchHashKeyName') || 'NAME'; - # now make the keyfield the index - $keyfield = $index; - - # and jump out of the loop :) - last; - } + # get a hashref mapping field names to their corresponding indexes. indexes + # start at zero + my $names_hash = $sth->FETCH("${hash_key_name}_hash"); + + # as of DBI v1.48, the $keyfield argument can be either an arrayref of field + # names/indexes or a single field name/index + my @key_fields = ref $keyfield ? @{$keyfield} : $keyfield; + + my $num_fields = $sth->FETCH('NUM_OF_FIELDS'); + + # get the index(es) of the given key field(s). a key field can be specified + # as either the name of a field or an integer column number + my @key_indexes; + foreach my $field (@key_fields) { + if (defined $names_hash->{$field}) { + push @key_indexes, $names_hash->{$field}; + } + elsif (DBI::looks_like_number($field) && $field >= 1 && $field <= $num_fields) { + # convert from column number to array index. column numbers start at + # one, while indexes start at zero + push @key_indexes, $field - 1; } - unless ($found) { - $dbh->set_err( 1, "Could not find key field '$keyfield'" ); + else { + my $err = "Could not find key field '$field' (not one of " . + join(' ', keys %{$names_hash}) . ')'; + $dbh->set_err( 1, $err ); return; } } + my $tracker = $sth->FETCH('mock_my_history'); + my $rethash = {}; + # now loop through all the records ... while ( my $record = $tracker->next_record() ) { - # copy the values so as to preserve - # the original record... - my @values = @{$record}; - - # populate the hash - $rethash->{ $record->[$keyfield] } = - { map { $_ => shift(@values) } @{$fields} }; + # populate the hash, adding a layer of nesting for each key field + # specified by the user + my $ref = $rethash; + foreach my $index (@key_indexes) { + my $value = $record->[$index]; + $ref->{$value} = {} if ! defined $ref->{$value}; + $ref = $ref->{$value}; + } + + # copy all of the returned data into the most-nested level of the hash + foreach my $field (keys %{$names_hash}) { + my $index = $names_hash->{$field}; + $ref->{$field} = $record->[$index]; + } } return $rethash; diff -rupN DBD-Mock-1.45/t/024_selcol_fetchhash.t DBD-Mock-1.45-new/t/024_selcol_fetchhash.t --- DBD-Mock-1.45/t/024_selcol_fetchhash.t 2011-06-30 21:15:57.000000000 -0500 +++ DBD-Mock-1.45-new/t/024_selcol_fetchhash.t 2018-08-13 12:03:31.000000000 -0500 @@ -3,7 +3,7 @@ use 5.006; use strict; use warnings; -use Test::More tests => 8; +use Test::More tests => 11; BEGIN { use_ok('DBD::Mock'); @@ -88,8 +88,27 @@ is_deeply( '... selectall_hashref worked correctly'); is_deeply( - $dbh->selectall_hashref($items_sql, 1, "Checking selectall_hashref with named key."), + $dbh->selectall_hashref($items_sql, 2, "Checking selectall_hashref with numeric key."), { 'coconuts' => $coco_hash, 'not coconuts' => $not_coco_hash, }, '... selectall_hashref worked correctly'); + +is_deeply( + $dbh->selectall_hashref($items_sql, ['id', 'name'], "Checking selectall_hashref with array of named keys."), + { 2 => { 'coconuts' => $coco_hash, }, + 42 => { 'not coconuts' => $not_coco_hash }, + }, + '... selectall_hashref worked correctly'); + +is_deeply( + $dbh->selectall_hashref($items_sql, [1, 2], "Checking selectall_hashref with array of numeric keys."), + { 2 => { 'coconuts' => $coco_hash, }, + 42 => { 'not coconuts' => $not_coco_hash }, + }, + '... selectall_hashref worked correctly'); + +is_deeply( + $dbh->selectall_hashref($items_sql, [], "Checking selectall_hashref with empty array of keys."), + { %{$not_coco_hash} }, + '... selectall_hashref worked correctly');
On Mon Aug 13 13:54:50 2018, MHC wrote: Show quoted text
> On Thu Jun 20 17:19:37 2013, pianohacker@gmail.com wrote:
> > From the DBI documentation: > > > > For queries returning more than one 'key' column, you can specify > > multiple column names by passing $key_field as a reference to an > > array > > containing one or more key column names (or > > index numbers). For example: > > > > $sth = $dbh->prepare("SELECT foo, bar, baz FROM table"); > > $sth->execute; > > $hash_ref = $sth->fetchall_hashref( [ qw(foo bar) ] ); > > print "For foo 42 and bar 38, baz is > > $hash_ref->{42}->{38}->{baz}\n" > >
> > I've attached a patch that adds support for multiple key fields. It > also fixes a bug when the key field is given as a number instead of a > column name: numeric key fields are one-indexed, but the code was > assuming they are zero-indexed. > > This patch is based on the implementation of fetchall_hashref in DBI > v1.641 (https://metacpan.org/source/TIMB/DBI-1.641/DBI.pm#L2101). > Support for multiple key fields was introduced in DBI v1.48; previous > versions choke if you pass an arrayref. If DBD::Mock needs to mimic > that behavior for older versions of DBI, this patch will need to be > tweaked.
I've recently picked up the maintenance of DBD::Mock, and I've just applied your patch to the codebase, so this issue should be resolved when v1.47 gets released. Regards, Jason.
Hi, Just to let you know that Version 1.47 of DBD::Mock has gone live, which contains MHC's patch. So this issue should now be resolved. Regards, Jason.