diff options
author | Harald Eilertsen <haraldei@anduin.net> | 2024-01-29 14:30:52 +0100 |
---|---|---|
committer | Harald Eilertsen <haraldei@anduin.net> | 2024-02-26 15:11:39 +0100 |
commit | 25dbc8a9f69ea33a489d94bb69705297664550c2 (patch) | |
tree | b0c5090224c3cd3b09fb4b16e1e8a5b1ba44c0f1 /include | |
parent | c639704f3c1a34bdd8bccd0e6ce37f3eef3921d7 (diff) | |
download | volse-hubzilla-25dbc8a9f69ea33a489d94bb69705297664550c2.tar.gz volse-hubzilla-25dbc8a9f69ea33a489d94bb69705297664550c2.tar.bz2 volse-hubzilla-25dbc8a9f69ea33a489d94bb69705297664550c2.zip |
include/dba: Make Dba driver transaction aware.
This patch introduced database transaction support to the Dba driver via
the DbaTransaction class.
The goal of this is to allow the driver control over the creation and
finalization of database transactions.
Until now code that has needed transaction support has done so directly
by issuing "BEGIN", "ROLLBACK" and "COMMIT" commands to the underlying
database directly.
This has several disadvantages:
- We do have no control or knowledge of whether any transactions being
active.
- Since transactions can not be nested, we run the risk of unrelated
code trying to create a transaction when one is already active.
- Code using transactions are not testable, as the test runner wraps
all tests within a transaction to begin with.
This patch should eliminate all these problems.
A transaction is started by instantiating the DbaTransaction class:
$my_transaction = new \DbaTransaction();
The transaction will automatically be _rolled back_ if it has not been
committed before the instance is destroyed. (When the variable holding
it goes out of scope, i.e when the containing function returns.)
A transaction is committed like this:
$my_transaction->commit();
This will immediately commit the changes in the transaction, and the
transaction will be marked as committed, so it will not be attempted to
be rolled back on destruction.
I have chosen to "ignore" the problem of nested transactions by having
the DbaTransaction class _not_ initiate a new transaction if one is
already active. This also makes the rollback and commit actions of the
DbaTransaction class into no-ops.
An alternative would be to simulate nested transactions by using save
points if a transaction is already active. However, I'm unsure about
wether there's any safe way to avoid all potential pitfalls when doing
that.
In any case, nested transactions should preferably be avoided, and
afaict we don't rely on that in any of the existing code. The reason we
need to support it in some way is that it's needed for testing where the
code under test is creating a transaction on it's own. (Since each test
is run within a db transaction to begin with.)
Also, I have taken the liberty to assume a PDO based db driver for this
stuff. I don't think that's going to be a problem, as that's the only
thing supported by the rest of the code in any case.
Diffstat (limited to 'include')
-rw-r--r-- | include/dba/dba_transaction.php | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/include/dba/dba_transaction.php b/include/dba/dba_transaction.php new file mode 100644 index 000000000..02e9945ca --- /dev/null +++ b/include/dba/dba_transaction.php @@ -0,0 +1,64 @@ +<?php +/** + * Class to represent a database transaction. + * + * A database transaction is initiated upon construction of an object of this + * class. The transaction will be automatically rolled back upon destruction + * unless it has been explicitly committed by calling the `commit` method. + * + * Wrapping multiple database operation within a transaction ensures that all + * (or none) of the operations are successfully completed at the same time. + * + * If a transaction is already active when constructing an object of this + * class, it will _not_ try to initiate a transaction, but constructs an object + * that will in practice be a stub. This prevents that "nested" transactions + * will cause problems with the existing active transaction. + * + * It also means that any rollbacks or commits perfomed on the "nested" + * transaction will be ignored, and postponed to the outer transaction is + * committed or rolled back. + * + * Also note that any modification to the database schema will implicitly + * commit active transactions in most cases, so be careful about relying on + * transactions in those cases. + * + * @Note This class assumes the actual underlying database driver is PDO. + */ +class DbaTransaction { + private bool $committed = false; + private bool $active = false; + + /** + * Creates a database transaction object. + * + * If a transaction is already active for this db connection, + * no transaction is initiated, and the constructed object will + * not perform any commit or rollback actions. + */ + public function __construct(private dba_driver $dba) { + if (! $this->dba->db->inTransaction()) { + $this->active = $this->dba->db->beginTransaction(); + } + } + + /** + * Roll back the transaction if it is active and not already committed. + */ + public function __destruct() { + if ($this->active && ! $this->committed) { + $this->dba->db->rollBack(); + } + } + + /** + * Commit the transaction if active. + * + * This will also mark the transaction as committed, preventing it from + * being attempted rolled back on destruction. + */ + public function commit(): void { + if ($this->active && ! $this->committed) { + $this->committed = $this->dba->db->commit(); + } + } +} |