Revised the transaction API.
This commit is contained in:
parent
bf7977f511
commit
0dbe21683a
11 changed files with 229 additions and 163 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.2410.182054
|
||||
0.2410.191450
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<?php
|
||||
// DbConnection.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2024-10-04
|
||||
// Updated: 2024-10-19
|
||||
|
||||
namespace Index\Db;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\Closeable;
|
||||
|
||||
/**
|
||||
|
@ -53,4 +54,11 @@ interface DbConnection extends Closeable {
|
|||
* @return int|string Number of rows affected by the query.
|
||||
*/
|
||||
function execute(string $query): int|string;
|
||||
|
||||
/**
|
||||
* Begins a transaction.
|
||||
*
|
||||
* @throws RuntimeException If beginning the transaction failed.
|
||||
*/
|
||||
function beginTransaction(): DbTransaction;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
// DbTools.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2024-10-16
|
||||
// Updated: 2024-10-19
|
||||
|
||||
namespace Index\Db;
|
||||
|
||||
|
@ -11,28 +11,6 @@ use Countable;
|
|||
* Common database actions.
|
||||
*/
|
||||
final class DbTools {
|
||||
/**
|
||||
* Transaction wrapper.
|
||||
*
|
||||
* Takes a database connection with transaction support and a callable that may return a boolean based on the success of the actions.
|
||||
* If the callable returns nothing, nothing will happen.
|
||||
* If the callable returns true, commit will be called.
|
||||
* If the callable returns false, rollback will be called.
|
||||
*
|
||||
* @param DbTransactions $connection A database connection with transaction support.
|
||||
* @param callable $callable A callable that handles the transaction, may return a bool.
|
||||
*/
|
||||
public static function transaction(DbTransactions $connection, callable $callable): void {
|
||||
$connection->beginTransaction();
|
||||
$result = $callable($connection) ?? null;
|
||||
if(is_bool($result)) {
|
||||
if($result)
|
||||
$connection->commit();
|
||||
else
|
||||
$connection->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the DbType of the passed argument. Should be used for DbType::AUTO.
|
||||
*
|
||||
|
|
47
src/Db/DbTransaction.php
Normal file
47
src/Db/DbTransaction.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
// DbTransaction.php
|
||||
// Created: 2024-10-19
|
||||
// Updated: 2024-10-19
|
||||
|
||||
namespace Index\Db;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\Closeable;
|
||||
|
||||
/**
|
||||
* Represents a transaction to be performed on the database.
|
||||
*/
|
||||
interface DbTransaction extends Closeable {
|
||||
/**
|
||||
* Commits the actions done during a transaction and ends the transaction.
|
||||
* A new transaction will be started if auto-commit is disabled.
|
||||
*
|
||||
* @throws RuntimeException If the commit failed.
|
||||
*/
|
||||
function commit(): void;
|
||||
|
||||
/**
|
||||
* Creates a save point in the transaction that can be rolled back to.
|
||||
*
|
||||
* @param string $name Name for the save point.
|
||||
* @throws RuntimeException If save point creation failed.
|
||||
*/
|
||||
function save(string $name): void;
|
||||
|
||||
/**
|
||||
* Releases a specified save point.
|
||||
*
|
||||
* @param string $name Name of the save point.
|
||||
* @throws RuntimeException If save points are not supported by this implementation.
|
||||
*/
|
||||
function release(string $name): void;
|
||||
|
||||
/**
|
||||
* Rolls back to the state before a transaction start or to a specified save point.
|
||||
*
|
||||
* @param ?string $name Name of the save point, null for the entire transaction.
|
||||
* @throws RuntimeException If rollback failed.
|
||||
* @throws RuntimeException If save points are not supported by this implementation and $name was non-null.
|
||||
*/
|
||||
function rollback(?string $name = null): void;
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
// DbTransactions.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2024-10-04
|
||||
|
||||
namespace Index\Db;
|
||||
|
||||
/**
|
||||
* Indicates supports for transactions in a database connection.
|
||||
*/
|
||||
interface DbTransactions extends DbConnection {
|
||||
/**
|
||||
* Sets whether changes should be applied immediately or whether commit should always be called first.
|
||||
*
|
||||
* @param bool $state true if things should automatically be committed, false if not.
|
||||
*/
|
||||
function setAutoCommit(bool $state): void;
|
||||
|
||||
/**
|
||||
* Enters a transaction.
|
||||
*
|
||||
* @throws \RuntimeException If the creation of the transaction failed.
|
||||
*/
|
||||
function beginTransaction(): void;
|
||||
|
||||
/**
|
||||
* Commits the actions done during a transaction and ends the transaction.
|
||||
* A new transaction will be started if auto-commit is disabled.
|
||||
*
|
||||
* @throws \RuntimeException If the commit failed.
|
||||
*/
|
||||
function commit(): void;
|
||||
|
||||
/**
|
||||
* Rolls back to the state before a transaction start or to a specified save point.
|
||||
*
|
||||
* @param ?string $name Name of the save point, null for the entire transaction.
|
||||
* @throws \RuntimeException If rollback failed.
|
||||
*/
|
||||
function rollback(?string $name = null): void;
|
||||
|
||||
/**
|
||||
* Creates a save point in the transaction that can be rolled back to.
|
||||
*
|
||||
* @param string $name Name for the save point.
|
||||
* @throws \RuntimeException If save point creation failed.
|
||||
*/
|
||||
function savePoint(string $name): void;
|
||||
|
||||
/**
|
||||
* Releases a save point.
|
||||
*
|
||||
* @param string $name Name of the save point.
|
||||
* @throws \RuntimeException If releasing the save point failed.
|
||||
*/
|
||||
function releaseSavePoint(string $name): void;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
// MariaDbConnection.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2024-10-04
|
||||
// Updated: 2024-10-19
|
||||
|
||||
namespace Index\Db\MariaDb;
|
||||
|
||||
|
@ -10,12 +10,12 @@ use mysqli_sql_exception;
|
|||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\Db\{DbConnection,DbTransactions};
|
||||
use Index\Db\{DbConnection,DbTransaction};
|
||||
|
||||
/**
|
||||
* Represents a connection with a MariaDB or MySQL database server.
|
||||
*/
|
||||
class MariaDbConnection implements DbConnection, DbTransactions {
|
||||
class MariaDbConnection implements DbConnection {
|
||||
/**
|
||||
* Refresh grant tables.
|
||||
*
|
||||
|
@ -144,6 +144,8 @@ class MariaDbConnection implements DbConnection, DbTransactions {
|
|||
} catch(mysqli_sql_exception $ex) {
|
||||
throw new RuntimeException($ex->getMessage(), $ex->getCode(), $ex);
|
||||
}
|
||||
|
||||
$this->connection->autocommit(true);
|
||||
}
|
||||
|
||||
public function getAffectedRows(): int|string {
|
||||
|
@ -189,7 +191,7 @@ class MariaDbConnection implements DbConnection, DbTransactions {
|
|||
*/
|
||||
public function switchUser(string $userName, string $password, string|null $database = null): void {
|
||||
if(!$this->connection->change_user($userName, $password, $database === null ? null : $database))
|
||||
throw new RuntimeException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
$this->throwLastError();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -200,7 +202,7 @@ class MariaDbConnection implements DbConnection, DbTransactions {
|
|||
*/
|
||||
public function switchDatabase(string $database): void {
|
||||
if(!$this->connection->select_db($database))
|
||||
throw new RuntimeException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
$this->throwLastError();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,6 +232,15 @@ class MariaDbConnection implements DbConnection, DbTransactions {
|
|||
return $this->connection->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws the last error as a RuntimeException.
|
||||
*
|
||||
* @throws RuntimeException Based on the last error code and string.
|
||||
*/
|
||||
public function throwLastError(): never {
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current SQL State of the connection.
|
||||
*
|
||||
|
@ -302,33 +313,11 @@ class MariaDbConnection implements DbConnection, DbTransactions {
|
|||
$this->connection->refresh($flags);
|
||||
}
|
||||
|
||||
public function setAutoCommit(bool $state): void {
|
||||
$this->connection->autocommit($state);
|
||||
}
|
||||
|
||||
public function beginTransaction(): void {
|
||||
public function beginTransaction(): DbTransaction {
|
||||
if(!$this->connection->begin_transaction())
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
$this->throwLastError();
|
||||
|
||||
public function commit(): void {
|
||||
if(!$this->connection->commit())
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function rollback(?string $name = null): void {
|
||||
if(!$this->connection->rollback(0, $name))
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function savePoint(string $name): void {
|
||||
if(!$this->connection->savepoint($name))
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function releaseSavePoint(string $name): void {
|
||||
if(!$this->connection->release_savepoint($name))
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
return new MariaDbTransaction($this, $this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -360,7 +349,7 @@ class MariaDbConnection implements DbConnection, DbTransactions {
|
|||
throw new RuntimeException($ex->getMessage(), $ex->getCode(), $ex);
|
||||
}
|
||||
if($statement === false)
|
||||
throw new RuntimeException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
$this->throwLastError();
|
||||
return new MariaDbStatement($statement);
|
||||
}
|
||||
|
||||
|
@ -375,7 +364,7 @@ class MariaDbConnection implements DbConnection, DbTransactions {
|
|||
}
|
||||
|
||||
if($result === false)
|
||||
throw new RuntimeException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
$this->throwLastError();
|
||||
|
||||
// the mysql library is very adorable
|
||||
if($result === true)
|
||||
|
@ -388,7 +377,7 @@ class MariaDbConnection implements DbConnection, DbTransactions {
|
|||
public function execute(string $query): int|string {
|
||||
try {
|
||||
if(!$this->connection->real_query($query))
|
||||
throw new RuntimeException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
$this->throwLastError();
|
||||
} catch(mysqli_sql_exception $ex) {
|
||||
throw new RuntimeException($ex->getMessage(), $ex->getCode(), $ex);
|
||||
}
|
||||
|
|
45
src/Db/MariaDb/MariaDbTransaction.php
Normal file
45
src/Db/MariaDb/MariaDbTransaction.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
// MariaDbTransaction.php
|
||||
// Created: 2024-10-19
|
||||
// Updated: 2024-10-19
|
||||
|
||||
namespace Index\Db\MariaDb;
|
||||
|
||||
use mysqli;
|
||||
use Index\Db\DbTransaction;
|
||||
|
||||
/**
|
||||
* Represents a MariaDB transaction.
|
||||
*/
|
||||
class MariaDbTransaction implements DbTransaction {
|
||||
/**
|
||||
* @param MariaDbConnection $conn Underlying connection
|
||||
* @param mysqli $connRaw Underlying connection (off vocal)
|
||||
*/
|
||||
public function __construct(
|
||||
private MariaDbConnection $conn,
|
||||
private mysqli $connRaw
|
||||
) {}
|
||||
|
||||
public function commit(): void {
|
||||
if(!$this->connRaw->commit())
|
||||
$this->conn->throwLastError();
|
||||
}
|
||||
|
||||
public function save(string $name): void {
|
||||
if(!$this->connRaw->savepoint($name))
|
||||
$this->conn->throwLastError();
|
||||
}
|
||||
|
||||
public function release(string $name): void {
|
||||
if(!$this->connRaw->release_savepoint($name))
|
||||
$this->conn->throwLastError();
|
||||
}
|
||||
|
||||
public function rollback(?string $name = null): void {
|
||||
if(!$this->connRaw->rollback(0, $name))
|
||||
$this->conn->throwLastError();
|
||||
}
|
||||
|
||||
public function close(): void {}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
<?php
|
||||
// NullDbConnection.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2024-10-04
|
||||
// Updated: 2024-10-19
|
||||
|
||||
namespace Index\Db\NullDb;
|
||||
|
||||
use Index\Db\{DbConnection,DbStatement,DbResult};
|
||||
use Index\Db\{DbConnection,DbResult,DbStatement,DbTransaction};
|
||||
|
||||
/**
|
||||
* Represents a dummy database connection.
|
||||
|
@ -31,5 +31,9 @@ class NullDbConnection implements DbConnection {
|
|||
return 0;
|
||||
}
|
||||
|
||||
public function beginTransaction(): DbTransaction {
|
||||
return new NullDbTransaction;
|
||||
}
|
||||
|
||||
public function close(): void {}
|
||||
}
|
||||
|
|
22
src/Db/NullDb/NullDbTransaction.php
Normal file
22
src/Db/NullDb/NullDbTransaction.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
// NullDbTransaction.php
|
||||
// Created: 2024-10-19
|
||||
// Updated: 2024-10-19
|
||||
|
||||
namespace Index\Db\NullDb;
|
||||
|
||||
use Index\Db\{DbConnection,DbTransaction};
|
||||
/**
|
||||
* Represents a dummy database transaction.
|
||||
*/
|
||||
class NullDbTransaction implements DbTransaction {
|
||||
public function commit(): void {}
|
||||
|
||||
public function save(string $name): void {}
|
||||
|
||||
public function release(string $name): void {}
|
||||
|
||||
public function rollback(?string $name = null): void {}
|
||||
|
||||
public function close(): void {}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
<?php
|
||||
// SqliteConnection.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2024-10-04
|
||||
// Updated: 2024-10-19
|
||||
|
||||
namespace Index\Db\Sqlite;
|
||||
|
||||
use RuntimeException;
|
||||
use SQLite3;
|
||||
use Index\Db\{DbConnection,DbTransactions,DbStatement,DbResult};
|
||||
use Index\Db\{DbConnection,DbResult,DbStatement,DbTransaction};
|
||||
|
||||
/**
|
||||
* Represents a client for an SQLite database.
|
||||
*/
|
||||
class SqliteConnection implements DbConnection, DbTransactions {
|
||||
class SqliteConnection implements DbConnection {
|
||||
/**
|
||||
* CREATE INDEX authorizer.
|
||||
*
|
||||
|
@ -352,7 +352,6 @@ class SqliteConnection implements DbConnection, DbTransactions {
|
|||
public const IGNORE = SQLite3::IGNORE;
|
||||
|
||||
private SQLite3 $connection;
|
||||
private bool $autoCommit = true;
|
||||
|
||||
/**
|
||||
* Creates a new SQLite client.
|
||||
|
@ -456,6 +455,15 @@ class SqliteConnection implements DbConnection, DbTransactions {
|
|||
return $this->connection->lastErrorCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws the last error as a RuntimeException.
|
||||
*
|
||||
* @throws RuntimeException Based on the last error code and string.
|
||||
*/
|
||||
public function throwLastError(): never {
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a BLOB field as a resource.
|
||||
*
|
||||
|
@ -468,7 +476,7 @@ class SqliteConnection implements DbConnection, DbTransactions {
|
|||
public function getBlobStream(string $table, string $column, int $rowId): mixed {
|
||||
$handle = $this->connection->openBlob($table, $column, $rowId);
|
||||
if(!is_resource($handle))
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
$this->throwLastError();
|
||||
|
||||
return $handle;
|
||||
}
|
||||
|
@ -480,63 +488,42 @@ class SqliteConnection implements DbConnection, DbTransactions {
|
|||
public function prepare(string $query): DbStatement {
|
||||
$statement = $this->connection->prepare($query);
|
||||
if($statement === false)
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
$this->throwLastError();
|
||||
|
||||
return new SqliteStatement($this, $statement);
|
||||
}
|
||||
|
||||
public function query(string $query): DbResult {
|
||||
$result = $this->connection->query($query);
|
||||
if($result === false)
|
||||
throw new RuntimeException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
$this->throwLastError();
|
||||
|
||||
return new SqliteResult($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to execute a query but throws an exception if it fails.
|
||||
*
|
||||
* @param string $query Command to execute.
|
||||
* @throws RuntimeException If the command failed to execute.
|
||||
*/
|
||||
public function executeThrow(string $query): void {
|
||||
if(!$this->connection->exec($query))
|
||||
$this->throwLastError();
|
||||
}
|
||||
|
||||
public function execute(string $query): int|string {
|
||||
if(!$this->connection->exec($query))
|
||||
throw new RuntimeException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
$this->throwLastError();
|
||||
|
||||
return $this->connection->changes();
|
||||
}
|
||||
|
||||
public function setAutoCommit(bool $state): void {
|
||||
// there's not really a completely reliable way to set a persistent auto commit disable state
|
||||
if($state === $this->autoCommit)
|
||||
return;
|
||||
$this->autoCommit = $state;
|
||||
|
||||
if($state) {
|
||||
$this->commit();
|
||||
} else {
|
||||
$this->beginTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public function beginTransaction(): void {
|
||||
public function beginTransaction(): DbTransaction {
|
||||
if(!$this->connection->exec('BEGIN;'))
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
$this->throwLastError();
|
||||
|
||||
public function commit(): void {
|
||||
if(!$this->connection->exec('COMMIT;'))
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
if(!$this->autoCommit)
|
||||
$this->beginTransaction();
|
||||
}
|
||||
|
||||
public function rollback(?string $name = null): void {
|
||||
if(!$this->connection->exec(empty($name) ? 'ROLLBACK;' : "ROLLBACK TO {$name};"))
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
if(!$this->autoCommit)
|
||||
$this->beginTransaction();
|
||||
}
|
||||
|
||||
public function savePoint(string $name): void {
|
||||
if(!$this->connection->exec("SAVEPOINT {$name};"))
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function releaseSavePoint(string $name): void {
|
||||
if(!$this->connection->exec("RELEASE SAVEPOINT {$name};"))
|
||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
return new SqliteTransaction($this);
|
||||
}
|
||||
|
||||
public function close(): void {
|
||||
|
|
43
src/Db/Sqlite/SqliteTransaction.php
Normal file
43
src/Db/Sqlite/SqliteTransaction.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
// SqliteTransaction.php
|
||||
// Created: 2024-10-19
|
||||
// Updated: 2024-10-19
|
||||
|
||||
namespace Index\Db\Sqlite;
|
||||
|
||||
use SQLite3;
|
||||
use Index\Db\DbTransaction;
|
||||
|
||||
/**
|
||||
* Represents an SQLite transaction.
|
||||
*/
|
||||
class SqliteTransaction implements DbTransaction {
|
||||
/**
|
||||
* @param SqliteConnection $conn Underlying connection.
|
||||
*/
|
||||
public function __construct(
|
||||
private SqliteConnection $conn
|
||||
) {}
|
||||
|
||||
public function commit(): void {
|
||||
$this->conn->executeThrow('COMMIT;');
|
||||
}
|
||||
|
||||
public function save(string $name): void {
|
||||
$this->conn->executeThrow(sprintf('SAVEPOINT %s;', SQLite3::escapeString($name)));
|
||||
}
|
||||
|
||||
public function release(string $name): void {
|
||||
$this->conn->executeThrow(sprintf('RELEASE SAVEPOINT %s;', SQLite3::escapeString($name)));
|
||||
}
|
||||
|
||||
public function rollback(?string $name = null): void {
|
||||
$this->conn->executeThrow(
|
||||
$name === null
|
||||
? 'ROLLBACK;'
|
||||
: sprintf('ROLLBACK TO SAVEPOINT %s;', SQLite3::escapeString($name))
|
||||
);
|
||||
}
|
||||
|
||||
public function close(): void {}
|
||||
}
|
Loading…
Reference in a new issue