modifs.mineures sans commentaires
This commit is contained in:
parent
7a3b0e456d
commit
907f17af33
|
@ -5,59 +5,44 @@ namespace nur\sery\db;
|
|||
* Class AbstractCapacitor: implémentation de base d'un {@link ICapacitor}
|
||||
*/
|
||||
abstract class AbstractCapacitor implements ICapacitor {
|
||||
abstract protected function getChannel(?string $name=null): CapacitorChannel;
|
||||
abstract protected function getChannel(?string $name): CapacitorChannel;
|
||||
|
||||
abstract function _exists(CapacitorChannel $channel): bool;
|
||||
|
||||
/** tester si le canal spécifié existe */
|
||||
function exists(?string $channel=null): bool {
|
||||
function exists(?string $channel): bool {
|
||||
return $this->_exists($this->getChannel($channel));
|
||||
}
|
||||
|
||||
abstract function _reset(CapacitorChannel $channel): void;
|
||||
|
||||
/** supprimer le canal spécifié */
|
||||
function reset(?string $channel=null): void {
|
||||
function reset(?string $channel): void {
|
||||
$this->_reset($this->getChannel($channel));
|
||||
}
|
||||
|
||||
abstract function _charge($item, CapacitorChannel $channel): void;
|
||||
abstract function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): bool;
|
||||
|
||||
/** charger une valeur dans le canal */
|
||||
function charge($item, ?string $channel=null): void {
|
||||
$this->_charge($item, $this->getChannel($channel));
|
||||
function charge(?string $channel, $item, ?callable $func=null, ?array $args=null): bool {
|
||||
return $this->_charge($this->getChannel($channel), $item, $func, $args);
|
||||
}
|
||||
|
||||
abstract function _discharge($keys=null, CapacitorChannel $channel=null, ?bool $reset=null): iterable;
|
||||
abstract function _discharge(CapacitorChannel $channel, $filter, ?bool $reset): iterable;
|
||||
|
||||
/** décharger les données du canal spécifié */
|
||||
function discharge($keys=null, ?string $channel=null, ?bool $reset=null): iterable {
|
||||
return $this->_discharge($keys, $this->getChannel($channel), $reset);
|
||||
function discharge(?string $channel, $filter=null, ?bool $reset=null): iterable {
|
||||
return $this->_discharge($this->getChannel($channel), $filter, $reset);
|
||||
}
|
||||
|
||||
abstract function _get($keys, CapacitorChannel $channel=null);
|
||||
abstract function _get(CapacitorChannel $channel, $filter);
|
||||
|
||||
/**
|
||||
* obtenir l'élément identifié par les clés spécifiées sur le canal spécifié
|
||||
*
|
||||
* si $keys n'est pas un tableau, il est transformé en ["_id" => $keys]
|
||||
*/
|
||||
function get($keys, ?string $channel=null) {
|
||||
return $this->_get($keys, $this->getChannel($channel));
|
||||
function get(?string $channel, $filter) {
|
||||
return $this->_get($this->getChannel($channel), $filter);
|
||||
}
|
||||
|
||||
abstract function _each($keys, callable $func, ?array $args=null, CapacitorChannel $channel=null): void;
|
||||
abstract function _each(CapacitorChannel $channel, $filter, callable $func, ?array $args): void;
|
||||
|
||||
/**
|
||||
* appeler une fonction pour chaque élément du canal spécifié.
|
||||
*
|
||||
* $keys permet de filtrer parmi les élements chargés
|
||||
*
|
||||
* si $func retourne un tableau, il est utilisé pour mettre à jour
|
||||
* l'enregistrement.
|
||||
*/
|
||||
function each($keys, callable $func, ?array $args=null, ?string $channel=null): void {
|
||||
$this->_each($keys, $func, $args, $this->getChannel($channel));
|
||||
function each(?string $channel, $filter, callable $func, ?array $args=null): void {
|
||||
$this->_each($this->getChannel($channel), $filter, $func, $args);
|
||||
}
|
||||
|
||||
abstract function close(): void;
|
||||
|
|
|
@ -25,20 +25,20 @@ class Capacitor {
|
|||
$this->capacitor->_reset($this->channel);
|
||||
}
|
||||
|
||||
function charge($item) {
|
||||
$this->capacitor->_charge($item, $this->channel);
|
||||
function charge($item, ?callable $func=null, ?array $args=null): bool {
|
||||
return $this->capacitor->_charge($this->channel, $item, $func, $args);
|
||||
}
|
||||
|
||||
function discharge($keys=null, ?bool $reset=null): iterable {
|
||||
return $this->capacitor->_discharge($keys, $this->channel, $reset);
|
||||
function discharge($filter=null, ?bool $reset=null): iterable {
|
||||
return $this->capacitor->_discharge($this->channel, $filter, $reset);
|
||||
}
|
||||
|
||||
function get($keys) {
|
||||
return $this->capacitor->_get($keys, $this->channel);
|
||||
function get($filter) {
|
||||
return $this->capacitor->_get($this->channel, $filter);
|
||||
}
|
||||
|
||||
function each($keys, callable $func, ?array $args=null): void {
|
||||
$this->capacitor->_each($keys, $func, $args, $this->channel);
|
||||
function each($filter, callable $func, ?array $args=null): void {
|
||||
$this->capacitor->_each($this->channel, $filter, $func, $args);
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
|
|
|
@ -38,10 +38,24 @@ class CapacitorChannel {
|
|||
$this->created = $created;
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner un ensemble de définitions pour des colonnes supplémentaires à
|
||||
* insérer lors du chargement d'une valeur
|
||||
*
|
||||
* la colonne "_id" de définition "integer primary key autoincrement" est la
|
||||
* clé primaire par défaut. elle peut être redéfinie, et dans ce cas la valeur
|
||||
* à utiliser doit être retournée par {@link getKeyValues()}
|
||||
*/
|
||||
function getKeyDefinitions(): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* calculer les valeurs des colonnes supplémentaires à insérer pour le
|
||||
* chargement de $item
|
||||
*
|
||||
* Si "_id" est retourné, la ligne existante est mise à jour le cas échéant.
|
||||
*/
|
||||
function getKeyValues($item): ?array {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -7,33 +7,44 @@ namespace nur\sery\db;
|
|||
*/
|
||||
interface ICapacitor {
|
||||
/** tester si le canal spécifié existe */
|
||||
function exists(?string $channel=null): bool;
|
||||
function exists(?string $channel): bool;
|
||||
|
||||
/** supprimer le canal spécifié */
|
||||
function reset(?string $channel=null): void;
|
||||
function reset(?string $channel): void;
|
||||
|
||||
/** charger une valeur dans le canal */
|
||||
function charge($item, ?string $channel=null): void;
|
||||
/**
|
||||
* charger une valeur dans le canal
|
||||
*
|
||||
* Si $func!==null, après avoir calculé les valeurs des clés supplémentaires
|
||||
* avec {@link CapacitorChannel::getKeyValues()}, la fonction est appelée avec
|
||||
* les arguments ($item, $keyValues, $row, ...$args)
|
||||
* Si la fonction retourne un tableau, il est utilisé pour modifié les valeurs
|
||||
* insérées/mises à jour
|
||||
*
|
||||
* @return true si l'objet a été chargé ou mis à jour, false s'il existait
|
||||
* déjà à l'identique dans le canal
|
||||
*/
|
||||
function charge(?string $channel, $item, ?callable $func=null, ?array $args=null): bool;
|
||||
|
||||
/** décharger les données du canal spécifié */
|
||||
function discharge($keys=null, ?string $channel=null, ?bool $reset=null): iterable;
|
||||
function discharge(?string $channel, $filter=null, ?bool $reset=null): iterable;
|
||||
|
||||
/**
|
||||
* obtenir l'élément identifié par les clés spécifiées sur le canal spécifié
|
||||
*
|
||||
* si $keys n'est pas un tableau, il est transformé en ["_id" => $keys]
|
||||
* si $filter n'est pas un tableau, il est transformé en ["_id" => $filter]
|
||||
*/
|
||||
function get($keys, ?string $channel=null);
|
||||
function get(?string $channel, $filter);
|
||||
|
||||
/**
|
||||
* appeler une fonction pour chaque élément du canal spécifié.
|
||||
*
|
||||
* $keys permet de filtrer parmi les élements chargés
|
||||
* $filter permet de filtrer parmi les élements chargés
|
||||
*
|
||||
* si $func retourne un tableau, il est utilisé pour mettre à jour
|
||||
* l'enregistrement.
|
||||
*/
|
||||
function each($keys, callable $func, ?array $args=null, ?string $channel=null): void;
|
||||
function each(?string $channel, $filter, callable $func, ?array $args=null): void;
|
||||
|
||||
function close(): void;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@ class SqliteCapacitor extends AbstractCapacitor{
|
|||
$columns = cl::merge([
|
||||
"_id" => "integer primary key autoincrement",
|
||||
"_item" => "text",
|
||||
"_sum" => "varchar(40)",
|
||||
"_created" => "datetime",
|
||||
"_modified" => "datetime",
|
||||
], $channel->getKeyDefinitions());
|
||||
$this->sqlite->exec([
|
||||
"create table if not exists",
|
||||
|
@ -46,7 +49,7 @@ class SqliteCapacitor extends AbstractCapacitor{
|
|||
return $channel;
|
||||
}
|
||||
|
||||
protected function getChannel(?string $name=null): CapacitorChannel {
|
||||
protected function getChannel(?string $name): CapacitorChannel {
|
||||
$name = CapacitorChannel::verifix_name($name);
|
||||
$channel = $this->channels[$name] ?? null;
|
||||
if ($channel === null) {
|
||||
|
@ -73,25 +76,81 @@ class SqliteCapacitor extends AbstractCapacitor{
|
|||
$channel->setCreated(false);
|
||||
}
|
||||
|
||||
function _charge($item, CapacitorChannel $channel): void {
|
||||
function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): bool {
|
||||
$this->_create($channel);
|
||||
$values = cl::merge($channel->getKeyValues($item), [
|
||||
"_item" => serialize($item),
|
||||
]);
|
||||
$this->sqlite->exec([
|
||||
"insert",
|
||||
"into" => $channel->getTableName(),
|
||||
"values" => $values,
|
||||
]);
|
||||
$now = date("Y-m-d H:i:s");
|
||||
$_item = serialize($item);
|
||||
$_sum = sha1($_item);
|
||||
$values = cl::merge([
|
||||
"_item" => $_item,
|
||||
"_sum" => $_sum,
|
||||
], $channel->getKeyValues($item));
|
||||
$row = null;
|
||||
$id = $values["_id"] ?? null;
|
||||
if ($id !== null) {
|
||||
# modification
|
||||
$row = $this->sqlite->one([
|
||||
"select _item, _sum, _created, _modified",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => ["_id" => $id],
|
||||
]);
|
||||
}
|
||||
$insert = null;
|
||||
if ($row === null) {
|
||||
# création
|
||||
$values = cl::merge($values, [
|
||||
"_created" => $now,
|
||||
"_modified" => $now,
|
||||
]);
|
||||
$insert = true;
|
||||
} elseif ($_sum !== $row["_sum"]) {
|
||||
# modification
|
||||
$values = cl::merge($values, [
|
||||
"_modified" => $now,
|
||||
]);
|
||||
$insert = false;
|
||||
}
|
||||
if ($func !== null) {
|
||||
$context = func::_prepare($func);
|
||||
$args ??= [];
|
||||
$updates = func::_call($context, [$item, $values, $row, ...$args]);
|
||||
if (is_array($updates)) {
|
||||
if (array_key_exists("_item", $updates)) {
|
||||
$updates["_item"] = serialize($updates["_item"]);
|
||||
if (!array_key_exists("_modified", $updates)) {
|
||||
$updates["_modified"] = $now;
|
||||
}
|
||||
}
|
||||
$values = cl::merge($values, $updates);
|
||||
}
|
||||
}
|
||||
if ($insert === null) {
|
||||
# aucune modification
|
||||
return false;
|
||||
} elseif ($insert) {
|
||||
$this->sqlite->exec([
|
||||
"insert",
|
||||
"into" => $channel->getTableName(),
|
||||
"values" => $values,
|
||||
]);
|
||||
} else {
|
||||
$this->sqlite->exec([
|
||||
"update",
|
||||
"table" => $channel->getTableName(),
|
||||
"values" => $values,
|
||||
"where" => ["_id" => $id],
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function _discharge($keys=null, CapacitorChannel $channel=null, ?bool $reset=null): iterable {
|
||||
if ($keys !== null && !is_array($keys)) $keys = ["_id" => $keys];
|
||||
if ($reset === null) $reset = $keys === null;
|
||||
function _discharge(CapacitorChannel $channel, $filter, ?bool $reset): iterable {
|
||||
if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter];
|
||||
if ($reset === null) $reset = $filter === null;
|
||||
$rows = $this->sqlite->all([
|
||||
"select _item",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $keys,
|
||||
"where" => $filter,
|
||||
]);
|
||||
foreach ($rows as $row) {
|
||||
$item = unserialize($row['_item']);
|
||||
|
@ -100,43 +159,51 @@ class SqliteCapacitor extends AbstractCapacitor{
|
|||
if ($reset) $this->_reset($channel);
|
||||
}
|
||||
|
||||
function _get($keys, CapacitorChannel $channel=null) {
|
||||
if ($keys === null) throw ValueException::null("keys");
|
||||
if (!is_array($keys)) $keys = ["_id" => $keys];
|
||||
function _get(CapacitorChannel $channel, $filter) {
|
||||
if ($filter === null) throw ValueException::null("keys");
|
||||
if (!is_array($filter)) $filter = ["_id" => $filter];
|
||||
$row = $this->sqlite->one([
|
||||
"select _item",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $keys,
|
||||
"where" => $filter,
|
||||
]);
|
||||
if ($row === null) return null;
|
||||
else return unserialize($row["_item"]);
|
||||
}
|
||||
|
||||
function _each($keys, callable $func, ?array $args=null, CapacitorChannel $channel=null): void {
|
||||
if ($keys !== null && !is_array($keys)) $keys = ["_id" => $keys];
|
||||
function _each(CapacitorChannel $channel, $filter, callable $func, ?array $args): void {
|
||||
if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter];
|
||||
$context = func::_prepare($func);
|
||||
$sqlite = $this->sqlite;
|
||||
$tableName = $channel->getTableName();
|
||||
$rows = $sqlite->all([
|
||||
"select",
|
||||
"from" => $tableName,
|
||||
"where" => $keys,
|
||||
]);
|
||||
$args ??= [];
|
||||
foreach ($rows as $row) {
|
||||
$item = unserialize($row['_item']);
|
||||
$updates = func::_call($context, [$item, $row, ...$args]);
|
||||
if (is_array($updates)) {
|
||||
if (array_key_exists("_item", $updates)) {
|
||||
$updates["_item"] = serialize($updates["_item"]);
|
||||
$commited = false;
|
||||
$sqlite->beginTransaction();
|
||||
try {
|
||||
$rows = $sqlite->all([
|
||||
"select",
|
||||
"from" => $tableName,
|
||||
"where" => $filter,
|
||||
]);
|
||||
$args ??= [];
|
||||
foreach ($rows as $row) {
|
||||
$item = unserialize($row['_item']);
|
||||
$updates = func::_call($context, [$item, $row, ...$args]);
|
||||
if (is_array($updates)) {
|
||||
if (array_key_exists("_item", $updates)) {
|
||||
$updates["_item"] = serialize($updates["_item"]);
|
||||
}
|
||||
$sqlite->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $updates,
|
||||
"where" => ["_id" => $row["_id"]],
|
||||
]);
|
||||
}
|
||||
$sqlite->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $updates,
|
||||
"where" => ["_id" => $row["_id"]],
|
||||
]);
|
||||
}
|
||||
$sqlite->commit();
|
||||
$commited = true;
|
||||
} finally {
|
||||
if (!$commited) $sqlite->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
use nulib\tests\TestCase;
|
||||
use nur\sery\db\Capacitor;
|
||||
use nur\sery\db\CapacitorChannel;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SqliteCapacitorTest extends TestCase {
|
||||
function _testChargeStrings(SqliteCapacitor $capacitor, ?string $channel) {
|
||||
$capacitor->reset($channel);
|
||||
$capacitor->charge("first", $channel);
|
||||
$capacitor->charge("second", $channel);
|
||||
$capacitor->charge("third", $channel);
|
||||
$items = iterator_to_array($capacitor->discharge(null, $channel, false));
|
||||
$capacitor->charge($channel, "first");
|
||||
$capacitor->charge($channel, "second");
|
||||
$capacitor->charge($channel, "third");
|
||||
$items = iterator_to_array($capacitor->discharge($channel, null, false));
|
||||
self::assertSame(["first", "second", "third"], $items);
|
||||
}
|
||||
|
||||
function _testChargeArrays(SqliteCapacitor $capacitor, ?string $channel) {
|
||||
$capacitor->reset($channel);
|
||||
$capacitor->charge(["id" => 10, "name" => "first"], $channel);
|
||||
$capacitor->charge(["name" => "second", "id" => 20], $channel);
|
||||
$capacitor->charge(["name" => "third", "id" => "30"], $channel);
|
||||
$capacitor->charge($channel, ["id" => 10, "name" => "first"]);
|
||||
$capacitor->charge($channel, ["name" => "second", "id" => 20]);
|
||||
$capacitor->charge($channel, ["name" => "third", "id" => "30"]);
|
||||
}
|
||||
|
||||
function testChargeStrings() {
|
||||
|
@ -45,8 +47,9 @@ class SqliteCapacitorTest extends TestCase {
|
|||
|
||||
function testEach() {
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$capacitor->addChannel(new class extends CapacitorChannel {
|
||||
$capacitor = new Capacitor($capacitor, new class extends CapacitorChannel {
|
||||
const NAME = "each";
|
||||
|
||||
function getKeyDefinitions(): ?array {
|
||||
return [
|
||||
"age" => "integer",
|
||||
|
@ -60,12 +63,11 @@ class SqliteCapacitorTest extends TestCase {
|
|||
}
|
||||
});
|
||||
|
||||
$channel = "each";
|
||||
$capacitor->reset($channel);
|
||||
$capacitor->charge(["name" => "first", "age" => 5], $channel);
|
||||
$capacitor->charge(["name" => "second", "age" => 10], $channel);
|
||||
$capacitor->charge(["name" => "third", "age" => 15], $channel);
|
||||
$capacitor->charge(["name" => "fourth", "age" => 20], $channel);
|
||||
$capacitor->reset();
|
||||
$capacitor->charge(["name" => "first", "age" => 5]);
|
||||
$capacitor->charge(["name" => "second", "age" => 10]);
|
||||
$capacitor->charge(["name" => "third", "age" => 15]);
|
||||
$capacitor->charge(["name" => "fourth", "age" => 20]);
|
||||
|
||||
$setDone = function ($item, $row, $suffix=null) {
|
||||
$updates = ["done" => 1];
|
||||
|
@ -75,9 +77,39 @@ class SqliteCapacitorTest extends TestCase {
|
|||
}
|
||||
return $updates;
|
||||
};
|
||||
$capacitor->each(["age" => [">", 10]], $setDone, ["++"], $channel);
|
||||
$capacitor->each(["done" => 0], $setDone, null, $channel);
|
||||
Txx(iterator_to_array($capacitor->discharge(null, $channel, false)));
|
||||
$capacitor->each(["age" => [">", 10]], $setDone, ["++"]);
|
||||
$capacitor->each(["done" => 0], $setDone, null);
|
||||
Txx(iterator_to_array($capacitor->discharge(null, false)));
|
||||
|
||||
$capacitor->close();
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
function testPrimayKey() {
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($capacitor, new class extends CapacitorChannel {
|
||||
const NAME = "pk";
|
||||
|
||||
function getKeyDefinitions(): ?array {
|
||||
return [
|
||||
"_id" => "varchar primary key",
|
||||
"done" => "integer default 0",
|
||||
];
|
||||
}
|
||||
function getKeyValues($item): ?array {
|
||||
return [
|
||||
"_id" => $item["numero"],
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->charge(["numero" => "a", "name" => "first", "age" => 5]);
|
||||
$capacitor->charge(["numero" => "b", "name" => "second", "age" => 10]);
|
||||
$capacitor->charge(["numero" => "c", "name" => "third", "age" => 15]);
|
||||
$capacitor->charge(["numero" => "d", "name" => "fourth", "age" => 20]);
|
||||
sleep(2);
|
||||
$capacitor->charge(["numero" => "b", "name" => "second", "age" => 100]);
|
||||
$capacitor->charge(["numero" => "d", "name" => "fourth", "age" => 200]);
|
||||
|
||||
$capacitor->close();
|
||||
self::assertTrue(true);
|
||||
|
|
Loading…
Reference in New Issue