File Coverage

blib/lib/DBUnit.pm
Criterion Covered Total %
statement 117 735 15.9
branch 26 324 8.0
condition 6 137 4.3
subroutine 22 81 27.1
pod 57 57 100.0
total 228 1334 17.0


line stmt bran cond sub pod time code
1             package DBUnit;
2              
3 3     3   72399 use strict;
  3         8  
  3         132  
4 3     3   15 use warnings;
  3         6  
  3         104  
5 3     3   13 use vars qw(@EXPORT_OK %EXPORT_TAGS $VERSION);
  3         10  
  3         297  
6              
7             $VERSION = '0.15';
8              
9 3     3   2841 use Abstract::Meta::Class ':all';
  3         39145  
  3         683  
10 3     3   34 use base 'Exporter';
  3         6  
  3         246  
11 3     3   38 use Carp 'confess';
  3         7  
  3         124  
12 3     3   3282 use DBIx::Connection;
  3         173469  
  3         140  
13 3     3   3870 use Simple::SAX::Serializer;
  3         125453  
  3         211  
14              
15             @EXPORT_OK = qw(INSERT_LOAD_STRATEGY REFRESH_LOAD_STRATEGY reset_schema populate_schema expected_dataset dataset expected_xml_dataset xml_dataset);
16             %EXPORT_TAGS = (all => \@EXPORT_OK);
17              
18 3     3   30 use constant INSERT_LOAD_STRATEGY => 0;
  3         7  
  3         180  
19 3     3   29 use constant REFRESH_LOAD_STRATEGY => 1;
  3         5  
  3         34879  
