modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2024-05-24 17:40:31 +04:00
parent ec7ebfefc1
commit 0515de9642
6 changed files with 165 additions and 68 deletions

View File

@ -56,9 +56,17 @@ class CapacitorChannel {
* retourner un ensemble de définitions pour des colonnes supplémentaires à * retourner un ensemble de définitions pour des colonnes supplémentaires à
* insérer lors du chargement d'une valeur * insérer lors du chargement d'une valeur
* *
* la colonne "_id" de définition "integer primary key autoincrement" est la * la clé primaire "id_" a pour définition "integer primary key autoincrement".
* clé primaire par défaut. elle peut être redéfinie, et dans ce cas la valeur * elle peut être redéfinie, et dans ce cas la valeur à utiliser doit être
* à utiliser doit être retournée par {@link getKeyValues()} * retournée par {@link getKeyValues()}
*
* la colonne "item__" contient la valeur sérialisée de l'élément chargé. bien
* que ce soit possible techniquement, cette colonne n'a pas à être redéfinie
*
* les colonnes dont le nom se termine par "_" sont réservées.
* les colonnes dont le nom se termine par "__" sont automatiquement sérialisées
* lors de l'insertion dans la base de données, et automatiquement désérialisées
* avant d'être retournées à l'utilisateur (sans le suffixe "__")
*/ */
function getKeyDefinitions(): ?array { function getKeyDefinitions(): ?array {
return null; return null;
@ -68,28 +76,38 @@ class CapacitorChannel {
* calculer les valeurs des colonnes supplémentaires à insérer pour le * calculer les valeurs des colonnes supplémentaires à insérer pour le
* chargement de $item * chargement de $item
* *
* Si "_id" est retourné, la ligne existante est mise à jour le cas échéant. * Cette méthode est utilisée par {@link Capacitor::charge()}. Si une valeur
* "id_" est retourné, la ligne correspondate existante est mise à jour
*/ */
function getKeyValues($item): ?array { function getKeyValues($item): ?array {
return null; return null;
} }
/**
* Avant d'utiliser un id pour rechercher dans la base de donnée, corriger sa
* valeur le cas échéant.
*/
function verifixId(string &$id): void {
}
/** /**
* méthode appelée lors du chargement d'un élément avec * méthode appelée lors du chargement d'un élément avec
* {@link Capacitor::charge()} * {@link Capacitor::charge()}
* *
* @param mixed $item l'élément à charger * @param mixed $item l'élément à charger
* @param array $values les valeurs calculées par {@link getKeyValues()} * @param array $updates les valeurs calculées par {@link getKeyValues()}
* @param ?array $row la ligne à mettre à jour. vaut null s'il faut insérer * @param ?array $row la ligne à mettre à jour. vaut null s'il faut insérer
* une nouvelle ligne * une nouvelle ligne
* @return ?array le cas échéant, un tableau non null à marger dans $values et * @return ?array le cas échéant, un tableau non null à merger dans $updates
* utiliser pour provisionner la ligne nouvelle créée, ou mettre à jour la * et utilisé pour provisionner la ligne nouvellement créée, ou mettre à jour
* ligne existante * la ligne existante
* *
* Si $item est modifié dans cette méthode, il est possible de le retourner * Si $item est modifié dans cette méthode, il est possible de le retourner
* avec la clé "_item" pour mettre à jour la ligne correspondante * avec la clé "item" pour mettre à jour la ligne correspondante.
* La colonne "id_" ne peut pas être modifiée: si "id_" est retourné, il est
* ignoré
*/ */
function onCharge($item, array $values, ?array $row): ?array { function onCharge($item, array $updates, ?array $row): ?array {
return null; return null;
} }
@ -98,12 +116,14 @@ class CapacitorChannel {
* {@link Capacitor::each()} * {@link Capacitor::each()}
* *
* @param mixed $item l'élément courant * @param mixed $item l'élément courant
* @param ?array $row la ligne à mettre à jour * @param ?array $row la ligne à mettre à jour.
* @return ?array le cas échéant, un tableau non null utilisé pour mettre à * @return ?array le cas échéant, un tableau non null utilisé pour mettre à
* jour la ligne courante * jour la ligne courante
* *
* Si $item est modifié dans cette méthode, il est possible de le retourner * Si $item est modifié dans cette méthode, il est possible de le retourner
* avec la clé "_item" pour mettre à jour la ligne correspondante * avec la clé "item" pour mettre à jour la ligne correspondante
* La colonne "id_" ne peut pas être modifiée: si "id_" est retourné, il est
* ignoré
*/ */
function onEach($item, array $row): ?array { function onEach($item, array $row): ?array {
return null; return null;

View File

@ -1,6 +1,9 @@
<?php <?php
namespace nur\sery\db; namespace nur\sery\db;
use nur\sery\cl;
use nur\sery\php\func;
/** /**
* Class CapacitorStorage: objet permettant d'accumuler des données pour les * Class CapacitorStorage: objet permettant d'accumuler des données pour les
* réutiliser plus tard * réutiliser plus tard

View File

@ -5,6 +5,7 @@ use nur\sery\cl;
use nur\sery\db\CapacitorChannel; use nur\sery\db\CapacitorChannel;
use nur\sery\db\CapacitorStorage; use nur\sery\db\CapacitorStorage;
use nur\sery\php\func; use nur\sery\php\func;
use nur\sery\str;
use nur\sery\ValueException; use nur\sery\ValueException;
/** /**
@ -22,15 +23,65 @@ class SqliteCapacitor extends CapacitorStorage {
return $this->sqlite; return $this->sqlite;
} }
const KEY_DEFINITIONS = [
"id_" => "integer primary key autoincrement",
"item__" => "text",
"sum_" => "varchar(40)",
"created_" => "datetime",
"modified_" => "datetime",
];
/** sérialiser les valeurs qui doivent l'être dans $values */
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
if ($values === null) return null;
$columns = cl::merge(self::KEY_DEFINITIONS, $channel->getKeyDefinitions());
$index = 0;
$row = [];
foreach (array_keys($columns) as $column) {
$key = $column;
if ($key === $index) {
$index++;
continue;
} elseif (str::del_suffix($key, "__")) {
if (!array_key_exists($key, $values)) continue;
$value = $values[$key];
if ($value !== null) $value = serialize($value);
} else {
if (!array_key_exists($key, $values)) continue;
$value = $values[$key];
}
$row[$column] = $value;
}
return $row;
}
/** désérialiser les valeurs qui doivent l'être dans $values */
protected function unserialize(CapacitorChannel $channel, ?array $row): ?array {
if ($row === null) return null;
$columns = cl::merge(self::KEY_DEFINITIONS, $channel->getKeyDefinitions());
$index = 0;
$values = [];
foreach (array_keys($columns) as $column) {
$key = $column;
if ($key === $index) {
$index++;
continue;
} elseif (!array_key_exists($column, $row)) {
continue;
} elseif (str::del_suffix($key, "__")) {
$value = $row[$column];
if ($value !== null) $value = unserialize($value);
} else {
$value = $row[$column];
}
$values[$key] = $value;
}
return $values;
}
protected function _create(CapacitorChannel $channel): void { protected function _create(CapacitorChannel $channel): void {
if (!$channel->isCreated()) { if (!$channel->isCreated()) {
$columns = cl::merge([ $columns = cl::merge(self::KEY_DEFINITIONS, $channel->getKeyDefinitions());
"_id" => "integer primary key autoincrement",
"_item" => "text",
"_sum" => "varchar(40)",
"_created" => "datetime",
"_modified" => "datetime",
], $channel->getKeyDefinitions());
$this->sqlite->exec([ $this->sqlite->exec([
"create table if not exists", "create table if not exists",
"table" => $channel->getTableName(), "table" => $channel->getTableName(),
@ -83,34 +134,34 @@ class SqliteCapacitor extends CapacitorStorage {
function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int { function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int {
$this->_create($channel); $this->_create($channel);
$now = date("Y-m-d H:i:s"); $now = date("Y-m-d H:i:s");
$_item = serialize($item); $item__ = serialize($item);
$_sum = sha1($_item); $sum_ = sha1($item__);
$values = cl::merge([ $row = cl::merge([
"_item" => $_item, "item__" => $item__,
"_sum" => $_sum, "sum_" => $sum_,
], $channel->getKeyValues($item)); ], $this->unserialize($channel, $channel->getKeyValues($item)));
$row = null; $prow = null;
$id = $values["_id"] ?? null; $id_ = $row["id_"] ?? null;
if ($id !== null) { if ($id_ !== null) {
# modification # modification
$row = $this->sqlite->one([ $prow = $this->sqlite->one([
"select _item, _sum, _created, _modified", "select id_, item__, sum_, created_, modified_",
"from" => $channel->getTableName(), "from" => $channel->getTableName(),
"where" => ["_id" => $id], "where" => ["id_" => $id_],
]); ]);
} }
$insert = null; $insert = null;
if ($row === null) { if ($prow === null) {
# création # création
$values = cl::merge($values, [ $row = cl::merge($row, [
"_created" => $now, "created_" => $now,
"_modified" => $now, "modified_" => $now,
]); ]);
$insert = true; $insert = true;
} elseif ($_sum !== $row["_sum"]) { } elseif ($sum_ !== $prow["sum_"]) {
# modification # modification
$values = cl::merge($values, [ $row = cl::merge($row, [
"_modified" => $now, "modified_" => $now,
]); ]);
$insert = false; $insert = false;
} }
@ -118,17 +169,19 @@ class SqliteCapacitor extends CapacitorStorage {
if ($func === null) $func = [$channel, "onCharge"]; if ($func === null) $func = [$channel, "onCharge"];
$onCharge = func::_prepare($func); $onCharge = func::_prepare($func);
$args ??= []; $args ??= [];
$updates = func::_call($onCharge, [$item, $values, $row, ...$args]); $values = $this->unserialize($channel, $row);
$pvalues = $this->unserialize($channel, $prow);
$updates = func::_call($onCharge, [$item, $values, $pvalues, ...$args]);
if (is_array($updates)) { if (is_array($updates)) {
if (array_key_exists("_item", $updates)) { $updates = $this->serialize($channel, $updates);
$_item = serialize($updates["_item"]); if (array_key_exists("item__", $updates)) {
$updates["_item"] = $_item; # si item a été mis à jour, il faut mettre à jour sum_
$updates["_sum"] = sha1($_item); $updates["sum_"] = sha1($updates["item__"]);
if (!array_key_exists("_modified", $updates)) { if (!array_key_exists("modified_", $updates)) {
$updates["_modified"] = $now; $updates["modified_"] = $now;
} }
} }
$values = cl::merge($values, $updates); $row = cl::merge($row, $updates);
} }
if ($insert === null) { if ($insert === null) {
@ -138,21 +191,30 @@ class SqliteCapacitor extends CapacitorStorage {
$this->sqlite->exec([ $this->sqlite->exec([
"insert", "insert",
"into" => $channel->getTableName(), "into" => $channel->getTableName(),
"values" => $values, "values" => $row,
]); ]);
} else { } else {
$this->sqlite->exec([ $this->sqlite->exec([
"update", "update",
"table" => $channel->getTableName(), "table" => $channel->getTableName(),
"values" => $values, "values" => $row,
"where" => ["_id" => $id], "where" => ["id_" => $id_],
]); ]);
} }
return 1; return 1;
} }
protected function verifixFilter(CapacitorChannel $channel, &$filter): void {
if ($filter !== null && !is_array($filter)) {
$id = $filter;
$channel->verifixId($id);
$filter = ["id_" => $id];
}
$filter = $this->serialize($channel, $filter);
}
function _count(CapacitorChannel $channel, $filter): int { function _count(CapacitorChannel $channel, $filter): int {
if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter]; $this->verifixFilter($channel, $filter);
return $this->sqlite->get([ return $this->sqlite->get([
"select count(*)", "select count(*)",
"from" => $channel->getTableName(), "from" => $channel->getTableName(),
@ -161,36 +223,35 @@ class SqliteCapacitor extends CapacitorStorage {
} }
function _discharge(CapacitorChannel $channel, $filter, ?bool $reset): iterable { function _discharge(CapacitorChannel $channel, $filter, ?bool $reset): iterable {
if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter]; $this->verifixFilter($channel, $filter);
if ($reset === null) $reset = $filter === null; if ($reset === null) $reset = $filter === null;
$rows = $this->sqlite->all([ $rows = $this->sqlite->all([
"select _item", "select item__",
"from" => $channel->getTableName(), "from" => $channel->getTableName(),
"where" => $filter, "where" => $filter,
]); ]);
foreach ($rows as $row) { foreach ($rows as $row) {
$item = unserialize($row['_item']); yield unserialize($row['item__']);
yield $item;
} }
if ($reset) $this->_reset($channel); if ($reset) $this->_reset($channel);
} }
function _get(CapacitorChannel $channel, $filter) { function _get(CapacitorChannel $channel, $filter) {
if ($filter === null) throw ValueException::null("keys"); if ($filter === null) throw ValueException::null("filter");
if (!is_array($filter)) $filter = ["_id" => $filter]; $this->verifixFilter($channel, $filter);
$row = $this->sqlite->one([ $row = $this->sqlite->one([
"select _item", "select item__",
"from" => $channel->getTableName(), "from" => $channel->getTableName(),
"where" => $filter, "where" => $filter,
]); ]);
if ($row === null) return null; if ($row === null) return null;
else return unserialize($row["_item"]); else return unserialize($row["item__"]);
} }
function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int { function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int {
$this->verifixFilter($channel, $filter);
if ($func === null) $func = [$channel, "onEach"]; if ($func === null) $func = [$channel, "onEach"];
$onEach = func::_prepare($func); $onEach = func::_prepare($func);
if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter];
$sqlite = $this->sqlite; $sqlite = $this->sqlite;
$tableName = $channel->getTableName(); $tableName = $channel->getTableName();
$commited = false; $commited = false;
@ -205,17 +266,22 @@ class SqliteCapacitor extends CapacitorStorage {
]); ]);
$args ??= []; $args ??= [];
foreach ($rows as $row) { foreach ($rows as $row) {
$item = unserialize($row['_item']); $values = $this->unserialize($channel, $row);
$updates = func::_call($onEach, [$item, $row, ...$args]); $updates = func::_call($onEach, [$values["item"], $values, ...$args]);
if (is_array($updates)) { if (is_array($updates)) {
if (array_key_exists("_item", $updates)) { $updates = $this->serialize($channel, $updates);
$updates["_item"] = serialize($updates["_item"]); if (array_key_exists("item__", $updates)) {
# si item a été mis à jour, il faut mettre à jour sum_
$updates["sum_"] = sha1($updates["item__"]);
if (!array_key_exists("modified_", $updates)) {
$updates["modified_"] = date("Y-m-d H:i:s");
}
} }
$sqlite->exec([ $sqlite->exec([
"update", "update",
"table" => $tableName, "table" => $tableName,
"values" => $updates, "values" => $updates,
"where" => ["_id" => $row["_id"]], "where" => ["id_" => $row["id_"]],
]); ]);
if ($commitThreshold !== null) { if ($commitThreshold !== null) {
$commitThreshold--; $commitThreshold--;

View File

@ -109,9 +109,12 @@ class _query {
$value = $cond[$condkeys[$condkey]]; $value = $cond[$condkeys[$condkey]];
$condkey++; $condkey++;
} }
} else { } elseif ($cond !== null) {
$op = "="; $op = "=";
$value = $cond; $value = $cond;
} else {
$op = "is null";
$value = null;
} }
$cond = [$key, $op]; $cond = [$key, $op];
if ($value !== null) { if ($value !== null) {

View File

@ -73,7 +73,7 @@ class SqliteCapacitorTest extends TestCase {
$updates = ["done" => 1]; $updates = ["done" => 1];
if ($suffix !== null) { if ($suffix !== null) {
$item["name"] .= $suffix; $item["name"] .= $suffix;
$updates["_item"] = $item; $updates["item"] = $item;
} }
return $updates; return $updates;
}; };
@ -92,13 +92,13 @@ class SqliteCapacitorTest extends TestCase {
function getKeyDefinitions(): ?array { function getKeyDefinitions(): ?array {
return [ return [
"_id" => "varchar primary key", "id_" => "varchar primary key",
"done" => "integer default 0", "done" => "integer default 0",
]; ];
} }
function getKeyValues($item): ?array { function getKeyValues($item): ?array {
return [ return [
"_id" => $item["numero"], "id_" => $item["numero"],
]; ];
} }
}); });

View File

@ -15,6 +15,11 @@ class _queryTest extends TestCase {
self::assertNull($sql); self::assertNull($sql);
self::assertNull($params); self::assertNull($params);
$sql = $params = null;
_query::parse_conds(["col" => null], $sql, $params);
self::assertSame(["col is null"], $sql);
self::assertNull($params);
$sql = $params = null; $sql = $params = null;
_query::parse_conds(["col = 'value'"], $sql, $params); _query::parse_conds(["col = 'value'"], $sql, $params);
self::assertSame(["col = 'value'"], $sql); self::assertSame(["col = 'value'"], $sql);