mirror of
https://github.com/silverstripe/silverstripe-sqlite3
synced 2024-10-22 17:05:37 +02:00
FIX transaction depth related errors with invalid savepoint names
The logic for cancelling a savepoint was incorrect, as the behaviour the logic was modelled on was for a different RDBMS - where a COMMIT would always close the most recently opened transaction. SQLite on contrast will commit the entire transaction, not just the most recent savepoint marker until current execution point. The correct manner to deal with a 'partial' commit is to call RELEASE <savepoint>. This revealed an error in the savepoint logic, in that if someone had supplied a savepoint name instead of relying on generated ones, the rollback command did not factor for this and always assumed generated savepoint names - again causing error. For this reason a new class member field has been introduced to track savepoint names in a stack fashion.
This commit is contained in:
parent
62ef14f711
commit
0fa6b0fde7
@ -65,6 +65,11 @@ class SQLite3Database extends Database
|
||||
*/
|
||||
protected $transactionNesting = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $transactionSavepoints = [];
|
||||
|
||||
/**
|
||||
* List of default pragma values
|
||||
*
|
||||
@ -471,28 +476,27 @@ class SQLite3Database extends Database
|
||||
public function transactionStart($transaction_mode = false, $session_characteristics = false)
|
||||
{
|
||||
if ($this->transactionDepth()) {
|
||||
$this->transactionSavepoint($this->getTransactionSavepointName());
|
||||
$this->transactionSavepoint('NESTEDTRANSACTION' . $this->transactionDepth());
|
||||
} else {
|
||||
$this->query('BEGIN');
|
||||
$this->transactionDepthIncrease();
|
||||
}
|
||||
$this->transactionDepthIncrease();
|
||||
}
|
||||
|
||||
public function transactionSavepoint($savepoint)
|
||||
{
|
||||
$this->query("SAVEPOINT \"$savepoint\"");
|
||||
$this->transactionDepthIncrease($savepoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the name of the current savepoint
|
||||
* {@see transactionDepth} should be greater than zero
|
||||
* or the name will be invalid (because there are none).
|
||||
* Fetch the name of the most recent savepoint
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getTransactionSavepointName()
|
||||
{
|
||||
return 'NESTEDTRANSACTION' . $this->transactionDepth();
|
||||
return end($this->transactionSavepoints);
|
||||
}
|
||||
|
||||
public function transactionRollback($savepoint = false)
|
||||
@ -500,6 +504,7 @@ class SQLite3Database extends Database
|
||||
// Named transaction
|
||||
if ($savepoint) {
|
||||
$this->query("ROLLBACK TO $savepoint;");
|
||||
$this->transactionDepthDecrease();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -508,11 +513,11 @@ class SQLite3Database extends Database
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->transactionDepthDecrease();
|
||||
if ($this->transactionDepth()) {
|
||||
if ($this->transactionIsNested()) {
|
||||
$this->transactionRollback($this->getTransactionSavepointName());
|
||||
} else {
|
||||
$this->query('ROLLBACK;');
|
||||
$this->transactionDepthDecrease();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -528,24 +533,60 @@ class SQLite3Database extends Database
|
||||
if (!$this->transactionDepth()) {
|
||||
return false;
|
||||
}
|
||||
$this->query('COMMIT;');
|
||||
$this->transactionDepthDecrease();
|
||||
|
||||
if ($this->transactionIsNested()) {
|
||||
$savepoint = $this->getTransactionSavepointName();
|
||||
$this->query('RELEASE ' . $savepoint);
|
||||
$this->transactionDepthDecrease();
|
||||
} else {
|
||||
$this->query('COMMIT;');
|
||||
$this->resetTransactionNesting();
|
||||
}
|
||||
|
||||
if ($chain) {
|
||||
$this->transactionStart();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the nested transaction level by one
|
||||
* Indicate whether or not the current transaction is nested
|
||||
* Returns false if there are no transactions, or the open
|
||||
* transaction is the 'outer' transaction, i.e. not nested.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function transactionDepthIncrease()
|
||||
protected function transactionIsNested()
|
||||
{
|
||||
return $this->transactionNesting > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the nested transaction level by one
|
||||
* savepoint tracking is optional because BEGIN
|
||||
* opens a transaction, but is not a named reference
|
||||
*
|
||||
* @param string $savepoint
|
||||
*/
|
||||
protected function transactionDepthIncrease($savepoint = null)
|
||||
{
|
||||
++$this->transactionNesting;
|
||||
if ($savepoint) {
|
||||
array_push($this->transactionSavepoints, $savepoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease the nested transaction level by one
|
||||
* and reduce the savepoint tracking if we are
|
||||
* nesting, as the last one is no longer valid
|
||||
*/
|
||||
protected function transactionDepthDecrease()
|
||||
{
|
||||
if ($this->transactionIsNested()) {
|
||||
array_pop($this->transactionSavepoints);
|
||||
}
|
||||
--$this->transactionNesting;
|
||||
}
|
||||
|
||||
@ -555,6 +596,7 @@ class SQLite3Database extends Database
|
||||
protected function resetTransactionNesting()
|
||||
{
|
||||
$this->transactionNesting = 0;
|
||||
$this->transactionSavepoints = [];
|
||||
}
|
||||
|
||||
public function query($sql, $errorLevel = E_USER_ERROR)
|
||||
|
Loading…
Reference in New Issue
Block a user