20              
21             =head1 NAME
22              
23             DBUnit - Database testing API
24              
25             =head1 SYNOPSIS
26              
27             use DBUnit ':all';
28              
29             my $dbunit = DBUnit->new(connection_name => 'test');
30             $dbunit->reset_schema($script);
31             $dbunit->populate_schema($script);
32              
33             $dbunit->dataset(
34             emp => [empno => 1, ename => 'scott', deptno => 10],
35             emp => [empno => 2, ename => 'john', deptno => 10],
36             bonus => [ename => 'scott', job => 'consultant', sal => 30],
37             );
38             #business logic here
39              
40             my $differences = $dbunit->expected_dataset(
41             emp => [empno => 1, ename => 'scott', deptno => 10],
42             emp => [empno => 2, ename => 'John'],
43             emp => [empno => 2, ename => 'Peter'],
44             );
45              
46             $dbunit->reset_sequence('emp_seq');
47              
48             $dbunit->xml_dataset('t/file.xml');
49              
50             $dbunit->expected_xml_dataset('t/file.xml');
51              
52              
53             B
54              
55             This code snippet will populate database blob_content column with the binary data pointed by file attribute,
56             size of the lob will be stored in size_column
57              
58             $dbunit->dataset(
59             emp => [empno => 1, ename => 'scott', deptno => 10],
60             image => [id => 1, name => 'Moon'
61             blob_content => {file => 'data/image1.jpg', size_column => 'doc_size'}
62             ]
63             );
64              
65              
66             This code snippet will validate database binary data with expected content pointed by file attribute,
67              
68             $dbunit->expected_dataset(
69             emp => [empno => 1, ename => 'scott', deptno => 10],
70             image => [id => 1, name => 'Moon'
71             blob_content => {file => 'data/image1.jpg', size_column => 'doc_size'}
72             ]
73             );
74             or xml
75            
76            
77             "1" name="Moon">
78            
79            
80            
81              
82              
83             =head1 DESCRIPTION
84              
85             Database test framework to verify that your database data match expected set of values.
86             It has ability to populate dataset and expected set from xml files.
87              
88             =head2 EXPORT
89              
90             None by default.
91             reset_schema
92             populate_schema
93             expected_dataset
94             expected_xml_dataset
95             dataset
96             xml_dataset by tag 'all'
97              
98             =head2 ATTRIBUTES
99              
100             =over
101              
102             =item connection_name
103              
104             =cut
105              
106             has '$.connection_name' => (required => 1);
107              
108              
109             =item load_strategy
110              
111             INSERT_LOAD_STRATEGY(default)
112             Deletes all data from tables that are present in test dataset in reverse order
113             unless empty table without attribute is stated, that force deletion in occurrence order
114             In this strategy expected dataset is also tested against number of rows for all used tables.
115              
116             REFRESH_LOAD_STRATEGY
117             Merges (update/insert) data to the given dataset snapshot.
118             In this scenario only rows in expected dataset are tested.
119              
120             =cut
121              
122             has '$.load_strategy' => (default => INSERT_LOAD_STRATEGY());
123              
124              
125             =item primary_key_definition_cache
126              
127             This option is stored as hash_ref:
128             the key is the table name with the schema prefix
129             and value is stored as array ref of primary key column names.
130              
131              
132             =cut
133              
134             has '%.primary_key_definition_cache';
135              
136              
137             =back
138              
139             =head2 METHODS
140              
141             =over
142              
143             =item reset_schema
144              
145             Resets schema
146              
147             $dbunit->reset_schema;
148              
149             =cut
150              
151              
152             sub reset_schema {
153 0     0 1 0 my ($self, $file_name) = @_;
154 0         0 my @tables_list = $self->objects_to_create(_load_file_content($file_name));
155 0         0 my @to_drop;
156             my @to_create;
157 0         0 for (my $i = 0; $i <= $#tables_list; $i += 2) {
158 0         0 push @to_drop, $tables_list[$i];
159 0         0 push @to_create, $tables_list[$i + 1];
160             }
161 0         0 $self->drop_objects(reverse @to_drop);
162 0         0 $self->create_tables(@to_create);
163             }
164              
165              
166             =item populate_schema
167              
168             Populates database schema.
169              
170             =cut
171              
172             sub populate_schema {
173 0     0 1 0 my ($self, $file_name) = @_;
174 0         0 my @rows = $self->rows_to_insert(_load_file_content($file_name));
175 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
176 0         0 for my $sql (@rows) {
177 0         0 $connection->do($sql);
178             }
179 0         0 $connection->close();
180             }
181              
182              
183             =item dataset
184              
185             Synchronizes/populates database to the passed in dataset.
186              
187             $dbunit->dataset(
188             table1 => [], #this deletes all data from table1 (DELETE FROM table1)
189             table2 => [], #this deletes all data from table2 (DELETE FROM table2)
190             table1 => [col1 => 'va1', col2 => 'val2'], #this insert or update depend on strategy
191             table1 => [col1 => 'xval1', col2 => 'xval2'],
192             )
193              
194             =cut
195              
196             sub dataset {
197 0     0 1 0 my ($self, @dataset) = @_;
198 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
199 0         0 $self->delete_data(\@dataset, $connection);
200 0 0       0 my $operation = ($self->load_strategy eq INSERT_LOAD_STRATEGY()) ? 'insert' : 'merge';
201 0         0 for (my $i = 0; $i < $#dataset; $i += 2) {
202 0         0 my $table = $dataset[$i];
203 0         0 my $lob_values = $self->_extract_lob_values($dataset[$i + 1]);
204 0         0 my $data = $self->_extract_column_values($dataset[$i + 1]);
205 0 0       0 next unless %$data;
206 0         0 $self->$operation($table, $data, $connection);
207 0         0 $self->_update_lobs($lob_values, $table, $data, $connection);
208             }
209 0         0 $connection->close();
210             }
211              
212              
213             =item expected_dataset
214              
215             Validates database schema against passed in dataset.
216             Return differences report or undef is there are not discrepancies.
217              
218             my $differences = $dbunit->expected_dataset(
219             table1 => [col1 => 'va1', col2 => 'val2'],
220             table1 => [col1 => 'xval1', col2 => 'xval2'],
221             );
222              
223             =cut
224              
225             sub expected_dataset {
226 0     0 1 0 my ($self, @dataset) = @_;
227 0 0       0 my $operation = ($self->load_strategy eq INSERT_LOAD_STRATEGY())
228             ? 'expected_dataset_for_insert_load_strategy'
229             : 'expected_dataset_for_refresh_load_strategy';
230 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
231 0         0 my $result = $self->$operation(\@dataset, $connection);
232 0         0 $connection->close();
233 0         0 $result;
234             }
235              
236              
237             =item reset_sequence
238              
239             Resets passed in sequence
240              
241             $dbunit->reset_sequence('emp_seq');
242            
243             =cut
244              
245             sub reset_sequence {
246 0     0 1 0 my ($self, $sequence_name) = @_;
247 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
248 0         0 $connection->reset_sequence($sequence_name);
249 0         0 $connection->close();
250             }
251              
252              
253             =item throws
254              
255             Returns errorcode, error message for the specified sql or plsql code.
256              
257             my ($error_code, $error_message) = $dbunit->throws(
258             "INSERT INTO emp(empno, ename) VALUES (NULL, 'Smith')"
259             );
260              
261             =cut
262              
263              
264             sub throws {
265 0     0 1 0 my ($self, $pl_sql) = @_;
266 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
267 0         0 my $dbms = lc $connection->dbms_name;
268 0 0       0 $pl_sql .= ';' unless ($pl_sql =~ /;$/);
269 0         0 my ($error_code, $error_message);
270 0 0 0     0 if ($dbms eq 'oracle' && !($pl_sql =~ /begin/i)) {
271 0         0 $pl_sql = sprintf("BEGIN\n%sEND;", $pl_sql);
272             }
273 0         0 my $dbh = $connection->dbh;
274 0         0 my $sth = $connection->plsql_handler(plsql => $pl_sql);
275 0         0 eval { $sth->execute(); };
  0         0  
276 0         0 $error_code = $dbh->err;
277 0         0 $error_message = $dbh->errstr;
278 0         0 $connection->close();
279 0         0 return ($error_code, $error_message);
280             }
281              
282              
283             =item execute
284              
285             Returns hash reference where keys are the bind variables
286              
287             my $plsql = "SELECT NOW() INTO :var";
288             my $result = $dbunit->execute($plsql);
289             my $result = $dbunit->execute($plsql, $bind_variables_definition);
290              
291             See L for more detail
292              
293             =cut
294              
295             sub execute {
296 0     0 1 0 my ($self, $pl_sql, $bind_variables_definition) = @_;
297 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
298 0 0       0 $pl_sql .= ';' unless ($pl_sql =~ /;$/);
299 0 0       0 my $sth = $connection->plsql_handler(
300             plsql => sprintf("BEGIN\n%sEND;",$pl_sql),
301             ($bind_variables_definition ? (bind_variables => $bind_variables_definition) :())
302             );
303 0         0 my $result = $sth->execute();
304 0         0 $connection->close();
305 0         0 $result;
306             }
307              
308              
309             =back
310              
311             =head2 SCHEMA TEST METHODS
312              
313             The following methods check for existence.of the particular database
314             schema objects like table, column, index, triggers,
315             function, procedures packages.
316              
317             =over
318              
319             =item has_table
320              
321             Returns true if the specified table exists.
322              
323             $dbunit->has_table($schema, $table);
324             $dbunit->has_table($table);
325              
326             =cut
327              
328             sub has_table {
329 0     0 1 0 my ($self, @args) = @_;
330 0 0       0 my ($table, $schema) = (@args == 1) ? $args[0] : reverse @args;
331 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
332 0         0 my $result = $connection->has_table($table, $schema);
333 0         0 $connection->close();
334 0         0 return $result;
335             }
336              
337              
338             =item has_view
339              
340             Returns true if the specified view exists.
341              
342             $dbunit->has_view($schema, $view);
343             $dbunit->hasnt_table($view);
344              
345             =cut
346              
347             sub has_view {
348 0     0 1 0 my ($self, @args) = @_;
349 0 0       0 my ($view, $schema) = (@args == 1) ? $args[0] : reverse @args;
350 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
351 0         0 my $result = $connection->has_view($view, $schema);
352 0         0 $connection->close();
353 0         0 return $result;
354             }
355              
356              
357             =item has_column
358              
359             Returns true if the specified column for given table exists.
360              
361             $dbunit->has_column($schema, $table, $columm);
362             $dbunit->has_column($table, $columm);
363              
364             =cut
365              
366             sub has_column {
367 0     0 1 0 my ($self, @args) = @_;
368 0 0       0 my ($table, $column, $schema) = (@args == 2) ? @args : @args[1,2,0];
369 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
370 0         0 my $result = !! $connection->column($table, lc $column, $schema);
371 0         0 $connection->close();
372 0         0 return $result;
373             }
374              
375              
376             =item has_columns
377              
378             Returns true if all specified columns exist for given table otherwise undef.
379             Check additionally failed_test_info method.
380              
381             my $columms = ['id', 'name']
382             $dbunit->has_columns($schema, $table, $columms);
383             $dbunit->has_column($table, $columms);
384              
385             =cut
386              
387             sub has_columns {
388 0     0 1 0 my ($self, @args) = @_;
389 0 0       0 my ($table, $columns, $schema) = (@args == 2) ? @args : @args[1,2,0];
390 0 0       0 confess 'columns must be an array ref type'
391             unless ref($columns) eq 'ARRAY';
392 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
393 0   0     0 my $db_columns = $connection->columns($table, $schema) || [];
394 0         0 my @db_columns = map {lc($_->{name})} @$db_columns;
  0         0  
395 0         0 $connection->close();
396            
397 0         0 my @missing = map { my $column = $_;
  0         0  
398 0 0       0 (! (grep { lc($_) eq $column} @$columns) ? ($column) : ())
  0         0  
399             } @db_columns;
400            
401 0         0 my @additional = map { my $column = lc($_);
  0         0  
402 0 0       0 (! (grep { $_ eq $column} @db_columns) ? ($column) : ())
  0         0  
403             } @$columns;
404            
405 0         0 my $result;
406 0         0 $self->_set_failed_test_info('');
407 0 0 0     0 if(@missing || @additional) {
408 0         0 my $plural_missing = @missing > 1;
409 0         0 $self->_set_failed_test_info(
410             sprintf("got %s colunms: %s\nexpected: %s (-%s +%s)",
411             $table,
412             join (", ", @db_columns),
413             join (", ", @$columns),
414             join (", ", @missing),
415             join (", ", @additional),
416             )
417             );
418 0         0 $result = undef;
419             } else {
420 0         0 $result = 1;
421             }
422 0         0 return $result;
423             }
424              
425              
426             =item column_is_null
427              
428             Returns true if the specified column for given table can be nullable.
429              
430             $dbunit->column_is_null($schema, $table, $columm);
431             $dbunit->column_is_null($table, $columm);
432              
433             =cut
434              
435             sub column_is_null {
436 0     0 1 0 my ($self, @args) = @_;
437 0 0       0 my ($table, $column, $schema) = (@args == 2) ? @args : @args[1,2,0];
438 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
439 0         0 my $column_def = $connection->column($table, lc ($column), $schema);
440 0         0 $connection->close();
441 0 0       0 return undef unless $column_def;
442 0 0       0 return exists ($column_def->{nullable}) ? $column_def->{nullable} : undef;
443             }
444              
445              
446             =item column_is_not_null
447              
448             Returns true if the specified column for given table cant be nullable.
449              
450             $dbunit->column_is_not_null($schema, $table, $columm);
451             $dbunit->column_is_not_null($table, $columm);
452              
453             =cut
454              
455             sub column_is_not_null {
456 0     0 1 0 my ($self, @args) = @_;
457 0 0       0 my ($table, $column, $schema) = (@args == 2) ? @args : @args[1,2,0];
458 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
459 0         0 my $column_def = $connection->column($table, lc ($column), $schema);
460 0         0 $connection->close();
461 0 0       0 return undef unless $column_def;
462 0 0       0 return exists ($column_def->{nullable}) ? ! $column_def->{nullable} : undef;
463             }
464              
465              
466             {
467             my @data_type_aliases = (
468             ['TEXT', 'VARCHAR', 'CHARACTER VARYING', 'VARCHAR2'],
469             ['BPCHAR', 'CHAR', 'CHARACTER'],
470             ['NUMERIC', 'FLOAT', 'DOUBLE PRECISION', 'DECIMAL'],
471             );
472              
473              
474             =item _check_type_family
475              
476             Checks data type families, tests if the specified testes type belongs to the same group as db_type (or dbi type)
477             There are currently the following synonyms for the families
478              
479             - 'TEXT', 'VARCHAR', 'CHARACTER VARYING', 'VARCHAR2'
480             - 'BPCHAR', 'CHAR', 'CHARACTER'
481             - 'NUMERIC', 'FLOAT'
482              
483             =cut
484              
485             sub _check_type_family {
486 0     0   0 my ($self, $tested_type, $db_type) = @_;
487 0         0 my $result;
488 0         0 for my $type_family (@data_type_aliases) {
489 0 0       0 if (scalar (grep {($tested_type =~ /$_/) || $db_type eq $_} @$type_family) > 1) {
  0 0       0  
490 0         0 $result = $tested_type;
491 0         0 last;
492             }
493             }
494 0 0       0 unless($result) {
495 0   0     0 $result = (lc($tested_type) eq lc $db_type) || ($tested_type =~ /\(/ && $tested_type =~ /$db_type/);
496             }
497              
498 0         0 return $result ;
499             }
500              
501             =item _data_type_aliases
502              
503             =cut
504              
505             sub _data_type_aliases {
506 0     0   0 \@data_type_aliases;
507             }
508              
509             =item _match_data_type
510              
511             Returns undef if the specified data type matches underlying database type otherwise type name
512              
513             =cut
514              
515             sub _match_data_type {
516 0     0   0 my ($self, $tested_type, $dbi_type, $width, $db_type) = @_;
517 0         0 my ($expected_width) = ($tested_type =~ /\(([^\)]+)/);
518 0         0 my $result = $self->_check_type_family($tested_type, $dbi_type);
519 0 0 0     0 if ($result && $expected_width) {
520 0         0 $result = ($expected_width eq $width);
521             }
522              
523 0 0 0     0 return $result ? undef : $db_type
524             || ($dbi_type . (($dbi_type =~ /CHAR|NUM|FLOAT/ && $width > 0) ? "(${width})" : ''));
525             }
526             }
527              
528              
529             =item column_type_is
530              
531             Returns true if the specified column's type for given table matches
532             underlying column type otherwise undef;
533             Check additionally failed_test_info method.
534              
535             $dbunit->column_type_is($schema, $table, $columm, $type);
536             $dbunit->column_type_is($table, $columm, $type);
537              
538             =cut
539              
540             sub column_type_is {
541 0     0 1 0 my ($self, @args) = @_;
542 0 0       0 my ($table, $column, $type, $schema) = (@args == 3) ? @args : @args[1,2,3,0];
543 0         0 $self->_set_failed_test_info('');
544 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
545 0         0 my $column_def = $connection->column($table, lc ($column), $schema);
546 0         0 $connection->close();
547 0 0       0 unless ($column_def) {
548 0         0 $self->_set_failed_test_info(sprintf("column %s doesn't exists in table %s", $column, $table));
549 0         0 return undef;
550             }
551 0   0     0 my $type_ref = $column_def->{type_info} || {};
552 0         0 my ($type_name, $width) = ($type_ref->{TYPE_NAME}, $column_def->{width});
553 0 0       0 if($column_def->{db_type}) {
554 0         0 ($type_name, $width) = ($column_def->{db_type} =~ /([^\(]+)\(([^\)]+)\)/);
555 0 0       0 $type_name = $column_def->{db_type}
556             unless $type_name;
557             }
558 0 0       0 if(my $result = $self->_match_data_type(uc($type), uc($type_name), $width, uc $column_def->{db_type})) {
559 0         0 $self->_set_failed_test_info(sprintf("got %s type: %s\nexpected: %s", $column, $result, $type));
560 0         0 return undef;
561             }
562 0         0 return !! $self;
563             }
564              
565              
566             =item column_default_is
567              
568             Returns true if the specified column's default value matches database definition otherwise undef.
569             Check additionally failed_test_info.
570              
571             $dbunit->column_default_is($schema, $table, $columm, $default);
572             $dbunit->column_default_is($table, $columm, $default);
573              
574             =cut
575              
576             sub column_default_is {
577 0     0 1 0 my ($self, @args) = @_;
578 0 0       0 my ($table, $column, $default, $schema) = (@args == 3) ? @args : @args[1,2,3,0];
579 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
580 0         0 my $column_def = $connection->column($table, lc ($column), $schema);
581 0         0 $self->_set_failed_test_info('');
582 0         0 $connection->close();
583 0 0       0 unless ($column_def) {
584 0         0 $self->_set_failed_test_info(sprintf("column %s doesn't exists in table %s", $column, $table));
585 0         0 return undef;
586             }
587 0         0 my $quted_default = quotemeta($default);
588 0 0       0 unless($column_def->{default} =~ /$quted_default/) {
589 0         0 $self->_set_failed_test_info(sprintf("got default value: %s\nexpected: %s", $column_def->{default}, $default));
590 0         0 return undef;
591             }
592 0         0 return !! $self;
593             }
594              
595              
596             =item column_is_unique
597              
598             Returns true if the specified column for given table has unique constraint.
599              
600             $dbunit->column_is_unique($schema, $table, $column);
601             $dbunit->column_is_unique($table, $column);
602              
603             =cut
604              
605             sub column_is_unique {
606 0     0 1 0 my ($self, @args) = @_;
607 0 0       0 my ($table, $column, $schema) = (@args == 2) ? @args : @args[1,2,0];
608 0         0 $self->_set_failed_test_info('');
609 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
610 0         0 my $column_def = $connection->column($table, lc ($column), $schema);
611 0         0 $connection->close();
612 0 0       0 return undef unless $column_def;
613 0         0 return $column_def->{unique};
614             }
615              
616              
617             =item has_pk
618              
619             Returns true if the specified column or columns are part of the primary key
620             for the given table.
621              
622             my $columns = ['id']; #or my $columns = ['master_id', 'seq_no'];
623              
624             $dbunit->has_pk($table, $columns);
625             $dbunit->has_pk($schema, $table, $columns);
626              
627              
628             $dbunit->has_pk($table, $column);
629             $dbunit->has_pk($schema, $table, $column);
630              
631             $dbunit->has_pk($table);
632             $dbunit->has_pk($schema, $table);
633              
634             =cut
635              
636             sub has_pk {
637 0     0 1 0 my ($self, $schema, $table, $columns) = @_;
638 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
639 0 0 0     0 my @primary_key_columns = $table && (! ref $table) ? $connection->primary_key_columns($table, $schema) : ();
640 0         0 $self->_set_failed_test_info("");
641 0         0 my $result;
642 0 0       0 unless (@primary_key_columns) {
643 0         0 $columns = $table;
644 0         0 $table = $schema;
645 0         0 $schema = undef;
646 0         0 @primary_key_columns = $connection->primary_key_columns($table, $schema)
647             }
648 0         0 $connection->close;
649 0         0 $result = !! @primary_key_columns;
650 0 0       0 unless($result) {
651 0         0 $self->_set_failed_test_info(sprintf("primary key doesn't exist on table %s", $table));
652             }
653 0 0 0     0 if ($result && $columns) {
654 0 0       0 $columns = [$columns] unless ref($columns);
655 0         0 for my $colunm (@$columns) {
656 0 0       0 if(grep {$_ eq $colunm} @primary_key_columns) {
  0         0  
657 0         0 $result = 1;
658             } else {
659 0         0 $result = undef;
660 0         0 last;
661             }
662             }
663 0 0       0 unless($result) {
664 0         0 $self->_set_failed_test_info(sprintf("%s primary key columns don't match got: %s\nexpected: %s ",
665             $table,
666             join(", ",@$columns),
667             join(", ", @primary_key_columns)
668             ));
669             }
670             }
671 0         0 return $result;
672             }
673              
674              
675              
676             =item has_fk
677              
678             Returns true if the specified column or columns for given table are part
679             of the foreign key for the referenced table.
680              
681             my $columns = ['id']; #or my $columns = ['master_id', 'seq_no'];
682             $dbunit->has_fk($schema, $table, $columns, $referenced_schema, $referenced_table);
683             $dbunit->has_fk($table, $columns, $referenced_table);
684              
685             =cut
686              
687             sub has_fk {
688 0     0 1 0 my ($self, @args) = @_;
689 0 0       0 my ($table, $columns, $referenced_table, $schema, $referenced_schema) = (@args == 3)
690             ? @args : @args[1, 2, 4, 0, 3];
691 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
692 0         0 $self->_set_failed_test_info("");
693 0   0     0 my $foreign_key_info = $connection->foreign_key_info($table, $referenced_table) || [];
694 0         0 $connection->close;
695 0         0 my %fk;
696 0         0 for my $row (@$foreign_key_info) {
697 0   0     0 my $id = $row->[11] || $row->[2];
698 0         0 push @{$fk{$id}}, $row;
  0         0  
699             }
700 0         0 my $result = !! scalar %fk;
701 0         0 for my $fk (values %fk) {
702 0         0 my @foreign_key_columns = map {$_->[7]} @$fk;
  0         0  
703 0 0       0 $columns = [$columns] unless ref($columns);
704 0         0 for my $i (0 .. $#foreign_key_columns) {
705 0 0       0 if(lc $columns->[$i] ne $foreign_key_columns[$i]) {
706 0         0 $result = undef;
707 0         0 last;
708             } else {
709 0         0 $result = 1;
710             }
711             }
712 0 0       0 unless($result) {
713 0         0 $self->_set_failed_test_info(sprintf("%s -> %s foreign key columns don't match got: %s\nexpected: %s ",
714             $table, $referenced_table,
715             join(", ",@$columns),
716             join(", ", @foreign_key_columns)
717             ));
718             }
719            
720 0 0       0 if ($result) {
721 0         0 $self->_set_failed_test_info('');
722 0         0 last;
723             }
724            
725             }
726 0 0       0 unless ($result) {
727 0         0 $self->_set_failed_test_info(sprintf("foreign key doesn't exist for tables %s AND %s", $table, $referenced_table));
728             }
729 0         0 return $result;
730             }
731              
732              
733             =item has_index
734              
735             Returns true if the specified column or columns are part of the index
736             for the given table.
737              
738             my $columns = ['id']; #or my $columns = ['master_id', 'seq_no'];
739              
740             $dbunit->has_index($table, $index, $column_or_expressions);
741             $dbunit->has_index($schema, $table, $index, $column_or_expressions);
742              
743             $dbunit->has_index($table, $index, $columns);
744             $dbunit->has_index($schema, $table, $index, $columns);
745            
746             $dbunit->has_index($table, $index);
747             $dbunit->has_index($schema, $table, $index);
748              
749             =cut
750              
751             sub has_index {
752 0     0 1 0 my ($self, $schema, $table, $index, @args) = @_;
753 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
754 0         0 $self->_set_failed_test_info('');
755 0         0 my $index_info = $connection->index_info($index, $schema, $table);
756 0         0 my $columns;
757             my $result;
758 0 0 0     0 if(! $index_info || !@$index_info) {
759 0         0 $columns = $index;
760 0         0 $index = $table;
761 0         0 $table = $schema;
762 0         0 $schema = undef;
763 0         0 $index_info = $connection->index_info($index, $schema, $table);
764 0         0 $connection->close;
765 0 0 0     0 return $result
766             if (!$index_info || !@$index_info);
767             }
768 0         0 $connection->close;
769            
770 0 0       0 if(lc($index_info->[0]->{table_name}) ne lc($table)) {
771 0         0 $self->_set_failed_test_info(sprintf("index %s doesn't match table got: %s\nexpected: %s",
772             lc($index_info->[0]->{table_name}),
773             lc($table)
774             ));
775             }
776 0 0 0     0 $columns = ($index && @args ? shift @args : undef)
    0          
777             unless $columns;
778 0 0       0 if($columns) {
779 0 0       0 $columns = [$columns] unless ref($columns);
780 0         0 my @index_columns = map {$_->{column_name}} @$index_info;
  0         0  
781 0         0 for my $i(0 .. $#index_columns) {
782 0 0       0 if(lc $index_columns[$i] ne lc $columns->[$i]) {
783 0         0 $result = undef;
784 0         0 last;
785             } else {
786 0         0 $result = 1;
787             }
788             }
789            
790 0         0 $self->_set_failed_test_info(sprintf("index %s columns don't match got: %s\nexpected: %s",
791             $index,
792             join (', ', @index_columns),
793             join (', ', @$columns)));
794             } else {
795 0         0 $result = 1;
796             }
797 0         0 return $result;
798             }
799              
800              
801             =item index_is_unique
802              
803             Returns true if the specified index is unique.
804              
805             $dbunit->index_is_unique($schema, $table, $index);
806             $dbunit->index_is_unique($table, $index);
807              
808             =cut
809              
810             sub index_is_unique {
811 0     0 1 0 my ($self, @args) = @_;
812 0 0       0 my ($table, $index, $schema) = (@args == 2) ? @args : @args[1,2,0];
813 0         0 $self->_set_failed_test_info('');
814 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
815 0         0 my $index_info = $connection->index_info($index, $schema, $table);
816 0         0 $connection->close;
817 0 0 0     0 return undef if(! $index_info || !@$index_info);
818 0         0 return !! $index_info->[0]->{is_unique};
819             }
820              
821              
822             =item index_is_primary
823              
824             Returns true if the specified index is primary key.
825              
826             $dbunit->index_is_primary($schema, $table, $index);
827             $dbunit->index_is_primary($table, $index);
828              
829             =cut
830              
831             sub index_is_primary {
832 0     0 1 0 my ($self, @args) = @_;
833 0 0       0 my ($table, $index, $schema) = (@args == 2) ? @args : @args[1,2,0];
834 0         0 $self->_set_failed_test_info('');
835 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
836 0         0 my $index_info = $connection->index_info($index, $schema, $table);
837 0         0 $connection->close;
838 0 0 0     0 return undef if(! $index_info || !@$index_info);
839 0         0 return !! ($index_info->[0]->{is_pk});
840             }
841              
842              
843             =item index_is_type
844              
845             Returns true if the specified index's type is the index type
846             from underlying database, otherwise undef.
847             Check additionally failed_test_info method.
848              
849             $dbunit->index_is_type($schema, $table, $index, $type);
850             $dbunit->index_is_type($table, $index, $type);
851              
852             =cut
853              
854             sub index_is_type {
855 0     0 1 0 my ($self, @args) = @_;
856 0 0       0 my ($table, $index, $type, $schema) = (@args == 3) ? @args : @args[1,2,3,0];
857 0         0 $self->_set_failed_test_info('');
858 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
859 0         0 my $index_info = $connection->index_info($index, $schema, $table);
860 0         0 $connection->close;
861 0         0 $self->_set_failed_test_info('');
862 0 0 0     0 if(! $index_info || !@$index_info) {
863 0         0 $self->_set_failed_test_info("index ${index} doesn't exist");
864             }
865            
866 0 0       0 if (lc($index_info->[0]->{index_type}) ne $type) {
867 0         0 $self->_set_failed_test_info(sprintf("got index type: %s\nexpected: %s", $index_info->[0]->{index_type}, $type));
868 0         0 return undef;
869             }
870 0         0 return $self;
871             }
872              
873              
874             =item has_trigger
875              
876             Returns true if the specified trigger exists for the given table.
877              
878             $dbunit->has_trigger($schema, $table, $trigger);
879             $dbunit->has_trigger($table, $trigger);
880              
881             =cut
882              
883             sub has_trigger {
884 0     0 1 0 my ($self, @args) = @_;
885 0 0       0 my ($table, $trigger, $schema) = (@args == 2) ? @args : @args[1,2,0];
886 0         0 $self->_set_failed_test_info('');
887 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
888 0 0       0 my $trigger_info = $connection->trigger_info($trigger, $schema)
889             or return undef;
890             return undef
891 0 0       0 if (lc($trigger_info->{table_name}) ne lc($table));
892 0         0 return !! $trigger_info
893             }
894              
895              
896              
897              
898             =item has_sequence
899              
900             Returns true if the specified sequence exists.
901              
902             =cut
903              
904             sub has_sequence {
905 0     0 1 0 my ($self, @args) = @_;
906 0 0       0 my ($sequence, $schema) = (@args == 1) ? $args[0] : reverse @args;
907 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
908 0         0 my $result = $connection->has_sequence($sequence, $schema);
909 0         0 $connection->close();
910 0         0 return $result;
911             }
912              
913              
914             =item trigger_is
915              
916             Returns true if the specified trigger body matches the trigger body (or function in case of postgresql)
917             for given table, otherwise undef check additionally failed_test_info method.
918              
919              
920             $dbunit->trigger_is($schema, $table, $trigger, $trigger_body);
921             $dbunit->trigger_is($table, $trigger, $trigger_body);
922              
923             =cut
924              
925             sub trigger_is {
926 0     0 1 0 my ($self, @args) = @_;
927 0 0       0 my ($table, $trigger, $trigger_body, $schema) = (@args == 3) ? @args : @args[1,2,3,0];
928 0         0 $self->_set_failed_test_info('');
929 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
930 0         0 my $trigger_info = $connection->trigger_info($trigger, $schema);
931 0         0 $self->_set_failed_test_info('');
932 0 0       0 unless ($trigger_info) {
933 0         0 $self->_set_failed_test_info(sprintf("trigger %s doesn't exist", $trigger));
934             }
935 0         0 $connection->close;
936 0 0       0 if (lc($trigger_info->{table_name}) ne lc($table)) {
937 0   0     0 $self->_set_failed_test_info(sprintf("trigger %s doesn't exist for table %s, \ntrigger is defined on %s table",
938             $trigger,
939             ($trigger_info->{table_name} || ''),
940             $table)
941             );
942 0         0 return undef;
943             }
944            
945 0   0     0 my $trigger_func = $trigger_info->{trigger_func} || '';
946 0         0 my $trigger_body_ = $trigger_func . ' ' . $trigger_info->{trigger_body} ;
947 0 0       0 unless($trigger_body_ =~ /$trigger_body/i) {
948 0         0 $self->_set_failed_test_info(sprintf("got body: %s\nexpected: %s",$trigger_body, $trigger_body_));
949 0         0 return undef;
950             }
951 0         0 return $self;
952             }
953              
954              
955             =item has_routine
956              
957             Returns true if the specified routine exists and have matched prototype
958              
959             my $args = ['type1', 'type2', 'return_type'];
960             or
961             my $args = ['IN type1', 'OUT type2', 'type3'];
962             or
963             my $args = ['name1 type1', 'name2 type2', 'return type3'];
964             or
965             my $args = ['IN name1 type1', 'INOUT name2 type2', 'return type3'];
966            
967             $dbunit->has_routine($schema, $function);
968             $dbunit->has_routine($function);
969             $dbunit->has_routine($schema, $function, $args);
970             $dbunit->has_routine($function, $args);
971              
972             In case of testing function arguments, the last one is the function return type.
973             Check additionally failed_test_info method.
974              
975             =cut
976              
977             sub has_routine {
978 0     0 1 0 my ($self, $schema, $function, $args) = @_;
979 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
980 0         0 $self->_set_failed_test_info('');
981 0         0 my $functions_info = $connection->routine_info($function, $schema);
982 0         0 $self->_set_failed_test_info('');
983 0 0       0 if (! $functions_info) {
984 0         0 $args = $function;
985 0         0 $function = $schema;
986 0         0 $schema = undef;
987 0         0 $functions_info = $connection->routine_info($function, $schema);
988 0 0       0 unless ($functions_info) {
989 0         0 $self->_set_failed_test_info(sprintf("function %s doesn't exist", $function));
990             return undef
991 0         0 }
992             }
993 0         0 $connection->close;
994 0         0 my $result = 1;
995 0 0       0 if($args) {
996 0 0       0 $args =[$args] unless ref($args) eq 'ARRAY';
997 0         0 $result = undef;
998 0         0 for my $routine_info (@$functions_info) {
999 0         0 my $routine_args = $routine_info->{args};
1000            
1001 0 0       0 push @$routine_args, {type => $routine_info->{return_type}, name => 'return', mode => 'return'}
1002             if $routine_info->{return_type};
1003              
1004 0         0 for my $i (0 .. $#{$routine_args}) {
  0         0  
1005 0         0 my $res = $self->_validate_routine_argument($routine_args->[$i], $args->[$i], $routine_info);
1006 0 0       0 if($res) {
1007 0         0 $result = 1;
1008             } else {
1009 0         0 $result = undef;
1010 0         0 last;
1011             }
1012             }
1013 0 0       0 last if $result;
1014             }
1015              
1016 0 0       0 unless($result) {
1017 0 0       0 $self->_set_failed_test_info(sprintf("function %s doesn't match the specified arguments %s\nexistsing prototypes: %s",
1018             $function,
1019             join (', ',@$args),
1020 0         0 join ("\n", map { $function .'(' . $_->{routine_arguments} .')'
1021             . ($_->{return_type} ? ' RETURNS ' . $_->{return_type} : '') } @$functions_info))
1022             );
1023             } else {
1024 0         0 $self->_set_failed_test_info('');
1025             }
1026             }
1027 0         0 return $result;
1028             }
1029              
1030              
1031             =item _validate_routine_argument
1032              
1033             =cut
1034              
1035             sub _validate_routine_argument {
1036 0     0   0 my ($self, $routine_arg, $arg, $routine_info) = @_;
1037 0 0       0 my $mode = ($arg =~ s/(IN OUT|IN|OUT|INOUT) //i) ? $1 : undef;
1038            
1039 0 0 0     0 if ($mode && lc($mode) ne lc $routine_arg->{mode}) {
1040 0         0 return undef;
1041             }
1042            
1043 0         0 my ($name, $type) = ($arg =~ /([^\s]+)\s+([^\s]+)/);
1044 0 0       0 $type = $arg unless $type;
1045              
1046 0 0 0     0 if ($name && lc($name) ne lc($routine_arg->{name})) {
1047 0         0 return undef;
1048             }
1049              
1050 0 0 0     0 if ($type && ! $self->_check_type_family(lc($type), lc($routine_arg->{type}))) {
1051 0         0 return undef;
1052             }
1053 0         0 return 1;
1054             }
1055              
1056              
1057              
1058             =item _set_failed_test_info
1059              
1060             =cut
1061              
1062             sub _set_failed_test_info {
1063 0     0   0 my ($self, $value) = @_;
1064 0         0 $self->{_failed_test_info} = $value;
1065             }
1066              
1067              
1068             =item failed_test_info
1069              
1070             Stores the last failed test detail.
1071              
1072             =cut
1073              
1074             sub failed_test_info {
1075 0 0   0 1 0 shift()->{_failed_test_info} ||'';
1076             }
1077              
1078              
1079             =item routine_is
1080              
1081             Returns true if the specified function matches passed in body
1082              
1083              
1084             $dbunit->has_routine($schema, $function, $args, $routine_body);
1085             $dbunit->has_routine($function, $args. $routine_body);
1086              
1087             =cut
1088              
1089             sub routine_is {
1090 0     0 1 0 my ($self, @args) = @_;
1091 0 0       0 my ($table, $function, $routine_body, $schema) = (@args == 2) ? @args : @args[1,2,3,0];
1092 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
1093 0         0 my $functions_info = $connection->routine_info($function, $schema);
1094 0 0       0 if (! $functions_info) {
1095 0         0 $routine_body = $function;
1096 0         0 $function = $schema;
1097 0         0 $schema = undef;
1098 0 0       0 $functions_info = $connection->routine_info($function, $schema)
1099             or return undef
1100             }
1101 0         0 my $result;
1102 0         0 foreach my $routine_info (@$functions_info) {
1103 0 0       0 if ($routine_info->{routine_body} =~ /$routine_body/) {
1104 0         0 $result = 1;
1105 0         0 last;
1106             }
1107             }
1108 0         0 return $result;
1109             }
1110              
1111              
1112              
1113             =item xml_dataset
1114              
1115             Loads xml file to dataset and populates/synchronizes it to the database schema.
1116             Takes xml file as parameter.
1117              
1118            
1119            
1120            
1121            
1122            
1123            
1124              
1125             =cut
1126              
1127             sub xml_dataset {
1128 0     0 1 0 my ($self, $file) = @_;
1129 0         0 my $xml = $self->load_xml($file);
1130 0         0 $self->apply_properties($xml->{properties});
1131 0         0 $self->dataset(@{$xml->{dataset}});
  0         0  
1132             }
1133              
1134              
1135             =item expected_xml_dataset
1136              
1137             Takes xml file as parameter.
1138             Return differences report or undef is there are not discrepancies.
1139              
1140             =cut
1141              
1142             sub expected_xml_dataset {
1143 0     0 1 0 my ($self, $file) = @_;
1144 0         0 my $xml = $self->load_xml($file);
1145 0         0 $self->apply_properties($xml->{properties});
1146 0         0 $self->expected_dataset(@{$xml->{dataset}});
  0         0  
1147             }
1148              
1149              
1150              
1151             =item apply_properties
1152              
1153             Sets properties for this object.
1154              
1155             =cut
1156              
1157             sub apply_properties {
1158 0     0 1 0 my ($self, $properties) = @_;
1159 0         0 my $strategy = $properties->{load_strategy};
1160 0         0 $self->set_load_strategy(__PACKAGE__->$strategy);
1161 0         0 my $reset_sequences = $properties->{reset_sequences};
1162 0 0       0 if ($reset_sequences) {
1163 0         0 my @seqs = split /,/, $reset_sequences;
1164 0         0 for my $sequence_name (@seqs) {
1165 0         0 $self->reset_sequence($sequence_name);
1166             }
1167             }
1168             }
1169              
1170              
1171             =back
1172              
1173             =head2 PRIVATE METHODS
1174              
1175             =over
1176              
1177             =item rows_to_insert
1178              
1179             =cut
1180              
1181             sub rows_to_insert {
1182 1     1 1 688 my ($self, $sql) = @_;
1183 1 100       14 map {($_ =~ /\w+/ ? $_ .')' : ())} split qr{\)\W*;}, $sql;
  3         16  
1184            
1185             }
1186              
1187              
1188             =item drop_objects
1189              
1190             Removes existing schema
1191              
1192             =cut
1193              
1194             sub drop_objects {
1195 0     0 1 0 my ($self, @objects) = @_;
1196 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
1197 0         0 my $dbms_name = lc($connection->dbms_name);
1198 0 0       0 my $cascade = ($dbms_name eq "postgresql" ? 'CASCADE' : '');
1199 0         0 for my $object (@objects) {
1200 0 0       0 next if ($object =~ /^\d+$/);
1201 0 0 0     0 if($object =~ m/table\s+`*(\w+)`*/i) {
    0          
    0          
    0          
1202 0         0 my $table = $1;
1203 0 0       0 $connection->do("DROP ${object} ${cascade}")
1204             if $connection->has_table($table);
1205             } elsif($object =~ m/view\s+`*(\w+)`*/i) {
1206 0         0 my $table = $1;
1207 0 0       0 $connection->do("DROP $object")
1208             if $connection->has_view($table);
1209            
1210             } elsif($object =~ m/sequence\s+`*(\w+)`*/i) {
1211 0         0 my $sequence = $1;
1212 0 0       0 $connection->do("DROP ${object} ${cascade}")
1213             if $connection->has_sequence($sequence);
1214             } elsif(($object =~ m/(procedure)\s+`*(\w+)`*/i) || ($object =~ m/(function)\s+`*(\w+)`*/i)) {
1215 0         0 my ($type, $function) = ($1,$2);
1216 0 0       0 if (my $routines_info = $connection->routine_info($function)) {
1217 0         0 for my $routines_info(@$routines_info) {
1218 0 0 0     0 next if(lc($type) eq 'procedure' && $routines_info->{return_type});
1219 0         0 my $declation = '(' . $routines_info->{routine_arguments} . ')';
1220 0 0       0 $connection->do("DROP $object "
1221             . ((lc($connection->dbms_name) eq 'postgresql') ? $declation : ''));
1222             }
1223            
1224             }
1225             }
1226            
1227             }
1228 0         0 $connection->close();
1229             }
1230              
1231              
1232             =item create_tables
1233              
1234             =cut
1235              
1236             sub create_tables {
1237 0     0 1 0 my ($self, @tables) = @_;
1238 0         0 my $connection = DBIx::Connection->connection($self->connection_name);
1239 0         0 for my $sql (@tables) {
1240 0         0 $connection->do($sql);
1241             }
1242 0         0 $connection->close();
1243             }
1244              
1245              
1246              
1247             =item objects_to_create
1248              
1249             Returns list of pairs values('object_type object_name', create_sql, ..., 'object_typeN object_nameN', create_sqlN)
1250              
1251             =cut
1252              
1253             sub objects_to_create {
1254 1     1 1 1293 my ($self, $sql) = @_;
1255 1         2 my @result;
1256 1         9 my @create_sql = split /CREATE/i, $sql;
1257            
1258 1         3 my $i = 0;
1259 1         2 my $plsql_block = "";
1260 1         2 my $inside_plsql_block;
1261              
1262 1         3 for my $sql_statement (@create_sql) {
1263 5 100       15 next unless ($sql_statement =~ /\w+/);
1264 4         13 my ($object) = ($sql_statement =~ m/^\s+or\s+replace\s+(\w+\s+\w+)/i);
1265 4 100       7 unless($object) {
1266 3         8 ($object, my $name) = ($sql_statement =~ m/^\s+(\w+)\s+if\s+not\s+exists\s+(\w+)/i);
1267 3 50       19 $object .= " " . $name if $name;
1268             }
1269 4 100       8 unless($object) {
1270 3         11 ($object) = ($sql_statement =~ m/^\s+(\w+\s+\w+)/i);
1271             }
1272 4         22 $sql_statement =~ s/[;\n\r\s]+$//g;
1273 4 100       28 $sql_statement = "CREATE" . $sql_statement . ($object =~ /trigger|function|procedure/i ? ';': '');
1274 4         8 push @result, $object, $sql_statement;
1275             }
1276 1         9 @result;
1277             }
1278              
1279              
1280             =item insert
1281              
1282             Inserts data
1283              
1284             =cut
1285              
1286             sub insert {
1287 0     0 1 0 my ($self, $table, $field_values, $connection) = @_;
1288 0         0 my @fields = keys %$field_values;
1289 0         0 my $sql = sprintf "INSERT INTO %s (%s) VALUES (%s)",
1290             $table, join(",", @fields), join(",", ("?")x @fields);
1291 0         0 $connection->execute_statement($sql, map {$field_values->{$_}} @fields);
  0         0  
1292             }
1293              
1294              
1295             =item merge
1296              
1297             Merges passed in data
1298              
1299             =cut
1300              
1301             sub merge {
1302 0     0 1 0 my ($self, $table, $field_values, $connection) = @_;
1303 0         0 my %pk_values = $self->primary_key_values($table, $field_values, $connection);
1304 0 0       0 my $values = (%pk_values) ? \%pk_values : $field_values;
1305 0         0 my $exists = $self->_exists_in_database($table, $values, $connection);
1306 0 0       0 if($exists) {
1307 0         0 my $pk_columns = $self->primary_key_definition_cache->{$table};
1308 0 0 0     0 return if(! $pk_columns || !(@$pk_columns));
1309             }
1310 0 0       0 my $operation = $exists ? 'update' : 'insert';
1311 0         0 $self->$operation($table, $field_values, $connection);
1312             }
1313              
1314              
1315             =item update
1316              
1317             Updates table values.
1318              
1319             =cut
1320              
1321             sub update {
1322 0     0 1 0 my ($self, $table, $field_values, $connection) = @_;
1323 0         0 my %pk_values = $self->primary_key_values($table, $field_values, $connection);
1324 0         0 my @fields = keys %$field_values;
1325 0         0 my @pk_fields = (sort keys %pk_values);
1326 0         0 my $where_clause = join(" AND ", map { $_ ." = ? " } @pk_fields);
  0         0  
1327 0         0 my $sql = sprintf "UPDATE %s SET %s WHERE %s",
1328             $table,
1329 0         0 join (", ", map { $_ . ' = ?' } @fields),
1330             $where_clause;
1331 0         0 $connection->execute_statement($sql, (map {$field_values->{$_}} @fields), (map { $pk_values{$_} } @pk_fields));
  0         0  
  0         0  
1332             }
1333              
1334              
1335             =item has_primary_key_values
1336              
1337             Returns true if passed in dataset have primary key values
1338              
1339             =cut
1340              
1341             sub has_primary_key_values {
1342 0     0 1 0 my ($self, $table_name, $dataset, $connection) = @_;
1343 0         0 !! $self->primary_key_values($table_name, $dataset, $connection);
1344             }
1345              
1346              
1347             =item primary_key_values
1348              
1349             Returns primary key values, Takes table name, hash ref as fields of values, db connection object.
1350              
1351             =cut
1352              
1353             sub primary_key_values {
1354 0     0 1 0 my ($self, $table_name, $dataset, $connection) = @_;
1355 0   0     0 my $pk_columns = $self->primary_key_definition_cache->{$table_name} ||= [$connection->primary_key_columns($table_name)];
1356 0         0 my @result;
1357 0         0 for my $column (@$pk_columns) {
1358 0         0 my $value = $dataset->{$column};
1359 0 0       0 return () unless defined $value;
1360 0         0 push @result, $column, $value;
1361             }
1362 0         0 @result;
1363             }
1364              
1365              
1366             =item delete_data
1367              
1368             Deletes data from passed in tables.
1369              
1370             =cut
1371              
1372             sub delete_data {
1373 0     0 1 0 my ($self, $dataset, $connection) = @_;
1374 0         0 my @tables = $self->tables_to_delete($dataset);
1375 0         0 for my $table (@tables) {
1376 0         0 $connection->do("DELETE FROM $table");
1377             }
1378             }
1379              
1380              
1381             =item tables_to_delete
1382              
1383             Returns list of tables to delete.
1384              
1385             =cut
1386              
1387             sub tables_to_delete {
1388 1     1 1 622 my ($self, $dataset) = @_;
1389 1         3 my @result = $self->empty_tables_to_delete($dataset);
1390 1 50       6 return @result if ($self->load_strategy ne INSERT_LOAD_STRATEGY());
1391 1         37 my %has_table = (map { $_ => 1 } @result);
  2         7  
1392 1         2 for (my $i = $#{$dataset} - 1; $i >= 0; $i -= 2) {
  1         6  
1393 7         8 my $table = $dataset->[$i];
1394 7 100       19 next if $has_table{$table};
1395 3         6 $has_table{$table} = 1;
1396 3         8 push @result, $table;
1397             }
1398 1         8 @result;
1399             }
1400              
1401              
1402             =item empty_tables_to_delete
1403              
1404             Returns list of table that are part of dataset table and are represented by table without attributes
1405              
1406             table1 => [],
1407              
1408             or in xml file
1409              
1410            
1411              
1412             =cut
1413              
1414             sub empty_tables_to_delete {
1415 2     2 1 634 my ($self, $dataset) = @_;
1416 2         3 my @result;
1417 2         3 for (my $i = 0; $i < $#{$dataset}; $i += 2) {
  16         32  
1418 14 100       12 next if @{$dataset->[$i + 1]};
  14         30  
1419 4         7 push @result, $dataset->[$i]
1420             }
1421 2         7 @result;
1422             }
1423              
1424              
1425             =item expected_dataset_for_insert_load_strategy
1426              
1427             Validates expected dataset for the insert load strategy.
1428              
1429             =cut
1430              
1431             sub expected_dataset_for_insert_load_strategy {
1432 0     0 1 0 my ($self, $exp_dataset, $connection) = @_;
1433 0         0 my $tables = $self->_exp_table_with_column($exp_dataset, $connection);
1434 0         0 my %tables_rows = (map { ($_ => 0) } keys %$tables);
  0         0  
1435 0         0 my $tables_rows = $self->retrive_tables_data($connection, $tables);
1436 0         0 for (my $i = 0; $i < $#{$exp_dataset}; $i += 2) {
  0         0  
1437 0         0 my $table_name = $exp_dataset->[$i];
1438 0         0 my $fields = $exp_dataset->[$i + 1];
1439 0 0 0     0 if(ref($fields) eq 'HASH' && ! scalar(%$fields)) {
1440 0 0       0 if(my $rows = $self->count_table_rows($table_name, $connection)) {
1441 0         0 return sprintf("table ${table_name} should not have rows, has %s row(s)", $rows);
1442             }
1443 0         0 next;
1444             }
1445 0         0 my %lob_values = $self->_extract_lob_values($fields);
1446 0         0 my %values = $self->_extract_column_values($fields);
1447 0 0 0     0 next if(! %values && !%lob_values);
1448 0         0 $tables_rows{$table_name}++;
1449 0   0     0 my $pk_columns = $self->primary_key_definition_cache->{$table_name} ||= [$connection->primary_key_columns($table_name)];
1450 0         0 my $result = $self->validate_dataset($tables_rows->{$table_name}, \%values, $pk_columns, $table_name, $connection, \%lob_values);
1451 0 0       0 return $result if $result;
1452             }
1453 0         0 $self->validate_number_of_rows(\%tables_rows, $connection);
1454             }
1455              
1456              
1457             =item _update_lobs
1458              
1459             Updates lobs.
1460              
1461             =cut
1462              
1463             sub _update_lobs {
1464 0     0   0 my ($self, $lob_values, $table_name, $data, $connection) = @_;
1465 0   0     0 my $pk_columns = $self->primary_key_definition_cache->{$table_name} ||= [$connection->primary_key_columns($table_name)];
1466 0 0 0     0 my $fields_values = ($pk_columns && @$pk_columns) ? {map {($_ => $data->{$_})} @$pk_columns} : $data;
  0         0  
1467 0         0 foreach my $lob_column (keys %$lob_values) {
1468 0         0 my $lob_attr = $lob_values->{$lob_column};
1469 0         0 my $lob_content = $lob_attr->{content};
1470 0         0 $connection->update_lob($table_name => $lob_column, $lob_content, $fields_values, $lob_attr->{size_column});
1471             }
1472             }
1473              
1474             =item _exp_table_with_column
1475              
1476             Return hash ref of the tables with it columns.
1477              
1478             =cut
1479              
1480             sub _exp_table_with_column {
1481 1     1   960 my ($self, $dataset, $connection) = @_;
1482 1         2 my $result = {};
1483 1         4 for (my $i = 0; $i < $#{$dataset}; $i += 2) {
  4         12  
1484 3   100     17 my $columns = $result->{$dataset->[$i]} ||= {};
1485 3         11 my $data = $self->_extract_column_values($dataset->[$i + 1]);
1486 3         21 $columns->{$_} = 1 for keys %$data;
1487             }
1488              
1489 1 50       6 if ($connection) {
1490 0         0 foreach my $table_name (keys %$result) {
1491 0   0     0 my $pk_columns = $self->primary_key_definition_cache->{$table_name} ||= [$connection->primary_key_columns($table_name)];
1492 0   0     0 my $columns = $result->{$table_name} ||= {};
1493 0         0 $columns->{$_} = 1 for @$pk_columns;
1494             }
1495             }
1496            
1497 1         3 foreach my $k(keys %$result) {
1498 2         3 $result->{$k} = [sort keys %{$result->{$k}}];
  2         16  
1499             }
1500 1         4 $result;
1501             }
1502              
1503              
1504             =item _extract_column_values
1505              
1506             =cut
1507              
1508             sub _extract_column_values {
1509 3     3   4 my ($self, $dataset) = @_;
1510 3         11 my %values = @$dataset;
1511 3 50       9 my $result = {map {(!(ref($values{$_}) eq 'HASH') ? ($_ => $values{$_}) : ())} keys %values};
  6         21  
1512 3 50       11 wantarray ? (%$result) : $result;
1513             }
1514              
1515              
1516             =item _extract_column_values
1517              
1518             =cut
1519              
1520             sub _extract_lob_values {
1521 0     0   0 my ($self, $dataset) = @_;
1522 0         0 my %values = @$dataset;
1523 0 0       0 my $result = {map {(ref($values{$_}) eq 'HASH' ? ($_ => $values{$_}) : ())} keys %values};
  0         0  
1524 0         0 $self->_process_lob($result);
1525 0 0       0 wantarray ? (%$result) : $result;
1526             }
1527              
1528              
1529             =item _process_lob
1530              
1531             =cut
1532              
1533             sub _process_lob {
1534 0     0   0 my ($self, $lobs) = @_;
1535 0 0 0     0 return if(! $lobs || !(keys %$lobs));
1536 0         0 for my $k(keys %$lobs) {
1537 0         0 my $lob_attr= $lobs->{$k};
1538 0         0 my $content = '';
1539 0 0       0 if($lob_attr->{file}) {
1540 0         0 $lob_attr->{content} = _load_file_content($lob_attr->{file});
1541             }
1542             }
1543             }
1544              
1545              
1546             =item validate_number_of_rows
1547              
1548             Validates number of rows.
1549              
1550             =cut
1551              
1552             sub validate_number_of_rows {
1553 0     0 1 0 my ($self, $expected_result, $connection) = @_;
1554 0         0 foreach my $table_name (keys %$expected_result) {
1555 0         0 my $rows_no =$self->count_table_rows($table_name, $connection);
1556 0 0 0     0 return "found difference in number of the ${table_name} rows - has " . $rows_no . " rows, should have " . $expected_result->{$table_name}
1557             if (! defined $rows_no || $expected_result->{$table_name} ne $rows_no);
1558             }
1559             }
1560              
1561              
1562             =item validate_dataset
1563              
1564             Validates passed exp dataset against fetched rows.
1565             Return undef if there are not difference otherwise returns validation error.
1566              
1567             =cut
1568              
1569             sub validate_dataset {
1570 0     0 1 0 my ($self, $rows, $exp_dataset, $pk_columns, $table_name, $connection, $lob_values) = @_;
1571 0         0 my $hash_key = primary_key_hash_value($pk_columns, $exp_dataset);
1572              
1573 0 0 0     0 if ($lob_values && %$lob_values) {
1574 0         0 my $result = $self->validate_lobs($lob_values, $table_name, $pk_columns, $exp_dataset, $connection);
1575 0 0       0 return $result if $result;
1576             }
1577              
1578 0         0 my @columns = keys %$exp_dataset;
1579 0 0       0 if ($hash_key) {
1580 0         0 my $result = compare_datasets($rows->{$hash_key}, $exp_dataset, $table_name, @columns);
1581 0 0       0 if ($rows->{$hash_key}) {
1582 0 0       0 return $result if $result;
1583 0         0 delete $rows->{$hash_key};
1584 0         0 return;
1585             }
1586             } else {#validation without primary key values
1587 0 0       0 my $exp_hash = join("-", map { $_ || '' } values %$exp_dataset);
  0         0  
1588 0         0 foreach my $k (keys %$rows) {
1589 0         0 my $dataset = $rows->{$k};
1590 0 0       0 my $rowhash = join("-", map {($dataset->{$_} || '')} @columns);
  0         0  
1591 0 0       0 if ($rowhash eq $exp_hash) {
1592 0         0 delete $rows->{$k};
1593 0         0 return;
1594             }
1595             }
1596             }
1597 0         0 "found difference in $table_name - missing row: "
1598             . "\n ". format_values($exp_dataset, @columns);
1599             }
1600              
1601              
1602             =item validate_lobs
1603              
1604             Validates lob values
1605              
1606             =cut
1607              
1608             sub validate_lobs {
1609 0     0 1 0 my ($self, $lob_values, $table_name, $pk_column, $exp_dataset, $connection) = @_;
1610 0 0 0     0 return if(! $lob_values || ! (%$lob_values));
1611 0         0 my $fields_value = ($pk_column && @$pk_column)
1612 0 0 0     0 ? {map {($_ => $exp_dataset->{$_})} @$pk_column}
1613             : $exp_dataset;
1614 0         0 for my $lob_column(keys %$lob_values) {
1615 0         0 my $lob_attr = $lob_values->{$lob_column};
1616 0         0 my $exp_lob_content = $lob_attr->{content};
1617 0         0 my $lob_content = $connection->fetch_lob($table_name => $lob_column, $fields_value, $lob_attr->{size_column});
1618 0 0 0     0 return "found difference at LOB value ${table_name}.${lob_column}: " . format_values($fields_value, keys %$fields_value)
      0        
      0        
      0        
      0        
1619             if(length($exp_lob_content || '') ne length($lob_content || '') || ($exp_lob_content || '') ne ($lob_content || ''));
1620             }
1621             }
1622              
1623              
1624             =item expected_dataset_for_refresh_load_strategy
1625              
1626             Validates expected dataset for the refresh load strategy.
1627              
1628             =cut
1629              
1630             sub expected_dataset_for_refresh_load_strategy {
1631 0     0 1 0 my ($self, $exp_dataset, $connection) = @_;
1632 0         0 for (my $i = 0; $i < $#{$exp_dataset}; $i += 2) {
  0         0  
1633 0         0 my $table_name = $exp_dataset->[$i];
1634 0         0 my $fields = $exp_dataset->[$i + 1];
1635 0 0 0     0 if (ref($fields) eq 'HASH' && ! scalar(%$fields)) {
1636 0 0       0 if(my $rows = $self->count_table_rows($table_name, $connection)) {
1637 0         0 return sprintf("table ${table_name} should not have rows, has %s row(s)", $rows);
1638             }
1639 0         0 next;
1640             }
1641 0         0 my %values = $self->_extract_column_values($fields);
1642 0         0 my %lob_values = $self->_extract_lob_values($fields);
1643 0   0     0 my $pk_columns = $self->primary_key_definition_cache->{$table_name} ||= [$connection->primary_key_columns($table_name)];
1644 0         0 my $result = $self->validate_expexted_dataset(\%values, $pk_columns, $table_name, $connection, \%lob_values);
1645 0 0       0 return $result if $result;
1646             }
1647             }
1648              
1649              
1650             =item count_table_rows
1651              
1652             Return number of the table rows,
1653            
1654             my $no_rows = $dbunit->has_empty_table($table, $connection);
1655              
1656             =cut
1657              
1658             sub count_table_rows {
1659 0     0 1 0 my ($self, $table_name, $connection) = @_;
1660 0         0 my $result = $connection->record("SELECT COUNT(*) AS cnt FROM ${table_name}");
1661 0         0 return $result->{cnt};
1662             }
1663              
1664              
1665             =item validate_expexted_dataset
1666              
1667             Validates passed exp dataset against database schema
1668             Return undef if there is not difference otherwise returns validation error.
1669              
1670             =cut
1671              
1672             sub validate_expexted_dataset {
1673 0     0 1 0 my ($self, $exp_dataset, $pk_columns, $table_name, $connection, $lob_values) = @_;
1674 0 0       0 my @condition_columns = (@$pk_columns ? @$pk_columns : map { (!ref($exp_dataset->{$_}) ? ($_) : ()) } keys %$exp_dataset);
  0 0       0  
1675 0 0 0     0 if ($lob_values && %$lob_values) {
1676 0         0 my $result = $self->validate_lobs($lob_values, $table_name, \@condition_columns, $exp_dataset, $connection);
1677 0 0       0 return $result if $result;
1678             }
1679            
1680 0         0 my $where_clause = join(" AND ", map { $_ ." = ? " } @condition_columns);
  0         0  
1681 0         0 my @columns = keys %$exp_dataset;
1682 0   0     0 my $record = $connection->record("SELECT " . (join(",", @columns) || '*') . " FROM ${table_name} WHERE ". $where_clause, map { $exp_dataset->{$_} } @condition_columns);
  0         0  
1683 0 0       0 if(grep { defined $_ } values %$record) {
  0         0  
1684 0         0 return compare_datasets($record, $exp_dataset, $table_name, keys %$exp_dataset);
1685             }
1686             "found difference in $table_name - missing row: "
1687 0         0 . "\n ". format_values($exp_dataset, keys %$exp_dataset);
1688             }
1689              
1690              
1691             =item compare_datasets
1692              
1693             Compares two dataset hashes using passed in keys
1694             Returns undef if there is not difference, otherwise difference details.
1695              
1696             =cut
1697              
1698             sub compare_datasets {
1699 2     2 1 1279 my ($dataset, $exp_dataset, $table_name, @keys) = @_;
1700 2         3 for my $k (@keys) {
1701 4 50       13 if (ref $exp_dataset->{$k}) {
1702 0         0 my $result = $exp_dataset->{$k}->($dataset->{$k});
1703 0 0       0 return "found difference in $table_name $k:"
1704             . "\n " . format_values($dataset, @keys)
1705             unless $result;
1706 0         0 next;
1707             }
1708 4 100 50     56 return "found difference in $table_name $k:"
      100        
1709             . "\n " . format_values($exp_dataset, @keys)
1710             . "\n " . format_values($dataset, @keys)
1711             if (($dataset->{$k} || '') ne ($exp_dataset->{$k} || ''));
1712             }
1713             }
1714              
1715              
1716             =item format_values
1717              
1718             Converts passed in list to string.
1719              
1720             =cut
1721              
1722             sub format_values {
1723 2     2 1 5 my ($dataset, @keys) = @_;
1724 2 100       5 "[ " . join(" ", map { $_ . " => '" . (defined $dataset->{$_} ? $dataset->{$_} : '') . "'" } @keys) ." ]";
  4         29  
1725             }
1726              
1727              
1728             =item retrive_tables_data
1729              
1730             Returns retrieved data for passed in tables
1731              
1732             =cut
1733              
1734             sub retrive_tables_data {
1735 0     0 1 0 my ($self, $connection, $tables) = @_;
1736 0         0 my $result = {};
1737 0         0 for my $table_name (keys %$tables) {
1738 0         0 $result->{$table_name} = $self->retrive_table_data($connection, $table_name, $tables->{$table_name});
1739             }
1740 0         0 $result;
1741             }
1742              
1743              
1744             =item retrive_table_data
1745              
1746             Returns retrieved data for passed in table.
1747              
1748             =cut
1749              
1750             sub retrive_table_data {
1751 0     0 1 0 my ($self, $connection, $table_name, $columns) = @_;
1752 0         0 my $counter = 0;
1753 0   0     0 my $pk_columns = $self->primary_key_definition_cache->{$table_name} ||= [$connection->primary_key_columns($table_name)];
1754 0   0     0 my $cursor = $connection->query_cursor(sql => "SELECT " . (join(",", @$columns) || '*') . " FROM ${table_name}");
1755 0         0 my $result_set = $cursor->execute();
1756 0         0 my $has_pk = !! @$pk_columns;
1757 0         0 my $result = {};
1758 0         0 while ($cursor->fetch()) {
1759 0 0       0 my $key = $has_pk ? primary_key_hash_value($pk_columns, $result_set) : "__" . ($counter++);
1760 0         0 $result->{$key} = {%$result_set};
1761             }
1762 0         0 $result;
1763             }
1764              
1765              
1766             =item primary_key_hash_value
1767              
1768             Returns primary key values hash.
1769              
1770             =cut
1771              
1772             sub primary_key_hash_value {
1773 0     0 1 0 my ($primary_key_columns, $field_values) = @_;
1774 0         0 my $result = "";
1775 0         0 for (@$primary_key_columns) {
1776 0 0       0 return undef unless defined($field_values->{$_});
1777 0         0 $result .= $field_values->{$_} . "#";
1778             }
1779 0         0 $result;
1780             }
1781              
1782              
1783              
1784             =item xml_dataset_handler
1785              
1786             =cut
1787              
1788             { my $xml;
1789              
1790             sub xml_dataset_handler {
1791 1 50   1 1 6 unless($xml) {
1792 1         10 $xml = Simple::SAX::Serializer->new;
1793             $xml->handler('dataset', sub {
1794 1     1   237 my ($self, $element, $parent) = @_;
1795 1         9 $element->validate_attributes([],
1796             {load_strategy => "INSERT_LOAD_STRATEGY", reset_sequences => undef}
1797             );
1798 1         49 my $attributes = $element->attributes;
1799 1         14 my $children_result = $element->children_result;
1800 1         18 {properties => $attributes, dataset => $children_result}
1801             }
1802 1         223 );
1803             $xml->handler('*', sub {
1804 4     4   91217 my ($self, $element, $parent) = @_;
1805 4         16 my $parent_name = $parent->name;
1806 4         54 my $attributes = $element->attributes;
1807 4 50       45 if($parent_name eq 'dataset') {
1808 4   50     13 my $children_result = $element->children_result || {};
1809 4         65 my $parent_result = $parent->children_array_result;
1810 4         53 my $result = $parent->children_result;
1811 4         47 push @$parent_result, $element->name => [%$children_result, map { $_ => $attributes->{$_}} sort keys %$attributes];
  15         117  
1812             } else {
1813             # hacky
1814 0         0 my $children_result = $parent->children_hash_result;
1815 0         0 my $value = $element->value(1);
1816 0 0       0 unless(scalar %$attributes) {
1817 0         0 $children_result->{$element->name} = eval "sub { $value }";
1818             } else {
1819 0         0 $element->validate_attributes([], {size_column => undef, file => undef});
1820 0         0 my $children_result = $parent->children_hash_result;
1821 0         0 $children_result->{$element->name} = {%$attributes};
1822 0 0       0 $children_result->{content} = $value if $value;
1823             }
1824             }
1825 1         27 });
1826             }
1827 1         14 $xml;
1828             }
1829             }
1830              
1831              
1832             =item _exists_in_database
1833              
1834             Check is rows exists in database.
1835             Takes table name, hash ref of field values, connection object
1836              
1837             =cut
1838              
1839             sub _exists_in_database {
1840 0     0   0 my ($self, $table_name, $field_values, $connection) = @_;
1841 0         0 my $sql = "SELECT 1 AS cnt FROM ${table_name} WHERE ".join(" AND ", map {($_ . " = ? ")} sort keys %$field_values);
  0         0  
1842 0         0 my $record = $connection->record($sql, map {$field_values->{$_}} sort keys %$field_values);
  0         0  
1843 0 0       0 $record && $record->{cnt};
1844             }
1845              
1846              
1847             =item load_xml
1848              
1849             Loads xml
1850              
1851             =cut
1852              
1853             sub load_xml {
1854 1     1 1 413 my ($self, $file) = @_;
1855 1         5 my $xml = $self->xml_dataset_handler;
1856 1         7 $xml->parse_file($file);
1857             }
1858              
1859              
1860             =item _load_file_content
1861              
1862             =cut
1863              
1864             sub _load_file_content {
1865 0     0     my $file_name = shift;
1866 0 0         open my $fh, '<', $file_name or confess "cant open file ${file_name}";
1867 0           binmode $fh;
1868 0           local $/ = undef;
1869 0           my $content = <$fh>;
1870 0           close $fh;
1871 0           $content;
1872             }
1873              
1874             1;
1875              
1876             __END__