maj nur-sery
This commit is contained in:
parent
416d0a44d4
commit
a590cd1c17
|
@ -34,16 +34,20 @@ class Capacitor {
|
||||||
return $this->storage->_charge($this->channel, $item, $func, $args);
|
return $this->storage->_charge($this->channel, $item, $func, $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function discharge($filter=null, ?bool $reset=null): iterable {
|
||||||
|
return $this->storage->_discharge($this->channel, $reset);
|
||||||
|
}
|
||||||
|
|
||||||
function count($filter=null): int {
|
function count($filter=null): int {
|
||||||
return $this->storage->_count($this->channel, $filter);
|
return $this->storage->_count($this->channel, $filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function discharge($filter=null, ?bool $reset=null): iterable {
|
function one($filter): ?array {
|
||||||
return $this->storage->_discharge($this->channel, $filter, $reset);
|
return $this->storage->_one($this->channel, $filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get($filter) {
|
function all($filter): iterable {
|
||||||
return $this->storage->_get($this->channel, $filter);
|
return $this->storage->_all($this->channel, $filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function each($filter, ?callable $func=null, ?array $args=null): int {
|
function each($filter, ?callable $func=null, ?array $args=null): int {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\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
|
||||||
|
@ -47,6 +50,13 @@ abstract class CapacitorStorage {
|
||||||
return $this->_charge($this->getChannel($channel), $item, $func, $args);
|
return $this->_charge($this->getChannel($channel), $item, $func, $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract function _discharge(CapacitorChannel $channel, bool $reset=true): iterable;
|
||||||
|
|
||||||
|
/** décharger les données du canal spécifié */
|
||||||
|
function discharge(?string $channel, bool $reset=true): iterable {
|
||||||
|
return $this->_discharge($this->getChannel($channel), $reset);
|
||||||
|
}
|
||||||
|
|
||||||
abstract function _count(CapacitorChannel $channel, $filter): int;
|
abstract function _count(CapacitorChannel $channel, $filter): int;
|
||||||
|
|
||||||
/** indiquer le nombre d'éléments du canal spécifié */
|
/** indiquer le nombre d'éléments du canal spécifié */
|
||||||
|
@ -54,22 +64,26 @@ abstract class CapacitorStorage {
|
||||||
return $this->_count($this->getChannel($channel), $filter);
|
return $this->_count($this->getChannel($channel), $filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract function _discharge(CapacitorChannel $channel, $filter, ?bool $reset): iterable;
|
abstract function _one(CapacitorChannel $channel, $filter): ?array;
|
||||||
|
|
||||||
/** décharger les données du canal spécifié */
|
|
||||||
function discharge(?string $channel, $filter=null, ?bool $reset=null): iterable {
|
|
||||||
return $this->_discharge($this->getChannel($channel), $filter, $reset);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract function _get(CapacitorChannel $channel, $filter);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* obtenir l'élément identifié par les clés spécifiées sur le canal spécifié
|
* obtenir la ligne correspondant au filtre sur le canal spécifié
|
||||||
*
|
*
|
||||||
* si $filter n'est pas un tableau, il est transformé en ["_id" => $filter]
|
* si $filter n'est pas un tableau, il est transformé en ["id_" => $filter]
|
||||||
*/
|
*/
|
||||||
function get(?string $channel, $filter) {
|
function one(?string $channel, $filter): ?array {
|
||||||
return $this->_get($this->getChannel($channel), $filter);
|
return $this->_one($this->getChannel($channel), $filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract function _all(CapacitorChannel $channel, $filter): iterable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* obtenir les lignes correspondant au filtre sur le canal spécifié
|
||||||
|
*
|
||||||
|
* si $filter n'est pas un tableau, il est transformé en ["id_" => $filter]
|
||||||
|
*/
|
||||||
|
function all(?string $channel, $filter): iterable {
|
||||||
|
return $this->_one($this->getChannel($channel), $filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int;
|
abstract function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int;
|
||||||
|
|
|
@ -1,242 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace nulib\db\sqlite;
|
|
||||||
|
|
||||||
use nulib\cl;
|
|
||||||
use nulib\db\CapacitorChannel;
|
|
||||||
use nulib\db\CapacitorStorage;
|
|
||||||
use nulib\php\func;
|
|
||||||
use nulib\ValueException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class SqliteCapacitor
|
|
||||||
*/
|
|
||||||
class SqliteCapacitor extends CapacitorStorage {
|
|
||||||
function __construct($sqlite) {
|
|
||||||
$this->sqlite = Sqlite::with($sqlite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var Sqlite */
|
|
||||||
protected $sqlite;
|
|
||||||
|
|
||||||
function sqlite(): Sqlite {
|
|
||||||
return $this->sqlite;
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
$this->sqlite->exec([
|
|
||||||
"create table if not exists",
|
|
||||||
"table" => $channel->getTableName(),
|
|
||||||
"cols" => $columns,
|
|
||||||
]);
|
|
||||||
$channel->setCreated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var CapacitorChannel[] */
|
|
||||||
protected $channels;
|
|
||||||
|
|
||||||
function addChannel(CapacitorChannel $channel): CapacitorChannel {
|
|
||||||
$this->_create($channel);
|
|
||||||
$this->channels[$channel->getName()] = $channel;
|
|
||||||
return $channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getChannel(?string $name): CapacitorChannel {
|
|
||||||
$name = CapacitorChannel::verifix_name($name);
|
|
||||||
$channel = $this->channels[$name] ?? null;
|
|
||||||
if ($channel === null) {
|
|
||||||
$channel = $this->addChannel(new CapacitorChannel($name));
|
|
||||||
}
|
|
||||||
return $channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _exists(CapacitorChannel $channel): bool {
|
|
||||||
$tableName = $this->sqlite->get([
|
|
||||||
"select name from sqlite_schema",
|
|
||||||
"where" => [
|
|
||||||
"name" => $channel->getTableName(),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
return $tableName !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _ensureExists(CapacitorChannel $channel): void {
|
|
||||||
$this->_create($channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _reset(CapacitorChannel $channel): void {
|
|
||||||
$this->sqlite->exec([
|
|
||||||
"drop table if exists",
|
|
||||||
$channel->getTableName(),
|
|
||||||
]);
|
|
||||||
$channel->setCreated(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
# 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) $func = [$channel, "onCharge"];
|
|
||||||
$onCharge = func::_prepare($func);
|
|
||||||
$args ??= [];
|
|
||||||
$updates = func::_call($onCharge, [$item, $values, $row, ...$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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$values = cl::merge($values, $updates);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($insert === null) {
|
|
||||||
# aucune modification
|
|
||||||
return 0;
|
|
||||||
} 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 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _count(CapacitorChannel $channel, $filter): int {
|
|
||||||
if ($filter !== null && !is_array($filter)) $filter = ["_id" => $filter];
|
|
||||||
return $this->sqlite->get([
|
|
||||||
"select count(*)",
|
|
||||||
"from" => $channel->getTableName(),
|
|
||||||
"where" => $filter,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
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" => $filter,
|
|
||||||
]);
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$item = unserialize($row['_item']);
|
|
||||||
yield $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];
|
|
||||||
$row = $this->sqlite->one([
|
|
||||||
"select _item",
|
|
||||||
"from" => $channel->getTableName(),
|
|
||||||
"where" => $filter,
|
|
||||||
]);
|
|
||||||
if ($row === null) return null;
|
|
||||||
else return unserialize($row["_item"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int {
|
|
||||||
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;
|
|
||||||
$count = 0;
|
|
||||||
$sqlite->beginTransaction();
|
|
||||||
$commitThreshold = $channel->getEachCommitThreshold();
|
|
||||||
try {
|
|
||||||
$rows = $sqlite->all([
|
|
||||||
"select",
|
|
||||||
"from" => $tableName,
|
|
||||||
"where" => $filter,
|
|
||||||
]);
|
|
||||||
$args ??= [];
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$item = unserialize($row['_item']);
|
|
||||||
$updates = func::_call($onEach, [$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"]],
|
|
||||||
]);
|
|
||||||
if ($commitThreshold !== null) {
|
|
||||||
$commitThreshold--;
|
|
||||||
if ($commitThreshold == 0) {
|
|
||||||
$sqlite->commit();
|
|
||||||
$sqlite->beginTransaction();
|
|
||||||
$commitThreshold = $channel->getEachCommitThreshold();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$count++;
|
|
||||||
}
|
|
||||||
$sqlite->commit();
|
|
||||||
$commited = true;
|
|
||||||
return $count;
|
|
||||||
} finally {
|
|
||||||
if (!$commited) $sqlite->rollback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function close(): void {
|
|
||||||
$this->sqlite->close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,309 @@
|
||||||
|
<?php
|
||||||
|
namespace nulib\db\sqlite;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\db\CapacitorChannel;
|
||||||
|
use nulib\db\CapacitorStorage;
|
||||||
|
use nulib\php\func;
|
||||||
|
use nulib\str;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SqliteStorage
|
||||||
|
*/
|
||||||
|
class SqliteStorage extends CapacitorStorage {
|
||||||
|
function __construct($sqlite) {
|
||||||
|
$this->sqlite = Sqlite::with($sqlite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Sqlite */
|
||||||
|
protected $sqlite;
|
||||||
|
|
||||||
|
function sqlite(): 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 {
|
||||||
|
if (!$channel->isCreated()) {
|
||||||
|
$columns = cl::merge(self::KEY_DEFINITIONS, $channel->getKeyDefinitions());
|
||||||
|
$this->sqlite->exec([
|
||||||
|
"create table if not exists",
|
||||||
|
"table" => $channel->getTableName(),
|
||||||
|
"cols" => $columns,
|
||||||
|
]);
|
||||||
|
$channel->setCreated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var CapacitorChannel[] */
|
||||||
|
protected $channels;
|
||||||
|
|
||||||
|
function addChannel(CapacitorChannel $channel): CapacitorChannel {
|
||||||
|
$this->_create($channel);
|
||||||
|
$this->channels[$channel->getName()] = $channel;
|
||||||
|
return $channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getChannel(?string $name): CapacitorChannel {
|
||||||
|
$name = CapacitorChannel::verifix_name($name);
|
||||||
|
$channel = $this->channels[$name] ?? null;
|
||||||
|
if ($channel === null) {
|
||||||
|
$channel = $this->addChannel(new CapacitorChannel($name));
|
||||||
|
}
|
||||||
|
return $channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _exists(CapacitorChannel $channel): bool {
|
||||||
|
$tableName = $this->sqlite->get([
|
||||||
|
"select name from sqlite_schema",
|
||||||
|
"where" => [
|
||||||
|
"name" => $channel->getTableName(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
return $tableName !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _ensureExists(CapacitorChannel $channel): void {
|
||||||
|
$this->_create($channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _reset(CapacitorChannel $channel): void {
|
||||||
|
$this->sqlite->exec([
|
||||||
|
"drop table if exists",
|
||||||
|
$channel->getTableName(),
|
||||||
|
]);
|
||||||
|
$channel->setCreated(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
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__);
|
||||||
|
$row = cl::merge([
|
||||||
|
"item__" => $item__,
|
||||||
|
"sum_" => $sum_,
|
||||||
|
], $this->unserialize($channel, $channel->getKeyValues($item)));
|
||||||
|
$prow = null;
|
||||||
|
$id_ = $row["id_"] ?? null;
|
||||||
|
if ($id_ !== null) {
|
||||||
|
# modification
|
||||||
|
$prow = $this->sqlite->one([
|
||||||
|
"select id_, item__, sum_, created_, modified_",
|
||||||
|
"from" => $channel->getTableName(),
|
||||||
|
"where" => ["id_" => $id_],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$insert = null;
|
||||||
|
if ($prow === null) {
|
||||||
|
# création
|
||||||
|
$row = cl::merge($row, [
|
||||||
|
"created_" => $now,
|
||||||
|
"modified_" => $now,
|
||||||
|
]);
|
||||||
|
$insert = true;
|
||||||
|
} elseif ($sum_ !== $prow["sum_"]) {
|
||||||
|
# modification
|
||||||
|
$row = cl::merge($row, [
|
||||||
|
"modified_" => $now,
|
||||||
|
]);
|
||||||
|
$insert = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($func === null) $func = [$channel, "onCharge"];
|
||||||
|
$onCharge = func::_prepare($func);
|
||||||
|
$args ??= [];
|
||||||
|
$values = $this->unserialize($channel, $row);
|
||||||
|
$pvalues = $this->unserialize($channel, $prow);
|
||||||
|
$updates = func::_call($onCharge, [$item, $values, $pvalues, ...$args]);
|
||||||
|
if (is_array($updates)) {
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$row = cl::merge($row, $updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($insert === null) {
|
||||||
|
# aucune modification
|
||||||
|
return 0;
|
||||||
|
} elseif ($insert) {
|
||||||
|
$this->sqlite->exec([
|
||||||
|
"insert",
|
||||||
|
"into" => $channel->getTableName(),
|
||||||
|
"values" => $row,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$this->sqlite->exec([
|
||||||
|
"update",
|
||||||
|
"table" => $channel->getTableName(),
|
||||||
|
"values" => $row,
|
||||||
|
"where" => ["id_" => $id_],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _discharge(CapacitorChannel $channel, bool $reset=true): iterable {
|
||||||
|
$rows = $this->sqlite->all([
|
||||||
|
"select item__",
|
||||||
|
"from" => $channel->getTableName(),
|
||||||
|
]);
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
yield unserialize($row['item__']);
|
||||||
|
}
|
||||||
|
if ($reset) $this->_reset($channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
$this->verifixFilter($channel, $filter);
|
||||||
|
return $this->sqlite->get([
|
||||||
|
"select count(*)",
|
||||||
|
"from" => $channel->getTableName(),
|
||||||
|
"where" => $filter,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _one(CapacitorChannel $channel, $filter): ?array {
|
||||||
|
if ($filter === null) throw ValueException::null("filter");
|
||||||
|
$this->verifixFilter($channel, $filter);
|
||||||
|
$row = $this->sqlite->one([
|
||||||
|
"select",
|
||||||
|
"from" => $channel->getTableName(),
|
||||||
|
"where" => $filter,
|
||||||
|
]);
|
||||||
|
return $this->unserialize($channel, $row);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _all(CapacitorChannel $channel, $filter): iterable {
|
||||||
|
$this->verifixFilter($channel, $filter);
|
||||||
|
$rows = $this->sqlite->all([
|
||||||
|
"select",
|
||||||
|
"from" => $channel->getTableName(),
|
||||||
|
"where" => $filter,
|
||||||
|
]);
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
yield $this->unserialize($channel, $row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int {
|
||||||
|
if ($func === null) $func = [$channel, "onEach"];
|
||||||
|
$onEach = func::_prepare($func);
|
||||||
|
$sqlite = $this->sqlite;
|
||||||
|
$tableName = $channel->getTableName();
|
||||||
|
$commited = false;
|
||||||
|
$count = 0;
|
||||||
|
$sqlite->beginTransaction();
|
||||||
|
$commitThreshold = $channel->getEachCommitThreshold();
|
||||||
|
try {
|
||||||
|
$args ??= [];
|
||||||
|
foreach ($this->_all($channel, $filter) as $row) {
|
||||||
|
$updates = func::_call($onEach, [$row["item"], $row, ...$args]);
|
||||||
|
if (is_array($updates)) {
|
||||||
|
$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_"]],
|
||||||
|
]);
|
||||||
|
if ($commitThreshold !== null) {
|
||||||
|
$commitThreshold--;
|
||||||
|
if ($commitThreshold == 0) {
|
||||||
|
$sqlite->commit();
|
||||||
|
$sqlite->beginTransaction();
|
||||||
|
$commitThreshold = $channel->getEachCommitThreshold();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
$sqlite->commit();
|
||||||
|
$commited = true;
|
||||||
|
return $count;
|
||||||
|
} finally {
|
||||||
|
if (!$commited) $sqlite->rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
$this->sqlite->close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -39,6 +39,8 @@ class RunFile {
|
||||||
$dateStart = $withDateStart? new DateTime(): null;
|
$dateStart = $withDateStart? new DateTime(): null;
|
||||||
return [
|
return [
|
||||||
"name" => $this->name,
|
"name" => $this->name,
|
||||||
|
"id" => bin2hex(random_bytes(16)),
|
||||||
|
"pid" => posix_getpid(),
|
||||||
"serial" => 0,
|
"serial" => 0,
|
||||||
"date_start" => $dateStart,
|
"date_start" => $dateStart,
|
||||||
"date_stop" => null,
|
"date_stop" => null,
|
||||||
|
|
|
@ -44,7 +44,7 @@ class Word {
|
||||||
$spec = substr($spec, strlen($ms[0]));
|
$spec = substr($spec, strlen($ms[0]));
|
||||||
} elseif (preg_match('/^m(asc(ulin)?)?\s*:\s*/i', $spec, $ms)) {
|
} elseif (preg_match('/^m(asc(ulin)?)?\s*:\s*/i', $spec, $ms)) {
|
||||||
$fem = false;
|
$fem = false;
|
||||||
$spec = substr($spec, $ms[0]);
|
$spec = substr($spec, strlen($ms[0]));
|
||||||
} elseif (preg_match('/\s*\|f(?:[eé]m(?:inin)?)?\s*$/iu', $spec, $ms, PREG_OFFSET_CAPTURE)) {
|
} elseif (preg_match('/\s*\|f(?:[eé]m(?:inin)?)?\s*$/iu', $spec, $ms, PREG_OFFSET_CAPTURE)) {
|
||||||
$fem = true;
|
$fem = true;
|
||||||
$spec = substr($spec, 0, $ms[0][1]);
|
$spec = substr($spec, 0, $ms[0][1]);
|
||||||
|
|
|
@ -5,32 +5,32 @@ use nulib\tests\TestCase;
|
||||||
use nulib\db\Capacitor;
|
use nulib\db\Capacitor;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
|
|
||||||
class SqliteCapacitorTest extends TestCase {
|
class SqliteStorageTest extends TestCase {
|
||||||
function _testChargeStrings(SqliteCapacitor $capacitor, ?string $channel) {
|
function _testChargeStrings(SqliteStorage $storage, ?string $channel) {
|
||||||
$capacitor->reset($channel);
|
$storage->reset($channel);
|
||||||
$capacitor->charge($channel, "first");
|
$storage->charge($channel, "first");
|
||||||
$capacitor->charge($channel, "second");
|
$storage->charge($channel, "second");
|
||||||
$capacitor->charge($channel, "third");
|
$storage->charge($channel, "third");
|
||||||
$items = iterator_to_array($capacitor->discharge($channel, null, false));
|
$items = iterator_to_array($storage->discharge($channel, false));
|
||||||
self::assertSame(["first", "second", "third"], $items);
|
self::assertSame(["first", "second", "third"], $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _testChargeArrays(SqliteCapacitor $capacitor, ?string $channel) {
|
function _testChargeArrays(SqliteStorage $storage, ?string $channel) {
|
||||||
$capacitor->reset($channel);
|
$storage->reset($channel);
|
||||||
$capacitor->charge($channel, ["id" => 10, "name" => "first"]);
|
$storage->charge($channel, ["id" => 10, "name" => "first"]);
|
||||||
$capacitor->charge($channel, ["name" => "second", "id" => 20]);
|
$storage->charge($channel, ["name" => "second", "id" => 20]);
|
||||||
$capacitor->charge($channel, ["name" => "third", "id" => "30"]);
|
$storage->charge($channel, ["name" => "third", "id" => "30"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testChargeStrings() {
|
function testChargeStrings() {
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$this->_testChargeStrings($capacitor, null);
|
$this->_testChargeStrings($storage, null);
|
||||||
$capacitor->close();
|
$storage->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function testChargeArrays() {
|
function testChargeArrays() {
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$capacitor->addChannel(new class extends CapacitorChannel {
|
$storage->addChannel(new class extends CapacitorChannel {
|
||||||
const NAME = "arrays";
|
const NAME = "arrays";
|
||||||
function getKeyDefinitions(): ?array {
|
function getKeyDefinitions(): ?array {
|
||||||
return ["id" => "integer"];
|
return ["id" => "integer"];
|
||||||
|
@ -40,14 +40,14 @@ class SqliteCapacitorTest extends TestCase {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->_testChargeStrings($capacitor, "strings");
|
$this->_testChargeStrings($storage, "strings");
|
||||||
$this->_testChargeArrays($capacitor, "arrays");
|
$this->_testChargeArrays($storage, "arrays");
|
||||||
$capacitor->close();
|
$storage->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function testEach() {
|
function testEach() {
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$capacitor = new Capacitor($capacitor, new class extends CapacitorChannel {
|
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||||
const NAME = "each";
|
const NAME = "each";
|
||||||
|
|
||||||
function getKeyDefinitions(): ?array {
|
function getKeyDefinitions(): ?array {
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -86,19 +86,19 @@ class SqliteCapacitorTest extends TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
function testPrimayKey() {
|
function testPrimayKey() {
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$capacitor = new Capacitor($capacitor, new class extends CapacitorChannel {
|
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||||
const NAME = "pk";
|
const NAME = "pk";
|
||||||
|
|
||||||
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"],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue