diff --git a/src/A.php b/src/A.php new file mode 100644 index 0000000..a044fb1 --- /dev/null +++ b/src/A.php @@ -0,0 +1,39 @@ +wrappedArray(); + if ($array === null || $array === false) $array = []; + elseif ($array instanceof Traversable) $array = iterator_to_array($array); + else $array = [$array]; + return false; + } + + /** + * s'assurer que $array est un array s'il est non null. retourner true si + * $array n'a pas été modifié (s'il était déjà un array ou s'il valait null). + */ + static final function ensure_narray(&$array): bool { + if ($array === null || is_array($array)) return true; + if ($array instanceof IArrayWrapper) $array = $array->wrappedArray(); + if ($array === false) $array = []; + elseif ($array instanceof Traversable) $array = iterator_to_array($array); + else $array = [$array]; + return false; + } + +} diff --git a/src/IArrayWrapper.php b/src/IArrayWrapper.php new file mode 100644 index 0000000..9630072 --- /dev/null +++ b/src/IArrayWrapper.php @@ -0,0 +1,11 @@ +db, $result); } - function _exec(string $query): bool { + protected function db(): SQLite3 { $this->open(); - return $this->db->exec($query); + return $this->db; + } + + function _exec(string $query): bool { + return $this->db()->exec($query); } function exec($query, ?array $params=null): bool { - $this->open(); + $db = $this->db(); $query = new _query($query, $params); - $db = $this->db; if ($query->useStmt($db, $stmt, $sql)) { try { return $stmt->execute()->finalize(); @@ -132,15 +135,25 @@ class Sqlite { } } + function beginTransaction(): void { + $this->db()->exec("begin"); + } + + function commit(): void { + $this->db()->exec("commit"); + } + + function rollback(): void { + $this->db()->exec("commit"); + } + function _get(string $query, bool $entireRow=false) { - $this->open(); - return $this->db->querySingle($query, $entireRow); + return $this->db()->querySingle($query, $entireRow); } function get($query, ?array $params=null, bool $entireRow=false) { - $this->open(); + $db = $this->db(); $query = new _query($query, $params); - $db = $this->db; if ($query->useStmt($db, $stmt, $sql)) { try { $result = $this->checkResult($stmt->execute()); @@ -164,27 +177,23 @@ class Sqlite { return $this->get($query, $params, true); } - protected function _fetchResult(SQLite3Result $result): Generator { + protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null): Generator { try { while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) { yield $row; } } finally { $result->finalize(); + if ($stmt !== null) $stmt->close(); } } function all($query, ?array $params=null): iterable { - $this->open(); + $db = $this->db(); $query = new _query($query, $params); - $db = $this->db; if ($query->useStmt($db, $stmt, $sql)) { - try { - $result = $this->checkResult($stmt->execute()); - return $this->_fetchResult($result); - } finally { - $stmt->close(); - } + $result = $this->checkResult($stmt->execute()); + return $this->_fetchResult($result, $stmt); } else { $result = $this->checkResult($db->query($sql)); return $this->_fetchResult($result); diff --git a/src/db/sqlite/SqliteCapacitor.php b/src/db/sqlite/SqliteCapacitor.php index 540b356..8cf075b 100644 --- a/src/db/sqlite/SqliteCapacitor.php +++ b/src/db/sqlite/SqliteCapacitor.php @@ -17,6 +17,10 @@ class SqliteCapacitor { /** @var Sqlite */ protected $sqlite; + function sqlite(): Sqlite { + return $this->sqlite; + } + protected function getTableName(?string $channel): string { return ($channel ?? "default")."_channel"; } @@ -30,7 +34,7 @@ class SqliteCapacitor { function reset(?string $channel=null) { $tableName = $this->getTableName($channel); $this->sqlite->exec("drop table if exists $tableName"); - #XXX maj de la tables channels + #XXX maj de la tables channels dans une transaction } /** @var array */ @@ -68,7 +72,7 @@ class SqliteCapacitor { ]); #XXX^^^ migrer vers la syntaxe tableau de create $this->sqlite->exec($query); - #XXX maj de la tables channels + #XXX maj de la tables channels dans une transaction $this->created[$channel] = true; } @@ -85,10 +89,13 @@ class SqliteCapacitor { } /** décharger les données du canal spécifié */ - function discharge(?string $channel=null, bool $reset=true): iterable { + function discharge($keys=null, ?string $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), + "where" => $keys, ]); foreach ($rows as $row) { $item = unserialize($row['_item']); @@ -97,22 +104,6 @@ class SqliteCapacitor { if ($reset) $this->reset($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(callable $func, ?array $keys, ?string $channel=null): void { - $context = func::_prepare($func); - foreach ($this->discharge($channel, false) as $item) { - $result = func::_call($context, [$item]); - #XXX - } - } - /** * obtenir l'élément identifié par les clés spécifiées sur le canal spécifié * @@ -121,34 +112,56 @@ class SqliteCapacitor { function get($keys, ?string $channel=null) { if ($keys === null) throw ValueException::null("keys"); if (!is_array($keys)) $keys = ["_id" => $keys]; - #XXX + $row = $this->sqlite->one([ + "select _item", + "from" => $this->getTableName($channel), + "where" => $keys, + ]); + if ($row === null) return null; + else return unserialize($row["_item"]); + } + + /** + * 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 { + if ($keys !== null && !is_array($keys)) $keys = ["_id" => $keys]; + $context = func::_prepare($func); + $sqlite = $this->sqlite; + $tableName = $this->getTableName($channel); + $rows = $sqlite->all([ + "select", + "from" => $tableName, + "where" => $keys, + ]); + $args ??= []; + foreach ($rows as $row) { + $item = unserialize($row['_item']); + $updates = func::_call($context, [$item, $row, ...$args]); + if (is_array($updates)) { + 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 + } + } } function close(): void { $this->sqlite->close(); } - - function sqlite__exec(string $query): bool { - return $this->sqlite->_exec($query); - } - - function sqlite_exec($query, ?array $params=null): bool { - return $this->sqlite->exec($query, $params); - } - - function sqlite__get(string $query, bool $entireRow=false) { - return $this->sqlite->_get($query, $entireRow); - } - - function sqlite_get($query, ?array $params=null, bool $entireRow=false) { - return $this->sqlite->get($query, $params, $entireRow); - } - - function sqlite_one($query, ?array $params=null): ?array { - return $this->sqlite->one($query, $params); - } - - function sqlite_all($query, ?array $params=null): iterable { - return $this->sqlite->all($query, $params); - } } diff --git a/src/db/sqlite/_query.php b/src/db/sqlite/_query.php index a3d18aa..110abfe 100644 --- a/src/db/sqlite/_query.php +++ b/src/db/sqlite/_query.php @@ -91,6 +91,8 @@ class _query { } # value ou [operator, value] if (is_array($cond)) { + #XXX implémenter le support de ["between", lower, upper] + # et aussi ["in", values] $op = null; $value = null; $condkeys = array_keys($cond); diff --git a/src/php/coll/BaseArray.php b/src/php/coll/BaseArray.php index b9e97b1..bcb35a2 100644 --- a/src/php/coll/BaseArray.php +++ b/src/php/coll/BaseArray.php @@ -5,12 +5,13 @@ use ArrayAccess; use Countable; use Iterator; use nur\sery\cl; +use nur\sery\IArrayWrapper; /** * Class BaseArray: implémentation de base d'un objet array-like, qui peut aussi * servir comme front-end pour un array */ -class BaseArray implements ArrayAccess, Countable, Iterator { +class BaseArray implements ArrayAccess, Countable, Iterator, IArrayWrapper { function __construct(?array &$data=null) { $this->reset($data); } @@ -18,10 +19,11 @@ class BaseArray implements ArrayAccess, Countable, Iterator { /** @var array */ protected $data; + function &wrappedArray(): ?array { return $this->data; } + function __toString(): string { return var_export($this->data, true); } #function __debugInfo() { return $this->data; } function reset(?array &$data): void { $this->data =& $data; } - function &array(): ?array { return $this->data; } function count(): int { return $this->data !== null? count($this->data): 0; } function keys(): array { return $this->data !== null? array_keys($this->data): []; } diff --git a/tests/db/sqlite/SqliteCapacitorTest.php b/tests/db/sqlite/SqliteCapacitorTest.php index 3fc8f65..86b5fbb 100644 --- a/tests/db/sqlite/SqliteCapacitorTest.php +++ b/tests/db/sqlite/SqliteCapacitorTest.php @@ -9,7 +9,7 @@ class SqliteCapacitorTest extends TestCase { $capacitor->charge("first", $channel); $capacitor->charge("second", $channel); $capacitor->charge("third", $channel); - $items = iterator_to_array($capacitor->discharge($channel, false)); + $items = iterator_to_array($capacitor->discharge(null, $channel, false)); self::assertSame(["first", "second", "third"], $items); } function _testChargeArrays(SqliteCapacitor $capacitor, ?string $channel) { @@ -45,4 +45,42 @@ class SqliteCapacitorTest extends TestCase { $this->_testChargeArrays($capacitor, "arrays"); $capacitor->close(); } + + function testEach() { + $capacitor = new class(__DIR__.'/capacitor.db') extends SqliteCapacitor { + protected function getKeyDefinitions(?string $channel): ?array { + return [ + "age" => "integer", + "done" => "integer default 0", + ]; + } + protected function getKeyValues($item, ?string $channel): ?array { + return [ + "age" => $item["age"], + ]; + } + }; + + $channel = "each"; + $capacitor->reset($channel); + $capacitor->charge(["name" => "first", "age" => 5], $channel); + $capacitor->charge(["name" => "second", "age" => 10], $channel); + $capacitor->charge(["name" => "third", "age" => 15], $channel); + $capacitor->charge(["name" => "fourth", "age" => 20], $channel); + + $setDone = function ($item, $row, $suffix=null) { + $updates = ["done" => 1]; + if ($suffix !== null) { + $item["name"] .= $suffix; + $updates["_item"] = $item; + } + return $updates; + }; + $capacitor->each(["age" => [">", 10]], $setDone, ["++"], $channel); + $capacitor->each(["done" => 0], $setDone, null, $channel); + Txx(iterator_to_array($capacitor->discharge(null, $channel, false))); + + $capacitor->close(); + self::assertTrue(true); + } }