ajout transactor
This commit is contained in:
parent
ff6c5e8da4
commit
ad603e8e81
|
@ -1,11 +1,14 @@
|
|||
<?php
|
||||
namespace nur\sery\db;
|
||||
|
||||
use nur\sery\php\func;
|
||||
use nur\sery\ValueException;
|
||||
|
||||
/**
|
||||
* Class Capacitor: un objet permettant d'attaquer un canal spécifique d'une
|
||||
* instance de {@link CapacitorStorage}
|
||||
*/
|
||||
class Capacitor {
|
||||
class Capacitor implements ITransactor {
|
||||
function __construct(CapacitorStorage $storage, CapacitorChannel $channel, bool $ensureExists=true) {
|
||||
$this->storage = $storage;
|
||||
$this->channel = $channel;
|
||||
|
@ -34,6 +37,80 @@ class Capacitor {
|
|||
return $this->getChannel()->getTableName();
|
||||
}
|
||||
|
||||
/** @var CapacitorChannel[] */
|
||||
protected ?array $subChannels = null;
|
||||
|
||||
protected ?array $subManageTransactions = null;
|
||||
|
||||
function willUpdate(...$channels): self {
|
||||
if ($this->subChannels === null) {
|
||||
# désactiver la gestion des transaction sur le channel local aussi
|
||||
$this->subChannels[] = $this->channel;
|
||||
}
|
||||
if ($channels) {
|
||||
foreach ($channels as $channel) {
|
||||
if ($channel instanceof Capacitor) $channel = $channel->getChannel();
|
||||
if ($channel instanceof CapacitorChannel) {
|
||||
$this->subChannels[] = $channel;
|
||||
} else {
|
||||
throw ValueException::invalid_type($channel, CapacitorChannel::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function inTransaction(): bool {
|
||||
return $this->db()->inTransaction();
|
||||
}
|
||||
|
||||
function beginTransaction(?callable $func=null): void {
|
||||
if ($this->subManageTransactions === null && $this->subChannels !== null) {
|
||||
foreach ($this->subChannels as $channel) {
|
||||
$name = $channel->getName();
|
||||
$this->subManageTransactions ??= [];
|
||||
if (!array_key_exists($name, $this->subManageTransactions)) {
|
||||
$this->subManageTransactions[$name] = $channel->isManageTransactions();
|
||||
}
|
||||
$channel->setManageTransactions(false);
|
||||
}
|
||||
$db = $this->db();
|
||||
if (!$db->inTransaction()) $db->beginTransaction();
|
||||
}
|
||||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
func::call($func, $this);
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
} finally {
|
||||
if (!$commited) $this->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function beforeEndTransaction(): void {
|
||||
if ($this->subManageTransactions !== null) {
|
||||
foreach ($this->subChannels as $channel) {
|
||||
$name = $channel->getName();
|
||||
$channel->setManageTransactions($this->subManageTransactions[$name]);
|
||||
}
|
||||
$this->subManageTransactions = null;
|
||||
}
|
||||
}
|
||||
|
||||
function commit(): void {
|
||||
$this->beforeEndTransaction();
|
||||
$db = $this->db();
|
||||
if ($db->inTransaction()) $this->db()->commit();
|
||||
}
|
||||
|
||||
function rollback(): void {
|
||||
$this->beforeEndTransaction();
|
||||
$db = $this->db();
|
||||
if ($db->inTransaction()) $this->db()->rollback();
|
||||
}
|
||||
|
||||
function getCreateSql(): string {
|
||||
return $this->storage->_getCreateSql($this->channel);
|
||||
}
|
||||
|
@ -51,6 +128,7 @@ class Capacitor {
|
|||
}
|
||||
|
||||
function charge($item, $func=null, ?array $args=null, ?array &$values=null): int {
|
||||
$this->beginTransaction();
|
||||
return $this->storage->_charge($this->channel, $item, $func, $args, $values);
|
||||
}
|
||||
|
||||
|
@ -71,10 +149,12 @@ class Capacitor {
|
|||
}
|
||||
|
||||
function each($filter, $func=null, ?array $args=null): int {
|
||||
$this->beginTransaction();
|
||||
return $this->storage->_each($this->channel, $filter, $func, $args);
|
||||
}
|
||||
|
||||
function delete($filter, $func=null, ?array $args=null): int {
|
||||
$this->beginTransaction();
|
||||
return $this->storage->_delete($this->channel, $filter, $func, $args);
|
||||
}
|
||||
|
||||
|
|
|
@ -204,6 +204,7 @@ EOT;
|
|||
function _charge(CapacitorChannel $channel, $item, $func, ?array $args, ?array &$values=null): int {
|
||||
$this->_create($channel);
|
||||
$tableName = $channel->getTableName();
|
||||
$db = $this->db();
|
||||
|
||||
$initFunc = [$channel, "getItemValues"];
|
||||
$initArgs = $args;
|
||||
|
@ -217,7 +218,7 @@ EOT;
|
|||
$rowIds = $this->getRowIds($channel, $row, $primaryKeys);
|
||||
if ($rowIds !== null) {
|
||||
# modification
|
||||
$prow = $this->db()->one([
|
||||
$prow = $db->one([
|
||||
"select",
|
||||
"from" => $tableName,
|
||||
"where" => $rowIds,
|
||||
|
@ -276,28 +277,41 @@ EOT;
|
|||
}
|
||||
}
|
||||
|
||||
if ($insert === null) {
|
||||
# aucune modification
|
||||
return 0;
|
||||
} elseif ($insert) {
|
||||
$id = $this->db()->exec([
|
||||
"insert",
|
||||
"into" => $tableName,
|
||||
"values" => $row,
|
||||
]);
|
||||
if (count($primaryKeys) == 1 && $rowIds === null) {
|
||||
# mettre à jour avec l'id généré
|
||||
$values[$primaryKeys[0]] = $id;
|
||||
}
|
||||
} else {
|
||||
$this->db()->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $row,
|
||||
"where" => $rowIds,
|
||||
]);
|
||||
# aucune modification
|
||||
if ($insert === null) return 0;
|
||||
|
||||
$manageTransactions = $channel->isManageTransactions();
|
||||
if ($manageTransactions) {
|
||||
$commited = false;
|
||||
$db->beginTransaction();
|
||||
}
|
||||
try {
|
||||
if ($insert) {
|
||||
$id = $db->exec([
|
||||
"insert",
|
||||
"into" => $tableName,
|
||||
"values" => $row,
|
||||
]);
|
||||
if (count($primaryKeys) == 1 && $rowIds === null) {
|
||||
# mettre à jour avec l'id généré
|
||||
$values[$primaryKeys[0]] = $id;
|
||||
}
|
||||
} else {
|
||||
$db->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $row,
|
||||
"where" => $rowIds,
|
||||
]);
|
||||
}
|
||||
if ($manageTransactions) {
|
||||
$db->commit();
|
||||
$commited = true;
|
||||
}
|
||||
return 1;
|
||||
} finally {
|
||||
if ($manageTransactions && !$commited) $db->rollback();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
function charge(?string $channel, $item, $func=null, ?array $args=null, ?array &$values=null): int {
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
<?php
|
||||
namespace nur\sery\db;
|
||||
|
||||
interface IDatabase {
|
||||
function beginTransaction(): void;
|
||||
|
||||
function commit(): void;
|
||||
|
||||
function rollback(): void;
|
||||
|
||||
interface IDatabase extends ITransactor {
|
||||
/**
|
||||
* - si c'est un insert, retourner l'identifiant autogénéré de la ligne
|
||||
* - sinon retourner le nombre de lignes modifiées en cas de succès, ou false
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
namespace nur\sery\db;
|
||||
|
||||
/**
|
||||
* Class ITransactor: un objet qui peut faire des opérations dans une
|
||||
* transaction
|
||||
*/
|
||||
interface ITransactor {
|
||||
/**
|
||||
* Indiquer qu'une transaction va être étendue à tous les objets mentionnés
|
||||
*/
|
||||
function willUpdate(...$transactors): self;
|
||||
|
||||
function inTransaction(): bool;
|
||||
|
||||
/** si $func!==null, la lancer puis commiter la transaction */
|
||||
function beginTransaction(?callable $func=null): void;
|
||||
|
||||
function commit(): void;
|
||||
|
||||
function rollback(): void;
|
||||
}
|
|
@ -4,9 +4,11 @@ namespace nur\sery\db\pdo;
|
|||
use Generator;
|
||||
use nur\sery\cl;
|
||||
use nur\sery\db\IDatabase;
|
||||
use nur\sery\db\ITransactor;
|
||||
use nur\sery\php\func;
|
||||
use nur\sery\php\time\Date;
|
||||
use nur\sery\php\time\DateTime;
|
||||
use nur\sery\ValueException;
|
||||
|
||||
class Pdo implements IDatabase {
|
||||
static function with($pdo, ?array $params=null): self {
|
||||
|
@ -144,16 +146,60 @@ class Pdo implements IDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
function beginTransaction(): void {
|
||||
/** @var ITransactor[] */
|
||||
protected ?array $transactors = null;
|
||||
|
||||
function willUpdate(...$transactors): self {
|
||||
foreach ($transactors as $transactor) {
|
||||
if ($transactor instanceof ITransactor) {
|
||||
$this->transactors[] = $transactor;
|
||||
$transactor->willUpdate();
|
||||
} else {
|
||||
throw ValueException::invalid_type($transactor, ITransactor::class);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function inTransaction(): bool {
|
||||
return $this->db()->inTransaction();
|
||||
}
|
||||
|
||||
function beginTransaction(?callable $func=null): void {
|
||||
$this->db()->beginTransaction();
|
||||
if ($this->transactors !== null) {
|
||||
foreach ($this->transactors as $transactor) {
|
||||
$transactor->beginTransaction();
|
||||
}
|
||||
}
|
||||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
func::call($func, $this);
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
} finally {
|
||||
if (!$commited) $this->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commit(): void {
|
||||
$this->db()->commit();
|
||||
if ($this->transactors !== null) {
|
||||
foreach ($this->transactors as $transactor) {
|
||||
$transactor->commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rollback(): void {
|
||||
$this->db()->rollBack();
|
||||
if ($this->transactors !== null) {
|
||||
foreach ($this->transactors as $transactor) {
|
||||
$transactor->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -82,7 +82,10 @@ class _query_base extends _base {
|
|||
}
|
||||
}
|
||||
|
||||
const DEBUG_QUERIES = false;
|
||||
|
||||
function useStmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
|
||||
if (static::DEBUG_QUERIES) error_log($this->sql); #XXX
|
||||
if ($this->bindings !== null) {
|
||||
$stmt = $db->prepare($this->sql);
|
||||
foreach ($this->bindings as $name => $value) {
|
||||
|
|
|
@ -4,6 +4,9 @@ namespace nur\sery\db\sqlite;
|
|||
use Generator;
|
||||
use nur\sery\cl;
|
||||
use nur\sery\db\IDatabase;
|
||||
use nur\sery\db\ITransactor;
|
||||
use nur\sery\php\func;
|
||||
use nur\sery\ValueException;
|
||||
use SQLite3;
|
||||
use SQLite3Result;
|
||||
use SQLite3Stmt;
|
||||
|
@ -82,6 +85,7 @@ class Sqlite implements IDatabase {
|
|||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
||||
#
|
||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||
$this->inTransaction = false;
|
||||
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
||||
$this->open();
|
||||
}
|
||||
|
@ -113,11 +117,14 @@ class Sqlite implements IDatabase {
|
|||
/** @var SQLite3 */
|
||||
protected $db;
|
||||
|
||||
protected bool $inTransaction;
|
||||
|
||||
function open(): self {
|
||||
if ($this->db === null) {
|
||||
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
|
||||
_config::with($this->config)->configure($this);
|
||||
_migration::with($this->migration)->migrate($this);
|
||||
$this->inTransaction = false;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
@ -126,6 +133,7 @@ class Sqlite implements IDatabase {
|
|||
if ($this->db !== null) {
|
||||
$this->db->close();
|
||||
$this->db = null;
|
||||
$this->inTransaction = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,16 +180,64 @@ class Sqlite implements IDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
function beginTransaction(): void {
|
||||
/** @var ITransactor[] */
|
||||
protected ?array $transactors = null;
|
||||
|
||||
function willUpdate(...$transactors): self {
|
||||
foreach ($transactors as $transactor) {
|
||||
if ($transactor instanceof ITransactor) {
|
||||
$this->transactors[] = $transactor;
|
||||
$transactor->willUpdate();
|
||||
} else {
|
||||
throw ValueException::invalid_type($transactor, ITransactor::class);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function inTransaction(): bool {
|
||||
#XXX très imparfait, mais y'a rien de mieux pour le moment :-(
|
||||
return $this->inTransaction;
|
||||
}
|
||||
|
||||
function beginTransaction(?callable $func=null): void {
|
||||
$this->db()->exec("begin");
|
||||
$this->inTransaction = true;
|
||||
if ($this->transactors !== null) {
|
||||
foreach ($this->transactors as $transactor) {
|
||||
$transactor->beginTransaction();
|
||||
}
|
||||
}
|
||||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
func::call($func, $this);
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
} finally {
|
||||
if (!$commited) $this->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commit(): void {
|
||||
$this->inTransaction = false;
|
||||
$this->db()->exec("commit");
|
||||
if ($this->transactors !== null) {
|
||||
foreach ($this->transactors as $transactor) {
|
||||
$transactor->commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rollback(): void {
|
||||
$this->db()->exec("commit");
|
||||
$this->inTransaction = false;
|
||||
$this->db()->exec("rollback");
|
||||
if ($this->transactors !== null) {
|
||||
foreach ($this->transactors as $transactor) {
|
||||
$transactor->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _get(string $query, bool $entireRow=false) {
|
||||
|
|
|
@ -32,7 +32,10 @@ class _query_base extends _base {
|
|||
}
|
||||
}
|
||||
|
||||
const DEBUG_QUERIES = false;
|
||||
|
||||
function useStmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
|
||||
if (static::DEBUG_QUERIES) error_log($this->sql); #XXX
|
||||
if ($this->bindings !== null) {
|
||||
/** @var SQLite3Stmt $stmt */
|
||||
$stmt = SqliteException::check($db, $db->prepare($this->sql));
|
||||
|
|
|
@ -233,6 +233,18 @@ class DateTime extends \DateTime {
|
|||
return static::with($this->add(new \DateInterval("P${nbDays}D")));
|
||||
}
|
||||
|
||||
function getElapsedAt(?DateTime $now=null, ?int $resolution=null): string {
|
||||
return Elapsed::format_at($this, $now, $resolution);
|
||||
}
|
||||
|
||||
function getElapsedSince(?DateTime $now=null, ?int $resolution=null): string {
|
||||
return Elapsed::format_since($this, $now, $resolution);
|
||||
}
|
||||
|
||||
function getElapsedDelay(?DateTime $now=null, ?int $resolution=null): string {
|
||||
return Elapsed::format_delay($this, $now, $resolution);
|
||||
}
|
||||
|
||||
function __toString(): string {
|
||||
return $this->format();
|
||||
}
|
||||
|
|
|
@ -10,14 +10,16 @@ class Elapsed {
|
|||
const DAY = 60 * 60 * 24;
|
||||
|
||||
/** @var int résolution */
|
||||
const RES_SECONDS = 0, RES_MINUTES = 1, RES_HOURS = 2, RES_DAYS = 3;
|
||||
const RESOLUTION_SECONDS = 0, RESOLUTION_MINUTES = 1, RESOLUTION_HOURS = 2, RESOLUTION_DAYS = 3;
|
||||
|
||||
const DEFAULT_RESOLUTION = self::RESOLUTION_SECONDS;
|
||||
|
||||
private static function format(int $seconds, int $resolution, ?string $prefix=null, ?string $zero=null): string {
|
||||
if ($prefix === null) $prefix = "depuis";
|
||||
switch ($resolution) {
|
||||
case self::RES_DAYS: return self::format_days($seconds, $prefix, $zero);
|
||||
case self::RES_HOURS: return self::format_hours($seconds, $prefix, $zero);
|
||||
case self::RES_MINUTES: return self::format_minutes($seconds, $prefix, $zero);
|
||||
case self::RESOLUTION_DAYS: return self::format_days($seconds, $prefix, $zero);
|
||||
case self::RESOLUTION_HOURS: return self::format_hours($seconds, $prefix, $zero);
|
||||
case self::RESOLUTION_MINUTES: return self::format_minutes($seconds, $prefix, $zero);
|
||||
default: return self::format_seconds($seconds, $prefix, $zero);
|
||||
}
|
||||
}
|
||||
|
@ -122,27 +124,28 @@ class Elapsed {
|
|||
return self::format_generic($prefix, $d, 0, 0);
|
||||
}
|
||||
|
||||
static function format_at(DateTime $start, ?DateTime $now=null): string {
|
||||
static function format_at(DateTime $start, ?DateTime $now=null, ?int $resolution=null): string {
|
||||
$now ??= new DateTime();
|
||||
$seconds = $now->getTimestamp() - $start->getTimestamp();
|
||||
return (new self($seconds))->formatAt();
|
||||
return (new self($seconds, $resolution))->formatAt();
|
||||
}
|
||||
|
||||
static function format_since(DateTime $start, ?DateTime $now=null): string {
|
||||
static function format_since(DateTime $start, ?DateTime $now=null, ?int $resolution=null): string {
|
||||
$now ??= new DateTime();
|
||||
$seconds = $now->getTimestamp() - $start->getTimestamp();
|
||||
return (new self($seconds))->formatSince();
|
||||
return (new self($seconds, $resolution))->formatSince();
|
||||
}
|
||||
|
||||
static function format_delay(DateTime $start, ?DateTime $now=null): string {
|
||||
static function format_delay(DateTime $start, ?DateTime $now=null, ?int $resolution=null): string {
|
||||
$now ??= new DateTime();
|
||||
$seconds = $now->getTimestamp() - $start->getTimestamp();
|
||||
return (new self($seconds))->formatDelay();
|
||||
return (new self($seconds, $resolution))->formatDelay();
|
||||
}
|
||||
|
||||
function __construct(int $seconds, int $resolution=self::RES_SECONDS) {
|
||||
if ($resolution < self::RES_SECONDS) $resolution = self::RES_SECONDS;
|
||||
elseif ($resolution > self::RES_DAYS) $resolution = self::RES_DAYS;
|
||||
function __construct(int $seconds, ?int $resolution=null) {
|
||||
$resolution ??= static::DEFAULT_RESOLUTION;
|
||||
if ($resolution < self::RESOLUTION_SECONDS) $resolution = self::RESOLUTION_SECONDS;
|
||||
elseif ($resolution > self::RESOLUTION_DAYS) $resolution = self::RESOLUTION_DAYS;
|
||||
$this->seconds = $seconds;
|
||||
$this->resolution = $resolution;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue