...
 
Commits (2)
......@@ -878,6 +878,75 @@ All functionality listed here is highly experimental and should be used with gre
$dbh->{mock_clear_table_info} = 1;
- Result Set Callbacks
If you need your result sets to be more dynamic (e.g. if they need to return different results based upon a bound parameter) then you can use a callback.
$dbh->{mock_add_resultset} = {
sql => 'SELECT a FROM b WHERE c = ?',
callback => sub {
my @bound_params = @_;
my %result = (
fields => [ "a" ],
rows => [[ 1] ]
);
if ($bound_params[0] == 1) {
$result{rows} = [ [32] ];
} elsif ($bound_params[0] == 2) {
$result{rows} = [ [43] ];
}
return %result;
},
};
my $sth = $dbh->prepare('SELECT a FROM b WHERE c = ?');
my $rows = $sth->execute(1);
my ($result) = $sth->fetchrow_array(); # $result will be 32
$rows = $sth->execute(2);
($result) = $sth->fetchrow_array(); # $result this time will be 43
$rows = $sth->execute(33); # $results this time will be 1
($result) = $sth->fetchrow_array();
The callback needs to return a hash with a `rows` key that is an array ref of arrays containing the values to return as the answer to the query. In addition a `fields` key can also be returned with an array ref of field names. If a `fields` key isn't present in the returned the hash then the fields will be taken from the `mock_add_resultset`'s `results` parameter.
$dbh->{mock_add_resultset} = {
sql => 'SELECT x FROM y WHERE z = ?',
results => [ ["x"] ],
callback => sub {
my @bound_params = @_;
my %result = ( rows => [[ 1] ] );
if ($bound_params[0] == 1) {
$result{rows} = [ [32] ];
} elsif ($bound_params[0] == 2) {
$result{rows} = [ [43] ];
}
return %result;
},
};
my $sth = $dbh->prepare('SELECT x FROM y WHERE z = ?');
my $rows = $sth->execute(1);
my ($result) = $sth->fetchrow_array(); # $result will be 32
$rows = $sth->execute(2);
($result) = $sth->fetchrow_array(); # $result will be 43
$rows = $sth->execute(33);
($result) = $sth->fetchrow_array(); # $result will be 1
By default result sets which only define their field names in their callback return values will have a `NUM_OF_FIELDS` property of 0 until after the statement has actually been executed. This is to make sure that DBD::Mock stays compatible with previous versions. If you need the `NUM_OF_FIELDS` property to be undef in this situation then set the `$DBD::Mock::DefaultFieldsToUndef` flag to 1.
# BUGS
- Odd $dbh attribute behavior
......@@ -894,8 +963,6 @@ All functionality listed here is highly experimental and should be used with gre
I would like to have the DBD::Mock::StatementTrack object handle more of the mock\_\* attributes. This would encapsulate much of the mock\_\* behavior in one place, which would be a good thing.
I would also like to add the ability to bind a subroutine (or possibly an object) to the result set, so that the results can be somewhat more dynamic and allow for a more realistic interaction.
# SEE ALSO
[DBI](https://metacpan.org/pod/DBI)
......
......@@ -36,6 +36,10 @@ our $drh = undef; # will hold driver handle
our $err = 0; # will hold any error codes
our $errstr = ''; # will hold any error messages
# Defaulting a result set's fields to undef changes the way DBD::Mock responds, so we default it to off
our $DefaultFieldsToUndef = 0;
sub driver {
return $drh if defined $drh;
my ( $class, $attributes ) = @_;
......@@ -1071,6 +1075,78 @@ To clear the current mocked table info set the database handle's C<mock_clear_ta
$dbh->{mock_clear_table_info} = 1;
=item Result Set Callbacks
If you need your result sets to be more dynamic (e.g. if they need to return different results based upon a bound parameter) then you can use a callback.
$dbh->{mock_add_resultset} = {
sql => 'SELECT a FROM b WHERE c = ?',
callback => sub {
my @bound_params = @_;
my %result = (
fields => [ "a" ],
rows => [[ 1] ]
);
if ($bound_params[0] == 1) {
$result{rows} = [ [32] ];
} elsif ($bound_params[0] == 2) {
$result{rows} = [ [43] ];
}
return %result;
},
};
my $sth = $dbh->prepare('SELECT a FROM b WHERE c = ?');
my $rows = $sth->execute(1);
my ($result) = $sth->fetchrow_array(); # $result will be 32
$rows = $sth->execute(2);
($result) = $sth->fetchrow_array(); # $result this time will be 43
$rows = $sth->execute(33); # $results this time will be 1
($result) = $sth->fetchrow_array();
The callback needs to return a hash with a C<rows> key that is an array ref of arrays containing the values to return as the answer to the query. In addition a C<fields> key can also be returned with an array ref of field names. If a C<fields> key isn't present in the returned the hash then the fields will be taken from the C<mock_add_resultset>'s C<results> parameter.
$dbh->{mock_add_resultset} = {
sql => 'SELECT x FROM y WHERE z = ?',
results => [ ["x"] ],
callback => sub {
my @bound_params = @_;
my %result = ( rows => [[ 1] ] );
if ($bound_params[0] == 1) {
$result{rows} = [ [32] ];
} elsif ($bound_params[0] == 2) {
$result{rows} = [ [43] ];
}
return %result;
},
};
my $sth = $dbh->prepare('SELECT x FROM y WHERE z = ?');
my $rows = $sth->execute(1);
my ($result) = $sth->fetchrow_array(); # $result will be 32
$rows = $sth->execute(2);
($result) = $sth->fetchrow_array(); # $result will be 43
$rows = $sth->execute(33);
($result) = $sth->fetchrow_array(); # $result will be 1
By default result sets which only define their field names in their callback return values will have a C<NUM_OF_FIELDS> property of 0 until after the statement has actually been executed. This is to make sure that DBD::Mock stays compatible with previous versions. If you need the C<NUM_OF_FIELDS> property to be undef in this situation then set the C<$DBD::Mock::DefaultFieldsToUndef> flag to 1.
=back
=head1 BUGS
......@@ -1095,8 +1171,6 @@ Each DBD has its own quirks and issues, it would be nice to be able to handle th
I would like to have the DBD::Mock::StatementTrack object handle more of the mock_* attributes. This would encapsulate much of the mock_* behavior in one place, which would be a good thing.
I would also like to add the ability to bind a subroutine (or possibly an object) to the result set, so that the results can be somewhat more dynamic and allow for a more realistic interaction.
=back
=head1 SEE ALSO
......
......@@ -9,11 +9,12 @@ sub new {
# these params have default values
# but can be overridden
$params{return_data} ||= [];
$params{fields} ||= [];
$params{fields} ||= $DBD::Mock::DefaultFieldsToUndef ? undef : [];
$params{bound_params} ||= [];
$params{bound_param_attrs} ||= [];
$params{statement} ||= "";
$params{failure} ||= undef;
$params{callback} ||= undef;
# these params should never be overridden
# and should always start out in a default
......@@ -43,7 +44,7 @@ sub get_failure {
sub num_fields {
my ($self) = @_;
return scalar @{ $self->{fields} };
return $self->{fields} ? scalar @{ $self->{fields} } : $self->{fields};
}
sub num_rows {
......@@ -121,6 +122,7 @@ sub is_finished {
sub mark_executed {
my ($self) = @_;
push @{$self->{execution_history} }, {
params => [ @{ $self->{bound_params} } ],
attrs => [ @{ $self->{bound_param_attrs} } ],
......@@ -128,6 +130,18 @@ sub mark_executed {
$self->is_executed('yes');
$self->current_record_num(0);
if (ref $self->{callback} eq "CODE") {
my %recordSet = $self->{callback}->(@{ $self->{bound_params} });
if (ref $recordSet{fields} eq "ARRAY") {
$self->{fields} = $recordSet{fields};
}
if (ref $recordSet{rows} eq "ARRAY") {
$self->{return_data} = $recordSet{rows};
}
}
}
sub next_record {
......@@ -181,6 +195,12 @@ sub current_record_num {
return $self->{current_record_num};
}
sub callback {
my ( $self, $callback ) = @_;
$self->{callback} = $callback if defined $callback;
return $self->{callback};
}
# multi-element fields
sub return_data {
......@@ -191,7 +211,11 @@ sub return_data {
sub fields {
my ( $self, @values ) = @_;
$self->{fields} ||= [];
push @{ $self->{fields} }, @values if scalar @values;
return $self->{fields};
}
......
......@@ -111,10 +111,12 @@ sub prepare {
# If we have available resultsets seed the tracker with one
my $rs;
my $callback;
if ( my $all_rs = $dbh->{mock_rs} ) {
if ( my $by_name = defined $all_rs->{named}{$statement} ? $all_rs->{named}{$statement} : first { $statement =~ m/$_->{regexp}/ } @{ $all_rs->{matching} } ) {
# We want to copy this, because it is meant to be reusable
$rs = [ @{ $by_name->{results} } ];
$callback = $by_name->{callback};
if ( exists $by_name->{failure} ) {
$track_params{failure} = [ @{ $by_name->{failure} } ];
}
......@@ -124,12 +126,17 @@ sub prepare {
}
}
if ( ref($rs) eq 'ARRAY' && scalar( @{$rs} ) > 0 ) {
if ( ref($rs) eq 'ARRAY' && ( scalar( @{$rs} ) > 0 || $callback ) ) {
my $fields = shift @{$rs};
$track_params{return_data} = $rs;
$track_params{fields} = $fields;
$sth->STORE( NAME => $fields );
$sth->STORE( NUM_OF_FIELDS => scalar @{$fields} );
$track_params{callback} = $callback;
if( $fields ) {
$sth->STORE( NAME => $fields );
$sth->STORE( NUM_OF_FIELDS => scalar @{$fields});
}
}
else {
$sth->trace_msg( "No return data set in DBH\n", 1 );
......@@ -354,15 +361,16 @@ sub STORE {
"as hashref key to 'mock_add_resultset'.\n";
}
my @copied_values = @{ $value->{results} };
my @copied_values = @{ $value->{results} ? $value->{results} : [] };
if ( ref $name eq "Regexp" ) {
push @{ $dbh->{mock_rs}{matching} }, {
regexp => $name,
results => \@copied_values,
callback => $value->{callback},
};
} else {
$dbh->{mock_rs}{named}{$name} = { results => \@copied_values, };
$dbh->{mock_rs}{named}{$name} = { results => \@copied_values, callback => $value->{callback} };
}
if ( exists $value->{failure} ) {
......
......@@ -120,6 +120,9 @@ sub execute {
$tracker->mark_executed;
my $fields = $tracker->fields;
$sth->STORE( NUM_OF_FIELDS => scalar @{ $fields ? $fields : [] } );
$sth->STORE( NAME => $fields );
$sth->STORE( NUM_OF_PARAMS => $tracker->num_params );
# handle INSERT statements and the mock_last_insert_ids
......
......@@ -30,12 +30,12 @@ $dbh->{mock_add_resultset} = {
my $rows = $sth->execute();
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
my ($result) = $sth->fetchrow_array();
is($result, 10, '... got the result we expected');
$sth->finish();
}
......@@ -50,11 +50,11 @@ $dbh->{mock_add_resultset} = {
my $rows = $sth->execute();
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
my ($result) = $sth->fetchrow_array();
is($result, 50, '... got the result we expected');
$sth->finish();
}
......@@ -65,11 +65,11 @@ $dbh->{mock_add_resultset} = {
my $rows = $sth->execute();
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
my ($result) = $sth->fetchrow_array();
is($result, 50, '... got the result we expected');
$sth->finish();
}
......@@ -84,7 +84,7 @@ $dbh->{mock_add_resultset} = {
my ($result) = $sth->fetchrow_array();
is($result, 50, '... got the result we expected');
$sth->finish();
}
......@@ -100,7 +100,7 @@ $dbh->{mock_add_resultset} = {
results => [ [ 'foo' ], [ 300 ] ],
};
{
{
my $sth = $dbh->prepare('SELECT foo FROM oof');
isa_ok($sth, 'DBI::st');
......@@ -164,4 +164,137 @@ $dbh->{mock_add_resultset} = {
}
$dbh->{mock_add_resultset} = {
sql => 'SELECT x FROM y WHERE z = ?',
results => [ ["x"] ],
callback => sub {
my @bound_params = @_;
my %result = ( rows => [[ 1] ] );
if ($bound_params[0] == 1) {
$result{rows} = [ [32] ];
} elsif ($bound_params[0] == 2) {
$result{rows} = [ [43] ];
}
return %result;
},
};
{
my $sth = $dbh->prepare('SELECT x FROM y WHERE z = ?');
isa_ok($sth, 'DBI::st');
is($sth->{NUM_OF_FIELDS}, 1, "... When we specify the fields in the results parameter then we expect an answer from NUM_OF_FIELDS before we execute the statement");
my $rows = $sth->execute(1);
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
is($sth->{NUM_OF_FIELDS}, 1, "... When we specify the fields in the results parameter then we expect an answer from NUM_OF_FIELDS after we execute the statement");
my ($result) = $sth->fetchrow_array();
is($result, 32, '... got the result we expected');
$rows = $sth->execute(2);
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
($result) = $sth->fetchrow_array();
is($result, 43, '... got the result we expected');
$rows = $sth->execute(33);
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
($result) = $sth->fetchrow_array();
is($result, 1, '... got the result we expected');
$sth->finish();
}
$dbh->{mock_add_resultset} = {
sql => 'SELECT a FROM b WHERE c = ?',
callback => sub {
my @bound_params = @_;
my %result = (
fields => [ "a" ],
rows => [[ 1] ]
);
if ($bound_params[0] == 1) {
$result{rows} = [ [32] ];
} elsif ($bound_params[0] == 2) {
$result{rows} = [ [43] ];
}
return %result;
},
};
{
my $sth = $dbh->prepare('SELECT a FROM b WHERE c = ?');
isa_ok($sth, 'DBI::st');
is($sth->{NUM_OF_FIELDS}, 0 , "... When we don't specify the fields in the results parameter and we haven't activated the DefaultFieldsToUndef feature, then we expect the NUM_OF_FIELDS to be 0 before we execute the statement");
my $rows = $sth->execute(1);
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
is($sth->{NUM_OF_FIELDS}, 1, "... When we don't specify the fields in the results parameter then we still expect an answer from NUM_OF_FIELDS after we've execute the statement");
my ($result) = $sth->fetchrow_array();
is($result, 32, '... got the result we expected');
$rows = $sth->execute(2);
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
($result) = $sth->fetchrow_array();
is($result, 43, '... got the result we expected');
$rows = $sth->execute(33);
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
($result) = $sth->fetchrow_array();
is($result, 1, '... got the result we expected');
$sth->finish();
}
{
# Activate the DefaultFieldsToUndef feature
$DBD::Mock::DefaultFieldsToUndef = 1;
my $sth = $dbh->prepare('SELECT a FROM b WHERE c = ?');
isa_ok($sth, 'DBI::st');
is($sth->{NUM_OF_FIELDS}, undef , "... When we don't specify the fields in the results parameter then we expect the NUM_OF_FIELDS to be undef before we execute the statement");
my $rows = $sth->execute(1);
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
is($sth->{NUM_OF_FIELDS}, 1, "... When we don't specify the fields in the results parameter then we still expect an answer from NUM_OF_FIELDS after we've execute the statement");
my ($result) = $sth->fetchrow_array();
is($result, 32, '... got the result we expected');
$rows = $sth->execute(2);
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
($result) = $sth->fetchrow_array();
is($result, 43, '... got the result we expected');
$rows = $sth->execute(33);
is($rows, '0E0', '... got back 0E0 for rows with a SELECT statement');
($result) = $sth->fetchrow_array();
is($result, 1, '... got the result we expected');
$sth->finish();
}
done_testing();