Fix transactions do not get rollbacked in saveAssociated/saveMany

Refs #2849
This commit is contained in:
chinpei215 2014-08-02 10:12:33 +09:00
parent 3d77ce5d34
commit 799500ce6d
2 changed files with 253 additions and 137 deletions

View file

@ -2215,6 +2215,7 @@ class Model extends Object implements CakeEventListener {
* @return mixed If atomic: True on success, or false on failure. * @return mixed If atomic: True on success, or false on failure.
* Otherwise: array similar to the $data array passed, but values are set to true/false * Otherwise: array similar to the $data array passed, but values are set to true/false
* depending on whether each record saved successfully. * depending on whether each record saved successfully.
* @throws PDOException
* @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savemany-array-data-null-array-options-array * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savemany-array-data-null-array-options-array
*/ */
public function saveMany($data = null, $options = array()) { public function saveMany($data = null, $options = array()) {
@ -2245,8 +2246,11 @@ class Model extends Object implements CakeEventListener {
if ($options['atomic']) { if ($options['atomic']) {
$db = $this->getDataSource(); $db = $this->getDataSource();
$transactionBegun = $db->begin(); $transactionBegun = $db->begin();
} else {
$transactionBegun = false;
} }
try {
$return = array(); $return = array();
foreach ($data as $key => $record) { foreach ($data as $key => $record) {
$validates = $this->create(null) !== null; $validates = $this->create(null) !== null;
@ -2284,8 +2288,16 @@ class Model extends Object implements CakeEventListener {
return true; return true;
} }
if ($transactionBegun) {
$db->rollback(); $db->rollback();
}
return false; return false;
} catch (Exception $e) {
if ($transactionBegun) {
$db->rollback();
}
throw $e;
}
} }
/** /**
@ -2337,6 +2349,7 @@ class Model extends Object implements CakeEventListener {
* @return mixed If atomic: True on success, or false on failure. * @return mixed If atomic: True on success, or false on failure.
* Otherwise: array similar to the $data array passed, but values are set to true/false * Otherwise: array similar to the $data array passed, but values are set to true/false
* depending on whether each record saved successfully. * depending on whether each record saved successfully.
* @throws PDOException
* @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array
*/ */
public function saveAssociated($data = null, $options = array()) { public function saveAssociated($data = null, $options = array()) {
@ -2368,8 +2381,11 @@ class Model extends Object implements CakeEventListener {
if ($options['atomic']) { if ($options['atomic']) {
$db = $this->getDataSource(); $db = $this->getDataSource();
$transactionBegun = $db->begin(); $transactionBegun = $db->begin();
} else {
$transactionBegun = false;
} }
try {
$associations = $this->getAssociated(); $associations = $this->getAssociated();
$return = array(); $return = array();
$validates = true; $validates = true;
@ -2493,8 +2509,16 @@ class Model extends Object implements CakeEventListener {
return true; return true;
} }
if ($transactionBegun) {
$db->rollback(); $db->rollback();
}
return false; return false;
} catch (Exception $e) {
if ($transactionBegun) {
$db->rollback();
}
throw $e;
}
} }
/** /**

View file

@ -4143,6 +4143,25 @@ class ModelWriteTest extends BaseModelTest {
); );
$Post->saveAll($data, array('atomic' => true, 'validate' => true)); $Post->saveAll($data, array('atomic' => true, 'validate' => true));
// If exception thrown, rollback() should be called too.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => 'New Fifth Post', 'body' => $db->expression('PDO_EXCEPTION()'))
);
try {
$Post->saveAll($data, array('atomic' => true, 'validate' => true));
$this->fail('No exception thrown');
} catch (PDOException $e) {
}
// Otherwise, commit() should be called. // Otherwise, commit() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback')); $db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true)); $db->expects($this->once())->method('begin')->will($this->returnValue(true));
@ -4193,6 +4212,33 @@ class ModelWriteTest extends BaseModelTest {
); );
$Post->saveAll($data, array('validate' => true)); $Post->saveAll($data, array('validate' => true));
// If exception thrown, rollback() should be called too.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$data = array(
'Post' => array(
'title' => 'New post',
'body' => $db->expression('PDO_EXCEPTION()'),
'published' => 'Y'
),
'Author' => array(
'user' => 'New user',
'password' => "sekret"
)
);
try {
$Post->saveAll($data, array('validate' => true));
$this->fail('No exception thrown');
} catch (PDOException $e) {
}
// Otherwise, commit() should be called. // Otherwise, commit() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback')); $db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true)); $db->expects($this->once())->method('begin')->will($this->returnValue(true));
@ -5605,6 +5651,25 @@ class ModelWriteTest extends BaseModelTest {
); );
$Post->saveMany($data, array('validate' => true)); $Post->saveMany($data, array('validate' => true));
// If exception thrown, rollback() should be called too.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$data = array(
array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => 'New Fifth Post', 'body' => $db->expression('PDO_EXCEPTION()'))
);
try {
$Post->saveMany($data, array('validate' => true));
$this->fail('No exception thrown');
} catch (PDOException $e) {
}
// Otherwise, commit() should be called. // Otherwise, commit() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback')); $db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true)); $db->expects($this->once())->method('begin')->will($this->returnValue(true));
@ -5655,6 +5720,33 @@ class ModelWriteTest extends BaseModelTest {
); );
$Post->saveAssociated($data, array('validate' => true, 'atomic' => true)); $Post->saveAssociated($data, array('validate' => true, 'atomic' => true));
// If exception thrown, commit() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
$db->expects($this->never())->method('commit');
$db->expects($this->once())->method('rollback');
$Post->setDataSourceObject($db);
$Post->Author->setDataSourceObject($db);
$data = array(
'Post' => array(
'title' => 'New post',
'body' => $db->expression('PDO_EXCEPTION()'),
'published' => 'Y'
),
'Author' => array(
'user' => 'New user',
'password' => "sekret"
)
);
try {
$Post->saveAssociated($data, array('validate' => true, 'atomic' => true));
$this->fail('No exception thrown');
} catch (PDOException $e) {
}
// Otherwise, commit() should be called. // Otherwise, commit() should be called.
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback')); $db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
$db->expects($this->once())->method('begin')->will($this->returnValue(true)); $db->expects($this->once())->method('begin')->will($this->returnValue(true));