Using Test::MockDBI multiple times with different results - database

I'm trying to test some code in different situations (for different result sets). I've got the first test running well, but the next one is trying to reuse the first "table".
My result sets:
my $usernames_many = [
{ username => '1234567' },
{ username => '2345678' },
];
my $usernames_empty = [
];
but now when I try these calls:
$mock_dbi->set_retval_scalar(MOCKDBI_WILDCARD, "SELECT username FROM location", $usernames_many);
is_deeply(find_multiple_registrations($mock_db, 15), [ '1234567', '2345678' ], "many entries");
$mock_dbi->set_retval_scalar(MOCKDBI_WILDCARD, "SELECT username FROM location", $usernames_empty);
is_deeply(find_multiple_registrations($mock_db, 15), [ ], "no entries");
The first test passes, but the second one results in:
not ok 3 - no entries
# Failed test 'no entries'
# at ./report_many_registrations_test.pl line 28.
# Structures begin differing at:
# $got->[0] = '1234567'
# $expected->[0] = Does not exist
Which seems to indicate the first resultset was used again instead. How can I clean a resultset? Or reset the state in some other way?

The implementation of set_retval_scalar may at first appear discouraging:
sub set_retval_scalar {
my $self = shift; # my blessed self
my $type = shift; # type number from --dbitest=TYPE
my $sql = shift; # SQL pattern for badness
push #{ $scalar_retval{$type} },
{ "SQL" => $sql, "retval" => $_[0] };
}
The reason the first resultset appeared to be used again is successive calls to set_retval_scalar are cumulative. After the second call to set_retval_scalar, just before the second test, the internal bookkeeping for Test::MockDBI resembles
[ # first resultset
{ SQL => "SELECT username ...",
retval => [{ username => '1234567' }, ...]
},
# second resultset
{ SQL => "SELECT username ...",
retval => []
}
]
Under the hood when your second test queries SELECT username ..., _force_retval_scalar in Test::MockDBI searches this data structure for the currently executing query and stops on the first hit it finds. Both resultsets are associated with the same query, so the second doesn't have a chance to match.
But there's hope! Notice that set_retval_scalar copies only the outermost reference—a reference to an array that you control!
Modify your test slightly:
my #usernames_many = (
{ username => '1234567' },
{ username => '2345678' },
);
my #usernames_empty = ();
my $usernames = [];
$mock_dbi->set_retval_scalar(
MOCKDBI_WILDCARD,
"SELECT username FROM location",
$usernames);
With this fixture, you need only change the contents of #$usernames (that is, the array referred to by $usernames) to change the canned result of the query:
#$usernames = #usernames_many;
is_deeply(find_multiple_registrations($mock_db, 15),
[ '1234567', '2345678' ],
"many entries");
#$usernames = #usernames_empty;
is_deeply(find_multiple_registrations($mock_db, 15),
[ ],
"no entries");
With these modifications, both tests pass.
IMPORTANT: Always assign to #$usernames! You may be tempted to save a few keystrokes by writing
$usernames = []; # empty usernames
is_deeply(find_multiple_registrations($mock_db, 15),
[ ],
"no entries");
but this will cause your test to fail for nearly the same reason as the test from your question: the fixture will continue to have the same reference that you gave it in the call to set_retval_scalar. Doing it this way would be both incorrect and misleading, a nasty combination.
For completeness, a full working example is below.
#! /usr/bin/perl
use warnings;
use strict;
BEGIN { push #ARGV, "--dbitest" }
use Test::MockDBI qw/ :all /;
use Test::More tests => 2;
my #usernames_many = (
{ username => '1234567' },
{ username => '2345678' },
);
my #usernames_empty = ();
my $usernames = [];
my $mock_dbi = get_instance Test::MockDBI;
my $mock_db = DBI->connect("dbi:SQLite:dbname=:memory:", "", "");
$mock_db->{RaiseError} = 1;
$mock_db->do(q{CREATE TABLE location (username char(10))});
sub find_multiple_registrations {
my($dbh,$limit) = #_;
my $sth = $dbh->prepare("SELECT username FROM location");
$sth->execute;
[ map $_->{username} => #{ $sth->fetchall_arrayref } ];
}
$mock_dbi->set_retval_scalar(
MOCKDBI_WILDCARD,
"SELECT username FROM location",
$usernames);
#$usernames = #usernames_many;
is_deeply(find_multiple_registrations($mock_db, 15),
[ '1234567', '2345678' ],
"many entries");
#$usernames = ();
is_deeply(find_multiple_registrations($mock_db, 15),
[ ],
"no entries");
Output:
1..2
connect() 'CONNECT TO dbi:SQLite:dbname=:memory: AS WITH '
do() 'CREATE TABLE location (username char(10))'
prepare() 'SELECT username FROM location'
execute()
fetchall_arrayref()
ok 1 - many entries
prepare() 'SELECT username FROM location'
execute()
fetchall_arrayref()
ok 2 - no entries

If you (are able to) change the second test to something like:
$mock_dbi->set_retval_scalar(
MOCKDBI_WILDCARD,
"Get me username stuff", # <= something different
$usernames_empty
);
then you may find that the test now works.
This is because Test::MockDBI only uses the SQL text provided has a placeholder for which it returns the DBI object after a matching dbi->prepare( 'Get me username stuff' );
Update - Here is a workaround that doesn't require changing the SQL:
BEGIN { push #ARGV, "--dbitest=1"; }
use 5.012;
use warnings;
use Test::More;
use Test::MockDBI ':all';
my $mock_dbi = Test::MockDBI::get_instance;
my $dbh = DBI->connect(q{}, q{}, q{});
my $sql = 'SELECT username FROM location';
my $Data = [
{ username => '1234567' },
{ username => '2345678' },
];
$mock_dbi->set_retval_scalar( MOCKDBI_WILDCARD, $sql, sub { $Data } );
is_deeply( get_mock_user($dbh, $sql), [1234567,2345678], 'many entries' );
$Data = []; # change the data!
is_deeply( get_mock_user($dbh, $sql), [], 'no entries' );
done_testing;
sub get_mock_user {
my ($dbh, $sql) = #_;
$dbh->prepare( $sql );
[ map { $_->{username} } #{ $dbh->fetchrow_arrayref } ];
}
/I3az/

Related

How to combine two different tables into one in codeigniter?

In this code, I have two different tables i.e. skill_master and jobs_category. Now, I want to get these two different table data into one and also convert its data into JSON format using json_encode.
$this->db->select('category');
$this->db->from('jobs_category');
$this->db->order_by('category');
$query1 = $this->db->get();
$result1 = $query1->result_array();
$this->db->select('key_skills');
$this->db->from('skill_master');
$this->db->order_by('key_skills');
$query2 = $this->db->get();
$result2 =$query2->result_array();
$arr = array();
foreach($result1 as $row)
{
foreach($result2 as $rows)
{
$arr[] = $row['category'].','.$rows['skill_master'];
}
}
$json = json_encode($arr);
echo $json;
For example:
table1: skill_master
key_skills
==========
java
php
dot net
table2: jobs_category
category
========
IT Jobs
Air line Jobs
Hardware Jobs
Now, Here I have two tables here. Now, I want to combine these two tables and want data in JSON format like ["java", "PHP", "dot net", "IT Jobs", "Air Line Jobs", "Hardware Jobs"]. So, How can I do this? Please help me.
Thank You
$this->db->select('category');
$this->db->from('jobs_category');
$this->db->order_by('category');
$query_category= $this->db->get();
$result_category = $query_category->result_array();
$this->db->select('key_skills');
$this->db->from('skill_master');
$this->db->order_by('key_skills');
$query_skills = $this->db->get();
$result_skills =$query_skills->result_array();
If You get records from table jobs_category and skill_master like this
$result_category =
[
'0' => ['category' => 'IT Jobs'],
'1' => ['category' => 'Air line Jobs'],
'2' => ['category' => 'Hardware Jobs']
];
$result_skills =
[
'0' => ['skill_master' => 'java'],
'1' => ['skill_master' => 'php'],
'2' => ['skill_master' => 'dot net']
];
$final_arr = $final_category_arr = $final_skill_arr = [];
foreach($result_category as $category_row)
{
$final_category_arr[] = $category_row['category'];
}
foreach($result_skills as $skill_row)
{
$final_skill_arr[] = $skill_row['skill_master'];
}
$final_arr = array_merge($final_category_arr, $final_skill_arr);
$json = json_encode($final_arr);
echo $json;
Result will be like this
["IT Jobs","Air line Jobs","Hardware Jobs","java","php","dot net"]
renaming column while fetching and merging data should work. try following code
$this->db->select('category');
$this->db->from('jobs_category');
$this->db->order_by('category');
$query_category= $this->db->get();
$result_category = $query_category->result_array();
$this->db->select('key_skills as category');
$this->db->from('skill_master');
$this->db->order_by('key_skills');
$query_skills = $this->db->get();
$result_skills =$query_skills->result_array();
$result = array_merge($result_category,$result_skills);

Why assigning list containing array to array itself causes recursion in perl?

I'm using DBIX::Class and generating conditions for search like that:
my #array;
push #array, { condition1 => 'value1' };
push #array, [ { condition2 => 'value2' }, { condition3 => 'value3' } ];
All this conditions must be checked using AND operator, that's why I wrote this:
#array = ( -and => #array );
After running code with such conditions process on my virtual machine started to use up to 8 Gb memory. I thought that it was recursion problems and I didn't mistake. I checked logs and saw records about deep recursion but I couldn't find anything about my case in internet.
Is there problems with assigning list containing array to array itself?
Or maybe it is a problem with DBIX::Class (SQL::Abstract)? Why it causes deep recursion?
Update. This is the real code from project:
sub faq {
my ( $self ) = #_;
my #cond;
if ( $self->param('faq_type') ) {
push #cond,
{
'me.faq_type' => $self->param('faq_type'),
};
}
if ( my $search = $self->param('search') ) {
push #cond,
[
'me.title' => { ilike => "%$search%" },
'me.text' => { ilike => "%$search%" },
];
}
#cond = ( -and => #cond );
my %attr = (
join => 'page_category',
rows => $self->param('limit'),
offset => $self->param('offset'),
order_by => { -desc => 'id' },
result_class => 'BUX::Util::HashRefInflator',
'+select' => [ qw( page_category.name ) ],
'+as' => [ qw( category_name ) ],
);
my #pages = BUX::DB->rs('Page')->search( \#cond, \%attr )->all;
my $total_count = BUX::DB->rs('Page')->count( \#cond );
return $self->render(json => {
pages => \#pages,
count => $total_count
});
}
And log records:
Deep recursion on subroutine "SQL::Abstract::_SWITCH_refkind" at /opt/perlbrew/perls/perl-5.14.4/lib/site_perl/5.14.4/SQL/Abstract.pm line 719.
Deep recursion on subroutine "SQL::Abstract::_recurse_where" at /opt/perlbrew/perls/perl-5.14.4/lib/site_perl/5.14.4/SQL/Abstract.pm line 546.
Deep recursion on subroutine "SQL::Abstract::_where_ARRAYREF" at /opt/perlbrew/perls/perl-5.14.4/lib/site_perl/5.14.4/SQL/Abstract.pm line 687.
Deep recursion on subroutine "SQL::Abstract::_where_HASHREF" at /opt/perlbrew/perls/perl-5.14.4/lib/site_perl/5.14.4/SQL/Abstract.pm line 493.
Deep recursion on subroutine "SQL::Abstract::_where_unary_op" at /opt/perlbrew/perls/perl-5.14.4/lib/site_perl/5.14.4/SQL/Abstract.pm line 596.
Deep recursion on subroutine "SQL::Abstract::_where_op_ANDOR" at /opt/perlbrew/perls/perl-5.14.4/lib/site_perl/5.14.4/SQL/Abstract.pm line 645.
P.S. BUX::DB is the subclass of DBIx::Class and rs is a shortcut for resultset.
When specifying several conditions that should all be met to search
with DBIx::Class, the usual way to do this is by passing a hashref
with the column names as keys and the conditions as values.
While it is possible to instead specify an arrayref of hashrefs with the '-and' keyword, this is most often unnecessary - especially if you only have one condition to specify!
NOTE: I am not certain { -and => #cond } does what you want, have you tried replacing it with { -and => \#cond } ( Note the arrayref) ? This could be the reason why SQL::Abstract gets confused, though I'm unsure how that would end up being a recursion.
SECOND NOTE: I find #cond = ( -and => \#cond ) confusing and it may cause trouble. I would suggest working with a hashref passed into search, as it should be called with, and setting the -and key instead, by adapting my first example.
This is how I would specify the conditions:
my $cond;
if ( my $faq_type = $self->param('faq_type') ){
$cond->{'me.faq_type'} = $faq_type;
}
if ( my $search = $self->param('search') ){
$cond->{-or} = [
{ $cond->{'me.title'} = { ilike => '%$search%' }, },
{ $cond->{'me.text' } = { ilike => '%$search%' }, },
];
}
An alternative to consider, would be to first specify the 'faq_type' search and store the resulting rs, to then refine it further as necessary, this seems more in line with the spirit of DBIx::Class to me:
my $pages_rs = BUX::DB->rs('Page');
if ( my $faq_type = $self->param('faq_type') ){
$pages_rs = $pages_rs->search({ 'me.faq_type' => $faq_type });
}
if ( my $search = $self->param('search') ){
$pages_rs = $pages_rs->search({
-or => [
'me.title' => { ilike => "%$search%" },
'me.text' => { ilike => "%$search%" },
];
});
}
my %attr = (
join => 'page_category',
rows => $self->param('limit'),
offset => $self->param('offset'),
order_by => { -desc => 'id' },
result_class => 'BUX::Util::HashRefInflator',
'+select' => [ qw( page_category.name ) ],
'+as' => [ qw( category_name ) ],
);
$pages_rs = $pages_rs->search( undef, \%attr );
my #pages = $pages_rs->all; # This executes the query
Please keep in mind this is untested as I currently don't have an easy way of verifying this. If this does not help, feel free to comment and I'll try and fix whatever may be off.
EDIT: to not leave something in that is wrong, I've removed the (irrelevant) page count I put in.

Strange behaviour of Perl's push function

I am writing a dedicated ICS (iCalendar file) parser.
I pass an array to a subroutine. All variables are single values apart from $notdates which is a comma-separated list of dates.
#entryl = ($dtstart, $dtend, $attendee, $lastmod, $uid, $notdates);
&entrytoarray(#entryl);
sub entrytoarray {
# print Dumper #_;
my $shiftdur = (&stamptoepoc($_[1]) - &stamptoepoc($_[0])) / 60 / 60;
my $attendee = $_[2];
my $deleted = $_[5];
$attendee =~ /ATTENDEE;USER-KEY=([^;]*);CN=([^;]*);.*:(.*)/;
my %ehash = (
"STARTDATE" , &stamptodate($_[0]),
"ENDDATE" , &stamptodate($_[1]),
"STARTSTAMP" , $_[0],
"ENDSTAMP" , $_[1],
"USERKEY" , $1,
"CN" , $2,
"EMAIL" , $3,
"LASTMOD" , $_[3],
"UID" , $_[4],
"DURATION" , $shiftdur
);
# Only keep current data
my $fdays = 4;
my $tdays = 7;
chomp(my $curstamp = `TZ="UTC" date -d "$fdays days" +"%Y%m%d%H%M00"`);
chomp(my $stpstamp = `TZ="UTC" date -d "$tdays days" +"%Y%m%d%H%M00"`);
if (($_[0] > $curstamp) && ($_[1] < $stpstamp)) {
if (defined($deleted)) {
my #deleted = split /,/, $deleted;
foreach (#deleted) {
if ($_ ne $_[0]) {
push(#entry, \%ehash);
}
}
}
else {
push(#entry, \%ehash);
}
}
print Dumper #entry;
This works mostly as expected:
$VAR1 = {
'DURATION' => '5',
'STARTSTAMP' => '20141122230000',
'UID' => '20141114T010539Z--1092363482',
'LASTMOD' => '20141118214419',
'STARTDATE' => '2014-11-22 23:00:00',
'EMAIL' => 'xxxxxxxxxxxxx',
'ENDDATE' => '2014-11-23 04:00:00',
'CN' => 'xxxxxxxxxxx',
'ENDSTAMP' => '20141123040000',
'KEY' => 'xxxxxxxxxxxxxxxxxx'
};
$VAR2 = {
'EMAIL' => 'xxxxxxxxxxxxx',
'ENDDATE' => '2014-11-23 23:00:00',
'ENDSTAMP' => '20141123230000',
'KEY' => 'xxxxxxxxxxx',
'CN' => 'xxxxxxxxxxxxxx',
'STARTDATE' => '2014-11-23 19:00:00',
'LASTMOD' => '20141118205901',
'UID' => '20141114T010456Z--1092363482',
'DURATION' => '4',
'STARTSTAMP' => '20141123190000'
};
$VAR3 = $VAR2;
Where is the $VAR3 = $VAR2 coming from?
My guess is that this section is the culprit:
foreach (#deleted) {
if ($_ ne $_[0]) {
push(#entry, \%ehash);
}
}
If you have several values in the array, the if-statement can be true twice, and thus push a value twice. Unless this is wanted behaviour, I would make sure that only one value is pushed. You can do this by using grep instead:
if (grep { $_ ne $_[0] } #deleted) {
push #entry, \%ehash;
}
Note that this replaces the foreach loop.
Your array #entry contains hash references. Data::Dumper is saying that the first and second elements of the array refer to two different hashes, while the third refers to the same hash as the second.
You don't show where #entry comes from, but I would expect all three elements to be references to %ehash.
The problem is that, if you keep pushing a reference to %ehash onto #entry, they all point to the same data item, and the intermediate states of the hash won't be recorded.
Unless you mean entrytoarray to push only one copy of %ehash (in which case there's a separate problem that we can't see) you need to fix it by either writing
push #entry, { %ehash }
which copies the hash and returns a reference to the copy, or you can declare and populate %ehash inside the foreach loop, which will create a new hash each time around the loop.

Converting and printing array of hashes - Perl

I really dont know how to do it so I ended up here.
I want to convert this input:
my #sack_files_1 = (
'mgenv/1_2_3/parent.dx_environment',
'mgenv/1_2_3/doc/types.dat',
'u5env/1_2_3/parent.dx_environment',
'u5env/1_2_3/doc/types.dat',
);
To this:
my $sack_tree_1 = {
'mgenv' => {
'1_2_3' => [ 'parent.dx_environment', 'doc/types.dat' ],
},
'u5env' => {
'1_2_3' => [ 'parent.dx_environment', 'doc/types.dat' ],
}
};
Something like this should do the trick:
use strict;
use warnings;
use Data::Dumper;
my #sack_files_1 = (
'mgenv/1_2_3/parent.dx_environment',
'mgenv/1_2_3/doc/types.dat',
'u5env/1_2_3/parent.dx_environment',
'u5env/1_2_3/doc/types.dat',
);
my %sack_tree_1;
foreach (#sack_files_1) {
my ( $env, $number, #everything_else ) = split('/');
push( #{ $sack_tree_1{$env}{$number} }, join( "/", #everything_else ) );
}
print Dumper \%sack_tree_1
This will do as you ask. It uses File::Spec::Functions to split each path into its components.
The first two elements of the hash are used directly as hash keys, relying on autovivication to create the necessary hash elements.
A simple push to an implied array reference also autovivifies the lowest-level hash element.
I have used Data::Dump to display the resulting hash. It is not part of the core Perl installation and you may need to install it, but it is much superior to Data::Dumper.
use strict;
use warnings;
use File::Spec::Functions qw/ splitdir catfile /;
my #sack_files_1 = (
'mgenv/1_2_3/parent.dx_environment',
'mgenv/1_2_3/doc/types.dat',
'u5env/1_2_3/parent.dx_environment',
'u5env/1_2_3/doc/types.dat',
);
my %paths;
for my $path (#sack_files_1) {
my ($p1, $p2, #path) = splitdir $path;
push #{ $paths{$p1}{$p2} }, catfile #path;
}
use Data::Dump;
dd \%paths;
output
{
mgenv => { "1_2_3" => ["parent.dx_environment", "doc\\types.dat"] },
u5env => { "1_2_3" => ["parent.dx_environment", "doc\\types.dat"] },
}
my $sack_tree_1 = {};
foreach my $data (#sack_files_1) {
my #path = split '/', $data;
my ($file,$last_part) = pop #path, pop #path; # get the file name and last part of the path
my $hash_part = $sack_tree_1;
foreach my $path (#path) { # For every element in the remaining part of the path
$hash_part->{$path} //= {}; # Make sure we have a hash ref to play with
$hash_part = $hash_part->{$path} # Move down the hash past the current path element
}
$hash_part->{$last_part} = $file; # Add the file name to the last part of the path
}
This handles all path lengths of 2 or more

Invalid argument supplied for foreach() unpacking Facebook array in Laravel 4

I am trying to store the FBIDs of a Facebook user's friends in the column of a mysql database. I've tried looking up other answer on this issue, and I have tried to implement it (in Laravel 4). Here is what I have done:
In the Facebook.php file, one of the providers:
'friends' => 'https://graph.facebook.com/me/friends?access_token='.$token->access_token
In my Oauth2 Controller:
$friends_list = $user['friends'];
$friends_list_array = json_decode($friends_list,true);
$arr= $friends_list_array['data'];
$friend_ids_arr = array();
foreach($arr as $friend) {
$friend_ids_arr[] = $friend['id'];
}
$friend_ids = implode("," , $friend_ids_arr);
And then I want to store the $friend_ids object in a "text" column in my database. However, when running this, I keep getting the error: Invalid argument supplied for foreach()
But it is very clearly being supplied an array as it should. Is there something I'm not seeing? Thank you for your help.
Actually the returned result is a json, the returned object should look something like this
{
"id": "xxxxxxx",
"name": "Sheikh Heera",
"friends": {
"data": [
{ "name": "RaseL KhaN", "id": "xxx" },
{ "name": "Yizel Herrera", "id": "xxx" }
],
"paging": {
"next": "https://graph.facebook.com/xxx/friends?limit=..."
}
}
}
After you json_decode
$user = json_decode($user, true);
It should look something like
Array
(
[id] => xxxxxxx
[name] => Sheikh Heera
[friends] => Array
(
[data] => Array
(
[0] => Array
(
[name] => RaseL KhaN
[id] => xxx
)
[1] => Array
(
[name] => Yizel Herrera
[id] => xxx
)
)
[paging] => Array
(
[next] => https://graph.facebook.com/xxx/friends?limit=...
)
)
)
So, now you can
$friends_list = $user['friends'];
$data = $friends_list['data'];
Make sure your $data array is not empty and then loop
if(count($data)) {
$friend_ids_arr = array();
foreach($data as $friend) {
$friend_ids_arr[] = $friend['id'];
}
}
So, the foreach will run only when $data has items in it.
Update: It may help you
$url = "https://graph.facebook.com/me?fields=id,name,friends&access_token=YOUR_ACCESS_TOKEN";
$contents = json_decode(file_get_contents($url), true);
$friends = $contents['friends'];
$friend_ids_arr[]
foreach($friends['data'] as $friend)
{
$friend_ids_arr[] = $friend['id'];
}

Resources