File Coverage

Test/DBO.pm
Criterion Covered Total %
statement 412 509 80.9
branch 102 256 39.8
condition 25 66 37.8
subroutine 32 39 82.0
pod 0 17 0.0
total 571 887 64.3


line stmt bran cond sub pod time code
1 12     12   102411 use Test::More;
  12         1407191  
  12         188  
2              
3             package # Hide from PAUSE
4             Test::DBO;
5              
6 12     12   4460 use 5.014;
  12         41  
7 12     12   63 use warnings;
  12         38  
  12         881  
8 12     12   8228 use sigtrap qw(die normal-signals);
  12         21288  
  12         80  
9              
10 12     12   1716 use Scalar::Util qw(blessed reftype);
  12         28  
  12         802  
11 12     12   65 use Test::More;
  12         19  
  12         70  
12 12     12   10587 use DBIx::DBO;
  12         49  
  12         123  
13              
14             BEGIN {
15             # Set up DebugSQL if requested
16 12 50   12   84 if ($ENV{DBO_TEST_SQL}) {
17 0         0 diag "DBO_TEST_SQL=$ENV{DBO_TEST_SQL}";
18             }
19              
20             # Set up $Carp::Verbose if requested
21 12 50       86 if ($ENV{DBO_CARP_VERBOSE}) {
    50          
22 0         0 diag "DBO_CARP_VERBOSE=$ENV{DBO_CARP_VERBOSE}";
23 0         0 $Carp::Verbose = $ENV{DBO_CARP_VERBOSE};
24             } elsif ($ENV{AUTOMATED_TESTING}) {
25 12         27 $Carp::Verbose = 1;
26             }
27              
28             # Store the last SQL executed, and show debug info
29             DBIx::DBO->config(HookSQL => sub {
30 90         155 my $me = shift;
31 90         17674 my $loc = Carp::short_error_loc();
32 90         7903 my %i = Carp::caller_info($loc);
33 90         711 $me->config(LastSQL => [$i{'sub'}, @_]);
34 90 50       551 my $dbg = $ENV{DBO_TEST_SQL} or return;
35 0         0 my $trace;
36 0 0       0 if ($dbg > 1) {
37 0         0 $trace = "\t$i{sub_name} called at $i{file} line $i{line}\n";
38             # Add an extra stack level
39 0         0 %i = Carp::caller_info($loc - 1);
40 0         0 $trace = "\t$i{sub_name} called at $i{file} line $i{line}\n$trace";
41 0         0 $trace .= "\t$i{sub_name} called at $i{file} line $i{line}\n" while %i = Carp::caller_info(++$loc);
42             } else {
43 0         0 $trace = "\t$i{sub} called at $i{file} line $i{line}\n";
44             }
45 0         0 my $sql = shift;
46 0         0 Test::More::diag "RUN_SQL: $sql\nRUN_SQL: (".join(', ', map $me->rdbh->quote($_), @_).")\n".$trace;
47 12         254 });
48              
49             {
50 12     12   136 no warnings 'redefine';
  12         25  
  12         2905  
  12         23  
51             # Remove CARP_NOT during tests
52             package # Hide from PAUSE
53             DBIx::DBO;
54             *DBIx::DBO::croak =
55             *DBIx::DBO::Query::croak =
56             *DBIx::DBO::Table::croak =
57             *DBIx::DBO::Row::croak = sub {
58 42 50   42   401 local @DBIx::DBO::DBD::CARP_NOT = () if $Carp::Verbose;
59 42 50       119 local $Carp::CarpLevel = $Carp::CarpLevel + 1 if $Carp::Verbose;
60 42         9483 &Carp::croak;
61 12         103 };
62             # Fix SvREFCNT with Devel::Cover
63             package # Hide from PAUSE
64             DBIx::DBO::Query;
65             *DBIx::DBO::Query::SvREFCNT = sub (\[$@%&*]) {
66 80     80   121 return Devel::Peek::SvREFCNT(${$_[0]}) - 3;
  80         404  
67 12 50       9982 } if exists $INC{'Devel/Cover.pm'};
68             }
69             }
70              
71             our $dbd;
72             our $dbd_name;
73             our $test_db = "DBO_${DBIx::DBO::VERSION}_test_db" =~ s/\W/_/gr;
74             our $test_sch = "DBO_${DBIx::DBO::VERSION}_test_sch" =~ s/\W/_/gr;
75             our $test_tbl = "DBO_${DBIx::DBO::VERSION}_test_tbl" =~ s/\W/_/gr;
76             our @_cleanup_sql;
77             our $case_sensitivity_sql = 'SELECT ? LIKE ?';
78             our %can;
79             our $test_count = 117;
80              
81             sub import {
82 13     13   220 (my $class, $dbd, $dbd_name, my %opt) = @_;
83 13 50       66 defined $dbd or return;
84              
85 13 100       102 grep $_ eq $dbd, DBI->available_drivers
86             or plan skip_all => "No $dbd driver available!";
87              
88             # Catch install_driver errors
89 9         4399 eval { DBI->install_driver($dbd) };
  9         86  
90 9 50       134926 if ($@) {
91 0 0       0 die $@ if $@ !~ /\binstall_driver\b/;
92 0         0 plan skip_all => $@;
93             }
94              
95             # Skip tests with missing module requirements
96 9 100       23 unless (eval { DBIx::DBO::DBD->_require_dbd_class($dbd) }) {
  9         96  
97 1 50       28 if ($@ =~ /^Can't locate ([\w\/]+)\.pm in \@INC /m) {
    50          
    50          
98             # Module is not installed
99 0         0 $_ = "$1 is required" =~ s'/'::'gr;
100             } elsif ($@ =~ /^([\w:]+ version [\d\.]+ required.*?) at /m) {
101             # Module is not correct version
102 0         0 ($_ = $1);
103             } elsif ($@ =~ /^(\Q$dbd_name\E is not yet supported)/m) {
104             # DBM is not yet supported
105 1         3 ($_ = $1);
106             } else {
107 0         0 die $@;
108             }
109 1         7 plan skip_all => "Can't load $dbd driver: $_";
110             }
111              
112             {
113 12     12   180 no strict 'refs';
  12         22  
  12         98858  
  8         22  
114 8         22 *{caller().'::sql_err'} = \&sql_err;
  8         60  
115             }
116              
117 8 100       57 if (exists $opt{tempdir}) {
118 1         1267 require File::Temp;
119 1         31193 my $dir = File::Temp::tempdir('tmp_XXXX', CLEANUP => 1);
120 1 50       769 if (ref $opt{tempdir}) {
121 0         0 ${$opt{tempdir}} = $dir;
  0         0  
122             } else {
123 1 50       14 chdir $dir or die "Can't cd to $dir: $!\n";
124 1     1   96 eval "END { chdir '..' }";
  1         541  
125             }
126             }
127              
128             # Query tests must produce the same result regardless of caching
129 8   66     454 DBIx::DBO->config(CacheQuery => $ENV{DBO_CACHE_QUERY} // int rand 2);
130              
131 8 50       78 if (exists $opt{try_connect}) {
132 0         0 try_to_connect($opt{try_connect});
133             }
134              
135 8 100 66     74 note "DBD::$dbd ".${ $::DBD::{$dbd.'::'}{VERSION} } if exists $opt{try_connect} or exists $opt{connect_ok};
  2         25  
136              
137 8 100       2139 return unless exists $opt{tests};
138 7         33 $opt{tests} =~ s/^\+(\d+)$/$test_count + $1/e;
  1         7  
139              
140 7 100       25 if (exists $opt{connect_ok}) {
141 2 50       4 my $dbo = connect_ok(@{$opt{connect_ok}}) or plan skip_all => "Can't connect: $DBI::errstr";
  2         10  
142              
143 2         15 plan tests => $opt{tests} + 2;
144 2         2268 pass "Connect to $dbd_name";
145 2         988 isa_ok $dbo, 'DBIx::DBO', '$dbo';
146             } else {
147 5         28 plan tests => $opt{tests};
148             }
149             }
150              
151             sub sql_err {
152 0     0 0 0 my $me = shift;
153 0         0 my($cmd, $sql, @bind) = @{$me->config('LastSQL')};
  0         0  
154 0         0 $sql =~ s/^/ /mg;
155 0   0     0 my @err = ($DBI::errstr || $me->rdbh->errstr || '???');
156 0 0       0 unshift @err, 'Bind Values: ('.join(', ', map $me->rdbh->quote($_), @bind).')' if @bind;
157 0         0 unshift @err, "SQL command failed: $cmd", $sql.';';
158 0         0 $err[-1] =~ s/ at line \d+$//;
159 0         0 join "\n", @err;
160             }
161              
162             sub connect_dbo {
163 2     2 0 18 my($dsn, $user, $pass) = @_;
164 2   50     14 $dsn //= '';
165 2     0   28 DBIx::DBO->connect("DBI:$dbd:$dsn", $user, $pass, {HandleError => sub { note $_[0]; 1 }});
  0         0  
  0         0  
166             }
167              
168             sub try_to_connect {
169 2     2 0 4 my $dbo_ref = shift;
170 2         19 my @env = map $ENV{"DBO_TEST_\U$dbd\E_$_"}, qw(DSN USER PASS);
171 2 50       11 if (grep defined, @env) {
172 0 0       0 return $$dbo_ref if $$dbo_ref = connect_dbo(@env);
173 0         0 plan skip_all => "Can't connect: $DBI::errstr";
174             }
175 2         29 return undef;
176             }
177              
178             sub connect_ok {
179 2     2 0 5 my $dbo_ref = shift;
180 2   33     8 return try_to_connect($dbo_ref) || ($$dbo_ref = connect_dbo(@_));
181             }
182              
183             my $table_data;
184             my $quoted_table;
185             sub is_table_data($$) {
186 6     6 0 23 my($dbo, $message) = @_;
187 6 50       36 my $rv = $dbo->selectall_arrayref("SELECT * FROM $quoted_table") or diag sql_err($dbo);
188 6         982 @$rv = sort { $a->[0] <=> $b->[0] } @$rv; # Sort by id
  21         55  
189 6         27 is_deeply $rv, $table_data, $message;
190             }
191              
192             sub basic_methods {
193 1     1 0 270060 my $dbo = shift;
194 1         28 $quoted_table = $dbo->{dbd_class}->_qi($dbo, $test_sch, $test_tbl);
195              
196 1         4232 note 'Testing with: CacheQuery => '.DBIx::DBO->config('CacheQuery');
197              
198             # Create a DBO from DBI handles
199 1         1604 isa_ok(DBIx::DBO->new($dbo->{dbh}, $dbo->{rdbh}), 'DBIx::DBO', 'Method DBIx::DBO->new, $dbo');
200              
201 1         594 my @quoted_cols = map $dbo->{dbd_class}->_qi($dbo, $_), qw(type id name);
202 1         32 my $t;
203             my $create_table = "CREATE TABLE $quoted_table ($quoted_cols[1] ".
204             ($can{auto_increment_id} || 'INT NOT NULL').", $quoted_cols[2] VARCHAR(20)".
205 1 50 50     79 ($can{auto_increment_id} ? '' : ", PRIMARY KEY ($quoted_cols[1])").')';
206              
207             # Create a test table with a multi-column primary key
208 1 50       18 if ($dbo->do("CREATE TABLE $quoted_table ($quoted_cols[2] VARCHAR(20), $quoted_cols[1] INT, $quoted_cols[0] VARCHAR(8), PRIMARY KEY ($quoted_cols[0], $quoted_cols[1]))")) {
209 1         377 pass 'Create the test table: '.$quoted_table;
210              
211             # Create a table object
212 1         471 $t = $dbo->table([undef, $test_tbl]);
213 1         8 isa_ok $t, 'DBIx::DBO::Table', '$t';
214              
215             # Check the Primary Keys
216 1 50       1148 is_deeply $t->{PrimaryKeys}, ['type', 'id'], 'Check PrimaryKeys'
217             or diag Test::DBO::Dump($t);
218              
219             # Recreate our test table
220 1 50 33     1353 $dbo->do("DROP TABLE $quoted_table") && $dbo->do($create_table)
      33        
221             or diag sql_err($dbo) or die "Can't recreate the test table!\n";
222              
223             # Remove the created table during cleanup
224 1         316 todo_cleanup("DROP TABLE $quoted_table");
225              
226 1         9 $dbo->{dbd_class}->_get_table_info($dbo, $t->{Schema}, $t->{Name});
227 1         10 $t = $t->new($dbo, [$test_sch, $test_tbl]);
228             }
229             else {
230 0         0 diag sql_err($dbo);
231             SKIP: {
232 0         0 skip "Can't create a multi-column primary key", 1;
  0         0  
233             }
234              
235             # Create the test table
236 0 0 0     0 ok $dbo->do($create_table), 'Create the test table'
237             or diag sql_err($dbo) or die "Can't create the test table!\n";
238              
239             # Remove the created table during cleanup
240 0         0 todo_cleanup("DROP TABLE $quoted_table");
241              
242             # Create a table object
243 0         0 $t = $dbo->table([$test_sch, $test_tbl]);
244 0         0 isa_ok $t, 'DBIx::DBO::Table', '$t';
245             }
246 1 50       6 die "Couldn't create the DBIx::DBO::Table object!" unless $t;
247              
248 1         6 is $t->dbo, $dbo, 'Method DBIx::DBO::Table->dbo';
249              
250 1         1012 pass 'Method DBIx::DBO->do';
251              
252 1         895 ok my $table_info = $dbo->table_info([$test_sch, $test_tbl]), 'Method DBIx::DBO->table_info';
253 1         476 is $table_info, $dbo->table_info($quoted_table), 'Method DBIx::DBO->table_info (quoted)';
254 1 50       913 is $table_info, $dbo->table_info(defined $test_sch ? "$test_sch.$test_tbl" : $test_tbl),
255             'Method DBIx::DBO->table_info (unquoted)';
256              
257             # Insert data
258 1 50       411 $dbo->do("INSERT INTO $quoted_table VALUES (1, 'John Doe')") or diag sql_err($dbo);
259 1 50       91 $dbo->do("INSERT INTO $quoted_table VALUES (?, ?)", undef, 2, 'Jane Smith') or diag sql_err($dbo);
260              
261             # Check the DBO select* methods
262 1         250 my $rv = [];
263 1 50       9 @$rv = $dbo->selectrow_array("SELECT * FROM $quoted_table") or diag sql_err($dbo);
264 1         138 is_deeply $rv, [1,'John Doe'], 'Method DBIx::DBO->selectrow_array';
265              
266 1 50       831 $rv = $dbo->selectrow_arrayref("SELECT * FROM $quoted_table") or diag sql_err($dbo);
267 1         124 is_deeply $rv, [1,'John Doe'], 'Method DBIx::DBO->selectrow_arrayref';
268              
269 1         1292 $table_data = [[1,'John Doe'],[2,'Jane Smith']];
270 1         6 is_table_data $dbo, 'Method DBIx::DBO->selectall_arrayref';
271              
272             # Insert via table object
273 1 50       1745 $rv = $t->insert(id => 3, name => 'Uncle Arnie') or diag sql_err($t);
274 1         8 ok $rv, 'Method DBIx::DBO::Table->insert';
275 1         858 push @$table_data, [3,'Uncle Arnie'];
276              
277 1         8 is_deeply [$t->columns], [qw(id name)], 'Method DBIx::DBO::Table->columns';
278              
279             # Create a column object
280 1         1287 my $c = $t->column('id');
281 1         7 isa_ok $c, 'DBIx::DBO::Column', '$c';
282              
283             # Advanced insert using a column object
284 1 50       1008 $rv = $t->insert($c => {FUNC => '4'}, name => 'NotUsed', name => \"'James Bond'") or diag sql_err($t);
285 1         8 ok $rv, 'Method DBIx::DBO::Table->insert (complex values)';
286 1         474 push @$table_data, [4,'James Bond'];
287              
288 1         5 is_table_data $dbo, 'Method DBIx::DBO::Table->insert (remove duplicate cols)';
289              
290             # Delete via table object
291 1 50       2761 $rv = $t->delete(id => 3) or diag sql_err($t);
292 1         192 is $rv, 1, 'Method DBIx::DBO::Table->delete';
293              
294 1 50       941 if ($can{auto_increment_id}) {
295 1 50       7 $t->insert(name => 'Vernon Lyon') or diag sql_err($t);
296             } else {
297 0 0       0 $t->insert(id => 5, name => 'Vernon Lyon') or diag sql_err($t);
298             }
299              
300             SKIP: {
301 1 50       4 skip "No auto-increment $quoted_cols[1] column", 1 unless $can{auto_increment_id};
  1         6  
302 1 50       5 is $t->last_insert_id, 5, 'Method DBIx::DBO::Table->last_insert_id'
303             or $t->delete(name => 'Vernon Lyon'), $t->insert(id => 5, name => 'Vernon Lyon');
304             }
305              
306 1         925 my $bulk_data = $dbo->query($t)->arrayref({ Slice => {} });
307             SKIP: {
308 1 50       279 unless ($can{truncate}) {
  1         6  
309 1 50       5 $t->delete or diag sql_err($t);
310 1         143 skip 'TRUNCATE TABLE is not supported', 1;
311             }
312 0 0       0 $t->truncate or diag sql_err($t);
313 0         0 undef @$table_data;
314 0         0 is_table_data $dbo, 'Method DBIx::DBO::Table->truncate';
315             }
316 1         1030 @$table_data = sort { $a->[0] <=> $b->[0] } map [@$_{qw(id name)}], @$bulk_data;
  4         14  
317              
318             # Bulk insert
319 1 50       11 $rv = $t->bulk_insert(rows => [map [@$_{qw(id name)}], @$bulk_data]) or diag sql_err($t);
320 1         10 is $rv, 4, 'Method DBIx::DBO::Table->bulk_insert (ARRAY)';
321 1         962 is_table_data $dbo, 'Table contents are correct';
322 1 50       2340 $t->delete or diag sql_err($t);
323              
324 1 50       93 $rv = $t->bulk_insert(rows => \@$bulk_data) or diag sql_err($t);
325 1         10 is $rv, 4, 'Method DBIx::DBO::Table->bulk_insert (HASH)';
326 1         432 is_table_data $dbo, 'Table contents are correct';
327 1 50       1796 $t->delete or diag sql_err($t);
328              
329 1 50       98 $rv = $t->bulk_insert(columns => [qw(name id)], rows => [map [@$_{qw(name id)}], @$bulk_data]) or diag sql_err($t);
330 1         9 is $rv, 4, 'Method DBIx::DBO::Table->bulk_insert (ARRAY)';
331 1         431 is_table_data $dbo, 'Table contents are correct';
332 1 50       1740 $t->delete or diag sql_err($t);
333              
334 1 50       78 $rv = $t->bulk_insert(columns => [qw(name id)], rows => \@$bulk_data) or diag sql_err($t);
335 1         7 is $rv, 4, 'Method DBIx::DBO::Table->bulk_insert (HASH)';
336 1         465 is_table_data $dbo, 'Table contents are correct';
337              
338 1         1721 return $t;
339             }
340              
341             sub advanced_table_methods {
342 1     1 0 7 my $dbo = shift;
343 1         2 my $t = shift;
344              
345             SKIP: {
346 1 50       2 skip "No test table for advanced table tests", 2 unless $t;
  1         8  
347              
348             # Advanced insert
349 1 50       9 my $rv = $t->insert(id => { FUNC => '? + 3', VAL => 3 }, name => \"'Harry Harrelson'") or diag sql_err($t);
350 1         9 ok $rv, 'Method DBIx::DBO::Table->insert (advanced)';
351              
352 1 50       459 $t->insert(id => 7, name => 'Amanda Huggenkiss') or diag sql_err($t);
353 1 50       5 $t->insert(id => 8, name => undef) or diag sql_err($t);
354              
355             # Advanced delete
356 1 50       7 $rv = $t->delete(id => \'NOT NULL', name => undef) or diag sql_err($t);
357 1         158 ok $rv, 'Method DBIx::DBO::Table->delete (advanced)';
358             }
359             }
360              
361             sub skip_advanced_table_methods {
362 0     0 0 0 my $dbo = shift;
363 0         0 my $t = shift;
364              
365 0         0 skip "No advanced table tests for $dbd_name", 2;
366 0 0       0 $t->insert(id => 6, name => 'Harry Harrelson') or diag sql_err($t);
367 0 0       0 $t->insert(id => 7, name => 'Amanda Huggenkiss') or diag sql_err($t);
368             }
369              
370             sub row_methods {
371 1     1 0 458 my $dbo = shift;
372 1         3 my $t = shift;
373              
374 1         6 my $r = DBIx::DBO::Row->new($dbo, $t->_as_table);
375 1         7 isa_ok $r, 'DBIx::DBO::Row', '$r (using quoted table name)';
376              
377 1         506 $r = $dbo->row([ @$t{qw(Schema Name)} ]);
378 1         7 isa_ok $r, 'DBIx::DBO::Row', '$r (using table name array)';
379              
380 1         516 $r = $dbo->row($t);
381 1         7 isa_ok $r, 'DBIx::DBO::Row', '$r (using Table object)';
382              
383 1         473 is $r->dbo, $dbo, 'Method DBIx::DBO::Row->dbo';
384              
385 1         411 ok $r->is_empty, 'Method DBIx::DBO::Row->is_empty';
386 1         427 is_deeply [$r->columns], [qw(id name)], 'Method DBIx::DBO::Row->columns';
387              
388 1 50       756 ok $r->load(id => [2, 3], name => 'Jane Smith'), 'Method DBIx::DBO::Row->load' or diag sql_err($r);
389 1         416 is_deeply $$r->{array}, [ 2, 'Jane Smith' ], 'Row loaded correctly';
390              
391             # Access methods
392 1         758 is $r->[1], 'Jane Smith', 'Access row as an arrayref';
393 1         469 is $r->{name}, 'Jane Smith', 'Access row as a hashref';
394 1         445 is $r->value('name'), 'Jane Smith', 'Method DBIx::DBO::Row->value';
395 1         460 is $r->value($t->column('name')), 'Jane Smith', 'Method DBIx::DBO::Row->value (using Table->column)';
396              
397 1 50       463 is $r->update(name => 'Someone Else'), 1, 'Method DBIx::DBO::Row->update' or diag sql_err($r);
398 1 50       494 is_deeply \@$r, [ 2, 'Someone Else' ], 'Row updated correctly (internal)' or diag Test::DBO::Dump($r);
399 1 50       783 $r->load(id => 2) or diag sql_err($r);
400 1 50       5 is_deeply \@$r, [ 2, 'Someone Else' ], 'Row updated correctly (external)' or diag Test::DBO::Dump($r);
401              
402 1 50       801 $r->update(name => 'Nobody', $t ** 'name' => 'Anybody') or diag sql_err($r);
403 1 50       3 is_deeply \@{$r->load(id => 2)}, [ 2, 'Anybody' ], 'Row update removes duplicates' or diag sql_err($r);
  1         6  
404              
405             # UPDATE the primary key and a complex expression, requiring a reload
406 1         797 $r->config(OnRowUpdate => 'reload');
407 1 50       5 $r->update(id => 3, name => \"'Uncle Arnie'") or diag sql_err($r);
408 1 50 33     7 ok !$r->is_empty, 'Row reloaded on update' or $r->load(id => [2, 3]) or diag sql_err($r);
409              
410 1 50       460 ok $r->delete, 'Method DBIx::DBO::Row->delete' or diag sql_err($r);
411 1         640 $t->insert(id => 2, name => 'Jane Smith');
412              
413 1         7 is $r->load(name => 'non-existent'), undef, 'Load non-existent row';
414 1         582 is_deeply $$r->{array}, undef, 'Row is empty again';
415             }
416              
417             sub query_methods {
418 1     1 0 8 my $dbo = shift;
419 1         3 my $t = shift;
420 1         6 $quoted_table = $t->_as_table;
421              
422             # Create a query object
423 1         7 my $q = $dbo->query($t);
424 1         9 isa_ok $q, 'DBIx::DBO::Query', '$q';
425              
426 1         514 is $q->dbo, $dbo, 'Method DBIx::DBO::Query->dbo';
427              
428             # Default sql = select everything
429 1         412 is_deeply [$q->columns], [qw(id name)], 'Method DBIx::DBO::Query->columns';
430 1         821 my $sql = $q->sql;
431 1         7 is $sql, "SELECT * FROM $quoted_table", 'Method DBIx::DBO::Query->sql';
432              
433             # Sort the result
434 1         405 $q->order_by('id');
435 1         4 like $q->sql, qr/ ORDER BY .*id/, 'Method DBIx::DBO::Query->order_by';
436              
437             # Get a valid sth
438 1 50       408 ok $q->run, 'Method DBIx::DBO::Query->run' or diag sql_err($q);
439 1         490 isa_ok $q->{sth}, 'DBI::st', '$q->{sth}';
440              
441             # Get a Row object
442 1         463 my $r = $q->row;
443 1         6 isa_ok $r, 'DBIx::DBO::Row', '$q->row';
444 1         445 undef $q;
445 1         10 ok !exists $$r->{Parent}, 'Row detaches when the parent query is destroyed';
446              
447             # Get another attached Row
448 1         408 $q = $dbo->query($t);
449 1         6 $r = $q->row;
450              
451 1         5 $q->config(Testing => 123);
452 1         4 is $r->config('Testing'), 123, 'Row gets config from parent Query';
453              
454             # Alter the SQL to ensure the row is detached and rebuilt
455 1         427 my $r_str = "$r";
456 1         7 $q->order_by('id');
457 1         6 $r = $q->row;
458 1         9 isnt $r_str, "$r", 'Row rebuilds SQL and detaches when a ref still exists';
459 1         424 $r_str = "$r";
460              
461             # Remove the reference so that the row wont detach
462 1         3 undef $r;
463              
464             # Fetch the first row
465 1         8 $r = $q->fetch;
466 1         10 ok $r->isa('DBIx::DBO::Row'), 'Method DBIx::DBO::Query->fetch';
467 1         452 is "$r", $r_str, 'Re-use the same row object';
468 1         428 is_deeply [$q->columns], [qw(id name)], 'Method DBIx::DBO::Query->columns (after fetch)';
469              
470             # Fetch another row
471 1         826 $r_str = "$r";
472 1         6 $r = $q->fetch;
473 1         8 isnt $r_str, "$r", 'Row detaches during fetch when a ref still exists';
474              
475             # Re-run the query
476 1 50       425 $q->run or diag sql_err($q);
477 1         5 is $q->fetch->{name}, 'John Doe', 'Method DBIx::DBO::Query->run (rerun)';
478 1         423 $q->finish;
479 1         7 ok $q->{Row}->is_empty, 'Method DBIx::DBO::Query->finish (Row is now empty)';
480 1         400 is $q->fetch->{name}, 'John Doe', 'Method DBIx::DBO::Query->finish (fetch returns the first Row)';
481              
482             # Count the number of rows
483 1         459 is_deeply [map ref $q->fetch, 1..5], [('DBIx::DBO::Row') x 5], 'Fetched 5 more Rows';
484 1         844 is $q->fetch, undef, 'Fetch exhausted (no more rows)';
485 1         2397 is $q->rows, 6, 'Row count is 6';
486              
487             # WHERE clause
488 1 50       426 ok $q->where('name', 'LIKE', \"'%o%'"), 'Method DBIx::DBO::Query->where' or diag sql_err($q);
489              
490             # Parentheses
491 1         401 $q->open_bracket('OR');
492 1         4 $q->where('name', 'LIKE', \"'%a%'");
493 1         5 $q->where('id', '!=', \1);
494 1         5 $q->where('id', '=', undef);
495 1         4 $q->open_bracket('AND');
496 1         3 $q->where('id', '<>', 12345);
497 1         4 $q->where('id', '!=', undef);
498 1         134 $q->where('id', 'NOT IN', [1,22,333]);
499 1         7 $q->where('id', 'NOT BETWEEN', [123,456]);
500 1         7 my $got = $q->col_arrayref({ Columns => [1] });
501 1 50       318 is_deeply $got, [4,5,6], 'Method DBIx::DBO::Query->open_bracket' or diag sql_err($q);
502              
503 1         843 $q->where('id', 'NOT IN', 4444);
504 1         4 ok scalar(() = $q->sql =~ / NOT IN /g) == 1, 'Group multiple IN & NOT IN clauses together';
505              
506 1         425 $q->order_by;
507 1 50       9 is $q->update(id => { FUNC => '? + 10', COL => 'id' }), 3, 'Method DBIx::DBO::Query->update' or diag sql_err($q);
508 1         438 $q->order_by('id');
509              
510 1         5 my $old_sql = $q->sql;
511 1         8 $q->unwhere('name');
512 1         6 is $q->sql, $old_sql, 'Method DBIx::DBO::Query->unwhere (before close_bracket)';
513              
514 1         532 $q->close_bracket;
515 1         5 $q->close_bracket;
516 1         5 $q->unwhere('name');
517 1         6 isnt $q->sql, $old_sql, 'Method DBIx::DBO::Query->close_bracket';
518              
519 1         471 $got = $q->col_arrayref({ Columns => [1] });
520 1         312 is_deeply $got, [2,7,14,15,16], 'Method DBIx::DBO::Query->unwhere';
521              
522             # Reset the Query
523 1         910 $q->reset;
524 1         4 is $q->sql, $dbo->query($t)->sql, 'Method DBIx::DBO::Query->reset';
525              
526             # Group by the first initial
527 1         430 $q->show(\'COUNT(*)');
528 1 50       6 ok(($q->group_by({FUNC => 'SUBSTR(?, 1, 1)', COL => 'name'}), $q->run),
529             'Method DBIx::DBO::Query->group_by') or diag sql_err($q);
530              
531             # Update & Load a Row with aliased columns
532 1         496 $q->show($t, {COL => 'id', AS => 'key'});
533 1         9 $q->order_by({COL => 'id', ORDER => 'DESC' });
534 1         5 $q->group_by;
535 1         7 is_deeply [$q->columns], [qw(id name key)], 'Method DBIx::DBO::Query->columns (with aliases)';
536 1         805 $r = $q->fetch;
537 1         6 is_deeply [$q->columns], [qw(id name key)], 'Method DBIx::DBO::Query->columns (after fetch)';
538 1         822 isa_ok $q ** 'key', 'DBIx::DBO::Column', '$q ** $alias';
539              
540             # After changing the Query, test that the Row still works as before
541 1         534 $q->show('id');
542 1         5 $q->order_by('id');
543 1         6 is join(' ', $r->columns), 'id name key', 'Method DBIx::DBO::Row->columns (unchanged by parent)';
544 1 50       470 is $r->{key}, 16, 'Alias returns correct value' or diag sql_err($r);
545 1 50       486 ok $r->update(id => $r->{key}), 'Can update a Row despite using aliases' or diag sql_err($r);
546 1 50       410 ok $r->load(id => 15), 'Can load a Row despite using aliases' or diag sql_err($r);
547              
548             # Limit & limit with Offset
549 1         414 $q->limit(3);
550 1         3 $got = [];
551 1         7 for (my $row; $row = $q->fetch; push @$got, $row->[0]) {}
552 1         9 is_deeply $got, [1,2,7], 'Method DBIx::DBO::Query->limit';
553              
554 1         870 $q->limit(3, 2);
555 1         3 $got = [];
556 1         6 for (my $row; $row = $q->fetch; push @$got, $row->[0]) {}
557 1         9 is_deeply $got, [7,14,15], 'Method DBIx::DBO::Query->limit (with offset)';
558              
559 1         871 $q->finish;
560 1         8 return $q;
561             }
562              
563             sub advanced_query_methods {
564 1     1 0 8 my $dbo = shift;
565 1         3 my $t = shift;
566 1         2 my $q = shift;
567 1         7 $q->reset;
568              
569             # Show specific columns only
570             SKIP: {
571 1 50       2 skip 'COLLATE is not supported', 1 unless $can{collate};
  1         7  
572 1         8 $q->order_by({ COL => 'name', COLLATE => $can{collate} });
573 1 50       6 ok $q->run, 'Method DBIx::DBO::Query->order_by COLLATE' or diag sql_err($q);
574             }
575 1         482 $q->order_by('id');
576 1         9 $q->show({ FUNC => 'UPPER(?)', COL => 'name', AS => 'name' }, 'id', 'name');
577 1 50 33     6 ok $q->run && $q->fetch->{name} eq 'JOHN DOE', 'Method DBIx::DBO::Query->show' or diag sql_err($q);
578              
579 1         511 is $q->row->value($t ** 'name'), 'John Doe', 'Access specific column';
580 1         456 is_deeply [$q->row->columns], [qw(name id name)], 'Method DBIx::DBO::Row->columns (aliased)';
581 1         820 is_deeply [$q->columns], [qw(name id name)], 'Method DBIx::DBO::Query->columns (aliased)';
582              
583             # Show whole tables
584 1         801 $q->show({ FUNC => "'who?'", AS => 'name' }, $t);
585 1         8 is $q->fetch->value($t ** 'name'), 'John Doe', 'Access specific column from a shown table';
586              
587             # Check case sensitivity of LIKE
588 1 50       414 my $case_sensitive = $dbo->selectrow_arrayref($case_sensitivity_sql, undef, 'a', 'A') or diag sql_err($dbo);
589 1         126 $case_sensitive = not $case_sensitive->[0];
590 1 50       13 note "$dbd_name 'LIKE' is".($case_sensitive ? '' : ' NOT').' case sensitive';
591              
592             # WHERE clause
593 1         722 $q->show('id');
594 1         7 ok $q->where('name', 'LIKE', '%a%'), 'Method DBIx::DBO::Query->where LIKE';
595 1 50       430 my $a = $q->col_arrayref or diag sql_err($q);
596 1         140 is_deeply $a, [2,7,14,16], 'Method DBIx::DBO::Query->col_arrayref';
597 1         962 ok $q->where('id', 'BETWEEN', [6, \16]), 'Method DBIx::DBO::Query->where BETWEEN';
598 1 50       429 $a = $q->arrayref or diag sql_err($q);
599 1         209 is_deeply $a, [[7],[14],[16]], 'Method DBIx::DBO::Query->arrayref';
600 1         2820 ok $q->where('name', 'IN', ['Harry Harrelson', 'James Bond']), 'Method DBIx::DBO::Query->where IN';
601 1 50       4067 $a = $q->hashref('id') or diag sql_err($q);
602 1         448 is_deeply $a, {14 => {id => 14},16 => {id => 16}}, 'Method DBIx::DBO::Query->hashref';
603              
604             # HAVING clause
605 1 50       1352 my $concat = $dbd eq 'SQLite' ? '? || ?' : 'CONCAT(?, ?)';
606 1         7 my %concat_col = (FUNC => $concat, COL => [qw(id name)]);
607 1 50       9 my $having_col = $dbo->{dbd_class}->_alias_preference($q, 'having') ? 'combo' : \%concat_col;
608 1         11 $q->show('id', 'name', { %concat_col, AS => 'combo'});
609 1         104 $q->group_by('id', 'name');
610 1         7 $q->having($having_col, '=', '14James Bond');
611 1         4 $q->having($having_col, '=', 'ABC-XYZ');
612 1         5 $q->having($having_col, '=', 'XYZ-ABC');
613 1         3 is_deeply [@{$q->fetch}], [14, 'James Bond', '14James Bond'], 'Method DBIx::DBO::Query->having';
  1         6  
614              
615 1         928 $q->unhaving($having_col, '=', '14James Bond');
616 1         5 is $q->fetch, undef, 'Method DBIx::DBO::Query->unhaving';
617 1         660 $q->unhaving($having_col);
618 1         3 is_deeply [@{$q->fetch}], [14, 'James Bond', '14James Bond'], 'Method DBIx::DBO::Query->unhaving (whole column)';
  1         6  
619              
620 1         963 $q->finish;
621             }
622              
623             sub skip_advanced_query_methods {
624 0     0 0 0 skip "No advanced query tests for $dbd_name", 15;
625             }
626              
627             sub join_methods {
628 1     1 0 9 my $dbo = shift;
629 1         4 my $table = shift;
630              
631 1         7 my($q, $t1, $t2) = $dbo->query($table, $table);
632              
633             # DISTINCT clause
634 1         6 $q->order_by('id');
635 1         5 $q->show('id');
636 1         4 $q->distinct(1);
637 1         4 is_deeply $q->arrayref, [[1],[2],[7],[14],[15],[16]], 'Method DBIx::DBO::Query->distinct';
638 1         2523 $q->distinct(0);
639 1         5 $q->show($t1, $t2);
640              
641             # Counting rows
642 1         6 $q->limit(3);
643 1         5 $q->config(CalcFoundRows => 1);
644 1         6 ok $q, 'Comma JOIN';
645 1 50       406 is $q->count_rows, 3, 'Method DBIx::DBO::Query->count_rows' or diag sql_err($q);
646 1 50       437 is $q->found_rows, 36, 'Method DBIx::DBO::Query->found_rows' or diag sql_err($q);
647              
648             # JOIN
649 1         433 $q->join_on($t2, $t1 ** 'id', '=', { FUNC => '?/7.0', VAL => $t2 ** 'id' });
650 1         7 $q->order_by({ COL => $t1 ** 'name', ORDER => 'DESC' });
651 1         5 $q->where($t1 ** 'name', '<', $t2 ** 'name', FORCE => 'OR');
652 1         6 $q->where($t1 ** 'name', '>', $t2 ** 'name', FORCE => 'OR');
653 1         5 $q->where($t1 ** 'name', 'LIKE', '%');
654 1         3 my $r;
655             # Oracle Can't do a SELECT * from a subquery that has "ambiguous" columns (two columns with the same name)
656 1 50       5 $q->show() if $dbd eq 'Oracle';
657             SKIP: {
658 1 0 33     4 $q->run or fail 'JOIN ON' or diag sql_err($q) or skip 'No Left Join', 1;
  1   33     7  
659 1 50 33     6 $r = $q->fetch or fail 'JOIN ON' or skip 'No Left Join', 1;
660              
661 1         5 is_deeply \@$r, [ 1, 'John Doe', 7, 'Amanda Huggenkiss' ], 'JOIN ON';
662 1 50       1009 $r->load($t1 ** id => 2) or diag sql_err($r);
663 1         5 is_deeply \@$r, [ 2, 'Jane Smith', 14, 'James Bond' ], 'Method DBIx::DBO::Row->load';
664             }
665              
666             # LEFT JOIN
667 1         948 ($q, $t1) = $dbo->query($table);
668             # ... "t1" LEFT JOIN ... "t2"
669 1         5 $t2 = $q->join_table($table, 'left');
670             # ... "t1" LEFT JOIN ... "t2" ON "t1"."id" = "t2"."id"/2.0
671 1         7 $q->join_on($t2, $t1 ** 'id', '=', { FUNC => '?/2.0', COL => $t2 ** 'id' });
672 1         7 ok $q->open_join_on_bracket($t2, 'OR'), 'Method DBIx::DBO::Query->open_join_on_bracket';
673             # ... "t1" LEFT JOIN ... "t2" ON "t1"."id" = "t2"."id"/2.0 AND 1 = 2
674 1         496 $q->join_on($t2, \1, '=', \2);
675             # ... "t1" LEFT JOIN ... "t2" ON "t1"."id" = "t2"."id"/2.0 AND (1 = 2 OR 3 = 3)
676 1         7 $q->join_on($t2, \3, '=', \3);
677 1         7 ok $q->close_join_on_bracket($t2), 'Method DBIx::DBO::Query->close_join_on_bracket';
678              
679 1         411 $q->order_by({ COL => $t1 ** 'name', ORDER => 'DESC' });
680 1         7 $q->limit(1, 3);
681              
682             SKIP: {
683 1 0 33     2 $q->run or diag sql_err($q) or fail 'LEFT JOIN' or skip 'No Left Join', 3;
  1   33     1905  
684 1 50 33     6 $r = $q->fetch or fail 'LEFT JOIN' or skip 'No Left Join', 3;
685              
686 1         7 is_deeply [@$r[0..3]], [14, 'James Bond', undef, undef], 'LEFT JOIN';
687 1         952 is $r->_column_idx($t2 ** 'id'), 2, 'Method DBIx::DBO::Row->_column_idx';
688 1         434 is $r->value($t2 ** 'id'), undef, 'Method DBIx::DBO::Row->value';
689              
690             # Update the LEFT JOINed row
691             SKIP: {
692 1 50       564 skip "Multi-table UPDATE is not supported by $dbd_name", 1 unless $can{multi_table_update};
  1         13  
693 0 0       0 ok $r->update($t1 ** 'name' => 'Vernon Wayne Lyon'), 'Method DBIx::DBO::Row->update' or diag sql_err($r);
694             }
695             }
696              
697 1         905 $q->finish;
698             }
699              
700             sub skip_join_methods {
701 0     0 0 0 skip "No query join tests for $dbd_name", 12;
702             }
703              
704             sub todo_cleanup {
705 1     1 0 3 my $sql = shift;
706 1         3 unshift @_cleanup_sql, $sql;
707             }
708              
709             sub cleanup {
710 1     1 0 610 my $dbo = shift;
711              
712 1         6 note 'Doing cleanup';
713 1         733 for my $sql (@_cleanup_sql) {
714 1 50       7 $dbo->do($sql) or diag sql_err($dbo);
715             }
716              
717 1         212 $dbo->disconnect;
718 1   33     11 ok !defined $dbo->{dbh} && !defined $dbo->{rdbh}, 'Method DBIx::DBO->disconnect';
719             }
720              
721             sub Dump {
722 0     0 0 0 my($val, $var) = @_;
723 0 0 0     0 if (blessed $val and !defined $var) {
724 0 0       0 if ($val->isa('DBIx::DBO')) {
    0          
    0          
    0          
725 0         0 $var = 'dbo';
726             } elsif ($val->isa('DBIx::DBO::Table')) {
727 0         0 $var = 't';
728             } elsif ($val->isa('DBIx::DBO::Query')) {
729 0         0 $var = 'q';
730             } elsif ($val->isa('DBIx::DBO::Row')) {
731 0         0 $var = 'r';
732             }
733             }
734 0   0     0 $var //= 'dump';
735 0         0 require Data::Dumper;
736 0         0 local $Data::Dumper::Sortkeys = 1;
737 0         0 local $Data::Dumper::Quotekeys = 0;
738 0         0 my $d = Data::Dumper->new([$val], [$var]);
739 0 0       0 if (ref $val) {
740 0         0 my %seen;
741 0         0 my @_no_recursion = ($val);
742 0 0       0 if (reftype $val eq 'ARRAY') { _Find_Seen(\%seen, \@_no_recursion, $_) for @$val }
  0 0       0  
    0          
743 0         0 elsif (reftype $val eq 'HASH') { _Find_Seen(\%seen, \@_no_recursion, $_) for values %$val }
744 0         0 elsif (reftype $val eq 'REF') { _Find_Seen(\%seen, \@_no_recursion, $$val) }
745 0         0 $d->Seen(\%seen);
746             }
747 0 0       0 defined wantarray ? return $d->Dump : print $d->Dump;
748             }
749              
750             sub _Find_Seen {
751 0     0   0 my($seen, $_no_recursion, $val) = @_;
752 0 0       0 return unless ref $val;
753 0         0 for (@$_no_recursion) {
754 0 0       0 return if $val == $_;
755             }
756 0         0 push @$_no_recursion, $val;
757              
758 0 0       0 if (blessed $val) {
759 0 0       0 if ($val->isa('DBIx::DBO')) {
    0          
    0          
    0          
760 0         0 $seen->{dbo} = $val;
761 0         0 return;
762             } elsif ($val->isa('DBIx::DBO::Table')) {
763 0         0 my $t = 1;
764 0         0 while (my($k, $v) = each %$seen) {
765 0 0       0 next if $k !~ /^t\d+$/;
766 0 0       0 return if $val == $v;
767 0         0 $t++;
768             }
769 0         0 $seen->{"t$t"} = $val;
770 0         0 return;
771             } elsif ($val->isa('DBIx::DBO::Query')) {
772 0         0 $seen->{q} = $val;
773 0         0 return;
774             } elsif ($val->isa('DBIx::DBO::Row')) {
775 0         0 $seen->{r} = $val;
776 0         0 return;
777             }
778             }
779 0 0       0 if (reftype $val eq 'ARRAY') { _Find_Seen($seen, $_no_recursion, $_) for @$val }
  0 0       0  
    0          
780 0         0 elsif (reftype $val eq 'HASH') { _Find_Seen($seen, $_no_recursion, $_) for values %$val }
781 0         0 elsif (reftype $val eq 'REF') { _Find_Seen($seen, $_no_recursion, $$val) }
782             }
783              
784             # When testing via Sponge, use fake tables
785             package # Hide from PAUSE
786             DBIx::DBO::DBD::Sponge;
787             sub _get_table_schema {
788 18     18   53 return;
789             }
790             my $fake_table_info = {
791             PrimaryKeys => [],
792             Columns => [ 'id', 'name', 'age' ],
793             Column_Idx => { id => 1, name => 2, age => 3 },
794             };
795             sub _get_table_info {
796 12     12   25 my($class, $me, $schema, $table) = @_;
797 12 100       57 return $class->SUPER::_get_table_info($me, $schema, $table) if $table ne $Test::DBO::test_tbl;
798             # Fake table info
799 10   33     41 return $me->{TableInfo}{''}{$table} ||= $fake_table_info;
800             }
801              
802             # Replace DBI::connect for testing via MySpongeDBI
803             {
804 12     12   152 no warnings 'redefine';
  12         22  
  12         8340  
805             my $old_dbi_connect = \&DBI::connect;
806             *DBI::connect = sub {
807 24 100   24   4107 return $old_dbi_connect->('MySpongeDBI', @_[1 .. $#_]) if $_[1] eq 'DBI:Sponge:';
808 2         10 goto &$old_dbi_connect;
809             };
810             }
811              
812             # When testing via MySpongeDBI, fake table contents
813             package # Hide from PAUSE
814             MySpongeDBI::db;
815             @MySpongeDBI::ISA = ('DBI');
816             @MySpongeDBI::db::ISA = ('DBI::db');
817             @MySpongeDBI::st::ISA = ('DBI::st');
818             my $cols;
819             my @rows;
820             sub setup {
821 4     4   657140 ($cols, @rows) = @_;
822             }
823             sub prepare {
824 10     10   4682 my($dbh, $sql, $attr) = @_;
825 10   100     49 $attr ||= {};
826 10   66     68 $attr->{NAME} ||= $cols;
827 10   100     42 $attr->{rows} ||= [ @rows ];
828 10         106 $dbh->SUPER::prepare($sql, $attr);
829             }
830              
831             1;