modifs.mineures sans commentaires
This commit is contained in:
parent
c4e1496b82
commit
a38fc17607
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
namespace nur\sery\db;
|
||||
|
||||
/**
|
||||
* Class Capacitor: un objet permettant d'attaquer un canal spécique d'une
|
||||
* instance de {@link ICapacitor}
|
||||
*/
|
||||
class Capacitor {
|
||||
function __construct(ICapacitor $capacitor, CapacitorChannel $channel) {
|
||||
$this->capacitor = $capacitor;
|
||||
$this->channel = $channel;
|
||||
}
|
||||
|
||||
protected $capacitor;
|
||||
|
||||
protected $channel;
|
||||
|
||||
function exists(): bool {
|
||||
return $this->capacitor->_exists($this->channel);
|
||||
}
|
||||
|
||||
function reset(): void {
|
||||
$this->capacitor->_reset($this->channel);
|
||||
}
|
||||
|
||||
function charge($item) {
|
||||
$this->capacitor->_charge($item, $this->channel);
|
||||
}
|
||||
|
||||
function discharge($keys=null, ?bool $reset=null): iterable {
|
||||
return $this->capacitor->_discharge($keys, $this->channel, $reset);
|
||||
}
|
||||
|
||||
function get($keys) {
|
||||
return $this->capacitor->_get($keys, $this->channel);
|
||||
}
|
||||
|
||||
function each($keys, callable $func, ?array $args=null): void {
|
||||
$this->capacitor->_each($keys, $func, $args, $this->channel);
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
$this->capacitor->close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
namespace nur\sery\db;
|
||||
|
||||
/**
|
||||
* Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
|
||||
*/
|
||||
class CapacitorChannel {
|
||||
const NAME = null;
|
||||
|
||||
static function verifix_name(?string $name): string {
|
||||
if ($name === null) $name = "default";
|
||||
return strtolower($name);
|
||||
}
|
||||
|
||||
function __construct(?string $name=null) {
|
||||
$this->name = self::verifix_name($name ?? static::NAME);
|
||||
$this->created = false;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $name;
|
||||
|
||||
function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
function getTableName(): string {
|
||||
return $this->name."_channel";
|
||||
}
|
||||
|
||||
protected $created;
|
||||
|
||||
function isCreated(): bool {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
function setCreated(bool $created=true): void {
|
||||
$this->created = $created;
|
||||
}
|
||||
|
||||
function getKeyDefinitions(): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getKeyValues($item): ?array {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
namespace nur\sery\db;
|
||||
|
||||
/**
|
||||
* Interface ICapacitor: objet permettant d'accumuler des données pour les
|
||||
* réutiliser plus tard
|
||||
*/
|
||||
interface ICapacitor {
|
||||
/** tester si le canal spécifié existe */
|
||||
function exists(?string $channel=null): bool;
|
||||
|
||||
/** supprimer le canal spécifié */
|
||||
function reset(?string $channel=null): void;
|
||||
|
||||
/** charger une valeur dans le canal */
|
||||
function charge($item, ?string $channel=null): void;
|
||||
|
||||
/** décharger les données du canal spécifié */
|
||||
function discharge($keys=null, ?string $channel=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]
|
||||
*/
|
||||
function get($keys, ?string $channel=null);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
function close(): void;
|
||||
}
|
|
@ -2,14 +2,15 @@
|
|||
namespace nur\sery\db\sqlite;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\db\CapacitorChannel;
|
||||
use nur\sery\db\ICapacitor;
|
||||
use nur\sery\php\func;
|
||||
use nur\sery\ValueException;
|
||||
|
||||
/**
|
||||
* Class SqliteCapacitor: objet permettant d'accumuler des données pour les
|
||||
* réutiliser plus tard
|
||||
* Class SqliteCapacitor
|
||||
*/
|
||||
class SqliteCapacitor {
|
||||
class SqliteCapacitor implements ICapacitor {
|
||||
function __construct($sqlite) {
|
||||
$this->sqlite = Sqlite::with($sqlite);
|
||||
}
|
||||
|
@ -21,87 +22,114 @@ class SqliteCapacitor {
|
|||
return $this->sqlite;
|
||||
}
|
||||
|
||||
protected function getTableName(?string $channel): string {
|
||||
return ($channel ?? "default")."_channel";
|
||||
protected function _create(CapacitorChannel $channel): void {
|
||||
if (!$channel->isCreated()) {
|
||||
$columns = cl::merge([
|
||||
"_id" => "integer primary key autoincrement",
|
||||
"_item" => "text",
|
||||
], $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 channel(?string $name=null): 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;
|
||||
}
|
||||
|
||||
/** tester si le canal spécifié existe */
|
||||
function exists(?string $channel=null): bool {
|
||||
#XXX maintenir une table channels avec la liste des canaux valides
|
||||
return $this->_exists($this->channel($channel));
|
||||
}
|
||||
|
||||
function _reset(CapacitorChannel $channel): void {
|
||||
$this->sqlite->exec([
|
||||
"drop table if exists",
|
||||
$channel->getTableName(),
|
||||
]);
|
||||
$channel->setCreated(false);
|
||||
}
|
||||
|
||||
/** supprimer le canal spécifié */
|
||||
function reset(?string $channel=null) {
|
||||
$tableName = $this->getTableName($channel);
|
||||
$this->sqlite->exec("drop table if exists $tableName");
|
||||
#XXX maj de la tables channels dans une transaction
|
||||
function reset(?string $channel=null): void {
|
||||
$this->_reset($this->channel($channel));
|
||||
}
|
||||
|
||||
/** @var array */
|
||||
protected $created;
|
||||
|
||||
protected function getKeyDefinitions(?string $channel): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getKeyValues($item, ?string $channel): ?array {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function create(?string $channel): void {
|
||||
if ($this->created[$channel] ?? false) return;
|
||||
$columns = cl::merge([
|
||||
"_id" => "integer primary key autoincrement",
|
||||
"_item" => "text",
|
||||
], $this->getKeyDefinitions($channel));
|
||||
#XXXvvv migrer vers la syntaxe tableau de create
|
||||
$index = 0;
|
||||
foreach ($columns as $column => &$definition) {
|
||||
if ($column === $index) {
|
||||
$index++;
|
||||
} else {
|
||||
$definition = "$column $definition";
|
||||
}
|
||||
}; unset($definition);
|
||||
$query = implode("", [
|
||||
"create table if not exists ",
|
||||
$this->getTableName($channel),
|
||||
" (",
|
||||
implode(", ", $columns),
|
||||
")",
|
||||
]);
|
||||
#XXX^^^ migrer vers la syntaxe tableau de create
|
||||
$this->sqlite->exec($query);
|
||||
#XXX maj de la tables channels dans une transaction
|
||||
$this->created[$channel] = true;
|
||||
}
|
||||
|
||||
function charge($item, ?string $channel=null) {
|
||||
$this->create($channel);
|
||||
$values = cl::merge($this->getKeyValues($item, $channel), [
|
||||
function _charge($item, CapacitorChannel $channel): void {
|
||||
$this->_create($channel);
|
||||
$values = cl::merge($channel->getKeyValues($item), [
|
||||
"_item" => serialize($item),
|
||||
]);
|
||||
$this->sqlite->exec([
|
||||
"insert",
|
||||
"into" => $this->getTableName($channel),
|
||||
"into" => $channel->getTableName(),
|
||||
"values" => $values,
|
||||
]);
|
||||
}
|
||||
|
||||
/** décharger les données du canal spécifié */
|
||||
function discharge($keys=null, ?string $channel=null, ?bool $reset=null): iterable {
|
||||
/** charger une valeur dans le canal */
|
||||
function charge($item, ?string $channel=null): void {
|
||||
$this->_charge($item, $this->channel($channel));
|
||||
}
|
||||
|
||||
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;
|
||||
$rows = $this->sqlite->all([
|
||||
"select _item",
|
||||
"from" => $this->getTableName($channel),
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $keys,
|
||||
]);
|
||||
foreach ($rows as $row) {
|
||||
$item = unserialize($row['_item']);
|
||||
yield $item;
|
||||
}
|
||||
if ($reset) $this->reset($channel);
|
||||
if ($reset) $this->_reset($channel);
|
||||
}
|
||||
|
||||
/** 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->channel($channel), $reset);
|
||||
}
|
||||
|
||||
function _get($keys, CapacitorChannel $channel=null) {
|
||||
if ($keys === null) throw ValueException::null("keys");
|
||||
if (!is_array($keys)) $keys = ["_id" => $keys];
|
||||
$row = $this->sqlite->one([
|
||||
"select _item",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $keys,
|
||||
]);
|
||||
if ($row === null) return null;
|
||||
else return unserialize($row["_item"]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,30 +138,14 @@ class SqliteCapacitor {
|
|||
* si $keys n'est pas un tableau, il est transformé en ["_id" => $keys]
|
||||
*/
|
||||
function get($keys, ?string $channel=null) {
|
||||
if ($keys === null) throw ValueException::null("keys");
|
||||
if (!is_array($keys)) $keys = ["_id" => $keys];
|
||||
$row = $this->sqlite->one([
|
||||
"select _item",
|
||||
"from" => $this->getTableName($channel),
|
||||
"where" => $keys,
|
||||
]);
|
||||
if ($row === null) return null;
|
||||
else return unserialize($row["_item"]);
|
||||
return $this->_get($keys, $this->channel($channel));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
function _each($keys, callable $func, ?array $args=null, CapacitorChannel $channel=null): void {
|
||||
if ($keys !== null && !is_array($keys)) $keys = ["_id" => $keys];
|
||||
$context = func::_prepare($func);
|
||||
$sqlite = $this->sqlite;
|
||||
$tableName = $this->getTableName($channel);
|
||||
$tableName = $channel->getTableName();
|
||||
$rows = $sqlite->all([
|
||||
"select",
|
||||
"from" => $tableName,
|
||||
|
@ -147,20 +159,28 @@ class SqliteCapacitor {
|
|||
if (array_key_exists("_item", $updates)) {
|
||||
$updates["_item"] = serialize($updates["_item"]);
|
||||
}
|
||||
#XXXvvv migrer vers la syntaxe tableau de update
|
||||
$params = null;
|
||||
$sql = ["update", $tableName, "set"];
|
||||
_query::parse_set_values($updates, $setsql, $params);
|
||||
$sql[] = implode(", ", $setsql);
|
||||
$sql[] = "where";
|
||||
_query::parse_conds(["_id" => $row["_id"]], $wheresql, $params);
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
$sqlite->exec(implode(" ", $sql), $params);
|
||||
#XXX^^^ migrer vers la syntaxe tableau de update
|
||||
$sqlite->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $updates,
|
||||
"where" => ["_id" => $row["_id"]],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->channel($channel));
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
$this->sqlite->close();
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ class _query {
|
|||
$query = _query_update::parse($query, $params);
|
||||
} elseif (_query_delete::isa($prefix)) {
|
||||
$query = _query_delete::parse($query, $params);
|
||||
} elseif (_query_generic::isa($prefix)) {
|
||||
$query = _query_generic::parse($query, $params);
|
||||
} else {
|
||||
throw SqliteException::wrap(ValueException::invalid_kind($query, "query"));
|
||||
}
|
||||
|
|
|
@ -11,9 +11,37 @@ class _query_create extends _query {
|
|||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return false;
|
||||
//return preg_match("/^create(?:\s+table)?\b/i", $sql);
|
||||
#XXX implémentation minimale
|
||||
return preg_match("/^create\s+table\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$sql[] = $query["table"];
|
||||
|
||||
## columns
|
||||
$cols = $query["cols"];
|
||||
$index = 0;
|
||||
foreach ($cols as $col => &$definition) {
|
||||
if ($col === $index) {
|
||||
$index++;
|
||||
} else {
|
||||
$definition = "$col $definition";
|
||||
}
|
||||
}; unset($definition);
|
||||
$sql[] = "(".implode(", ", $cols).")";
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,35 @@ class _query_delete extends _query {
|
|||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return false;
|
||||
//return preg_match("/^delete(?:\s+from)?\b/i", $sql);
|
||||
#XXX implémentation minimale
|
||||
return preg_match("/^delete\s+from\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$sql[] = $query["table"];
|
||||
|
||||
## where
|
||||
$where = $query["where"] ?? null;
|
||||
if ($where !== null) {
|
||||
_query::parse_conds($where, $wheresql, $params);
|
||||
if ($wheresql) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
}
|
||||
}
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\ValueException;
|
||||
|
||||
class _query_generic extends _query {
|
||||
static function isa(string $sql): bool {
|
||||
return preg_match('/^(?:drop\s+table)\b/i', $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
if (!cl::is_list($query)) {
|
||||
throw new ValueException("Seuls les tableaux séquentiels sont supportés");
|
||||
}
|
||||
return self::merge_seq($query);
|
||||
}
|
||||
}
|
|
@ -13,9 +13,38 @@ class _query_update extends _query {
|
|||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return false;
|
||||
return preg_match("/^update\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
## préfixe
|
||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
||||
|
||||
## table
|
||||
$sql[] = $query["table"];
|
||||
|
||||
## set
|
||||
_query::parse_set_values($query["values"], $setsql, $params);
|
||||
$sql[] = "set";
|
||||
$sql[] = implode(", ", $setsql);
|
||||
|
||||
## where
|
||||
$where = $query["where"] ?? null;
|
||||
if ($where !== null) {
|
||||
_query::parse_conds($where, $wheresql, $params);
|
||||
if ($wheresql) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
}
|
||||
}
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
use nur\sery\db\CapacitorChannel;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SqliteCapacitorTest extends TestCase {
|
||||
|
@ -26,20 +27,16 @@ class SqliteCapacitorTest extends TestCase {
|
|||
}
|
||||
|
||||
function testChargeArrays() {
|
||||
$capacitor = new class(__DIR__.'/capacitor.db') extends SqliteCapacitor {
|
||||
protected function getKeyDefinitions(?string $channel): ?array {
|
||||
if ($channel === "arrays") {
|
||||
return ["id" => "integer"];
|
||||
}
|
||||
return parent::getKeyDefinitions($channel);
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$capacitor->addChannel(new class extends CapacitorChannel {
|
||||
const NAME = "arrays";
|
||||
function getKeyDefinitions(): ?array {
|
||||
return ["id" => "integer"];
|
||||
}
|
||||
protected function getKeyValues($item, ?string $channel): ?array {
|
||||
if ($channel === "arrays") {
|
||||
return ["id" => $item["id"] ?? null];
|
||||
}
|
||||
return parent::getKeyValues($item, $channel);
|
||||
function getKeyValues($item): ?array {
|
||||
return ["id" => $item["id"] ?? null];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$this->_testChargeStrings($capacitor, "strings");
|
||||
$this->_testChargeArrays($capacitor, "arrays");
|
||||
|
@ -47,19 +44,21 @@ class SqliteCapacitorTest extends TestCase {
|
|||
}
|
||||
|
||||
function testEach() {
|
||||
$capacitor = new class(__DIR__.'/capacitor.db') extends SqliteCapacitor {
|
||||
protected function getKeyDefinitions(?string $channel): ?array {
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$capacitor->addChannel(new class extends CapacitorChannel {
|
||||
const NAME = "each";
|
||||
function getKeyDefinitions(): ?array {
|
||||
return [
|
||||
"age" => "integer",
|
||||
"done" => "integer default 0",
|
||||
];
|
||||
}
|
||||
protected function getKeyValues($item, ?string $channel): ?array {
|
||||
function getKeyValues($item): ?array {
|
||||
return [
|
||||
"age" => $item["age"],
|
||||
];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$channel = "each";
|
||||
$capacitor->reset($channel);
|
||||
|
|
Loading…
Reference in New Issue