use strict; use warnings; no warnings 'uninitialized'; use DBI; use Data::Dumper; use SQL::Abstract::Test import => [qw/is_same_sql_bind/]; use constant N_DBI_MOCK_TESTS => 95; use constant N_BASIC_TESTS => 15; use Test::More tests => (N_BASIC_TESTS + N_DBI_MOCK_TESTS); # die_ok : succeeds if the supplied coderef dies with an exception sub die_ok(&) { my $code=shift; eval {$code->()}; ok($@, $@);} BEGIN { use_ok("DBIx::DataModel");} BEGIN { DBIx::DataModel->Schema('HR'); } # Human Resources ok(HR->isa("DBIx::DataModel::Schema"), 'Schema defined'); my ($lst, $emp, $emp2, $act); # will not override an existing package die_ok {DBIx::DataModel->Schema('DBI');}; BEGIN { HR->Table(Employee => T_Employee => qw/emp_id/) ->Table(Department => T_Department => qw/dpt_id/) ->Table(Activity => T_Activity => qw/act_id/); } ok(HR::Employee->isa("DBIx::DataModel::Table"), 'Table defined'); ok(HR::Employee->can("select"), 'select method defined'); package HR::Department; sub currentEmployees { my $self = shift; my $currentAct = $self->activities({d_end => [{-is => undef}, {"<=" => '01.01.2005'}]}); return map {$_->employee} @$currentAct; } package main; # switch back to the 'main' package is_deeply([HR::Employee->primKey], ['emp_id'], 'primKey'); die_ok {HR::Employee->Table(Foo => T_Foo => qw/foo_id/)}; HR->Composition([qw/Employee employee 1 /], [qw/Activity activities * /]) ->Association([qw/Department department 1 /], [qw/Activity activities * /]); ok(HR::Activity->can("employee"), 'Association 1'); ok(HR::Employee->can("activities"), 'Association 2'); HR->View(MyView => "DISTINCT column1 AS c1, t2.column2 AS c2", "Table1 AS t1 LEFT OUTER JOIN Table2 AS t2 ON t1.fk=t2.pk", {c1 => 'foo', c2 => {-like => 'bar%'}}, qw/Employee Activity/); ok(HR::MyView->isa("HR::Employee"), 'HR::MyView ISA HR::Employee'); ok(HR::MyView->isa("HR::Activity"), 'HR::MyView ISA HR::Activity'); ok(HR::MyView->can("employee"), 'View inherits roles'); HR->ColumnType(Date => fromDB => sub {$_[0] =~ s/(\d\d\d\d)-(\d\d)-(\d\d)/$3.$2.$1/}, toDB => sub {$_[0] =~ s/(\d\d)\.(\d\d)\.(\d\d\d\d)/$3-$2-$1/}, validate => sub {$_[0] =~ m/(\d\d)\.(\d\d)\.(\d\d\d\d)/});; HR::Employee->ColumnType(Date => qw/d_birth/); HR::Activity->ColumnType(Date => qw/d_begin d_end/); HR->NoUpdateColumns(qw/d_modif user_id/); HR::Employee->NoUpdateColumns(qw/last_login/); is_deeply([HR::Employee->noUpdateColumns], [qw/d_modif user_id last_login/], 'noUpdateColumns'); HR::Employee->ColumnHandlers(lastname => normalizeName => sub { $_[0] =~ s/\w+/\u\L$&/g }); HR::Employee->AutoExpand(qw/activities/); $emp = HR::Employee->blessFromDB({firstname => 'Joseph', lastname => 'BODIN DE BOISMORTIER', d_birth => '1775-12-16'}); $emp->applyColumnHandler('normalizeName'); is($emp->{d_birth}, '16.12.1775', 'fromDB handler'); is($emp->{lastname}, 'Bodin De Boismortier', 'ad hoc handler'); # test self-referential assoc. HR->Association([qw/Employee spouse 0..1 emp_id/], [qw/Employee --- 1 spouse_id/]); SKIP: { eval "use DBD::Mock 1.36; 1" or skip "DBD::Mock 1.36 does not seem to be installed", N_DBI_MOCK_TESTS; my $dbh = DBI->connect('DBI:Mock:', '', '', {RaiseError => 1}); # sqlLike : takes a list of SQL regex and bind params, and a test msg. # Checks if those match with the DBD::Mock history. sub sqlLike { # closure on $dbh my $msg = pop @_; for (my $hist_index = -(@_ / 2); $hist_index < 0; $hist_index++) { my ($sql, $bind) = (shift, shift); my $hist = $dbh->{mock_all_history}[$hist_index]; is_same_sql_bind($hist->statement, $hist->bound_params, $sql, $bind, "$msg [$hist_index]"); } $dbh->{mock_clear_history} = 1; } HR->dbh($dbh); isa_ok(HR->dbh, 'DBI::db', 'dbh handle'); $lst = HR::Employee->select; sqlLike('SELECT * FROM t_employee', [], 'empty select'); $lst = HR::Employee->select(-for => 'read only'); sqlLike('SELECT * FROM t_employee FOR READ ONLY', [], 'for read only'); $lst = HR::Employee->select([qw/firstname lastname emp_id/], {firstname => {-like => 'D%'}}); sqlLike('SELECT firstname, lastname, emp_id '. 'FROM t_employee ' . "WHERE (firstname LIKE ?)", ['D%'], 'like select'); $lst = HR::Employee->select({firstname => {-like => 'D%'}}); sqlLike('SELECT * '. 'FROM t_employee ' . "WHERE ( firstname LIKE ? )", ['D%'], 'implicit *'); $lst = HR::Employee->select("firstname AS fn, lastname AS ln", undef, [qw/d_birth/]); sqlLike('SELECT firstname AS fn, lastname AS ln '. 'FROM t_employee ' . "ORDER BY d_birth", [], 'order_by select'); $dbh->{mock_clear_history} = 1; $dbh->{mock_add_resultset} = [ [qw/ln db/], [qw/foo 2001-01-01/], [qw/bar 2002-02-02/] ]; $lst = HR::Employee->select(-columns => [qw/lastname|ln d_birth|db/]); sqlLike('SELECT lastname AS ln, d_birth AS db '. 'FROM t_employee', [], 'column aliases'); is($lst->[0]{db}, "01.01.2001", "fromDB handler on column alias"); $lst = HR::Employee->select(-distinct => "lastname, firstname"); sqlLike('SELECT DISTINCT lastname, firstname '. 'FROM t_employee' , [], 'distinct 1'); $lst = HR::Employee->select(-distinct => [qw/lastname firstname/]); sqlLike('SELECT DISTINCT lastname, firstname '. 'FROM t_employee' , [], 'distinct 2'); $lst = HR::Employee->select(-columns => ['lastname', 'COUNT(firstname) AS n_emp'], -groupBy => [qw/lastname/], -having => [n_emp => {">=" => 2}], -orderBy => 'n_emp DESC' ); sqlLike('SELECT lastname, COUNT(firstname) AS n_emp '. 'FROM t_employee '. 'GROUP BY lastname HAVING ((n_emp >= ?)) '. 'ORDER BY n_emp DESC', [2], 'group by'); $lst = HR::Employee->select(-orderBy => [qw/+col1 -col2 +col3/]); sqlLike('SELECT * FROM t_employee ORDER BY col1 ASC, col2 DESC, col3 ASC', [], '-orderBy prefixes'); $emp2 = HR::Employee->fetch(123); sqlLike('SELECT * FROM t_employee WHERE (emp_id = ?)', [123], 'fetch'); $emp2 = HR::Employee->select(-fetch => 123); sqlLike('SELECT * FROM t_employee WHERE (emp_id = ?)', [123], 'select(-fetch)'); $emp2 = HR::Employee->fetch(""); sqlLike('SELECT * FROM t_employee WHERE (emp_id = ?)', [""], 'fetch (empty string)'); $emp2 = HR::Employee->fetch(undef); sqlLike('SELECT * FROM t_employee WHERE (emp_id IS NULL)', [], 'fetch (undef)'); # successive calls to fetch_cached $dbh->{mock_clear_history} = 1; $dbh->{mock_add_resultset} = [ [qw/foo bar/], [123, 456] ]; $emp2 = HR::Employee->fetch_cached(123); is (@{$dbh->{mock_all_history}}, 1, "first fetch_cached : go to db"); my $emp3 = HR::Employee->fetch_cached(123); is (@{$dbh->{mock_all_history}}, 1, "second fetch_cached : no db"); is_deeply($emp3, {foo=>123, bar=>456}, "fetch_cached result"); $emp->{emp_id} = 999; # method call should break without autoload die_ok {$emp->emp_id}; # now turn it on HR->Autoload(1); is($emp->emp_id, 999, 'autoload schema'); # turn it off again HR->Autoload(0); die_ok {$emp->emp_id}; # turn it on just for the Employee class HR::Employee->Autoload(1); is($emp->emp_id, 999, 'autoload table'); # turn it off again HR::Employee->Autoload(0); die_ok {$emp->emp_id}; $lst = $emp->activities; sqlLike('SELECT * ' . 'FROM t_activity ' . "WHERE ( emp_id = ? )", [999], 'activities'); $lst = $emp->activities([qw/d_begin d_end/]); sqlLike('SELECT d_begin, d_end ' . 'FROM t_activity ' . "WHERE ( emp_id = ? )", [999], 'activities column list'); $lst = $emp->activities({d_begin => {">=" => '2000-01-01'}}); sqlLike('SELECT * ' . 'FROM t_activity ' . "WHERE (d_begin >= ? AND emp_id = ?)", ['2000-01-01', 999], 'activities where criteria'); $lst = $emp->activities("d_begin AS db, d_end AS de", {}, [qw/d_begin d_end/]); sqlLike('SELECT d_begin AS db, d_end AS de ' . 'FROM t_activity ' . "WHERE (emp_id = ?) ". 'ORDER BY d_begin, d_end', [999], 'activities order by'); $act = $emp->activities(-fetch => 123); sqlLike('SELECT * FROM t_activity WHERE (act_id = ? AND emp_id = ? )', [123, 999], 'activities(-fetch)'); # testing cached expanded values $emp->{activities} = "foo"; is ($emp->activities, "foo", "cached expanded values"); delete $emp->{activities}; # empty foreign key my $fake_emp = bless {}, 'HR::Employee'; die_ok {$fake_emp->activities()}; # unbless SKIP: { eval "use Acme::Damn; 1" or skip "Acme::Damn does not seem to be installed", 1; my $emp2 = HR::Employee->blessFromDB({ emp_id => 999, activities => [map {HR::Activity->blessFromDB({foo => $_})} 1..3], spouse => HR::Employee->blessFromDB({foo => 'spouse'}), }); is_deeply(HR->unbless($emp2), {emp_id => 999, spouse => {foo => 'spouse'}, activities => [{foo => 1}, {foo => 2}, {foo => 3}]}, "unbless"); } # testing combination of where criteria my $statement = HR::Employee->activities(-where => {foo => [3, 4]}); $act = $statement->bind($emp) ->select(-where => {foo => [4, 5]}); sqlLike('SELECT * FROM T_Activity ' . 'WHERE ( emp_id = ? AND ( ( foo = ? OR foo = ? ) ' . 'AND ( foo = ? OR foo = ? )))', [999, 3, 4, 4, 5], "combined where"); $statement = HR::Employee->activities(-where => [foo => "bar", bar => "foo"]); $act = $statement->bind($emp) ->select(-where => [foobar => 123, barfoo => 456]); sqlLike('SELECT * FROM T_Activity ' . 'WHERE ( ( (foo = ? OR bar = ?) ' . 'AND (foobar = ? OR barfoo = ?)' . ') AND emp_id = ? )', [qw/bar foo 123 456 999/], "combined where, arrayrefs"); # select with OR through an arrayref my $result = HR::Employee->select(-where => [foo => 1, bar => 2]); sqlLike('SELECT * FROM T_Employee WHERE foo = ? OR bar = ?', [qw/1 2/], "where arrayref, OR"); # select -resultAs => 'flat_arrayref' SKIP : { $DBD::Mock::VERSION >= 1.39 or skip "need DBD::Mock 1.39 or greater", 1; my @fake_rs = ([qw/col1 col2/], [qw/foo1 foo2/], [qw/bar1 bar2/]); $dbh->{mock_clear_history} = 1; $dbh->{mock_add_resultset} = \@fake_rs; my $pairs = HR::Employee->select(-columns => [qw/col1 col2/], -resultAs => 'flat_arrayref'); my %hash = @$pairs; is_deeply(\%hash, {foo1 => 'foo2', bar1 => 'bar2'}, "resultAs => 'flat_arrayref'"); } # subquery my $subquery = HR::Employee->select( -columns => 'emp_id', -where => {d_birth => {-between => [1950, 1980]}}, -resultAs => 'subquery', ); $act = HR::Activity->select(-where => {emp_id => {-not_in => $subquery}}); sqlLike('SELECT * FROM T_Activity WHERE emp_id NOT IN ' . '(SELECT emp_id FROM T_Employee WHERE d_birth BETWEEN ? AND ?)', [1950, 1980], 'subquery'); # plain insertion using arrayref syntax my ($bach_id, $berlioz_id, $monteverdi_id) = HR::Employee->insert([qw/ firstname lastname /], [qw/ Johann Bach /], [qw/ Hector Berlioz /], [qw/ Claudio Monteverdi /]); my $insert_sql = 'INSERT INTO T_Employee (firstname, lastname) VALUES (?, ?)'; sqlLike($insert_sql, [qw/ Johann Bach /], $insert_sql, [qw/ Hector Berlioz /], $insert_sql, [qw/ Claudio Monteverdi /], 'insert with arrayref syntax'); # insertion into related class $emp->insert_into_activities({d_begin =>'2000-01-01', d_end => '2000-02-02'}); sqlLike('INSERT INTO t_activity (d_begin, d_end, emp_id) ' . 'VALUES (?, ?, ?)', ['2000-01-01', '2000-02-02', 999], 'add_to_activities'); # test cascaded inserts my $tree = {firstname => "Johann Sebastian", lastname => "Bach", activities => [{d_begin => '01.01.1707', d_end => '01.07.1720', dpt_code => 'Maria-Barbara'}, {d_begin => '01.12.1721', d_end => '18.07.1750', dpt_code => 'Anna-Magdalena'}]}; my $emp_id = HR::Employee->insert($tree); my $sql_insert_activity = 'INSERT INTO t_activity (d_begin, d_end, ' . 'dpt_code, emp_id) VALUES (?, ?, ?, ?)'; sqlLike('INSERT INTO t_employee (firstname, lastname) VALUES (?, ?)', ["Johann Sebastian", "Bach"], $sql_insert_activity, ['1707-01-01', '1720-07-01', 'Maria-Barbara', $emp_id], $sql_insert_activity, ['1721-12-01', '1750-07-18', 'Anna-Magdalena', $emp_id], "cascaded insert"); HR::MyView->select({c3 => 22}); sqlLike('SELECT DISTINCT column1 AS c1, t2.column2 AS c2 ' . 'FROM Table1 AS t1 LEFT OUTER JOIN Table2 AS t2 '. 'ON t1.fk=t2.pk ' . 'WHERE (c1 = ? AND c2 LIKE ? AND c3 = ?)', ['foo', 'bar%', 22], 'HR::MyView'); my $view = HR->join(qw/Employee activities department/); $view->select("lastname, dpt_name", {gender => 'F'}); sqlLike('SELECT lastname, dpt_name ' . 'FROM t_employee LEFT OUTER JOIN t_activity ' . 'ON t_employee.emp_id=t_activity.emp_id ' . 'LEFT OUTER JOIN t_department ' . 'ON t_activity.dpt_id=t_department.dpt_id ' . 'WHERE (gender = ?)', ['F'], 'join'); my $view2 = HR->join(qw/Employee <=> activities => department/); $view2->select("lastname, dpt_name", {gender => 'F'}); sqlLike('SELECT lastname, dpt_name ' . 'FROM t_employee INNER JOIN t_activity ' . 'ON t_employee.emp_id=t_activity.emp_id ' . 'LEFT OUTER JOIN t_department ' . 'ON t_activity.dpt_id=t_department.dpt_id ' . 'WHERE (gender = ?)', ['F'], 'join with explicit roles'); my $view3 = HR->join(qw/Activity employee department/); $view3->select("lastname, dpt_name", {gender => 'F'}); sqlLike('SELECT lastname, dpt_name ' . 'FROM t_activity INNER JOIN t_employee ' . 'ON t_activity.emp_id=t_employee.emp_id ' . 'INNER JOIN t_department ' . 'ON t_activity.dpt_id=t_department.dpt_id ' . 'WHERE (gender = ?)', ['F'], 'join with indirect role'); die_ok {$emp->join(qw/activities foo/)}; die_ok {$emp->join(qw/foo bar/)}; $emp->join(qw/activities department/) ->select({gender => 'F'}); sqlLike('SELECT * ' . 'FROM t_activity ' . 'INNER JOIN t_department ' . 'ON t_activity.dpt_id=t_department.dpt_id ' . 'WHERE (emp_id = ? AND gender = ?)', [999, 'F'], 'join (instance method)'); # table aliases HR->join(qw/Activity|act employee|emp department|dpt/) ->select(-columns => [qw/lastname dpt_name/], -where => {gender => 'F'}); sqlLike('SELECT lastname, dpt_name ' . 'FROM t_activity AS act INNER JOIN t_employee AS emp ' . 'ON act.emp_id=emp.emp_id ' . 'INNER JOIN t_department AS dpt ' . 'ON act.dpt_id=dpt.dpt_id ' . 'WHERE (gender = ?)', ['F'], 'table aliases'); # explicit sources HR->join(qw/Activity Activity.employee Activity.department/) ->select(-columns => [qw/lastname dpt_name/], -where => {gender => 'F'}); sqlLike('SELECT lastname, dpt_name ' . 'FROM t_activity INNER JOIN t_employee ' . 'ON t_activity.emp_id=t_employee.emp_id ' . 'INNER JOIN t_department ' . 'ON t_activity.dpt_id=t_department.dpt_id ' . 'WHERE (gender = ?)', ['F'], 'explicit sources'); # both table aliases and explicit sources HR->join(qw/Activity|act act.employee|emp act.department|dpt/) ->select(-columns => [qw/lastname dpt_name/], -where => {gender => 'F'}); sqlLike('SELECT lastname, dpt_name ' . 'FROM t_activity AS act INNER JOIN t_employee AS emp ' . 'ON act.emp_id=emp.emp_id ' . 'INNER JOIN t_department AS dpt ' . 'ON act.dpt_id=dpt.dpt_id ' . 'WHERE (gender = ?)', ['F'], 'both table aliases and explicit sources'); HR->join(qw/Department|dpt dpt.activities|act act.employee|emp/) ->select(-columns => [qw/lastname dpt_name/], -where => {gender => 'F'}); sqlLike('SELECT lastname, dpt_name ' . 'FROM t_department AS dpt '. 'LEFT OUTER JOIN t_activity AS act ' . 'ON dpt.dpt_id=act.dpt_id ' . 'LEFT OUTER JOIN t_employee AS emp ' . 'ON act.emp_id=emp.emp_id ' . 'WHERE (gender = ?)', ['F'], 'both table aliases and explicit sources, reversed'); # column types on table and column aliases $dbh->{mock_clear_history} = 1; $dbh->{mock_add_resultset} = [ [qw/ln db/], [qw/foo 2001-01-01/], [qw/bar 2002-02-02/] ]; $lst = HR->join(qw/Department|dpt dpt.activities|act act.employee|emp/) ->select(-columns => [qw/emp.lastname|ln emp.d_birth|db/], -where => {gender => 'F'}); sqlLike('SELECT emp.lastname AS ln, emp.d_birth AS db ' . 'FROM t_department AS dpt '. 'LEFT OUTER JOIN t_activity AS act ' . 'ON dpt.dpt_id=act.dpt_id ' . 'LEFT OUTER JOIN t_employee AS emp ' . 'ON act.emp_id=emp.emp_id ' . 'WHERE (gender = ?)', ['F'], 'column types on table and column aliases (sql)'); is($lst->[0]{db}, "01.01.2001", "fromDB handler on table and column alias"); # stepwise statement prepare/execute $statement = HR::Employee->join(qw/activities department/); $statement->refine(-where => {gender => 'F'}); $statement->refine(-where => {gender => {'!=' => 'M'}}); $statement->prepare; die_ok {$statement->next}; # statement is not executed yet my $row = $statement->execute($emp)->next; sqlLike('SELECT * ' . 'FROM t_activity ' . 'INNER JOIN t_department ' . 'ON t_activity.dpt_id=t_department.dpt_id ' . 'WHERE (emp_id = ? AND gender = ? AND gender != ?)', [999, 'F', 'M'], 'statement prepare/execute'); # many-to-many association HR->Association([qw/Employee employees * activities employee/], [qw/Department departments * activities department/]); my $dpts = $emp->departments(-where =>{gender => 'F'}); sqlLike('SELECT * ' . 'FROM t_activity ' . 'INNER JOIN t_department ' . 'ON t_activity.dpt_id=t_department.dpt_id ' . 'WHERE (emp_id = ? AND gender = ?)', [999, 'F'], 'N-to-N Association '); my $dpt = bless {dpt_id => 123}, 'HR::Department'; my $empls = $dpt->employees; sqlLike('SELECT * ' . 'FROM t_activity ' . 'INNER JOIN t_employee ' . 'ON t_activity.emp_id=t_employee.emp_id ' . 'WHERE (dpt_id = ?)', [123], 'N-to-N Association 2 '); HR::Employee->update(999, {firstname => 'toto', d_modif => '02.09.2005', d_birth => '01.01.1950', last_login => '01.09.2005'}); sqlLike('UPDATE t_employee SET d_birth = ?, firstname = ? '. 'WHERE (emp_id = ?)', ['1950-01-01', 'toto', 999], 'update'); HR::Employee->update( {firstname => 'toto', d_modif => '02.09.2005', d_birth => '01.01.1950', last_login => '01.09.2005', emp_id => 999}); sqlLike('UPDATE t_employee SET d_birth = ?, firstname = ? '. 'WHERE (emp_id = ?)', ['1950-01-01', 'toto', 999], 'update2'); $emp->{firstname} = 'toto'; $emp->{d_modif} = '02.09.2005'; $emp->{d_birth} = '01.01.1950'; $emp->{last_login} = '01.09.2005'; my %emp2 = %$emp; $emp->update; sqlLike('UPDATE t_employee SET d_birth = ?, firstname = ?, lastname = ? '. 'WHERE (emp_id = ?)', ['1950-01-01', 'toto', 'Bodin De Boismortier', 999], 'update3'); HR->AutoUpdateColumns( last_modif => sub{"someUser, someTime"} ); HR::Employee->update(\%emp2); sqlLike('UPDATE t_employee SET d_birth = ?, firstname = ?, ' . 'last_modif = ?, lastname = ? WHERE (emp_id = ?)', ['1950-01-01', 'toto', "someUser, someTime", 'Bodin De Boismortier', 999], 'autoUpdate'); HR->AutoInsertColumns( created_by => sub{"firstUser, firstTime"} ); HR::Employee->insert({firstname => "Felix", lastname => "Mendelssohn"}); sqlLike('INSERT INTO t_employee (created_by, firstname, last_modif, lastname) ' . 'VALUES (?, ?, ?, ?)', ['firstUser, firstTime', 'Felix', 'someUser, someTime', 'Mendelssohn'], 'autoUpdate / insert'); $emp = HR::Employee->blessFromDB({emp_id => 999}); $emp->delete; sqlLike('DELETE FROM t_employee '. 'WHERE (emp_id = ?)', [999], 'delete'); $emp = HR::Employee->blessFromDB({emp_id => 999, spouse_id => 888}); my $emp_spouse = $emp->spouse; sqlLike('SELECT * ' . 'FROM t_employee ' . "WHERE ( emp_id = ? )", [888], 'spouse self-ref assoc.'); # testing -preExec / -postExec my %check_callbacks; HR::Employee->select(-where => {foo=>'bar'}, -preExec => sub {$check_callbacks{pre} = "was called"}, -postExec => sub {$check_callbacks{post} = "was called"},); is_deeply(\%check_callbacks, {pre =>"was called", post => "was called" }, 'select, pre/post callbacks'); %check_callbacks = (); HR::Employee->fetch(1234, {-preExec => sub {$check_callbacks{pre} = "was called"}, -postExec => sub {$check_callbacks{post} = "was called"}}); is_deeply(\%check_callbacks, {pre =>"was called", post => "was called" }, 'fetch, pre/post callbacks'); # testing transactions my $ok_trans = sub { return "scalar transaction OK" }; my $ok_trans_array = sub { return qw/array transaction OK/ }; my $fail_trans = sub { die "failed transaction" }; my $nested_1 = sub { HR->doTransaction($ok_trans) }; my $nested_many = sub { my $r1 = HR->doTransaction($nested_1); my @r2 = HR->doTransaction($ok_trans_array); return ($r1, @r2); }; is (HR->doTransaction($ok_trans), "scalar transaction OK", "scalar transaction"); sqlLike('BEGIN WORK', [], 'COMMIT', [], "scalar transaction commit"); is_deeply ([HR->doTransaction($ok_trans_array)], [qw/array transaction OK/], "array transaction"); sqlLike('BEGIN WORK', [], 'COMMIT', [], "array transaction commit"); die_ok {HR->doTransaction($fail_trans)}; sqlLike('BEGIN WORK', [], 'ROLLBACK', [], "fail transaction rollback"); $dbh->do('FAKE SQL, HISTORY MARKER'); is_deeply ([HR->doTransaction($nested_many)], ["scalar transaction OK", qw/array transaction OK/], "nested transaction"); sqlLike('FAKE SQL, HISTORY MARKER', [], 'BEGIN WORK', [], 'COMMIT', [], "nested transaction commit"); # transaction object eval {HR->doTransaction($fail_trans)}; my $err = $@; like ($err->initial_error, qr/^failed transaction/, "initial_error"); is_deeply([$err->rollback_errors], [], "rollback_errors"); # nested transactions on two different databases $dbh->{private_id} = "dbh1"; my $other_dbh = DBI->connect('DBI:Mock:', '', '', {private_id => "dbh2", RaiseError => 1}); $emp_id = 66; my $tell_dbh_id = sub {my $db_id = HR->dbh->{private_id}; HR::Employee->update({emp_id => $emp_id++, name => $db_id}); return "transaction on $db_id" }; my $nested_change_dbh = sub { my $r1 = HR->doTransaction($tell_dbh_id); my $r2 = HR->doTransaction($tell_dbh_id, $other_dbh); my $r3 = HR->doTransaction($tell_dbh_id); return ($r1, $r2, $r3); }; $dbh ->do('FAKE SQL, BEFORE TRANSACTION'); $other_dbh->do('FAKE SQL, BEFORE TRANSACTION'); is_deeply ([HR->doTransaction($nested_change_dbh)], ["transaction on dbh1", "transaction on dbh2", "transaction on dbh1"], "nested transaction, change dbh"); my $upd = 'UPDATE T_Employee SET last_modif = ?, name = ? WHERE ( emp_id = ? )'; my $last_modif = 'someUser, someTime'; sqlLike('FAKE SQL, BEFORE TRANSACTION', [], 'BEGIN WORK', [], $upd, [$last_modif, "dbh1", 66], $upd, [$last_modif, "dbh1", 68], 'COMMIT', [], "nested transaction on dbh1"); $dbh = $other_dbh; sqlLike('FAKE SQL, BEFORE TRANSACTION', [], 'BEGIN WORK', [], $upd, [$last_modif, "dbh2", 67], 'COMMIT', [], "nested transaction on dbh2"); } # END OF SKIP BLOCK __END__ TODO: hasInvalidFields expand autoExpand MethodFromRoles document the tests !!