From 0515de9642955f6a03bae5df7dde4c260a72c099 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 24 May 2024 17:40:31 +0400 Subject: [PATCH] modifs.mineures sans commentaires --- src/db/CapacitorChannel.php | 44 ++++-- src/db/CapacitorStorage.php | 3 + src/db/sqlite/SqliteCapacitor.php | 170 ++++++++++++++++-------- src/db/sqlite/_query.php | 5 +- tests/db/sqlite/SqliteCapacitorTest.php | 6 +- tests/db/sqlite/_queryTest.php | 5 + 6 files changed, 165 insertions(+), 68 deletions(-) diff --git a/src/db/CapacitorChannel.php b/src/db/CapacitorChannel.php index 3d656b8..af53b7e 100644 --- a/src/db/CapacitorChannel.php +++ b/src/db/CapacitorChannel.php @@ -56,9 +56,17 @@ class CapacitorChannel { * 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()} + * la clé primaire "id_" a pour définition "integer primary key autoincrement". + * elle peut être redéfinie, et dans ce cas la valeur à utiliser doit être + * 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 { return null; @@ -68,28 +76,38 @@ class CapacitorChannel { * 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. + * 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 { 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 * {@link Capacitor::charge()} * * @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 * une nouvelle ligne - * @return ?array le cas échéant, un tableau non null à marger dans $values et - * utiliser pour provisionner la ligne nouvelle créée, ou mettre à jour la - * ligne existante + * @return ?array le cas échéant, un tableau non null à merger dans $updates + * et utilisé pour provisionner la ligne nouvellement créée, ou mettre à jour + * la ligne existante * * 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; } @@ -98,12 +116,14 @@ class CapacitorChannel { * {@link Capacitor::each()} * * @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 à * jour la ligne courante * * 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 { return null; diff --git a/src/db/CapacitorStorage.php b/src/db/CapacitorStorage.php index 0e3bd39..1665592 100644 --- a/src/db/CapacitorStorage.php +++ b/src/db/CapacitorStorage.php @@ -1,6 +1,9 @@ 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 { if (!$channel->isCreated()) { - $columns = cl::merge([ - "_id" => "integer primary key autoincrement", - "_item" => "text", - "_sum" => "varchar(40)", - "_created" => "datetime", - "_modified" => "datetime", - ], $channel->getKeyDefinitions()); + $columns = cl::merge(self::KEY_DEFINITIONS, $channel->getKeyDefinitions()); $this->sqlite->exec([ "create table if not exists", "table" => $channel->getTableName(), @@ -83,34 +134,34 @@ class SqliteCapacitor extends CapacitorStorage { function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int { $this->_create($channel); $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) { + $item__ = serialize($item); + $sum_ = sha1($item__); + $row = cl::merge([ + "item__" => $item__, + "sum_" => $sum_, + ], $this->unserialize($channel, $channel->getKeyValues($item))); + $prow = null; + $id_ = $row["id_"] ?? null; + if ($id_ !== null) { # modification - $row = $this->sqlite->one([ - "select _item, _sum, _created, _modified", + $prow = $this->sqlite->one([ + "select id_, item__, sum_, created_, modified_", "from" => $channel->getTableName(), - "where" => ["_id" => $id], + "where" => ["id_" => $id_], ]); } $insert = null; - if ($row === null) { + if ($prow === null) { # création - $values = cl::merge($values, [ - "_created" => $now, - "_modified" => $now, + $row = cl::merge($row, [ + "created_" => $now, + "modified_" => $now, ]); $insert = true; - } elseif ($_sum !== $row["_sum"]) { + } elseif ($sum_ !== $prow["sum_"]) { # modification - $values = cl::merge($values, [ - "_modified" => $now, + $row = cl::merge($row, [ + "modified_" => $now, ]); $insert = false; } @@ -118,17 +169,19 @@ class SqliteCapacitor extends CapacitorStorage { if ($func === null) $func = [$channel, "onCharge"]; $onCharge = func::_prepare($func); $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 (array_key_exists("_item", $updates)) { - $_item = serialize($updates["_item"]); - $updates["_item"] = $_item; - $updates["_sum"] = sha1($_item); - if (!array_key_exists("_modified", $updates)) { - $updates["_modified"] = $now; + $updates = $this->serialize($channel, $updates); + 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_"] = $now; } } - $values = cl::merge($values, $updates); + $row = cl::merge($row, $updates); } if ($insert === null) { @@ -138,21 +191,30 @@ class SqliteCapacitor extends CapacitorStorage { $this->sqlite->exec([ "insert", "into" => $channel->getTableName(), - "values" => $values, + "values" => $row, ]); } else { $this->sqlite->exec([ "update", "table" => $channel->getTableName(), - "values" => $values, - "where" => ["_id" => $id], + "values" => $row, + "where" => ["id_" => $id_], ]); } 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 { - if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter]; + $this->verifixFilter($channel, $filter); return $this->sqlite->get([ "select count(*)", "from" => $channel->getTableName(), @@ -161,36 +223,35 @@ class SqliteCapacitor extends CapacitorStorage { } 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; $rows = $this->sqlite->all([ - "select _item", + "select item__", "from" => $channel->getTableName(), "where" => $filter, ]); foreach ($rows as $row) { - $item = unserialize($row['_item']); - yield $item; + yield unserialize($row['item__']); } if ($reset) $this->_reset($channel); } function _get(CapacitorChannel $channel, $filter) { - if ($filter === null) throw ValueException::null("keys"); - if (!is_array($filter)) $filter = ["_id" => $filter]; + if ($filter === null) throw ValueException::null("filter"); + $this->verifixFilter($channel, $filter); $row = $this->sqlite->one([ - "select _item", + "select item__", "from" => $channel->getTableName(), "where" => $filter, ]); 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 { + $this->verifixFilter($channel, $filter); if ($func === null) $func = [$channel, "onEach"]; $onEach = func::_prepare($func); - if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter]; $sqlite = $this->sqlite; $tableName = $channel->getTableName(); $commited = false; @@ -205,17 +266,22 @@ class SqliteCapacitor extends CapacitorStorage { ]); $args ??= []; foreach ($rows as $row) { - $item = unserialize($row['_item']); - $updates = func::_call($onEach, [$item, $row, ...$args]); + $values = $this->unserialize($channel, $row); + $updates = func::_call($onEach, [$values["item"], $values, ...$args]); if (is_array($updates)) { - if (array_key_exists("_item", $updates)) { - $updates["_item"] = serialize($updates["_item"]); + $updates = $this->serialize($channel, $updates); + 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([ "update", "table" => $tableName, "values" => $updates, - "where" => ["_id" => $row["_id"]], + "where" => ["id_" => $row["id_"]], ]); if ($commitThreshold !== null) { $commitThreshold--; diff --git a/src/db/sqlite/_query.php b/src/db/sqlite/_query.php index a89eca0..2bbe609 100644 --- a/src/db/sqlite/_query.php +++ b/src/db/sqlite/_query.php @@ -109,9 +109,12 @@ class _query { $value = $cond[$condkeys[$condkey]]; $condkey++; } - } else { + } elseif ($cond !== null) { $op = "="; $value = $cond; + } else { + $op = "is null"; + $value = null; } $cond = [$key, $op]; if ($value !== null) { diff --git a/tests/db/sqlite/SqliteCapacitorTest.php b/tests/db/sqlite/SqliteCapacitorTest.php index 704e5c6..f140d28 100644 --- a/tests/db/sqlite/SqliteCapacitorTest.php +++ b/tests/db/sqlite/SqliteCapacitorTest.php @@ -73,7 +73,7 @@ class SqliteCapacitorTest extends TestCase { $updates = ["done" => 1]; if ($suffix !== null) { $item["name"] .= $suffix; - $updates["_item"] = $item; + $updates["item"] = $item; } return $updates; }; @@ -92,13 +92,13 @@ class SqliteCapacitorTest extends TestCase { function getKeyDefinitions(): ?array { return [ - "_id" => "varchar primary key", + "id_" => "varchar primary key", "done" => "integer default 0", ]; } function getKeyValues($item): ?array { return [ - "_id" => $item["numero"], + "id_" => $item["numero"], ]; } }); diff --git a/tests/db/sqlite/_queryTest.php b/tests/db/sqlite/_queryTest.php index ebf8801..aac8f75 100644 --- a/tests/db/sqlite/_queryTest.php +++ b/tests/db/sqlite/_queryTest.php @@ -15,6 +15,11 @@ class _queryTest extends TestCase { self::assertNull($sql); 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; _query::parse_conds(["col = 'value'"], $sql, $params); self::assertSame(["col = 'value'"], $sql);