modifs.mineures sans commentaires
This commit is contained in:
parent
dbaf9af194
commit
fb05eef630
|
@ -3,6 +3,7 @@ namespace nur\sery\db;
|
||||||
|
|
||||||
use nur\sery\php\func;
|
use nur\sery\php\func;
|
||||||
use nur\sery\ValueException;
|
use nur\sery\ValueException;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Capacitor: un objet permettant d'attaquer un canal spécifique d'une
|
* Class Capacitor: un objet permettant d'attaquer un canal spécifique d'une
|
||||||
|
@ -140,7 +141,7 @@ class Capacitor implements ITransactor {
|
||||||
return $this->storage->_charge($this->channel, $item, $func, $args, $values);
|
return $this->storage->_charge($this->channel, $item, $func, $args, $values);
|
||||||
}
|
}
|
||||||
|
|
||||||
function discharge(bool $reset=true): iterable {
|
function discharge(bool $reset=true): Traversable {
|
||||||
return $this->storage->_discharge($this->channel, $reset);
|
return $this->storage->_discharge($this->channel, $reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +153,7 @@ class Capacitor implements ITransactor {
|
||||||
return $this->storage->_one($this->channel, $filter, $mergeQuery);
|
return $this->storage->_one($this->channel, $filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
function all($filter, ?array $mergeQuery=null): iterable {
|
function all($filter, ?array $mergeQuery=null): Traversable {
|
||||||
return $this->storage->_all($this->channel, $filter, $mergeQuery);
|
return $this->storage->_all($this->channel, $filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ namespace nur\sery\db;
|
||||||
|
|
||||||
use nur\sery\cl;
|
use nur\sery\cl;
|
||||||
use nur\sery\str;
|
use nur\sery\str;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
|
* Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
|
||||||
|
@ -20,10 +21,19 @@ class CapacitorChannel {
|
||||||
|
|
||||||
const EACH_COMMIT_THRESHOLD = 100;
|
const EACH_COMMIT_THRESHOLD = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool faut-il passer par le cache pour les requêtes de each()
|
||||||
|
* ça peut être nécessaire avec MySQL/MariaDB si on utilise les requêtes non
|
||||||
|
* bufférisées, et que la fonction
|
||||||
|
*/
|
||||||
|
const USE_CACHE = false;
|
||||||
|
|
||||||
static function verifix_name(?string &$name, ?string &$tableName=null): void {
|
static function verifix_name(?string &$name, ?string &$tableName=null): void {
|
||||||
if ($name !== null) {
|
if ($name !== null) {
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
if ($tableName === null) $tableName = "${name}_channel";
|
if ($tableName === null) {
|
||||||
|
$tableName = str_replace("-", "_", $tableName) . "_channel";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$name = static::class;
|
$name = static::class;
|
||||||
if ($name === self::class) {
|
if ($name === self::class) {
|
||||||
|
@ -53,6 +63,7 @@ class CapacitorChannel {
|
||||||
$this->tableName = $tableName;
|
$this->tableName = $tableName;
|
||||||
$this->manageTransactions = $manageTransactions ?? static::MANAGE_TRANSACTIONS;
|
$this->manageTransactions = $manageTransactions ?? static::MANAGE_TRANSACTIONS;
|
||||||
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
||||||
|
$this->useCache = static::USE_CACHE;
|
||||||
$this->setup = false;
|
$this->setup = false;
|
||||||
$this->created = false;
|
$this->created = false;
|
||||||
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
|
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
|
||||||
|
@ -122,6 +133,17 @@ class CapacitorChannel {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected bool $useCache;
|
||||||
|
|
||||||
|
function isUseCache(): bool {
|
||||||
|
return $this->useCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUseCache(bool $useCache=true): self {
|
||||||
|
$this->useCache = $useCache;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initialiser ce channel avant sa première utilisation.
|
* initialiser ce channel avant sa première utilisation.
|
||||||
*/
|
*/
|
||||||
|
@ -358,7 +380,7 @@ class CapacitorChannel {
|
||||||
return $this->capacitor->charge($item, $func, $args, $values);
|
return $this->capacitor->charge($item, $func, $args, $values);
|
||||||
}
|
}
|
||||||
|
|
||||||
function discharge(bool $reset=true): iterable {
|
function discharge(bool $reset=true): Traversable {
|
||||||
return $this->capacitor->discharge($reset);
|
return $this->capacitor->discharge($reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,7 +392,7 @@ class CapacitorChannel {
|
||||||
return $this->capacitor->one($filter, $mergeQuery);
|
return $this->capacitor->one($filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
function all($filter, ?array $mergeQuery=null): iterable {
|
function all($filter, ?array $mergeQuery=null): Traversable {
|
||||||
return $this->capacitor->all($filter, $mergeQuery);
|
return $this->capacitor->all($filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
namespace nur\sery\db;
|
namespace nur\sery\db;
|
||||||
|
|
||||||
use nur\sery\cl;
|
use nur\sery\cl;
|
||||||
|
use nur\sery\db\cache\cache;
|
||||||
use nur\sery\php\func;
|
use nur\sery\php\func;
|
||||||
use nur\sery\ValueException;
|
use nur\sery\ValueException;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class CapacitorStorage: objet permettant d'accumuler des données pour les
|
* Class CapacitorStorage: objet permettant d'accumuler des données pour les
|
||||||
|
@ -364,7 +366,7 @@ EOT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** décharger les données du canal spécifié */
|
/** décharger les données du canal spécifié */
|
||||||
function _discharge(CapacitorChannel $channel, bool $reset=true): iterable {
|
function _discharge(CapacitorChannel $channel, bool $reset=true): Traversable {
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
$rows = $this->db()->all([
|
$rows = $this->db()->all([
|
||||||
"select item__",
|
"select item__",
|
||||||
|
@ -376,7 +378,7 @@ EOT;
|
||||||
if ($reset) $this->_reset($channel);
|
if ($reset) $this->_reset($channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
function discharge(?string $channel, bool $reset=true): iterable {
|
function discharge(?string $channel, bool $reset=true): Traversable {
|
||||||
return $this->_discharge($this->getChannel($channel), $reset);
|
return $this->_discharge($this->getChannel($channel), $reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,12 +454,7 @@ EOT;
|
||||||
return $this->_one($this->getChannel($channel), $filter, $mergeQuery);
|
return $this->_one($this->getChannel($channel), $filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function _allCached(string $id, CapacitorChannel $channel, $filter, ?array $mergeQuery=null): Traversable {
|
||||||
* 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(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): iterable {
|
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
$this->verifixFilter($channel, $filter);
|
$this->verifixFilter($channel, $filter);
|
||||||
$rows = $this->db()->all(cl::merge([
|
$rows = $this->db()->all(cl::merge([
|
||||||
|
@ -465,12 +462,28 @@ EOT;
|
||||||
"from" => $channel->getTableName(),
|
"from" => $channel->getTableName(),
|
||||||
"where" => $filter,
|
"where" => $filter,
|
||||||
], $mergeQuery), null, $this->getPrimaryKeys($channel));
|
], $mergeQuery), null, $this->getPrimaryKeys($channel));
|
||||||
|
if ($channel->isUseCache()) {
|
||||||
|
$cacheIds = [$id, get_class($channel)];
|
||||||
|
cache::get()->resetCached($cacheIds);
|
||||||
|
$rows = cache::new(null, $cacheIds, function() use ($rows) {
|
||||||
|
yield from $rows;
|
||||||
|
});
|
||||||
|
}
|
||||||
foreach ($rows as $key => $row) {
|
foreach ($rows as $key => $row) {
|
||||||
yield $key => $this->unserialize($channel, $row);
|
yield $key => $this->unserialize($channel, $row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function all(?string $channel, $filter, $mergeQuery=null): 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(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): Traversable {
|
||||||
|
return $this->_allCached("all", $channel, $filter, $mergeQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
function all(?string $channel, $filter, $mergeQuery=null): Traversable {
|
||||||
return $this->_all($this->getChannel($channel), $filter, $mergeQuery);
|
return $this->_all($this->getChannel($channel), $filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,7 +517,8 @@ EOT;
|
||||||
$tableName = $channel->getTableName();
|
$tableName = $channel->getTableName();
|
||||||
try {
|
try {
|
||||||
$args ??= [];
|
$args ??= [];
|
||||||
foreach ($this->_all($channel, $filter, $mergeQuery) as $values) {
|
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
|
||||||
|
foreach ($all as $values) {
|
||||||
$rowIds = $this->getRowIds($channel, $values);
|
$rowIds = $this->getRowIds($channel, $values);
|
||||||
$updates = func::_call($onEach, [$values["item"], $values, ...$args]);
|
$updates = func::_call($onEach, [$values["item"], $values, ...$args]);
|
||||||
if (is_array($updates) && $updates) {
|
if (is_array($updates) && $updates) {
|
||||||
|
@ -571,7 +585,8 @@ EOT;
|
||||||
$tableName = $channel->getTableName();
|
$tableName = $channel->getTableName();
|
||||||
try {
|
try {
|
||||||
$args ??= [];
|
$args ??= [];
|
||||||
foreach ($this->_all($channel, $filter) as $values) {
|
$all = $this->_allCached("delete", $channel, $filter);
|
||||||
|
foreach ($all as $values) {
|
||||||
$rowIds = $this->getRowIds($channel, $values);
|
$rowIds = $this->getRowIds($channel, $values);
|
||||||
$delete = boolval(func::_call($onEach, [$values["item"], $values, ...$args]));
|
$delete = boolval(func::_call($onEach, [$values["item"], $values, ...$args]));
|
||||||
if ($delete) {
|
if ($delete) {
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\db\cache;
|
||||||
|
|
||||||
|
use nur\sery\cl;
|
||||||
|
use nur\sery\db\CapacitorChannel;
|
||||||
|
use nur\sery\php\time\DateTime;
|
||||||
|
use nur\sery\php\time\Delay;
|
||||||
|
|
||||||
|
class CacheChannel extends CapacitorChannel {
|
||||||
|
/** @var int durée de vie par défaut du cache */
|
||||||
|
const DURATION = "1D"; // jusqu'au lendemain
|
||||||
|
|
||||||
|
const INCLUDES = null;
|
||||||
|
|
||||||
|
const EXCLUDES = null;
|
||||||
|
|
||||||
|
const COLUMN_DEFINITIONS = [
|
||||||
|
"group_id" => "varchar(64) not null",
|
||||||
|
"id" => "varchar(64) not null",
|
||||||
|
"date_start" => "datetime",
|
||||||
|
"duration_" => "text",
|
||||||
|
"primary key (group_id, id)",
|
||||||
|
];
|
||||||
|
|
||||||
|
static function get_cache_ids($id): array {
|
||||||
|
if (is_array($id)) {
|
||||||
|
$keys = array_keys($id);
|
||||||
|
if (array_key_exists("group_id", $id)) $groupIdKey = "group_id";
|
||||||
|
else $groupIdKey = $keys[1] ?? null;
|
||||||
|
$groupId = $id[$groupIdKey] ?? "";
|
||||||
|
if (array_key_exists("id", $id)) $idKey = "id";
|
||||||
|
else $idKey = $keys[0] ?? null;
|
||||||
|
$id = $id[$idKey] ?? "";
|
||||||
|
} else {
|
||||||
|
$groupId = "";
|
||||||
|
}
|
||||||
|
if (preg_match('/^(.*\\\\)?([^\\\\]+)$/', $groupId, $ms)) {
|
||||||
|
# si le groupe est une classe, faire un hash du package pour limiter la
|
||||||
|
# longueur du groupe
|
||||||
|
[$package, $groupId] = [$ms[1], $ms[2]];
|
||||||
|
$package = substr(md5($package), 0, 4);
|
||||||
|
$groupId = "${groupId}_$package";
|
||||||
|
}
|
||||||
|
return ["group_id" => $groupId, "id" => $id];
|
||||||
|
}
|
||||||
|
|
||||||
|
function __construct(?string $duration=null, ?string $name=null) {
|
||||||
|
parent::__construct($name);
|
||||||
|
$this->duration = $duration ?? static::DURATION;
|
||||||
|
$this->includes = static::INCLUDES;
|
||||||
|
$this->excludes = static::EXCLUDES;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string $duration;
|
||||||
|
|
||||||
|
protected ?array $includes;
|
||||||
|
|
||||||
|
protected ?array $excludes;
|
||||||
|
|
||||||
|
function getItemValues($item): ?array {
|
||||||
|
return cl::merge(self::get_cache_ids($item), [
|
||||||
|
"item" => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreate($item, array $values, ?array $alwaysNull, ?string $duration=null): ?array {
|
||||||
|
$now = new DateTime();
|
||||||
|
$duration ??= $this->duration;
|
||||||
|
return [
|
||||||
|
"date_start" => $now,
|
||||||
|
"duration" => new Delay($duration, $now),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpdate($item, array $values, array $pvalues, ?string $duration=null): ?array {
|
||||||
|
$now = new DateTime();
|
||||||
|
$duration ??= $this->duration;
|
||||||
|
return [
|
||||||
|
"date_start" => $now,
|
||||||
|
"duration" => new Delay($duration, $now),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldUpdate($cacheIds, bool $noCache=false): bool {
|
||||||
|
if ($noCache) return true;
|
||||||
|
|
||||||
|
$cacheIds = self::get_cache_ids($cacheIds);
|
||||||
|
$groupId = $cacheIds["group_id"];
|
||||||
|
if ($groupId) {
|
||||||
|
$includes = $this->includes;
|
||||||
|
$shouldInclude = $includes !== null && in_array($groupId, $includes);
|
||||||
|
$excludes = $this->excludes;
|
||||||
|
$shouldExclude = $excludes !== null && in_array($groupId, $excludes);
|
||||||
|
if (!$shouldInclude || $shouldExclude) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
$expired = false;
|
||||||
|
$this->each($cacheIds,
|
||||||
|
function($item, $values) use (&$found, &$expired) {
|
||||||
|
$found = true;
|
||||||
|
$expired = $values["duration"]->isElapsed();
|
||||||
|
});
|
||||||
|
return !$found || $expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCached($cacheIds, ?string $duration=null): void {
|
||||||
|
$this->charge($cacheIds, null, [$duration]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetCached($cacheIds) {
|
||||||
|
$cacheIds = self::get_cache_ids($cacheIds);
|
||||||
|
$this->delete($cacheIds);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\db\cache;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use IteratorAggregate;
|
||||||
|
use nur\sery\cl;
|
||||||
|
use nur\sery\db\CapacitorChannel;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
class RowsChannel extends CapacitorChannel implements IteratorAggregate {
|
||||||
|
const COLUMN_DEFINITIONS = [
|
||||||
|
"key" => "varchar(128) primary key not null",
|
||||||
|
"all_values" => "mediumtext",
|
||||||
|
];
|
||||||
|
|
||||||
|
function __construct($id, callable $builder, ?string $duration=null) {
|
||||||
|
$this->cacheIds = $cacheIds = CacheChannel::get_cache_ids($id);
|
||||||
|
$this->builder = Closure::fromCallable($builder);
|
||||||
|
$this->duration = $duration;
|
||||||
|
$name = "{$cacheIds["group_id"]}-{$cacheIds["id"]}";
|
||||||
|
parent::__construct($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected array $cacheIds;
|
||||||
|
|
||||||
|
protected Closure $builder;
|
||||||
|
|
||||||
|
protected ?string $duration = null;
|
||||||
|
|
||||||
|
function getItemValues($item): ?array {
|
||||||
|
$key = array_keys($item)[0];
|
||||||
|
$row = $item[$key];
|
||||||
|
return [
|
||||||
|
"key" => $key,
|
||||||
|
"item" => $row,
|
||||||
|
"all_values" => implode(" ", cl::filter_n(cl::with($row))),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIterator(): Traversable {
|
||||||
|
$cm = cache::get();
|
||||||
|
if ($cm->shouldUpdate($this->cacheIds)) {
|
||||||
|
$this->capacitor->reset();
|
||||||
|
foreach (($this->builder)() as $key => $row) {
|
||||||
|
$this->charge([$key => $row]);
|
||||||
|
}
|
||||||
|
$cm->setCached($this->cacheIds, $this->duration);
|
||||||
|
}
|
||||||
|
return $this->discharge(false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\db\cache;
|
||||||
|
|
||||||
|
use nur\sery\db\Capacitor;
|
||||||
|
use nur\sery\db\CapacitorStorage;
|
||||||
|
use nur\sery\db\sqlite\SqliteStorage;
|
||||||
|
|
||||||
|
class cache {
|
||||||
|
protected static ?CapacitorStorage $storage = null;
|
||||||
|
|
||||||
|
static function set_storage(CapacitorStorage $storage): CapacitorStorage {
|
||||||
|
return self::$storage = $storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function get_storage(): CapacitorStorage {
|
||||||
|
return self::$storage ??= new SqliteStorage("");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static ?CacheChannel $channel = null;
|
||||||
|
|
||||||
|
static function set(?CacheChannel $channel): CacheChannel {
|
||||||
|
$channel ??= new CacheChannel();
|
||||||
|
new Capacitor(self::get_storage(), $channel);
|
||||||
|
return self::$channel = $channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function get(): CacheChannel {
|
||||||
|
if (self::$channel !== null) return self::$channel;
|
||||||
|
else return self::set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function new(?RowsChannel $channel, $id=null, ?callable $builder=null): RowsChannel {
|
||||||
|
$channel ??= new RowsChannel($id, $builder);
|
||||||
|
new Capacitor(self::get_storage(), $channel);
|
||||||
|
return $channel;
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,7 +84,7 @@ class Pdo implements IDatabase {
|
||||||
# options
|
# options
|
||||||
$this->options = $params["options"] ?? static::OPTIONS;
|
$this->options = $params["options"] ?? static::OPTIONS;
|
||||||
# configuration
|
# configuration
|
||||||
$config = $params["replace_config"];
|
$config = $params["replace_config"] ?? null;
|
||||||
if ($config === null) {
|
if ($config === null) {
|
||||||
$config = $params["config"] ?? static::CONFIG;
|
$config = $params["config"] ?? static::CONFIG;
|
||||||
if (is_callable($config)) $config = [$config];
|
if (is_callable($config)) $config = [$config];
|
||||||
|
|
|
@ -97,7 +97,7 @@ class Sqlite implements IDatabase {
|
||||||
$defaultAllowWal = static::ALLOW_WAL ?? !$inMemory;
|
$defaultAllowWal = static::ALLOW_WAL ?? !$inMemory;
|
||||||
$this->allowWal = $params["allow_wal"] ?? $defaultAllowWal;
|
$this->allowWal = $params["allow_wal"] ?? $defaultAllowWal;
|
||||||
# configuration
|
# configuration
|
||||||
$config = $params["replace_config"];
|
$config = $params["replace_config"] ?? null;
|
||||||
if ($config === null) {
|
if ($config === null) {
|
||||||
$config = $params["config"] ?? static::CONFIG;
|
$config = $params["config"] ?? static::CONFIG;
|
||||||
if (is_callable($config)) $config = [$config];
|
if (is_callable($config)) $config = [$config];
|
||||||
|
|
Loading…
Reference in New Issue