Compare commits
No commits in common. "wip74/update-capacitor" and "dev74" have entirely different histories.
wip74/upda
...
dev74
15
.idea/php-docker-settings.xml
generated
15
.idea/php-docker-settings.xml
generated
@ -17,21 +17,6 @@
|
|||||||
</DockerContainerSettings>
|
</DockerContainerSettings>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</entry>
|
||||||
<entry key="38915385-b3ff-4f4b-8a9a-d5f3ecae559e">
|
|
||||||
<value>
|
|
||||||
<DockerContainerSettings>
|
|
||||||
<option name="version" value="1" />
|
|
||||||
<option name="volumeBindings">
|
|
||||||
<list>
|
|
||||||
<DockerVolumeBindingImpl>
|
|
||||||
<option name="containerPath" value="/opt/project" />
|
|
||||||
<option name="hostPath" value="$PROJECT_DIR$" />
|
|
||||||
</DockerVolumeBindingImpl>
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</DockerContainerSettings>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
</map>
|
||||||
</list>
|
</list>
|
||||||
</component>
|
</component>
|
||||||
|
80
.idea/php.xml
generated
80
.idea/php.xml
generated
@ -22,56 +22,56 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="PhpIncludePathManager">
|
<component name="PhpIncludePathManager">
|
||||||
<include_path>
|
<include_path>
|
||||||
<path value="$PROJECT_DIR$/php/vendor/composer" />
|
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-ctype" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/dflydev/dot-access-data" />
|
<path value="$PROJECT_DIR$/php/vendor/theseer/tokenizer" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/doctrine/instantiator" />
|
<path value="$PROJECT_DIR$/php/vendor/symfony/deprecation-contracts" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/league/commonmark" />
|
<path value="$PROJECT_DIR$/php/vendor/symfony/yaml" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/league/config" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/myclabs/deep-copy" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/nette/schema" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/nette/utils" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/nikic/php-parser" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/nulib/tests" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/phar-io/manifest" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/phar-io/version" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/phpmailer/phpmailer" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-code-coverage" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-file-iterator" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-invoker" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-text-template" />
|
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-text-template" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-file-iterator" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-timer" />
|
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-timer" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/phpunit" />
|
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-code-coverage" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/psr/cache" />
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/type" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/psr/container" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/psr/event-dispatcher" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/psr/log" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/cli-parser" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/code-unit" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/code-unit-reverse-lookup" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/comparator" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/complexity" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/diff" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/environment" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/exporter" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/global-state" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/lines-of-code" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/object-enumerator" />
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/object-enumerator" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/object-reflector" />
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/version" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/global-state" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/complexity" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/environment" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/recursion-context" />
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/recursion-context" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/resource-operations" />
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/resource-operations" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/type" />
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/diff" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/version" />
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/cli-parser" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/doctrine/instantiator" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/comparator" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/lines-of-code" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/code-unit-reverse-lookup" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/code-unit" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/object-reflector" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/sebastian/exporter" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/phpunit/phpunit" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-invoker" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/phar-io/version" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/phar-io/manifest" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/nulib/tests" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/myclabs/deep-copy" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/nikic/php-parser" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/composer" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/league/config" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/nette/schema" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/league/commonmark" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/dflydev/dot-access-data" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/psr/event-dispatcher" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/nette/utils" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/psr/container" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/psr/log" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/psr/cache" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/phpmailer/phpmailer" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/symfony/expression-language" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/cache" />
|
<path value="$PROJECT_DIR$/php/vendor/symfony/cache" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/cache-contracts" />
|
<path value="$PROJECT_DIR$/php/vendor/symfony/cache-contracts" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/deprecation-contracts" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/expression-language" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-ctype" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-php73" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-php80" />
|
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-php80" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/service-contracts" />
|
<path value="$PROJECT_DIR$/php/vendor/symfony/service-contracts" />
|
||||||
|
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-php73" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/var-exporter" />
|
<path value="$PROJECT_DIR$/php/vendor/symfony/var-exporter" />
|
||||||
<path value="$PROJECT_DIR$/php/vendor/symfony/yaml" />
|
|
||||||
<path value="$PROJECT_DIR$/php/vendor/theseer/tokenizer" />
|
|
||||||
</include_path>
|
</include_path>
|
||||||
</component>
|
</component>
|
||||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
|
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
|
||||||
|
@ -923,48 +923,4 @@ class cl {
|
|||||||
A::usort($array, $keys, $assoc);
|
A::usort($array, $keys, $assoc);
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extraire d'un tableau les clés séquentielles et les clés associatives
|
|
||||||
*
|
|
||||||
* Retourner une liste [$list, $assoc] où $list est un tableau avec uniquement
|
|
||||||
* les valeurs des clés séquentielles et $assoc est un tableau avec uniquement
|
|
||||||
* les valeurs des clés associatives. S'il n'existe aucune clé séquentielle
|
|
||||||
* (resp. aucune clé associative), $list (resp. $assoc) vaut null.
|
|
||||||
*
|
|
||||||
* Par exemple: split_assoc(["a", "b" => "c"]) retourne [["a"], ["b" => "c"]]
|
|
||||||
*/
|
|
||||||
static final function split_assoc(?array $array): array {
|
|
||||||
$list = null;
|
|
||||||
$assoc = null;
|
|
||||||
if ($array !== null) {
|
|
||||||
$i = 0;
|
|
||||||
foreach ($array as $key => $value) {
|
|
||||||
if ($key === $i) {
|
|
||||||
$list[] = $value;
|
|
||||||
$i++;
|
|
||||||
} else {
|
|
||||||
$assoc[$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [$list, $assoc];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Joindre en un seul tableau un tableau avec des clés séquentielles et un
|
|
||||||
* tableau avec des clés associatives.
|
|
||||||
*
|
|
||||||
* Si $list_first==true, les clés séquentielles arrivent d'abord, ensuite les
|
|
||||||
* clés associatives. Sinon, ce sont les clés associatives qui arrivent d'abord
|
|
||||||
*/
|
|
||||||
static final function merge_assoc(?array &$array, ?array $list, ?array $assoc, bool $list_first=false): void {
|
|
||||||
if ($list === null && $assoc === null) $array = [];
|
|
||||||
elseif ($list === null) $array = $assoc;
|
|
||||||
elseif ($assoc === null) $array = $list;
|
|
||||||
elseif ($list_first) $array = array_merge($list, $assoc);
|
|
||||||
else $array = array_merge($assoc, $list);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,728 +3,215 @@ namespace nulib\db;
|
|||||||
|
|
||||||
use nulib\A;
|
use nulib\A;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\cv;
|
|
||||||
use nulib\db\_private\_migration;
|
|
||||||
use nulib\php\func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Capacitor: objet permettant d'accumuler des données pour les
|
* Class Capacitor: un objet permettant d'attaquer un canal spécifique d'une
|
||||||
* réutiliser plus tard
|
* instance de {@link CapacitorStorage}
|
||||||
*/
|
*/
|
||||||
abstract class Capacitor {
|
class Capacitor implements ITransactor {
|
||||||
abstract function db(): IDatabase;
|
function __construct(CapacitorStorage $storage, CapacitorChannel $channel, bool $ensureExists=true) {
|
||||||
|
$this->storage = $storage;
|
||||||
|
$this->channel = $channel;
|
||||||
|
$this->channel->setCapacitor($this);
|
||||||
|
if ($ensureExists) $this->ensureExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var CapacitorStorage */
|
||||||
|
protected $storage;
|
||||||
|
|
||||||
|
function getStorage(): CapacitorStorage {
|
||||||
|
return $this->storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
function db(): IDatabase {
|
||||||
|
return $this->getStorage()->db();
|
||||||
|
}
|
||||||
|
|
||||||
function ensureLive(): self {
|
function ensureLive(): self {
|
||||||
$this->db()->ensure();
|
$this->getStorage()->ensureLive();
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function newChannel($channel): CapacitorChannel {
|
/** @var CapacitorChannel */
|
||||||
if (!($channel instanceof CapacitorChannel)) {
|
protected $channel;
|
||||||
if (!is_array($channel)) $channel = ["name" => $channel];
|
|
||||||
$channel = new CapacitorChannel($channel);
|
function getChannel(): CapacitorChannel {
|
||||||
}
|
return $this->channel;
|
||||||
return $channel->initCapacitor($this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# les définitions sont par défaut pour MariaDB/MySQL
|
function getTableName(): string {
|
||||||
const CDATA_DEFINITION = "mediumtext";
|
return $this->getChannel()->getTableName();
|
||||||
const CSUM_DEFINITION = "varchar(40)";
|
}
|
||||||
const CTIMESTAMP_DEFINITION = "datetime";
|
|
||||||
const GSERIAL_DEFINITION = "integer primary key auto_increment";
|
|
||||||
const GLIC_DEFINITION = "varchar(80)";
|
|
||||||
const GLIB_DEFINITION = "varchar(255)";
|
|
||||||
const GTEXT_DEFINITION = "mediumtext";
|
|
||||||
const GBOOL_DEFINITION = "integer(1) default 0";
|
|
||||||
const GUUID_DEFINITION = "varchar(36)";
|
|
||||||
|
|
||||||
protected static function verifix_col($def): string {
|
function getCreateSql(): string {
|
||||||
if (!is_string($def)) $def = strval($def);
|
$channel = $this->channel;
|
||||||
$def = trim($def);
|
return $this->storage->_getMigration($channel)->getSql(get_class($channel), $this->db());
|
||||||
$parts = preg_split('/\s+/', $def, 2);
|
}
|
||||||
if (count($parts) == 2) {
|
|
||||||
$def = $parts[0];
|
/** @var CapacitorChannel[] */
|
||||||
$rest = " $parts[1]";
|
protected ?array $subChannels = null;
|
||||||
|
|
||||||
|
protected ?array $subManageTransactions = null;
|
||||||
|
|
||||||
|
function willUpdate(...$channels): self {
|
||||||
|
if ($this->subChannels === null) {
|
||||||
|
# désactiver la gestion des transaction sur le channel local aussi
|
||||||
|
$this->subChannels[] = $this->channel;
|
||||||
|
}
|
||||||
|
if ($channels) {
|
||||||
|
foreach ($channels as $channel) {
|
||||||
|
if ($channel instanceof Capacitor) $channel = $channel->getChannel();
|
||||||
|
if ($channel instanceof CapacitorChannel) {
|
||||||
|
$this->subChannels[] = $channel;
|
||||||
} else {
|
} else {
|
||||||
$rest = null;
|
throw ValueException::invalid_type($channel, CapacitorChannel::class);
|
||||||
}
|
}
|
||||||
switch ($def) {
|
|
||||||
case "serdata":
|
|
||||||
case "Cdata": $def = static::CDATA_DEFINITION; break;
|
|
||||||
case "sersum":
|
|
||||||
case "Csum": $def = static::CSUM_DEFINITION; break;
|
|
||||||
case "serts":
|
|
||||||
case "Ctimestamp": $def = static::CTIMESTAMP_DEFINITION; break;
|
|
||||||
case "genserial":
|
|
||||||
case "Gserial": $def = static::GSERIAL_DEFINITION; break;
|
|
||||||
case "genlic":
|
|
||||||
case "Glic": $def = static::GLIC_DEFINITION; break;
|
|
||||||
case "genlib":
|
|
||||||
case "Glib": $def = static::GLIB_DEFINITION; break;
|
|
||||||
case "gentext":
|
|
||||||
case "Gtext": $def = static::GTEXT_DEFINITION; break;
|
|
||||||
case "genbool":
|
|
||||||
case "Gbool": $def = static::GBOOL_DEFINITION; break;
|
|
||||||
case "genuuid":
|
|
||||||
case "Guuid": $def = static::GUUID_DEFINITION; break;
|
|
||||||
}
|
}
|
||||||
return "$def$rest";
|
}
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRIMARY_KEY_DEFINITION = [
|
function inTransaction(): bool {
|
||||||
"id_" => "Gserial",
|
return $this->db()->inTransaction();
|
||||||
];
|
|
||||||
|
|
||||||
const COLUMN_DEFINITIONS = [
|
|
||||||
"item__" => "Cdata",
|
|
||||||
"item__sum_" => "Csum",
|
|
||||||
"created_" => "Ctimestamp",
|
|
||||||
"modified_" => "Ctimestamp",
|
|
||||||
];
|
|
||||||
|
|
||||||
protected function getColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
|
|
||||||
$definitions = [];
|
|
||||||
if ($channel->getPrimaryKeys() === null) {
|
|
||||||
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
|
||||||
}
|
|
||||||
$definitions[] = $channel->getColumnDefinitions();
|
|
||||||
$definitions[] = static::COLUMN_DEFINITIONS;
|
|
||||||
# forcer les définitions sans clé à la fin (sqlite requière par exemple que
|
|
||||||
# primary key (columns) soit à la fin)
|
|
||||||
$tmp = cl::merge(...$definitions);
|
|
||||||
$definitions = [];
|
|
||||||
$constraints = [];
|
|
||||||
$index = 0;
|
|
||||||
foreach ($tmp as $col => $def) {
|
|
||||||
if ($col === $index) {
|
|
||||||
$index++;
|
|
||||||
if (is_array($def)) {
|
|
||||||
if (!$ignoreMigrations) {
|
|
||||||
$mdefs = $def;
|
|
||||||
$mindex = 0;
|
|
||||||
foreach ($mdefs as $mcol => $mdef) {
|
|
||||||
if ($mcol === $mindex) {
|
|
||||||
$mindex++;
|
|
||||||
} else {
|
|
||||||
if ($mdef) {
|
|
||||||
$definitions[$mcol] = self::verifix_col($mdef);
|
|
||||||
} else {
|
|
||||||
unset($definitions[$mcol]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$constraints[] = $def;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$definitions[$col] = self::verifix_col($def);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cl::merge($definitions, $constraints);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** sérialiser les valeurs qui doivent l'être dans $row */
|
function beginTransaction(?callable $func=null, bool $commit=true): void {
|
||||||
protected function serialize(CapacitorChannel $channel, ?array $row): ?array {
|
|
||||||
if ($row === null) return null;
|
|
||||||
$colDefs = $this->getColumnDefinitions($channel);
|
|
||||||
$index = 0;
|
|
||||||
$raw = [];
|
|
||||||
foreach (array_keys($colDefs) as $col) {
|
|
||||||
$key = $col;
|
|
||||||
if ($key === $index) {
|
|
||||||
$index++;
|
|
||||||
} elseif ($channel->isSerialCol($key)) {
|
|
||||||
[$serialCol, $sumCol] = $channel->getSumCols($key);
|
|
||||||
if (array_key_exists($key, $row)) {
|
|
||||||
$sum = $channel->getSum($key, $row[$key]);
|
|
||||||
$raw[$serialCol] = $sum[$serialCol];
|
|
||||||
if (array_key_exists($sumCol, $colDefs)) {
|
|
||||||
$raw[$sumCol] = $sum[$sumCol];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} elseif (array_key_exists($key, $row)) {
|
|
||||||
$raw[$col] = $row[$key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** désérialiser les valeurs qui doivent l'être dans $values */
|
|
||||||
protected function unserialize(CapacitorChannel $channel, ?array $raw): ?array {
|
|
||||||
if ($raw === null) return null;
|
|
||||||
$colDefs = $this->getColumnDefinitions($channel);
|
|
||||||
$index = 0;
|
|
||||||
$row = [];
|
|
||||||
foreach (array_keys($colDefs) as $col) {
|
|
||||||
$key = $col;
|
|
||||||
if ($key === $index) {
|
|
||||||
$index++;
|
|
||||||
} elseif (!array_key_exists($col, $raw)) {
|
|
||||||
} elseif ($channel->isSerialCol($key)) {
|
|
||||||
$value = $raw[$col];
|
|
||||||
if ($value !== null) $value = $channel->unserialize($value);
|
|
||||||
$row[$key] = $value;
|
|
||||||
} else {
|
|
||||||
$row[$key] = $raw[$col];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $row;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPrimaryKeys(CapacitorChannel $channel): array {
|
|
||||||
$primaryKeys = $channel->getPrimaryKeys();
|
|
||||||
if ($primaryKeys === null) $primaryKeys = ["id_"];
|
|
||||||
return $primaryKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRowIds(CapacitorChannel $channel, ?array $row, ?array &$primaryKeys=null): ?array {
|
|
||||||
$primaryKeys = $this->getPrimaryKeys($channel);
|
|
||||||
$rowIds = cl::select($row, $primaryKeys);
|
|
||||||
if (cl::all_n($rowIds)) return null;
|
|
||||||
else return $rowIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
# Migration et metadata
|
|
||||||
|
|
||||||
abstract protected function tableExists(string $tableName): bool;
|
|
||||||
|
|
||||||
const METADATA_TABLE = "_metadata";
|
|
||||||
const METADATA_COLS = [
|
|
||||||
"name" => "varchar not null primary key",
|
|
||||||
"value" => "varchar",
|
|
||||||
];
|
|
||||||
|
|
||||||
protected function prepareMetadata(): void {
|
|
||||||
if (!$this->tableExists(static::METADATA_TABLE)) {
|
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$db->exec([
|
if ($this->subChannels !== null) {
|
||||||
"create table",
|
# on gère des subchannels: ne débuter la transaction que si ce n'est déjà fait
|
||||||
"table" => static::METADATA_TABLE,
|
if ($this->subManageTransactions === null) {
|
||||||
"cols" => static::METADATA_COLS,
|
foreach ($this->subChannels as $channel) {
|
||||||
]);
|
|
||||||
$db->exec([
|
|
||||||
"insert",
|
|
||||||
"into" => static::METADATA_TABLE,
|
|
||||||
"values" => [
|
|
||||||
"name" => "version",
|
|
||||||
"value" => "1",
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getCreateChannelSql(CapacitorChannel $channel): array {
|
|
||||||
return [
|
|
||||||
"create table if not exists",
|
|
||||||
"table" => $channel->getTableName(),
|
|
||||||
"cols" => $this->getColumnDefinitions($channel, true),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract function getMigration(CapacitorChannel $channel): _migration;
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
# Catalogue
|
|
||||||
|
|
||||||
const CATALOG_TABLE = "_channels";
|
|
||||||
const CATALOG_COLS = [
|
|
||||||
"name" => "varchar not null primary key",
|
|
||||||
"table_name" => "varchar",
|
|
||||||
"class_name" => "varchar",
|
|
||||||
];
|
|
||||||
|
|
||||||
protected function getCreateCatalogSql(): array {
|
|
||||||
return [
|
|
||||||
"create table if not exists",
|
|
||||||
"table" => static::CATALOG_TABLE,
|
|
||||||
"cols" => static::CATALOG_COLS,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function addToCatalogSql(CapacitorChannel $channel): array {
|
|
||||||
return [
|
|
||||||
"insert",
|
|
||||||
"into" => static::CATALOG_TABLE,
|
|
||||||
"values" => [
|
|
||||||
"name" => $channel->getName(),
|
|
||||||
"table_name" => $channel->getTableName(),
|
|
||||||
"class_name" => get_class($channel),
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCatalog(): iterable {
|
|
||||||
return $this->db()->all([
|
|
||||||
"select",
|
|
||||||
"from" => static::CATALOG_TABLE,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInCatalog(array $filter, ?array &$raw=null): bool {
|
|
||||||
$raw = $this->db()->one([
|
|
||||||
"select",
|
|
||||||
"from" => static::CATALOG_TABLE,
|
|
||||||
"where" => $filter,
|
|
||||||
]);
|
|
||||||
return $raw !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
protected function afterCreate(CapacitorChannel $channel): void {
|
|
||||||
$db = $this->db();
|
|
||||||
$db->exec($this->getCreateCatalogSql());
|
|
||||||
$db->exec($this->addToCatalogSql($channel));
|
|
||||||
}
|
|
||||||
|
|
||||||
function create(CapacitorChannel $channel): void {
|
|
||||||
$this->prepareMetadata();
|
|
||||||
$this->getMigration($channel)->migrate($this->db());
|
|
||||||
$this->afterCreate($channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
function autocreate(CapacitorChannel $channel, bool $force=false): void {
|
|
||||||
if ($force || !$channel->isCreated()) {
|
|
||||||
$channel->ensureSetup();
|
|
||||||
$this->create($channel);
|
|
||||||
$channel->setCreated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** tester si le canal spécifié existe */
|
|
||||||
function exists(CapacitorChannel $channel): bool {
|
|
||||||
return $this->tableExists($channel->getTableName());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function beforeReset(CapacitorChannel $channel): void {
|
|
||||||
$db = $this->db;
|
|
||||||
$name = $channel->getName();
|
$name = $channel->getName();
|
||||||
$db->exec([
|
$this->subManageTransactions ??= [];
|
||||||
"delete",
|
if (!array_key_exists($name, $this->subManageTransactions)) {
|
||||||
"from" => _migration::MIGRATION_TABLE,
|
$this->subManageTransactions[$name] = $channel->isManageTransactions();
|
||||||
"where" => [
|
|
||||||
"channel" => $name,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$db->exec([
|
|
||||||
"delete",
|
|
||||||
"from" => static::CATALOG_TABLE,
|
|
||||||
"where" => [
|
|
||||||
"name" => $name,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
$channel->setManageTransactions(false);
|
||||||
/** supprimer le canal spécifié */
|
|
||||||
function reset(CapacitorChannel $channel, bool $recreate=false): void {
|
|
||||||
$this->beforeReset($channel);
|
|
||||||
$this->db()->exec([
|
|
||||||
"drop table if exists",
|
|
||||||
$channel->getTableName(),
|
|
||||||
]);
|
|
||||||
$channel->setCreated(false);
|
|
||||||
if ($recreate) $this->autocreate($channel);
|
|
||||||
}
|
}
|
||||||
|
if (!$db->inTransaction()) $db->beginTransaction();
|
||||||
/**
|
|
||||||
* charger une valeur dans le canal
|
|
||||||
*
|
|
||||||
* Après avoir calculé les valeurs des clés supplémentaires
|
|
||||||
* avec {@link CapacitorChannel::getItemValues()}, l'une des deux fonctions
|
|
||||||
* {@link CapacitorChannel::onCreate()} ou {@link CapacitorChannel::onUpdate()}
|
|
||||||
* est appelée en fonction du type d'opération: création ou mise à jour
|
|
||||||
*
|
|
||||||
* Ensuite, si $func !== null, la fonction est appelée avec la signature de
|
|
||||||
* {@link CapacitorChannel::onCreate()} ou {@link CapacitorChannel::onUpdate()}
|
|
||||||
* en fonction du type d'opération: création ou mise à jour
|
|
||||||
*
|
|
||||||
* Dans les deux cas, si la fonction retourne un tableau, il est utilisé pour
|
|
||||||
* modifier les valeurs insérées/mises à jour. De plus, $row obtient la
|
|
||||||
* valeur finale des données insérées/mises à jour
|
|
||||||
*
|
|
||||||
* Si $args est renseigné, il est ajouté aux arguments utilisés pour appeler
|
|
||||||
* les méthodes {@link CapacitorChannel::getItemValues()},
|
|
||||||
* {@link CapacitorChannel::onCreate()} et/ou
|
|
||||||
* {@link CapacitorChannel::onUpdate()}
|
|
||||||
*
|
|
||||||
* @return int 1 si l'objet a été chargé ou mis à jour, 0 s'il existait
|
|
||||||
* déjà à l'identique dans le canal
|
|
||||||
*/
|
|
||||||
function charge(CapacitorChannel $channel, $item, $func=null, ?array $args=null, ?array &$row=null): int {
|
|
||||||
$channel->initCapacitor($this);
|
|
||||||
|
|
||||||
$tableName = $channel->getTableName();
|
|
||||||
$db = $this->db();
|
|
||||||
$args ??= [];
|
|
||||||
|
|
||||||
$row = func::call([$channel, "getItemValues"], $item, ...$args);
|
|
||||||
if ($row === [false]) return 0;
|
|
||||||
|
|
||||||
if ($row !== null && array_key_exists("item", $row)) {
|
|
||||||
$item = A::pop($row, "item");
|
|
||||||
}
|
}
|
||||||
|
} elseif (!$db->inTransaction()) {
|
||||||
$raw = cl::merge(
|
$db->beginTransaction();
|
||||||
$channel->getSum("item", $item),
|
|
||||||
$this->serialize($channel, $row));
|
|
||||||
$praw = null;
|
|
||||||
$rowIds = $this->getRowIds($channel, $raw, $primaryKeys);
|
|
||||||
if ($rowIds !== null) {
|
|
||||||
# modification
|
|
||||||
$praw = $db->one([
|
|
||||||
"select",
|
|
||||||
"from" => $tableName,
|
|
||||||
"where" => $rowIds,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$now = date("Y-m-d H:i:s");
|
|
||||||
$insert = null;
|
|
||||||
if ($praw === null) {
|
|
||||||
# création
|
|
||||||
$raw = cl::merge($raw, [
|
|
||||||
"created_" => $now,
|
|
||||||
"modified_" => $now,
|
|
||||||
]);
|
|
||||||
$insert = true;
|
|
||||||
$initFunc = func::with([$channel, "onCreate"], $args);
|
|
||||||
$row = $this->unserialize($channel, $raw);
|
|
||||||
$prow = null;
|
|
||||||
} else {
|
|
||||||
# modification
|
|
||||||
# intégrer autant que possible les valeurs de praw dans raw, de façon que
|
|
||||||
# l'utilisateur puisse voir clairement ce qui a été modifié
|
|
||||||
if ($channel->_wasSumModified("item", $raw, $praw)) {
|
|
||||||
$insert = false;
|
|
||||||
$raw = cl::merge($praw, $raw, [
|
|
||||||
"modified_" => $now,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
$raw = cl::merge($praw, $raw);
|
|
||||||
}
|
|
||||||
$initFunc = func::with([$channel, "onUpdate"], $args);
|
|
||||||
$row = $this->unserialize($channel, $raw);
|
|
||||||
$prow = $this->unserialize($channel, $praw);
|
|
||||||
}
|
|
||||||
|
|
||||||
$updates = $initFunc->prependArgs([$item, $row, $prow])->invoke();
|
|
||||||
if ($updates === [false]) return 0;
|
|
||||||
if (is_array($updates) && $updates) {
|
|
||||||
if ($insert === null) $insert = false;
|
|
||||||
if (!array_key_exists("modified_", $updates)) {
|
|
||||||
$updates["modified_"] = $now;
|
|
||||||
}
|
|
||||||
$row = cl::merge($row, $updates);
|
|
||||||
$raw = cl::merge($raw, $this->serialize($channel, $updates));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$updates = func::with($func, $args)
|
|
||||||
->prependArgs([$item, $row, $prow])
|
|
||||||
->bind($channel)
|
|
||||||
->invoke();
|
|
||||||
if ($updates === [false]) return 0;
|
|
||||||
if (is_array($updates) && $updates) {
|
|
||||||
if ($insert === null) $insert = false;
|
|
||||||
if (!array_key_exists("modified_", $updates)) {
|
|
||||||
$updates["modified_"] = $now;
|
|
||||||
}
|
|
||||||
$row = cl::merge($row, $updates);
|
|
||||||
$raw = cl::merge($raw, $this->serialize($channel, $updates));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# aucune modification
|
|
||||||
if ($insert === null) return 0;
|
|
||||||
|
|
||||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
|
||||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
|
||||||
if ($manageTransactions) {
|
|
||||||
$commited = false;
|
$commited = false;
|
||||||
$db->beginTransaction();
|
|
||||||
}
|
|
||||||
$nbModified = 0;
|
|
||||||
try {
|
try {
|
||||||
if ($insert) {
|
func::call($func, $this);
|
||||||
$id = $db->exec([
|
if ($commit) {
|
||||||
"insert",
|
$this->commit();
|
||||||
"into" => $tableName,
|
|
||||||
"values" => $raw,
|
|
||||||
]);
|
|
||||||
if (count($primaryKeys) == 1 && $rowIds === null) {
|
|
||||||
# mettre à jour avec l'id généré
|
|
||||||
$row[$primaryKeys[0]] = $id;
|
|
||||||
}
|
|
||||||
$nbModified = 1;
|
|
||||||
} else {
|
|
||||||
# calculer ce qui a changé pour ne mettre à jour que le nécessaire
|
|
||||||
$updates = [];
|
|
||||||
foreach ($raw as $col => $value) {
|
|
||||||
if (array_key_exists($col, $rowIds)) {
|
|
||||||
# ne jamais mettre à jour la clé primaire
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!cv::equals($value, $praw[$col] ?? null)) {
|
|
||||||
$updates[$col] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (count($updates) == 1 && array_key_first($updates) == "modified_") {
|
|
||||||
# si l'unique modification porte sur la date de modification, alors
|
|
||||||
# la ligne n'est pas modifiée. ce cas se présente quand on altère la
|
|
||||||
# valeur de $item
|
|
||||||
$updates = null;
|
|
||||||
}
|
|
||||||
if ($updates) {
|
|
||||||
$db->exec([
|
|
||||||
"update",
|
|
||||||
"table" => $tableName,
|
|
||||||
"values" => $updates,
|
|
||||||
"where" => $rowIds,
|
|
||||||
]);
|
|
||||||
$nbModified = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($manageTransactions) {
|
|
||||||
$db->commit();
|
|
||||||
$commited = true;
|
$commited = true;
|
||||||
}
|
}
|
||||||
return $nbModified;
|
|
||||||
} finally {
|
} finally {
|
||||||
if ($manageTransactions && !$commited) $db->rollback();
|
if ($commit && !$commited) $this->rollback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function beforeEndTransaction(): void {
|
||||||
* décharger les données du canal spécifié. seul la valeur de $item est
|
if ($this->subManageTransactions !== null) {
|
||||||
* fournie
|
foreach ($this->subChannels as $channel) {
|
||||||
*/
|
$name = $channel->getName();
|
||||||
function discharge(CapacitorChannel $channel, bool $reset=true): Traversable {
|
$channel->setManageTransactions($this->subManageTransactions[$name]);
|
||||||
$channel->initCapacitor($this);
|
|
||||||
|
|
||||||
$raws = $this->db()->all([
|
|
||||||
"select item__",
|
|
||||||
"from" => $channel->getTableName(),
|
|
||||||
]);
|
|
||||||
foreach ($raws as $raw) {
|
|
||||||
yield unserialize($raw['item__']);
|
|
||||||
}
|
}
|
||||||
if ($reset) $this->reset($channel);
|
$this->subManageTransactions = null;
|
||||||
}
|
|
||||||
|
|
||||||
protected function convertValue2row(CapacitorChannel $channel, array $filter, array $cols): array {
|
|
||||||
$index = 0;
|
|
||||||
$fixed = [];
|
|
||||||
foreach ($filter as $key => $value) {
|
|
||||||
if ($key === $index) {
|
|
||||||
$index++;
|
|
||||||
if (is_array($value)) {
|
|
||||||
$value = $this->convertValue2row($channel, $value, $cols);
|
|
||||||
}
|
|
||||||
$fixed[] = $value;
|
|
||||||
} else {
|
|
||||||
$col = "${key}__";
|
|
||||||
if (array_key_exists($col, $cols)) {
|
|
||||||
# colonne sérialisée
|
|
||||||
$fixed[$col] = $channel->serialize($value);
|
|
||||||
} else {
|
|
||||||
$fixed[$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function verifixFilter(CapacitorChannel $channel, &$filter): void {
|
|
||||||
if ($filter !== null && !is_array($filter)) {
|
|
||||||
$primaryKeys = $this->getPrimaryKeys($channel);
|
|
||||||
$id = $filter;
|
|
||||||
$channel->verifixId($id);
|
|
||||||
$filter = [$primaryKeys[0] => $id];
|
|
||||||
}
|
|
||||||
$cols = $this->getColumnDefinitions($channel);
|
|
||||||
if ($filter !== null) {
|
|
||||||
$filter = $this->convertValue2row($channel, $filter, $cols);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** indiquer le nombre d'éléments du canal spécifié */
|
function commit(): void {
|
||||||
function count(CapacitorChannel $channel, $filter): int {
|
$this->beforeEndTransaction();
|
||||||
$channel->initCapacitor($this);
|
|
||||||
$this->verifixFilter($channel, $filter);
|
|
||||||
|
|
||||||
return $this->db()->get([
|
|
||||||
"select count(*)",
|
|
||||||
"from" => $channel->getTableName(),
|
|
||||||
"where" => $filter,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* obtenir la ligne correspondant au filtre sur le canal spécifié
|
|
||||||
*
|
|
||||||
* si $filter n'est pas un tableau, il est transformé en ["id_" => $filter]
|
|
||||||
*/
|
|
||||||
function one(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): ?array {
|
|
||||||
$channel->initCapacitor($this);
|
|
||||||
$this->verifixFilter($channel, $filter);
|
|
||||||
|
|
||||||
$raw = $this->db()->one(cl::merge([
|
|
||||||
"select",
|
|
||||||
"from" => $channel->getTableName(),
|
|
||||||
"where" => $filter,
|
|
||||||
], $mergeQuery));
|
|
||||||
return $this->unserialize($channel, $raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
$channel->initCapacitor($this);
|
|
||||||
$this->verifixFilter($channel, $filter);
|
|
||||||
|
|
||||||
$raws = $this->db()->all(cl::merge([
|
|
||||||
"select",
|
|
||||||
"from" => $channel->getTableName(),
|
|
||||||
"where" => $filter,
|
|
||||||
], $mergeQuery), null, $this->getPrimaryKeys($channel));
|
|
||||||
foreach ($raws as $key => $raw) {
|
|
||||||
yield $key => $this->unserialize($channel, $raw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* appeler une fonction pour chaque élément du canal spécifié.
|
|
||||||
*
|
|
||||||
* $filter permet de filtrer parmi les élements chargés
|
|
||||||
*
|
|
||||||
* $func est appelé avec la signature de {@link CapacitorChannel::onEach()}
|
|
||||||
* si la fonction retourne un tableau, il est utilisé pour mettre à jour la
|
|
||||||
* ligne
|
|
||||||
*
|
|
||||||
* @param int $nbUpdated reçoit le nombre de lignes mises à jour
|
|
||||||
* @return int le nombre de lignes parcourues
|
|
||||||
*/
|
|
||||||
function each(CapacitorChannel $channel, $filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
|
||||||
$channel->initCapacitor($this);
|
|
||||||
|
|
||||||
if ($func === null) $func = CapacitorChannel::onEach;
|
|
||||||
$onEach = func::with($func)->bind($channel);
|
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
if ($db->inTransaction()) $db->commit();
|
||||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
|
||||||
if ($manageTransactions) {
|
|
||||||
$commited = false;
|
|
||||||
$db->beginTransaction();
|
|
||||||
$commitThreshold = $channel->getEachCommitThreshold();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rollback(): void {
|
||||||
|
$this->beforeEndTransaction();
|
||||||
|
$db = $this->db();
|
||||||
|
if ($db->inTransaction()) $db->rollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
function exists(): bool {
|
||||||
|
return $this->storage->_exists($this->channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureExists(): void {
|
||||||
|
$this->storage->_ensureExists($this->channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset(bool $recreate=false): void {
|
||||||
|
$this->storage->_reset($this->channel, $recreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
function charge($item, $func=null, ?array $args=null, ?array &$row=null): int {
|
||||||
|
if ($this->subChannels !== null) $this->beginTransaction();
|
||||||
|
return $this->storage->_charge($this->channel, $item, $func, $args, $row);
|
||||||
|
}
|
||||||
|
|
||||||
|
function chargeAll(?iterable $items, $func=null, ?array $args=null): int {
|
||||||
$count = 0;
|
$count = 0;
|
||||||
$nbUpdated = 0;
|
if ($items !== null) {
|
||||||
$tableName = $channel->getTableName();
|
if ($func !== null) {
|
||||||
try {
|
$func = func::with($func, $args)->bind($this->channel);
|
||||||
$args ??= [];
|
|
||||||
$rows = $this->all($channel, $filter, $mergeQuery);
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$rowIds = $this->getRowIds($channel, $row);
|
|
||||||
$updates = $onEach->invoke([$row, ...$args]);
|
|
||||||
if ($updates === [false]) {
|
|
||||||
break;
|
|
||||||
} elseif ($updates !== null) {
|
|
||||||
if (!array_key_exists("modified_", $updates)) {
|
|
||||||
$updates["modified_"] = date("Y-m-d H:i:s");
|
|
||||||
}
|
}
|
||||||
$nbUpdated += $db->exec([
|
foreach ($items as $item) {
|
||||||
"update",
|
$count += $this->charge($item, $func);
|
||||||
"table" => $tableName,
|
|
||||||
"values" => $this->serialize($channel, $updates),
|
|
||||||
"where" => $rowIds,
|
|
||||||
]);
|
|
||||||
if ($manageTransactions && $commitThreshold !== null) {
|
|
||||||
$commitThreshold--;
|
|
||||||
if ($commitThreshold <= 0) {
|
|
||||||
$db->commit();
|
|
||||||
$db->beginTransaction();
|
|
||||||
$commitThreshold = $channel->getEachCommitThreshold();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
$count++;
|
|
||||||
}
|
|
||||||
if ($manageTransactions) {
|
|
||||||
$db->commit();
|
|
||||||
$commited = true;
|
|
||||||
}
|
|
||||||
return $count;
|
return $count;
|
||||||
} finally {
|
|
||||||
if ($manageTransactions && !$commited) $db->rollback();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function discharge(bool $reset=true): Traversable {
|
||||||
* supprimer tous les éléments correspondant au filtre et pour lesquels la
|
return $this->storage->_discharge($this->channel, $reset);
|
||||||
* fonction retourne une valeur vraie si elle est spécifiée
|
|
||||||
*
|
|
||||||
* $filter permet de filtrer parmi les élements chargés
|
|
||||||
*
|
|
||||||
* $func est appelé avec la signature de {@link CapacitorChannel::onDelete()}
|
|
||||||
* si la fonction retourne un tableau, il est utilisé pour mettre à jour la
|
|
||||||
* ligne
|
|
||||||
*
|
|
||||||
* @return int le nombre de lignes parcourues
|
|
||||||
*/
|
|
||||||
function delete(CapacitorChannel $channel, $filter, $func=null, ?array $args=null): int {
|
|
||||||
$channel->initCapacitor($this);
|
|
||||||
|
|
||||||
if ($func === null) $func = CapacitorChannel::onDelete;
|
|
||||||
$onDelete = func::with($func)->bind($channel);
|
|
||||||
$db = $this->db();
|
|
||||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
|
||||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
|
||||||
if ($manageTransactions) {
|
|
||||||
$commited = false;
|
|
||||||
$db->beginTransaction();
|
|
||||||
$commitThreshold = $channel->getEachCommitThreshold();
|
|
||||||
}
|
|
||||||
$count = 0;
|
|
||||||
$tableName = $channel->getTableName();
|
|
||||||
try {
|
|
||||||
$args ??= [];
|
|
||||||
$rows = $this->all($channel, $filter);
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$rowIds = $this->getRowIds($channel, $row);
|
|
||||||
$shouldDelete = boolval($onDelete->invoke([$row, ...$args]));
|
|
||||||
if ($shouldDelete) {
|
|
||||||
$db->exec([
|
|
||||||
"delete",
|
|
||||||
"from" => $tableName,
|
|
||||||
"where" => $rowIds,
|
|
||||||
]);
|
|
||||||
if ($manageTransactions && $commitThreshold !== null) {
|
|
||||||
$commitThreshold--;
|
|
||||||
if ($commitThreshold <= 0) {
|
|
||||||
$db->commit();
|
|
||||||
$db->beginTransaction();
|
|
||||||
$commitThreshold = $channel->getEachCommitThreshold();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$count++;
|
|
||||||
}
|
|
||||||
if ($manageTransactions) {
|
|
||||||
$db->commit();
|
|
||||||
$commited = true;
|
|
||||||
}
|
|
||||||
return $count;
|
|
||||||
} finally {
|
|
||||||
if ($manageTransactions && !$commited) $db->rollback();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract function close(): void;
|
function count($filter=null): int {
|
||||||
|
return $this->storage->_count($this->channel, $filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
function one($filter, ?array $mergeQuery=null): ?array {
|
||||||
|
return $this->storage->_one($this->channel, $filter, $mergeQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
function all($filter, ?array $mergeQuery=null): Traversable {
|
||||||
|
return $this->storage->_all($this->channel, $filter, $mergeQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
function each($filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||||
|
if ($this->subChannels !== null) $this->beginTransaction();
|
||||||
|
return $this->storage->_each($this->channel, $filter, $func, $args, $mergeQuery, $nbUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete($filter, $func=null, ?array $args=null): int {
|
||||||
|
if ($this->subChannels !== null) $this->beginTransaction();
|
||||||
|
return $this->storage->_delete($this->channel, $filter, $func, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbAll(array $query, ?array $params=null): iterable {
|
||||||
|
$primaryKeys = $this->channel->getPrimaryKeys();
|
||||||
|
return $this->storage->db()->all(cl::merge([
|
||||||
|
"select",
|
||||||
|
"from" => $this->getTableName(),
|
||||||
|
], $query), $params, $primaryKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbOne(array $query, ?array $params=null): ?array {
|
||||||
|
return $this->storage->db()->one(cl::merge([
|
||||||
|
"select",
|
||||||
|
"from" => $this->getTableName(),
|
||||||
|
], $query), $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return int|false */
|
||||||
|
function dbUpdate(array $query, ?array $params=null) {
|
||||||
|
return $this->storage->db()->exec(cl::merge([
|
||||||
|
"update",
|
||||||
|
"table" => $this->getTableName(),
|
||||||
|
], $query), $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
$this->storage->close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
use nulib\app;
|
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\php\func;
|
|
||||||
use nulib\str;
|
use nulib\str;
|
||||||
use nulib\ValueException;
|
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class CapacitorChannel: un canal de données
|
* Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
|
||||||
*/
|
*/
|
||||||
class CapacitorChannel implements ITransactor {
|
class CapacitorChannel implements ITransactor {
|
||||||
const NAME = null;
|
const NAME = null;
|
||||||
|
|
||||||
const TABLE_NAME = null;
|
const TABLE_NAME = null;
|
||||||
|
|
||||||
const AUTOCREATE = null;
|
|
||||||
|
|
||||||
protected function COLUMN_DEFINITIONS(): ?array {
|
protected function COLUMN_DEFINITIONS(): ?array {
|
||||||
return static::COLUMN_DEFINITIONS;
|
return static::COLUMN_DEFINITIONS;
|
||||||
} const COLUMN_DEFINITIONS = null;
|
} const COLUMN_DEFINITIONS = null;
|
||||||
@ -55,21 +50,16 @@ class CapacitorChannel implements ITransactor {
|
|||||||
return $eachCommitThreshold;
|
return $eachCommitThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
function __construct(?array $params=null) {
|
function __construct(?string $name=null, ?int $eachCommitThreshold=null, ?bool $manageTransactions=null) {
|
||||||
$this->capacitor = null;
|
$name ??= static::NAME;
|
||||||
|
$tableName ??= static::TABLE_NAME;
|
||||||
$name = $params["name"] ?? static::NAME;
|
|
||||||
$tableName = $params["tableName"] ?? static::TABLE_NAME;
|
|
||||||
self::verifix_name($name, $tableName);
|
self::verifix_name($name, $tableName);
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->tableName = $tableName;
|
$this->tableName = $tableName;
|
||||||
|
$this->manageTransactions = $manageTransactions ?? static::MANAGE_TRANSACTIONS;
|
||||||
#$autocreate = $params["autocreate"] ?? null;
|
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
||||||
#$autocreate ??= !app::get()->isProductionMode();
|
|
||||||
$autocreate = true; #XXX
|
|
||||||
$this->created = !$autocreate;
|
|
||||||
$this->setup = false;
|
$this->setup = false;
|
||||||
|
$this->created = false;
|
||||||
$columnDefinitions = $this->COLUMN_DEFINITIONS();
|
$columnDefinitions = $this->COLUMN_DEFINITIONS();
|
||||||
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
|
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
|
||||||
$migration = cl::withn(static::MIGRATION);
|
$migration = cl::withn(static::MIGRATION);
|
||||||
@ -127,13 +117,6 @@ class CapacitorChannel implements ITransactor {
|
|||||||
$this->columnDefinitions = $columnDefinitions;
|
$this->columnDefinitions = $columnDefinitions;
|
||||||
$this->primaryKeys = $primaryKeys;
|
$this->primaryKeys = $primaryKeys;
|
||||||
$this->migration = $migration;
|
$this->migration = $migration;
|
||||||
|
|
||||||
$manageTransactions = $params["manageTransactions"] ?? static::MANAGE_TRANSACTIONS;
|
|
||||||
$this->manageTransactions = $manageTransactions;
|
|
||||||
|
|
||||||
$eachCommitThreshold = $params["eachCommitThreshold"] ?? null;
|
|
||||||
$eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
|
||||||
$this->eachCommitThreshold = $eachCommitThreshold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string $name;
|
protected string $name;
|
||||||
@ -148,6 +131,40 @@ class CapacitorChannel implements ITransactor {
|
|||||||
return $this->tableName;
|
return $this->tableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool indiquer si les modifications de each doivent être gérées dans
|
||||||
|
* une transaction. si false, l'utilisateur doit lui même gérer la
|
||||||
|
* transaction.
|
||||||
|
*/
|
||||||
|
protected bool $manageTransactions;
|
||||||
|
|
||||||
|
function isManageTransactions(): bool {
|
||||||
|
return $this->manageTransactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setManageTransactions(bool $manageTransactions=true): self {
|
||||||
|
$this->manageTransactions = $manageTransactions;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ?int nombre maximum de modifications dans une transaction avant un
|
||||||
|
* commit automatique dans {@link Capacitor::each()}. Utiliser null pour
|
||||||
|
* désactiver la fonctionnalité.
|
||||||
|
*
|
||||||
|
* ce paramètre n'a d'effet que si $manageTransactions==true
|
||||||
|
*/
|
||||||
|
protected ?int $eachCommitThreshold;
|
||||||
|
|
||||||
|
function getEachCommitThreshold(): ?int {
|
||||||
|
return $this->eachCommitThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEachCommitThreshold(?int $eachCommitThreshold=null): self {
|
||||||
|
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initialiser ce channel avant sa première utilisation.
|
* initialiser ce channel avant sa première utilisation.
|
||||||
*/
|
*/
|
||||||
@ -302,40 +319,6 @@ class CapacitorChannel implements ITransactor {
|
|||||||
return $sum !== $psum;
|
return $sum !== $psum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool indiquer si les modifications de each doivent être gérées dans
|
|
||||||
* une transaction. si false, l'utilisateur doit lui même gérer la
|
|
||||||
* transaction.
|
|
||||||
*/
|
|
||||||
protected bool $manageTransactions;
|
|
||||||
|
|
||||||
function isManageTransactions(): bool {
|
|
||||||
return $this->manageTransactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setManageTransactions(bool $manageTransactions=true): self {
|
|
||||||
$this->manageTransactions = $manageTransactions;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ?int nombre maximum de modifications dans une transaction avant un
|
|
||||||
* commit automatique dans {@link Capacitor::each()}. Utiliser null pour
|
|
||||||
* désactiver la fonctionnalité.
|
|
||||||
*
|
|
||||||
* ce paramètre n'a d'effet que si $manageTransactions==true
|
|
||||||
*/
|
|
||||||
protected ?int $eachCommitThreshold;
|
|
||||||
|
|
||||||
function getEachCommitThreshold(): ?int {
|
|
||||||
return $this->eachCommitThreshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setEachCommitThreshold(?int $eachCommitThreshold=null): self {
|
|
||||||
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* méthode appelée lors du chargement avec {@link Capacitor::charge()} pour
|
* méthode appelée lors du chargement avec {@link Capacitor::charge()} pour
|
||||||
* créer un nouvel élément
|
* créer un nouvel élément
|
||||||
@ -417,20 +400,24 @@ class CapacitorChannel implements ITransactor {
|
|||||||
#############################################################################
|
#############################################################################
|
||||||
# Méthodes déléguées pour des workflows centrés sur le channel
|
# Méthodes déléguées pour des workflows centrés sur le channel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Capacitor|null instance de Capacitor par laquelle cette instance est
|
||||||
|
* utilisée
|
||||||
|
*/
|
||||||
protected ?Capacitor $capacitor;
|
protected ?Capacitor $capacitor;
|
||||||
|
|
||||||
function getCapacitor(): ?Capacitor {
|
function getCapacitor(): ?Capacitor {
|
||||||
return $this->capacitor;
|
return $this->capacitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initCapacitor(Capacitor $capacitor, bool $autocreate=true): self {
|
function setCapacitor(Capacitor $capacitor): self {
|
||||||
if ($this->capacitor === null) $this->capacitor = $capacitor;
|
$this->capacitor = $capacitor;
|
||||||
if ($autocreate) $this->capacitor->autocreate($this);
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function db(): IDatabase {
|
function initStorage(CapacitorStorage $storage): self {
|
||||||
return $this->capacitor->db();
|
new Capacitor($storage, $this);
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureLive(): self {
|
function ensureLive(): self {
|
||||||
@ -438,117 +425,52 @@ class CapacitorChannel implements ITransactor {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCreateSql(): string {
|
function willUpdate(...$transactors): ITransactor {
|
||||||
return $this->capacitor->getMigration($this)->getSql(get_class($this), $this->db());
|
return $this->capacitor->willUpdate(...$transactors);
|
||||||
}
|
|
||||||
|
|
||||||
/** @var CapacitorChannel[] */
|
|
||||||
protected ?array $subChannels = null;
|
|
||||||
|
|
||||||
protected ?array $subManageTransactions = null;
|
|
||||||
|
|
||||||
function willUpdate(...$channels): self {
|
|
||||||
if ($this->subChannels === null) {
|
|
||||||
# désactiver la gestion des transaction sur le channel local aussi
|
|
||||||
$this->subChannels[] = $this;
|
|
||||||
}
|
|
||||||
if ($channels) {
|
|
||||||
foreach ($channels as $channel) {
|
|
||||||
if ($channel instanceof CapacitorChannel) {
|
|
||||||
$this->subChannels[] = $channel;
|
|
||||||
} else {
|
|
||||||
throw ValueException::invalid_type($channel, CapacitorChannel::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function inTransaction(): bool {
|
function inTransaction(): bool {
|
||||||
return $this->db()->inTransaction();
|
return $this->capacitor->inTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
function beginTransaction(?callable $func=null, bool $commit=true): void {
|
function beginTransaction(?callable $func=null, bool $commit=true): void {
|
||||||
$db = $this->db();
|
$this->capacitor->beginTransaction($func, $commit);
|
||||||
if ($this->subChannels !== null) {
|
|
||||||
# on gère des subchannels: ne débuter la transaction que si ce n'est déjà fait
|
|
||||||
if ($this->subManageTransactions === null) {
|
|
||||||
foreach ($this->subChannels as $channel) {
|
|
||||||
$name = $channel->getName();
|
|
||||||
$this->subManageTransactions ??= [];
|
|
||||||
if (!array_key_exists($name, $this->subManageTransactions)) {
|
|
||||||
$this->subManageTransactions[$name] = $channel->isManageTransactions();
|
|
||||||
}
|
|
||||||
$channel->setManageTransactions(false);
|
|
||||||
}
|
|
||||||
if (!$db->inTransaction()) $db->beginTransaction();
|
|
||||||
}
|
|
||||||
} elseif (!$db->inTransaction()) {
|
|
||||||
$db->beginTransaction();
|
|
||||||
}
|
|
||||||
if ($func !== null) {
|
|
||||||
$commited = false;
|
|
||||||
try {
|
|
||||||
func::call($func, $this);
|
|
||||||
if ($commit) {
|
|
||||||
$this->commit();
|
|
||||||
$commited = true;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if ($commit && !$commited) $this->rollback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function beforeEndTransaction(): void {
|
|
||||||
if ($this->subManageTransactions !== null) {
|
|
||||||
foreach ($this->subChannels as $channel) {
|
|
||||||
$name = $channel->getName();
|
|
||||||
$channel->setManageTransactions($this->subManageTransactions[$name]);
|
|
||||||
}
|
|
||||||
$this->subManageTransactions = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function commit(): void {
|
function commit(): void {
|
||||||
$this->beforeEndTransaction();
|
$this->capacitor->commit();
|
||||||
$db = $this->db();
|
|
||||||
if ($db->inTransaction()) $db->commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function rollback(): void {
|
function rollback(): void {
|
||||||
$this->beforeEndTransaction();
|
$this->capacitor->rollback();
|
||||||
$db = $this->db();
|
}
|
||||||
if ($db->inTransaction()) $db->rollback();
|
|
||||||
|
function db(): IDatabase {
|
||||||
|
return $this->capacitor->getStorage()->db();
|
||||||
}
|
}
|
||||||
|
|
||||||
function exists(): bool {
|
function exists(): bool {
|
||||||
return $this->capacitor->exists($this);
|
return $this->capacitor->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureExists(): void {
|
||||||
|
$this->capacitor->ensureExists();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset(bool $recreate=false): void {
|
function reset(bool $recreate=false): void {
|
||||||
$this->capacitor->reset($this, $recreate);
|
$this->capacitor->reset($recreate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function charge($item, $func=null, ?array $args=null, ?array &$row=null): int {
|
function charge($item, $func=null, ?array $args=null, ?array &$row=null): int {
|
||||||
return $this->capacitor->charge($this, $item, $func, $args, $row);
|
return $this->capacitor->charge($item, $func, $args, $row);
|
||||||
}
|
}
|
||||||
|
|
||||||
function chargeAll(?iterable $items, $func=null, ?array $args=null): int {
|
function chargeAll(?iterable $items, $func=null, ?array $args=null): int {
|
||||||
$count = 0;
|
return $this->capacitor->chargeAll($items, $func, $args);
|
||||||
if ($items !== null) {
|
|
||||||
if ($func !== null) {
|
|
||||||
$func = func::with($func, $args)->bind($this);
|
|
||||||
}
|
|
||||||
foreach ($items as $item) {
|
|
||||||
$count += $this->charge($item, $func);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function discharge(bool $reset=true): Traversable {
|
function discharge(bool $reset=true): Traversable {
|
||||||
return $this->capacitor->discharge($this, $reset);
|
return $this->capacitor->discharge($reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -574,50 +496,40 @@ class CapacitorChannel implements ITransactor {
|
|||||||
|
|
||||||
function count($filter=null): int {
|
function count($filter=null): int {
|
||||||
$this->verifixFilter($filter);
|
$this->verifixFilter($filter);
|
||||||
return $this->capacitor->count($this, $filter);
|
return $this->capacitor->count($filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function one($filter, ?array $mergeQuery=null): ?array {
|
function one($filter, ?array $mergeQuery=null): ?array {
|
||||||
$this->verifixFilter($filter);
|
$this->verifixFilter($filter);
|
||||||
return $this->capacitor->one($this, $filter, $mergeQuery);
|
return $this->capacitor->one($filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
function all($filter, ?array $mergeQuery=null): Traversable {
|
function all($filter, ?array $mergeQuery=null): Traversable {
|
||||||
$this->verifixFilter($filter);
|
$this->verifixFilter($filter);
|
||||||
return $this->capacitor->all($this, $filter, $mergeQuery);
|
return $this->capacitor->all($filter, $mergeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
function each($filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
function each($filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||||
$this->verifixFilter($filter);
|
$this->verifixFilter($filter);
|
||||||
return $this->capacitor->each($this, $filter, $func, $args, $mergeQuery, $nbUpdated);
|
return $this->capacitor->each($filter, $func, $args, $mergeQuery, $nbUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
function delete($filter, $func=null, ?array $args=null): int {
|
function delete($filter, $func=null, ?array $args=null): int {
|
||||||
$this->verifixFilter($filter);
|
$this->verifixFilter($filter);
|
||||||
return $this->capacitor->delete($this, $filter, $func, $args);
|
return $this->capacitor->delete($filter, $func, $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dbAll(array $query, ?array $params=null): iterable {
|
function dbAll(array $query, ?array $params=null): iterable {
|
||||||
$primaryKeys = $this->getPrimaryKeys();
|
return $this->capacitor->dbAll($query, $params);
|
||||||
return $this->capacitor->db()->all(cl::merge([
|
|
||||||
"select",
|
|
||||||
"from" => $this->getTableName(),
|
|
||||||
], $query), $params, $primaryKeys);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function dbOne(array $query, ?array $params=null): ?array {
|
function dbOne(array $query, ?array $params=null): ?array {
|
||||||
return $this->capacitor->db()->one(cl::merge([
|
return $this->capacitor->dbOne($query, $params);
|
||||||
"select",
|
|
||||||
"from" => $this->getTableName(),
|
|
||||||
], $query), $params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return int|false */
|
/** @return int|false */
|
||||||
function dbUpdate(array $query, ?array $params=null) {
|
function dbUpdate(array $query, ?array $params=null) {
|
||||||
return $this->capacitor->db()->exec(cl::merge([
|
return $this->capacitor->dbUpdate($query, $params);
|
||||||
"update",
|
|
||||||
"table" => $this->getTableName(),
|
|
||||||
], $query), $params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function close(): void {
|
function close(): void {
|
||||||
|
770
php/src/db/CapacitorStorage.php
Normal file
770
php/src/db/CapacitorStorage.php
Normal file
@ -0,0 +1,770 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db;
|
||||||
|
|
||||||
|
use nulib\A;
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\cv;
|
||||||
|
use nulib\db\_private\_migration;
|
||||||
|
use nulib\php\func;
|
||||||
|
use nulib\ValueException;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CapacitorStorage: objet permettant d'accumuler des données pour les
|
||||||
|
* réutiliser plus tard
|
||||||
|
*/
|
||||||
|
abstract class CapacitorStorage {
|
||||||
|
abstract function db(): IDatabase;
|
||||||
|
|
||||||
|
function ensureLive(): self {
|
||||||
|
$this->db()->ensure();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @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 {
|
||||||
|
CapacitorChannel::verifix_name($name);
|
||||||
|
$channel = $this->channels[$name] ?? null;
|
||||||
|
if ($channel === null) {
|
||||||
|
$channel = $this->addChannel(new CapacitorChannel($name));
|
||||||
|
}
|
||||||
|
return $channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRIMARY_KEY_DEFINITION = [
|
||||||
|
"id_" => "genserial",
|
||||||
|
];
|
||||||
|
|
||||||
|
# les définitions sont par défaut pour MariaDB/MySQL
|
||||||
|
const SERDATA_DEFINITION = "mediumtext";
|
||||||
|
const SERSUM_DEFINITION = "varchar(40)";
|
||||||
|
const SERTS_DEFINITION = "datetime";
|
||||||
|
const GENSERIAL_DEFINITION = "integer primary key auto_increment";
|
||||||
|
const GENLIC_DEFINITION = "varchar(80)";
|
||||||
|
const GENLIB_DEFINITION = "varchar(255)";
|
||||||
|
const GENTEXT_DEFINITION = "mediumtext";
|
||||||
|
const GENBOOL_DEFINITION = "integer(1) default 0";
|
||||||
|
const GENUUID_DEFINITION = "varchar(36)";
|
||||||
|
|
||||||
|
protected static function gencol($def): string {
|
||||||
|
if (!is_string($def)) $def = strval($def);
|
||||||
|
$def = trim($def);
|
||||||
|
$parts = preg_split('/\s+/', $def, 2);
|
||||||
|
if (count($parts) == 2) {
|
||||||
|
$def = $parts[0];
|
||||||
|
$rest = " $parts[1]";
|
||||||
|
} else {
|
||||||
|
$rest = null;
|
||||||
|
}
|
||||||
|
switch ($def) {
|
||||||
|
case "serdata": $def = static::SERDATA_DEFINITION; break;
|
||||||
|
case "sersum": $def = static::SERSUM_DEFINITION; break;
|
||||||
|
case "serts": $def = static::SERTS_DEFINITION; break;
|
||||||
|
case "genserial": $def = static::GENSERIAL_DEFINITION; break;
|
||||||
|
case "genlic": $def = static::GENLIC_DEFINITION; break;
|
||||||
|
case "genlib": $def = static::GENLIB_DEFINITION; break;
|
||||||
|
case "gentext": $def = static::GENTEXT_DEFINITION; break;
|
||||||
|
case "genbool": $def = static::GENBOOL_DEFINITION; break;
|
||||||
|
case "genuuid": $def = static::GENUUID_DEFINITION; break;
|
||||||
|
}
|
||||||
|
return "$def$rest";
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLUMN_DEFINITIONS = [
|
||||||
|
"item__" => "serdata",
|
||||||
|
"item__sum_" => "sersum",
|
||||||
|
"created_" => "serts",
|
||||||
|
"modified_" => "serts",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function ColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
|
||||||
|
$definitions = [];
|
||||||
|
if ($channel->getPrimaryKeys() === null) {
|
||||||
|
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
||||||
|
}
|
||||||
|
$definitions[] = $channel->getColumnDefinitions();
|
||||||
|
$definitions[] = static::COLUMN_DEFINITIONS;
|
||||||
|
# forcer les définitions sans clé à la fin (sqlite requière par exemple que
|
||||||
|
# primary key (columns) soit à la fin)
|
||||||
|
$tmp = cl::merge(...$definitions);
|
||||||
|
$definitions = [];
|
||||||
|
$constraints = [];
|
||||||
|
$index = 0;
|
||||||
|
foreach ($tmp as $col => $def) {
|
||||||
|
if ($col === $index) {
|
||||||
|
$index++;
|
||||||
|
if (is_array($def)) {
|
||||||
|
if (!$ignoreMigrations) {
|
||||||
|
$mdefs = $def;
|
||||||
|
$mindex = 0;
|
||||||
|
foreach ($mdefs as $mcol => $mdef) {
|
||||||
|
if ($mcol === $mindex) {
|
||||||
|
$mindex++;
|
||||||
|
} else {
|
||||||
|
if ($mdef) {
|
||||||
|
$definitions[$mcol] = self::gencol($mdef);
|
||||||
|
} else {
|
||||||
|
unset($definitions[$mcol]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$constraints[] = $def;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$definitions[$col] = self::gencol($def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cl::merge($definitions, $constraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getMigration(CapacitorChannel $channel): ?array {
|
||||||
|
return $channel->getMigration($this->db()->getPrefix());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** sérialiser les valeurs qui doivent l'être dans $row */
|
||||||
|
protected function serialize(CapacitorChannel $channel, ?array $row): ?array {
|
||||||
|
if ($row === null) return null;
|
||||||
|
$cols = $this->ColumnDefinitions($channel);
|
||||||
|
$index = 0;
|
||||||
|
$raw = [];
|
||||||
|
foreach (array_keys($cols) as $col) {
|
||||||
|
$key = $col;
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
} elseif ($channel->isSerialCol($key)) {
|
||||||
|
[$serialCol, $sumCol] = $channel->getSumCols($key);
|
||||||
|
if (array_key_exists($key, $row)) {
|
||||||
|
$sum = $channel->getSum($key, $row[$key]);
|
||||||
|
$raw[$serialCol] = $sum[$serialCol];
|
||||||
|
if (array_key_exists($sumCol, $cols)) {
|
||||||
|
$raw[$sumCol] = $sum[$sumCol];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (array_key_exists($key, $row)) {
|
||||||
|
$raw[$col] = $row[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** désérialiser les valeurs qui doivent l'être dans $values */
|
||||||
|
protected function unserialize(CapacitorChannel $channel, ?array $raw): ?array {
|
||||||
|
if ($raw === null) return null;
|
||||||
|
$cols = $this->ColumnDefinitions($channel);
|
||||||
|
$index = 0;
|
||||||
|
$row = [];
|
||||||
|
foreach (array_keys($cols) as $col) {
|
||||||
|
$key = $col;
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
} elseif (!array_key_exists($col, $raw)) {
|
||||||
|
} elseif ($channel->isSerialCol($key)) {
|
||||||
|
$value = $raw[$col];
|
||||||
|
if ($value !== null) $value = $channel->unserialize($value);
|
||||||
|
$row[$key] = $value;
|
||||||
|
} else {
|
||||||
|
$row[$key] = $raw[$col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrimaryKeys(CapacitorChannel $channel): array {
|
||||||
|
$primaryKeys = $channel->getPrimaryKeys();
|
||||||
|
if ($primaryKeys === null) $primaryKeys = ["id_"];
|
||||||
|
return $primaryKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRowIds(CapacitorChannel $channel, ?array $row, ?array &$primaryKeys=null): ?array {
|
||||||
|
$primaryKeys = $this->getPrimaryKeys($channel);
|
||||||
|
$rowIds = cl::select($row, $primaryKeys);
|
||||||
|
if (cl::all_n($rowIds)) return null;
|
||||||
|
else return $rowIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _createSql(CapacitorChannel $channel): array {
|
||||||
|
return [
|
||||||
|
"create table if not exists",
|
||||||
|
"table" => $channel->getTableName(),
|
||||||
|
"cols" => $this->ColumnDefinitions($channel, true),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function tableExists(string $tableName): bool;
|
||||||
|
|
||||||
|
const METADATA_TABLE = "_metadata";
|
||||||
|
const METADATA_COLS = [
|
||||||
|
"name" => "varchar not null primary key",
|
||||||
|
"value" => "varchar",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function _prepareMetadata(): void {
|
||||||
|
if (!$this->tableExists(static::METADATA_TABLE)) {
|
||||||
|
$db = $this->db();
|
||||||
|
$db->exec([
|
||||||
|
"drop table if exists",
|
||||||
|
"table" => self::CHANNELS_TABLE,
|
||||||
|
]);
|
||||||
|
$db->exec([
|
||||||
|
"drop table if exists",
|
||||||
|
"table" => _migration::MIGRATION_TABLE,
|
||||||
|
]);
|
||||||
|
$db->exec([
|
||||||
|
"create table",
|
||||||
|
"table" => static::METADATA_TABLE,
|
||||||
|
"cols" => static::METADATA_COLS,
|
||||||
|
]);
|
||||||
|
$db->exec([
|
||||||
|
"insert",
|
||||||
|
"into" => static::METADATA_TABLE,
|
||||||
|
"values" => [
|
||||||
|
"name" => "version",
|
||||||
|
"value" => "1",
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract function _getMigration(CapacitorChannel $channel): _migration;
|
||||||
|
|
||||||
|
const CHANNELS_TABLE = "_channels";
|
||||||
|
const CHANNELS_COLS = [
|
||||||
|
"name" => "varchar not null primary key",
|
||||||
|
"table_name" => "varchar",
|
||||||
|
"class_name" => "varchar",
|
||||||
|
];
|
||||||
|
|
||||||
|
function channelExists(string $name, ?array &$raw=null): bool {
|
||||||
|
$raw = $this->db()->one([
|
||||||
|
"select",
|
||||||
|
"from" => static::CHANNELS_TABLE,
|
||||||
|
"where" => ["name" => $name],
|
||||||
|
]);
|
||||||
|
return $raw !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannels(): iterable {
|
||||||
|
return $this->db()->all([
|
||||||
|
"select",
|
||||||
|
"from" => static::CHANNELS_TABLE,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _createChannelsSql(): array {
|
||||||
|
return [
|
||||||
|
"create table if not exists",
|
||||||
|
"table" => static::CHANNELS_TABLE,
|
||||||
|
"cols" => static::CHANNELS_COLS,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||||
|
return [
|
||||||
|
"insert",
|
||||||
|
"into" => static::CHANNELS_TABLE,
|
||||||
|
"values" => [
|
||||||
|
"name" => $channel->getName(),
|
||||||
|
"table_name" => $channel->getTableName(),
|
||||||
|
"class_name" => get_class($channel),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||||
|
$db = $this->db();
|
||||||
|
$db->exec($this->_createChannelsSql());
|
||||||
|
$db->exec($this->_addToChannelsSql($channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _create(CapacitorChannel $channel): void {
|
||||||
|
$channel->ensureSetup();
|
||||||
|
if (!$channel->isCreated()) {
|
||||||
|
$this->_prepareMetadata();
|
||||||
|
$this->_getMigration($channel)->migrate($this->db());
|
||||||
|
$this->_afterCreate($channel);
|
||||||
|
$channel->setCreated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** tester si le canal spécifié existe */
|
||||||
|
function _exists(CapacitorChannel $channel): bool {
|
||||||
|
return $this->tableExists($channel->getTableName());
|
||||||
|
}
|
||||||
|
|
||||||
|
function exists(?string $channel): bool {
|
||||||
|
return $this->_exists($this->getChannel($channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** s'assurer que le canal spécifié existe */
|
||||||
|
function _ensureExists(CapacitorChannel $channel): void {
|
||||||
|
$this->_create($channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureExists(?string $channel): void {
|
||||||
|
$this->_ensureExists($this->getChannel($channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _beforeReset(CapacitorChannel $channel): void {
|
||||||
|
$db = $this->db;
|
||||||
|
$name = $channel->getName();
|
||||||
|
$db->exec([
|
||||||
|
"delete",
|
||||||
|
"from" => _migration::MIGRATION_TABLE,
|
||||||
|
"where" => [
|
||||||
|
"channel" => $name,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$db->exec([
|
||||||
|
"delete",
|
||||||
|
"from" => static::CHANNELS_TABLE,
|
||||||
|
"where" => [
|
||||||
|
"name" => $name,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** supprimer le canal spécifié */
|
||||||
|
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
|
||||||
|
$this->_beforeReset($channel);
|
||||||
|
$this->db()->exec([
|
||||||
|
"drop table if exists",
|
||||||
|
$channel->getTableName(),
|
||||||
|
]);
|
||||||
|
$channel->setCreated(false);
|
||||||
|
if ($recreate) $this->_ensureExists($channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset(?string $channel, bool $recreate=false): void {
|
||||||
|
$this->_reset($this->getChannel($channel), $recreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* charger une valeur dans le canal
|
||||||
|
*
|
||||||
|
* Après avoir calculé les valeurs des clés supplémentaires
|
||||||
|
* avec {@link CapacitorChannel::getItemValues()}, l'une des deux fonctions
|
||||||
|
* {@link CapacitorChannel::onCreate()} ou {@link CapacitorChannel::onUpdate()}
|
||||||
|
* est appelée en fonction du type d'opération: création ou mise à jour
|
||||||
|
*
|
||||||
|
* Ensuite, si $func !== null, la fonction est appelée avec la signature de
|
||||||
|
* {@link CapacitorChannel::onCreate()} ou {@link CapacitorChannel::onUpdate()}
|
||||||
|
* en fonction du type d'opération: création ou mise à jour
|
||||||
|
*
|
||||||
|
* Dans les deux cas, si la fonction retourne un tableau, il est utilisé pour
|
||||||
|
* modifier les valeurs insérées/mises à jour. De plus, $row obtient la
|
||||||
|
* valeur finale des données insérées/mises à jour
|
||||||
|
*
|
||||||
|
* Si $args est renseigné, il est ajouté aux arguments utilisés pour appeler
|
||||||
|
* les méthodes {@link CapacitorChannel::getItemValues()},
|
||||||
|
* {@link CapacitorChannel::onCreate()} et/ou
|
||||||
|
* {@link CapacitorChannel::onUpdate()}
|
||||||
|
*
|
||||||
|
* @return int 1 si l'objet a été chargé ou mis à jour, 0 s'il existait
|
||||||
|
* déjà à l'identique dans le canal
|
||||||
|
*/
|
||||||
|
function _charge(CapacitorChannel $channel, $item, $func, ?array $args, ?array &$row=null): int {
|
||||||
|
$this->_create($channel);
|
||||||
|
$tableName = $channel->getTableName();
|
||||||
|
$db = $this->db();
|
||||||
|
$args ??= [];
|
||||||
|
|
||||||
|
$row = func::call([$channel, "getItemValues"], $item, ...$args);
|
||||||
|
if ($row === [false]) return 0;
|
||||||
|
|
||||||
|
if ($row !== null && array_key_exists("item", $row)) {
|
||||||
|
$item = A::pop($row, "item");
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw = cl::merge(
|
||||||
|
$channel->getSum("item", $item),
|
||||||
|
$this->serialize($channel, $row));
|
||||||
|
$praw = null;
|
||||||
|
$rowIds = $this->getRowIds($channel, $raw, $primaryKeys);
|
||||||
|
if ($rowIds !== null) {
|
||||||
|
# modification
|
||||||
|
$praw = $db->one([
|
||||||
|
"select",
|
||||||
|
"from" => $tableName,
|
||||||
|
"where" => $rowIds,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = date("Y-m-d H:i:s");
|
||||||
|
$insert = null;
|
||||||
|
if ($praw === null) {
|
||||||
|
# création
|
||||||
|
$raw = cl::merge($raw, [
|
||||||
|
"created_" => $now,
|
||||||
|
"modified_" => $now,
|
||||||
|
]);
|
||||||
|
$insert = true;
|
||||||
|
$initFunc = func::with([$channel, "onCreate"], $args);
|
||||||
|
$row = $this->unserialize($channel, $raw);
|
||||||
|
$prow = null;
|
||||||
|
} else {
|
||||||
|
# modification
|
||||||
|
# intégrer autant que possible les valeurs de praw dans raw, de façon que
|
||||||
|
# l'utilisateur puisse voir clairement ce qui a été modifié
|
||||||
|
if ($channel->_wasSumModified("item", $raw, $praw)) {
|
||||||
|
$insert = false;
|
||||||
|
$raw = cl::merge($praw, $raw, [
|
||||||
|
"modified_" => $now,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$raw = cl::merge($praw, $raw);
|
||||||
|
}
|
||||||
|
$initFunc = func::with([$channel, "onUpdate"], $args);
|
||||||
|
$row = $this->unserialize($channel, $raw);
|
||||||
|
$prow = $this->unserialize($channel, $praw);
|
||||||
|
}
|
||||||
|
|
||||||
|
$updates = $initFunc->prependArgs([$item, $row, $prow])->invoke();
|
||||||
|
if ($updates === [false]) return 0;
|
||||||
|
if (is_array($updates) && $updates) {
|
||||||
|
if ($insert === null) $insert = false;
|
||||||
|
if (!array_key_exists("modified_", $updates)) {
|
||||||
|
$updates["modified_"] = $now;
|
||||||
|
}
|
||||||
|
$row = cl::merge($row, $updates);
|
||||||
|
$raw = cl::merge($raw, $this->serialize($channel, $updates));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($func !== null) {
|
||||||
|
$updates = func::with($func, $args)
|
||||||
|
->prependArgs([$item, $row, $prow])
|
||||||
|
->bind($channel)
|
||||||
|
->invoke();
|
||||||
|
if ($updates === [false]) return 0;
|
||||||
|
if (is_array($updates) && $updates) {
|
||||||
|
if ($insert === null) $insert = false;
|
||||||
|
if (!array_key_exists("modified_", $updates)) {
|
||||||
|
$updates["modified_"] = $now;
|
||||||
|
}
|
||||||
|
$row = cl::merge($row, $updates);
|
||||||
|
$raw = cl::merge($raw, $this->serialize($channel, $updates));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# aucune modification
|
||||||
|
if ($insert === null) return 0;
|
||||||
|
|
||||||
|
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||||
|
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||||
|
if ($manageTransactions) {
|
||||||
|
$commited = false;
|
||||||
|
$db->beginTransaction();
|
||||||
|
}
|
||||||
|
$nbModified = 0;
|
||||||
|
try {
|
||||||
|
if ($insert) {
|
||||||
|
$id = $db->exec([
|
||||||
|
"insert",
|
||||||
|
"into" => $tableName,
|
||||||
|
"values" => $raw,
|
||||||
|
]);
|
||||||
|
if (count($primaryKeys) == 1 && $rowIds === null) {
|
||||||
|
# mettre à jour avec l'id généré
|
||||||
|
$row[$primaryKeys[0]] = $id;
|
||||||
|
}
|
||||||
|
$nbModified = 1;
|
||||||
|
} else {
|
||||||
|
# calculer ce qui a changé pour ne mettre à jour que le nécessaire
|
||||||
|
$updates = [];
|
||||||
|
foreach ($raw as $col => $value) {
|
||||||
|
if (array_key_exists($col, $rowIds)) {
|
||||||
|
# ne jamais mettre à jour la clé primaire
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!cv::equals($value, $praw[$col] ?? null)) {
|
||||||
|
$updates[$col] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($updates) == 1 && array_key_first($updates) == "modified_") {
|
||||||
|
# si l'unique modification porte sur la date de modification, alors
|
||||||
|
# la ligne n'est pas modifiée. ce cas se présente quand on altère la
|
||||||
|
# valeur de $item
|
||||||
|
$updates = null;
|
||||||
|
}
|
||||||
|
if ($updates) {
|
||||||
|
$db->exec([
|
||||||
|
"update",
|
||||||
|
"table" => $tableName,
|
||||||
|
"values" => $updates,
|
||||||
|
"where" => $rowIds,
|
||||||
|
]);
|
||||||
|
$nbModified = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($manageTransactions) {
|
||||||
|
$db->commit();
|
||||||
|
$commited = true;
|
||||||
|
}
|
||||||
|
return $nbModified;
|
||||||
|
} finally {
|
||||||
|
if ($manageTransactions && !$commited) $db->rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function charge(?string $channel, $item, $func=null, ?array $args=null, ?array &$row=null): int {
|
||||||
|
return $this->_charge($this->getChannel($channel), $item, $func, $args, $row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* décharger les données du canal spécifié. seul la valeur de $item est
|
||||||
|
* fournie
|
||||||
|
*/
|
||||||
|
function _discharge(CapacitorChannel $channel, bool $reset=true): Traversable {
|
||||||
|
$this->_create($channel);
|
||||||
|
$raws = $this->db()->all([
|
||||||
|
"select item__",
|
||||||
|
"from" => $channel->getTableName(),
|
||||||
|
]);
|
||||||
|
foreach ($raws as $raw) {
|
||||||
|
yield unserialize($raw['item__']);
|
||||||
|
}
|
||||||
|
if ($reset) $this->_reset($channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function discharge(?string $channel, bool $reset=true): Traversable {
|
||||||
|
return $this->_discharge($this->getChannel($channel), $reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _convertValue2row(CapacitorChannel $channel, array $filter, array $cols): array {
|
||||||
|
$index = 0;
|
||||||
|
$fixed = [];
|
||||||
|
foreach ($filter as $key => $value) {
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
if (is_array($value)) {
|
||||||
|
$value = $this->_convertValue2row($channel, $value, $cols);
|
||||||
|
}
|
||||||
|
$fixed[] = $value;
|
||||||
|
} else {
|
||||||
|
$col = "${key}__";
|
||||||
|
if (array_key_exists($col, $cols)) {
|
||||||
|
# colonne sérialisée
|
||||||
|
$fixed[$col] = $channel->serialize($value);
|
||||||
|
} else {
|
||||||
|
$fixed[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function verifixFilter(CapacitorChannel $channel, &$filter): void {
|
||||||
|
if ($filter !== null && !is_array($filter)) {
|
||||||
|
$primaryKeys = $this->getPrimaryKeys($channel);
|
||||||
|
$id = $filter;
|
||||||
|
$channel->verifixId($id);
|
||||||
|
$filter = [$primaryKeys[0] => $id];
|
||||||
|
}
|
||||||
|
$cols = $this->ColumnDefinitions($channel);
|
||||||
|
if ($filter !== null) {
|
||||||
|
$filter = $this->_convertValue2row($channel, $filter, $cols);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** indiquer le nombre d'éléments du canal spécifié */
|
||||||
|
function _count(CapacitorChannel $channel, $filter): int {
|
||||||
|
$this->_create($channel);
|
||||||
|
$this->verifixFilter($channel, $filter);
|
||||||
|
return $this->db()->get([
|
||||||
|
"select count(*)",
|
||||||
|
"from" => $channel->getTableName(),
|
||||||
|
"where" => $filter,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function count(?string $channel, $filter=null): int {
|
||||||
|
return $this->_count($this->getChannel($channel), $filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* obtenir la ligne correspondant au filtre sur le canal spécifié
|
||||||
|
*
|
||||||
|
* si $filter n'est pas un tableau, il est transformé en ["id_" => $filter]
|
||||||
|
*/
|
||||||
|
function _one(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): ?array {
|
||||||
|
if ($filter === null) throw ValueException::null("filter");
|
||||||
|
$this->_create($channel);
|
||||||
|
$this->verifixFilter($channel, $filter);
|
||||||
|
$raw = $this->db()->one(cl::merge([
|
||||||
|
"select",
|
||||||
|
"from" => $channel->getTableName(),
|
||||||
|
"where" => $filter,
|
||||||
|
], $mergeQuery));
|
||||||
|
return $this->unserialize($channel, $raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
function one(?string $channel, $filter, ?array $mergeQuery=null): ?array {
|
||||||
|
return $this->_one($this->getChannel($channel), $filter, $mergeQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
$this->_create($channel);
|
||||||
|
$this->verifixFilter($channel, $filter);
|
||||||
|
$raws = $this->db()->all(cl::merge([
|
||||||
|
"select",
|
||||||
|
"from" => $channel->getTableName(),
|
||||||
|
"where" => $filter,
|
||||||
|
], $mergeQuery), null, $this->getPrimaryKeys($channel));
|
||||||
|
foreach ($raws as $key => $raw) {
|
||||||
|
yield $key => $this->unserialize($channel, $raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function all(?string $channel, $filter, $mergeQuery=null): Traversable {
|
||||||
|
return $this->_all($this->getChannel($channel), $filter, $mergeQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* appeler une fonction pour chaque élément du canal spécifié.
|
||||||
|
*
|
||||||
|
* $filter permet de filtrer parmi les élements chargés
|
||||||
|
*
|
||||||
|
* $func est appelé avec la signature de {@link CapacitorChannel::onEach()}
|
||||||
|
* si la fonction retourne un tableau, il est utilisé pour mettre à jour la
|
||||||
|
* ligne
|
||||||
|
*
|
||||||
|
* @param int $nbUpdated reçoit le nombre de lignes mises à jour
|
||||||
|
* @return int le nombre de lignes parcourues
|
||||||
|
*/
|
||||||
|
function _each(CapacitorChannel $channel, $filter, $func, ?array $args, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||||
|
$this->_create($channel);
|
||||||
|
if ($func === null) $func = CapacitorChannel::onEach;
|
||||||
|
$onEach = func::with($func)->bind($channel);
|
||||||
|
$db = $this->db();
|
||||||
|
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||||
|
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||||
|
if ($manageTransactions) {
|
||||||
|
$commited = false;
|
||||||
|
$db->beginTransaction();
|
||||||
|
$commitThreshold = $channel->getEachCommitThreshold();
|
||||||
|
}
|
||||||
|
$count = 0;
|
||||||
|
$nbUpdated = 0;
|
||||||
|
$tableName = $channel->getTableName();
|
||||||
|
try {
|
||||||
|
$args ??= [];
|
||||||
|
$rows = $this->_all($channel, $filter, $mergeQuery);
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$rowIds = $this->getRowIds($channel, $row);
|
||||||
|
$updates = $onEach->invoke([$row, ...$args]);
|
||||||
|
if ($updates === [false]) {
|
||||||
|
break;
|
||||||
|
} elseif ($updates !== null) {
|
||||||
|
if (!array_key_exists("modified_", $updates)) {
|
||||||
|
$updates["modified_"] = date("Y-m-d H:i:s");
|
||||||
|
}
|
||||||
|
$nbUpdated += $db->exec([
|
||||||
|
"update",
|
||||||
|
"table" => $tableName,
|
||||||
|
"values" => $this->serialize($channel, $updates),
|
||||||
|
"where" => $rowIds,
|
||||||
|
]);
|
||||||
|
if ($manageTransactions && $commitThreshold !== null) {
|
||||||
|
$commitThreshold--;
|
||||||
|
if ($commitThreshold <= 0) {
|
||||||
|
$db->commit();
|
||||||
|
$db->beginTransaction();
|
||||||
|
$commitThreshold = $channel->getEachCommitThreshold();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
if ($manageTransactions) {
|
||||||
|
$db->commit();
|
||||||
|
$commited = true;
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
} finally {
|
||||||
|
if ($manageTransactions && !$commited) $db->rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function each(?string $channel, $filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||||
|
return $this->_each($this->getChannel($channel), $filter, $func, $args, $mergeQuery, $nbUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* supprimer tous les éléments correspondant au filtre et pour lesquels la
|
||||||
|
* fonction retourne une valeur vraie si elle est spécifiée
|
||||||
|
*
|
||||||
|
* $filter permet de filtrer parmi les élements chargés
|
||||||
|
*
|
||||||
|
* $func est appelé avec la signature de {@link CapacitorChannel::onDelete()}
|
||||||
|
* si la fonction retourne un tableau, il est utilisé pour mettre à jour la
|
||||||
|
* ligne
|
||||||
|
*
|
||||||
|
* @return int le nombre de lignes parcourues
|
||||||
|
*/
|
||||||
|
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
|
||||||
|
$this->_create($channel);
|
||||||
|
if ($func === null) $func = CapacitorChannel::onDelete;
|
||||||
|
$onDelete = func::with($func)->bind($channel);
|
||||||
|
$db = $this->db();
|
||||||
|
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||||
|
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||||
|
if ($manageTransactions) {
|
||||||
|
$commited = false;
|
||||||
|
$db->beginTransaction();
|
||||||
|
$commitThreshold = $channel->getEachCommitThreshold();
|
||||||
|
}
|
||||||
|
$count = 0;
|
||||||
|
$tableName = $channel->getTableName();
|
||||||
|
try {
|
||||||
|
$args ??= [];
|
||||||
|
$rows = $this->_all($channel, $filter);
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$rowIds = $this->getRowIds($channel, $row);
|
||||||
|
$shouldDelete = boolval($onDelete->invoke([$row, ...$args]));
|
||||||
|
if ($shouldDelete) {
|
||||||
|
$db->exec([
|
||||||
|
"delete",
|
||||||
|
"from" => $tableName,
|
||||||
|
"where" => $rowIds,
|
||||||
|
]);
|
||||||
|
if ($manageTransactions && $commitThreshold !== null) {
|
||||||
|
$commitThreshold--;
|
||||||
|
if ($commitThreshold <= 0) {
|
||||||
|
$db->commit();
|
||||||
|
$db->beginTransaction();
|
||||||
|
$commitThreshold = $channel->getEachCommitThreshold();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
if ($manageTransactions) {
|
||||||
|
$db->commit();
|
||||||
|
$commited = true;
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
} finally {
|
||||||
|
if ($manageTransactions && !$commited) $db->rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete(?string $channel, $filter, $func=null, ?array $args=null): int {
|
||||||
|
return $this->_delete($this->getChannel($channel), $filter, $func, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract function close(): void;
|
||||||
|
}
|
@ -1,11 +1,5 @@
|
|||||||
# db
|
|
||||||
|
|
||||||
# db/Capacitor
|
# db/Capacitor
|
||||||
|
|
||||||
La source peut être un iterable
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
charge() permet de spécifier la clé associée avec la valeur chargée, et
|
charge() permet de spécifier la clé associée avec la valeur chargée, et
|
||||||
discharge() retourne les valeurs avec la clé primaire
|
discharge() retourne les valeurs avec la clé primaire
|
||||||
|
|
||||||
|
@ -25,11 +25,14 @@ class conds {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* retourner une condition "like" si la valeur s'y prête
|
* retourner une condition "like" si la valeur s'y prête
|
||||||
|
*
|
||||||
* - si la valeur fait moins de $likeThreshold caractères, faire une recherche
|
* - si la valeur fait moins de $likeThreshold caractères, faire une recherche
|
||||||
* exacte en retournant ["=", $value]
|
* exacte en retournant ["=", $value]
|
||||||
|
*
|
||||||
|
*
|
||||||
* - les espaces sont remplacés par %
|
* - les espaces sont remplacés par %
|
||||||
* - si $partial et que $value ne contient pas d'espaces, rajouter un % à la
|
*
|
||||||
* fin
|
* si $partial
|
||||||
*/
|
*/
|
||||||
static function like($value, bool $partial=false, ?int $likeThreshold=null) {
|
static function like($value, bool $partial=false, ?int $likeThreshold=null) {
|
||||||
if ($value === false || $value === null) return $value;
|
if ($value === false || $value === null) return $value;
|
||||||
|
@ -3,12 +3,12 @@ namespace nulib\db\mysql;
|
|||||||
|
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\Capacitor;
|
use nulib\db\CapacitorStorage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class MysqlStorage
|
* Class MysqlStorage
|
||||||
*/
|
*/
|
||||||
class MysqlCapacitor extends Capacitor {
|
class MysqlStorage extends CapacitorStorage {
|
||||||
function __construct($mysql) {
|
function __construct($mysql) {
|
||||||
$this->db = Mysql::with($mysql);
|
$this->db = Mysql::with($mysql);
|
||||||
}
|
}
|
||||||
@ -36,21 +36,21 @@ class MysqlCapacitor extends Capacitor {
|
|||||||
"value" => "varchar(255)",
|
"value" => "varchar(255)",
|
||||||
];
|
];
|
||||||
|
|
||||||
function getMigration(CapacitorChannel $channel): _mysqlMigration {
|
function _getMigration(CapacitorChannel $channel): _mysqlMigration {
|
||||||
$migrations = cl::merge([
|
$migrations = cl::merge([
|
||||||
"0init" => [$this->getCreateChannelSql($channel)],
|
"0init" => [$this->_createSql($channel)],
|
||||||
], $channel->getMigration($this->db->getPrefix()));
|
], $channel->getMigration($this->db->getPrefix()));
|
||||||
return new _mysqlMigration($migrations, $channel->getName());
|
return new _mysqlMigration($migrations, $channel->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
const CATALOG_COLS = [
|
const CHANNELS_COLS = [
|
||||||
"name" => "varchar(255) not null primary key",
|
"name" => "varchar(255) not null primary key",
|
||||||
"table_name" => "varchar(64)",
|
"table_name" => "varchar(64)",
|
||||||
"class_name" => "varchar(255)",
|
"class_name" => "varchar(255)",
|
||||||
];
|
];
|
||||||
|
|
||||||
protected function addToCatalogSql(CapacitorChannel $channel): array {
|
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||||
return cl::merge(parent::addToCatalogSql($channel), [
|
return cl::merge(parent::_addToChannelsSql($channel), [
|
||||||
"suffix" => "on duplicate key update name = name",
|
"suffix" => "on duplicate key update name = name",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
@ -3,16 +3,16 @@ namespace nulib\db\pgsql;
|
|||||||
|
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\Capacitor;
|
use nulib\db\CapacitorStorage;
|
||||||
|
|
||||||
class PgsqlCapacitor extends Capacitor {
|
class PgsqlStorage extends CapacitorStorage {
|
||||||
const CDATA_DEFINITION = "text";
|
const SERDATA_DEFINITION = "text";
|
||||||
const CSUM_DEFINITION = "varchar(40)";
|
const SERSUM_DEFINITION = "varchar(40)";
|
||||||
const CTIMESTAMP_DEFINITION = "timestamp";
|
const SERTS_DEFINITION = "timestamp";
|
||||||
const GSERIAL_DEFINITION = "serial primary key";
|
const GENSERIAL_DEFINITION = "serial primary key";
|
||||||
const GTEXT_DEFINITION = "text";
|
const GENTEXT_DEFINITION = "text";
|
||||||
const GBOOL_DEFINITION = "boolean default false";
|
const GENBOOL_DEFINITION = "boolean default false";
|
||||||
const GUUID_DEFINITION = "uuid";
|
const GENUUID_DEFINITION = "uuid";
|
||||||
|
|
||||||
function __construct($pgsql) {
|
function __construct($pgsql) {
|
||||||
$this->db = Pgsql::with($pgsql);
|
$this->db = Pgsql::with($pgsql);
|
||||||
@ -41,15 +41,15 @@ class PgsqlCapacitor extends Capacitor {
|
|||||||
return $found !== null;
|
return $found !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMigration(CapacitorChannel $channel): _pgsqlMigration {
|
function _getMigration(CapacitorChannel $channel): _pgsqlMigration {
|
||||||
$migrations = cl::merge([
|
$migrations = cl::merge([
|
||||||
"0init" => [$this->getCreateChannelSql($channel)],
|
"0init" => [$this->_createSql($channel)],
|
||||||
], $channel->getMigration($this->db->getPrefix()));
|
], $channel->getMigration($this->db->getPrefix()));
|
||||||
return new _pgsqlMigration($migrations, $channel->getName());
|
return new _pgsqlMigration($migrations, $channel->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function addToCatalogSql(CapacitorChannel $channel): array {
|
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||||
return cl::merge(parent::addToCatalogSql($channel), [
|
return cl::merge(parent::_addToChannelsSql($channel), [
|
||||||
"suffix" => "on conflict (name) do nothing",
|
"suffix" => "on conflict (name) do nothing",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
@ -3,13 +3,13 @@ namespace nulib\db\sqlite;
|
|||||||
|
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\Capacitor;
|
use nulib\db\CapacitorStorage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SqliteStorage
|
* Class SqliteStorage
|
||||||
*/
|
*/
|
||||||
class SqliteCapacitor extends Capacitor {
|
class SqliteStorage extends CapacitorStorage {
|
||||||
const GSERIAL_DEFINITION = "integer primary key autoincrement";
|
const GENSERIAL_DEFINITION = "integer primary key autoincrement";
|
||||||
|
|
||||||
function __construct($sqlite) {
|
function __construct($sqlite) {
|
||||||
$this->db = Sqlite::with($sqlite);
|
$this->db = Sqlite::with($sqlite);
|
||||||
@ -31,30 +31,30 @@ class SqliteCapacitor extends Capacitor {
|
|||||||
return $found !== null;
|
return $found !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMigration(CapacitorChannel $channel): _sqliteMigration {
|
function _getMigration(CapacitorChannel $channel): _sqliteMigration {
|
||||||
$migrations = cl::merge([
|
$migrations = cl::merge([
|
||||||
"0init" => [$this->getCreateChannelSql($channel)],
|
"0init" => [$this->_createSql($channel)],
|
||||||
], $channel->getMigration($this->db->getPrefix()));
|
], $channel->getMigration($this->db->getPrefix()));
|
||||||
return new _sqliteMigration($migrations, $channel->getName());
|
return new _sqliteMigration($migrations, $channel->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function addToCatalogSql(CapacitorChannel $channel): array {
|
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||||
$sql = parent::addToCatalogSql($channel);
|
$sql = parent::_addToChannelsSql($channel);
|
||||||
$sql[0] = "insert or ignore";
|
$sql[0] = "insert or ignore";
|
||||||
return $sql;
|
return $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function afterCreate(CapacitorChannel $channel): void {
|
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||||
$db = $this->db;
|
$db = $this->db;
|
||||||
if (!$this->tableExists(static::CATALOG_TABLE)) {
|
if (!$this->tableExists(static::CHANNELS_TABLE)) {
|
||||||
# ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
|
# ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
|
||||||
# verrou en écriture
|
# verrou en écriture
|
||||||
$db->exec($this->getCreateCatalogSql());
|
$db->exec($this->_createChannelsSql());
|
||||||
}
|
}
|
||||||
if (!$this->isInCatalog(["name" => $channel->getName()])) {
|
if (!$this->channelExists($channel->getName())) {
|
||||||
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
||||||
# verrou en écriture
|
# verrou en écriture
|
||||||
$db->exec($this->addToCatalogSql($channel));
|
$db->exec($this->_addToChannelsSql($channel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ use nulib\cl;
|
|||||||
use nulib\db\Capacitor;
|
use nulib\db\Capacitor;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\mysql\Mysql;
|
use nulib\db\mysql\Mysql;
|
||||||
use nulib\db\mysql\MysqlCapacitor;
|
use nulib\db\mysql\MysqlStorage;
|
||||||
use nulib\output\msg;
|
use nulib\output\msg;
|
||||||
use nulib\output\std\StdMessenger;
|
use nulib\output\std\StdMessenger;
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ class MyChannel extends CapacitorChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new Capacitor(new MysqlCapacitor($db), $channel = new MyChannel());
|
new Capacitor(new MysqlStorage($db), $channel = new MyChannel());
|
||||||
|
|
||||||
$channel->charge("hello world");
|
$channel->charge("hello world");
|
||||||
$channel->charge(["bonjour monde"]);
|
$channel->charge(["bonjour monde"]);
|
||||||
|
@ -5,7 +5,7 @@ use nulib\cl;
|
|||||||
use nulib\db\Capacitor;
|
use nulib\db\Capacitor;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\pgsql\Pgsql;
|
use nulib\db\pgsql\Pgsql;
|
||||||
use nulib\db\pgsql\PgsqlCapacitor;
|
use nulib\db\pgsql\PgsqlStorage;
|
||||||
|
|
||||||
$db = new Pgsql([
|
$db = new Pgsql([
|
||||||
"host" => "pegase-dre.self",
|
"host" => "pegase-dre.self",
|
||||||
@ -34,7 +34,7 @@ class MyChannel extends CapacitorChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new Capacitor(new PgsqlCapacitor($db), $channel = new MyChannel());
|
new Capacitor(new PgsqlStorage($db), $channel = new MyChannel());
|
||||||
|
|
||||||
$channel->charge("hello world");
|
$channel->charge("hello world");
|
||||||
$channel->charge(["bonjour monde"]);
|
$channel->charge(["bonjour monde"]);
|
||||||
|
@ -5,7 +5,7 @@ use nulib\cl;
|
|||||||
use nulib\db\Capacitor;
|
use nulib\db\Capacitor;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\sqlite\Sqlite;
|
use nulib\db\sqlite\Sqlite;
|
||||||
use nulib\db\sqlite\SqliteCapacitor;
|
use nulib\db\sqlite\SqliteStorage;
|
||||||
|
|
||||||
$db = new Sqlite(__DIR__.'/test_sqlite.db');
|
$db = new Sqlite(__DIR__.'/test_sqlite.db');
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ class MyChannel extends CapacitorChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new Capacitor(new SqliteCapacitor($db), $channel = new MyChannel());
|
new Capacitor(new SqliteStorage($db), $channel = new MyChannel());
|
||||||
|
|
||||||
$channel->charge("hello world");
|
$channel->charge("hello world");
|
||||||
$channel->charge(["bonjour monde"]);
|
$channel->charge(["bonjour monde"]);
|
||||||
|
@ -30,23 +30,23 @@ class ChannelMigrationTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function testMigration() {
|
function testMigration() {
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$data = [
|
$data = [
|
||||||
["first", "premier", new DateTime(), new DateTime(), 15],
|
["first", "premier", new DateTime(), new DateTime(), 15],
|
||||||
["second", "deuxieme", new DateTime(), new DateTime(), 15],
|
["second", "deuxieme", new DateTime(), new DateTime(), 15],
|
||||||
];
|
];
|
||||||
|
|
||||||
$capacitor->newChannel($channel = new MyChannel());
|
new Capacitor($storage, $channel = new MyChannel());
|
||||||
$channel->reset(true);
|
$channel->reset(true);
|
||||||
$this->addData($channel, $data);
|
$this->addData($channel, $data);
|
||||||
|
|
||||||
$capacitor->newChannel($channel = new MyChannelV2());
|
new Capacitor($storage, $channel = new MyChannelV2());
|
||||||
$this->addData($channel, $data);
|
$this->addData($channel, $data);
|
||||||
|
|
||||||
$capacitor->newChannel($channel = new MyChannelV3());
|
new Capacitor($storage, $channel = new MyChannelV3());
|
||||||
$this->addData($channel, $data);
|
$this->addData($channel, $data);
|
||||||
|
|
||||||
$sql = $channel->getCreateSql();
|
$sql = $channel->getCapacitor()->getCreateSql();
|
||||||
$class = MyChannelV3::class;
|
$class = MyChannelV3::class;
|
||||||
$expected = <<<EOT
|
$expected = <<<EOT
|
||||||
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||||
@ -75,17 +75,17 @@ EOT;
|
|||||||
}
|
}
|
||||||
|
|
||||||
function testMigrationIndex() {
|
function testMigrationIndex() {
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$data = [
|
$data = [
|
||||||
["un", "premier", "first"],
|
["un", "premier", "first"],
|
||||||
["deux", "deuxieme", "second"],
|
["deux", "deuxieme", "second"],
|
||||||
];
|
];
|
||||||
|
|
||||||
$capacitor->newChannel($channel = new MyIndexChannel());
|
new Capacitor($storage, $channel = new MyIndexChannel());
|
||||||
$channel->reset(true);
|
$channel->reset(true);
|
||||||
$channel->chargeAll($data);
|
$channel->chargeAll($data);
|
||||||
|
|
||||||
$sql = $channel->getCreateSql();
|
$sql = $channel->getCapacitor()->getCreateSql();
|
||||||
$class = MyIndexChannel::class;
|
$class = MyIndexChannel::class;
|
||||||
$expected = <<<EOT
|
$expected = <<<EOT
|
||||||
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||||
|
@ -6,48 +6,38 @@ use nulib\cl;
|
|||||||
use nulib\db\Capacitor;
|
use nulib\db\Capacitor;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
|
|
||||||
class SqliteCapacitorTest extends TestCase {
|
class SqliteStorageTest extends TestCase {
|
||||||
static function Txx(...$values): void {
|
static function Txx(...$values): void {
|
||||||
foreach ($values as $value) {
|
foreach ($values as $value) {
|
||||||
var_export($value);
|
var_export($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _testChargeStrings(SqliteCapacitor $capacitor, CapacitorChannel $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 = cl::all($capacitor->discharge($channel, false));
|
$items = cl::all($storage->discharge($channel, false));
|
||||||
self::assertSame(["first", "second", "third"], $items);
|
self::assertSame(["first", "second", "third"], $items);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _testChargeArrays(SqliteCapacitor $capacitor, CapacitorChannel $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($storage, null);
|
||||||
$channel = $capacitor->newChannel(null);
|
$storage->close();
|
||||||
$this->_testChargeStrings($capacitor, $channel);
|
|
||||||
self::Txx(cl::all($capacitor->discharge($channel, false)));
|
|
||||||
|
|
||||||
$channel = $capacitor->newChannel("strings");
|
|
||||||
$this->_testChargeStrings($capacitor, $channel);
|
|
||||||
self::Txx(cl::all($capacitor->discharge($channel, false)));
|
|
||||||
|
|
||||||
$capacitor->close();
|
|
||||||
self::assertTrue(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function testChargeArrays() {
|
function testChargeArrays() {
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
|
$storage->addChannel(new class extends CapacitorChannel {
|
||||||
$channel = $capacitor->newChannel(new class extends CapacitorChannel {
|
|
||||||
const NAME = "arrays";
|
const NAME = "arrays";
|
||||||
const COLUMN_DEFINITIONS = ["id" => "integer"];
|
const COLUMN_DEFINITIONS = ["id" => "integer"];
|
||||||
|
|
||||||
@ -55,16 +45,15 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
return ["id" => $item["id"] ?? null];
|
return ["id" => $item["id"] ?? null];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$this->_testChargeArrays($capacitor, $channel);
|
|
||||||
self::Txx(cl::all($capacitor->discharge($channel, false)));
|
|
||||||
|
|
||||||
$capacitor->close();
|
$this->_testChargeStrings($storage, "strings");
|
||||||
self::assertTrue(true);
|
$this->_testChargeArrays($storage, "arrays");
|
||||||
|
$storage->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function testEach() {
|
function testEach() {
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$each = $capacitor->newChannel(new class extends CapacitorChannel {
|
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||||
const NAME = "each";
|
const NAME = "each";
|
||||||
const COLUMN_DEFINITIONS = [
|
const COLUMN_DEFINITIONS = [
|
||||||
"age" => "integer",
|
"age" => "integer",
|
||||||
@ -78,11 +67,11 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->reset($each);
|
$capacitor->reset();
|
||||||
$capacitor->charge($each, ["name" => "first", "age" => 5]);
|
$capacitor->charge(["name" => "first", "age" => 5]);
|
||||||
$capacitor->charge($each, ["name" => "second", "age" => 10]);
|
$capacitor->charge(["name" => "second", "age" => 10]);
|
||||||
$capacitor->charge($each, ["name" => "third", "age" => 15]);
|
$capacitor->charge(["name" => "third", "age" => 15]);
|
||||||
$capacitor->charge($each, ["name" => "fourth", "age" => 20]);
|
$capacitor->charge(["name" => "fourth", "age" => 20]);
|
||||||
|
|
||||||
$setDone = function ($row, $suffix=null) {
|
$setDone = function ($row, $suffix=null) {
|
||||||
$item = $row["item"];
|
$item = $row["item"];
|
||||||
@ -93,18 +82,17 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
}
|
}
|
||||||
return $updates;
|
return $updates;
|
||||||
};
|
};
|
||||||
$capacitor->each($each, ["age" => [">", 10]], $setDone, ["++"]);
|
$capacitor->each(["age" => [">", 10]], $setDone, ["++"]);
|
||||||
$capacitor->each($each, ["done" => 0], $setDone);
|
$capacitor->each(["done" => 0], $setDone);
|
||||||
|
|
||||||
self::Txx(cl::all($capacitor->discharge($each, false)));
|
|
||||||
|
|
||||||
|
self::Txx(cl::all($capacitor->discharge(false)));
|
||||||
$capacitor->close();
|
$capacitor->close();
|
||||||
self::assertTrue(true);
|
self::assertTrue(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testPrimayKey() {
|
function testPrimayKey() {
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$channel = $capacitor->newChannel(new class extends CapacitorChannel {
|
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||||
const NAME = "pk";
|
const NAME = "pk";
|
||||||
const COLUMN_DEFINITIONS = [
|
const COLUMN_DEFINITIONS = [
|
||||||
"id_" => "varchar primary key",
|
"id_" => "varchar primary key",
|
||||||
@ -118,23 +106,21 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->charge($channel, ["numero" => "a", "name" => "first", "age" => 5]);
|
$capacitor->charge(["numero" => "a", "name" => "first", "age" => 5]);
|
||||||
$capacitor->charge($channel, ["numero" => "b", "name" => "second", "age" => 10]);
|
$capacitor->charge(["numero" => "b", "name" => "second", "age" => 10]);
|
||||||
$capacitor->charge($channel, ["numero" => "c", "name" => "third", "age" => 15]);
|
$capacitor->charge(["numero" => "c", "name" => "third", "age" => 15]);
|
||||||
$capacitor->charge($channel, ["numero" => "d", "name" => "fourth", "age" => 20]);
|
$capacitor->charge(["numero" => "d", "name" => "fourth", "age" => 20]);
|
||||||
sleep(2);
|
sleep(2);
|
||||||
$capacitor->charge($channel, ["numero" => "b", "name" => "second", "age" => 100]);
|
$capacitor->charge(["numero" => "b", "name" => "second", "age" => 100]);
|
||||||
$capacitor->charge($channel, ["numero" => "d", "name" => "fourth", "age" => 200]);
|
$capacitor->charge(["numero" => "d", "name" => "fourth", "age" => 200]);
|
||||||
|
|
||||||
self::Txx(cl::all($capacitor->discharge($channel, false)));
|
|
||||||
|
|
||||||
$capacitor->close();
|
$capacitor->close();
|
||||||
self::assertTrue(true);
|
self::assertTrue(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testSum() {
|
function testSum() {
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$channel = $capacitor->newChannel(new class extends CapacitorChannel {
|
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||||
const NAME = "sum";
|
const NAME = "sum";
|
||||||
const COLUMN_DEFINITIONS = [
|
const COLUMN_DEFINITIONS = [
|
||||||
"a__" => "varchar",
|
"a__" => "varchar",
|
||||||
@ -150,17 +136,19 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->reset($channel);
|
$capacitor->reset();
|
||||||
$capacitor->charge($channel, ["a" => null, "b" => null]);
|
$capacitor->charge(["a" => null, "b" => null]);
|
||||||
$capacitor->charge($channel, ["a" => "first", "b" => "second"]);
|
$capacitor->charge(["a" => "first", "b" => "second"]);
|
||||||
|
|
||||||
self::Txx("=== all");
|
self::Txx("=== all");
|
||||||
self::Txx(cl::all($capacitor->db()->all([
|
/** @var Sqlite $sqlite */
|
||||||
|
$sqlite = $capacitor->getStorage()->db();
|
||||||
|
self::Txx(cl::all($sqlite->all([
|
||||||
"select",
|
"select",
|
||||||
"from" => $channel->getTableName(),
|
"from" => $capacitor->getChannel()->getTableName(),
|
||||||
])));
|
])));
|
||||||
self::Txx("=== each");
|
self::Txx("=== each");
|
||||||
$capacitor->each($channel, null, function ($row) {
|
$capacitor->each(null, function ($row) {
|
||||||
self::Txx($row);
|
self::Txx($row);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -170,8 +158,8 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
|
|
||||||
function testEachValues() {
|
function testEachValues() {
|
||||||
# tester que values contient bien toutes les valeurs de la ligne
|
# tester que values contient bien toutes les valeurs de la ligne
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$channel = $capacitor->newChannel(new class extends CapacitorChannel {
|
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||||
const NAME = "each_values";
|
const NAME = "each_values";
|
||||||
const COLUMN_DEFINITIONS = [
|
const COLUMN_DEFINITIONS = [
|
||||||
"name" => "varchar primary key",
|
"name" => "varchar primary key",
|
||||||
@ -188,8 +176,8 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->reset($channel);
|
$capacitor->reset();
|
||||||
$capacitor->charge($channel, ["name" => "first", "age" => 5], function($item, ?array $row, ?array $prow) {
|
$capacitor->charge(["name" => "first", "age" => 5], function($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame("first", $item["name"]);
|
self::assertSame("first", $item["name"]);
|
||||||
self::assertSame(5, $item["age"]);
|
self::assertSame(5, $item["age"]);
|
||||||
self::assertnotnull($row);
|
self::assertnotnull($row);
|
||||||
@ -201,7 +189,7 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
], cl::select($row, ["name", "age", "item"]));
|
], cl::select($row, ["name", "age", "item"]));
|
||||||
self::assertNull($prow);
|
self::assertNull($prow);
|
||||||
});
|
});
|
||||||
$capacitor->charge($channel, ["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) {
|
$capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame("first", $item["name"]);
|
self::assertSame("first", $item["name"]);
|
||||||
self::assertSame(10, $item["age"]);
|
self::assertSame(10, $item["age"]);
|
||||||
self::assertnotnull($row);
|
self::assertnotnull($row);
|
||||||
@ -223,7 +211,7 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
], cl::select($prow, ["name", "age", "done", "notes", "item"]));
|
], cl::select($prow, ["name", "age", "done", "notes", "item"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->each($channel, null, function(array $row) {
|
$capacitor->each(null, function(array $row) {
|
||||||
$item = $row["item"];
|
$item = $row["item"];
|
||||||
self::assertSame("first", $item["name"]);
|
self::assertSame("first", $item["name"]);
|
||||||
self::assertSame(10, $item["age"]);
|
self::assertSame(10, $item["age"]);
|
||||||
@ -241,7 +229,7 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
"notes" => "modified",
|
"notes" => "modified",
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
$capacitor->charge($channel, ["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) {
|
$capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame("first", $item["name"]);
|
self::assertSame("first", $item["name"]);
|
||||||
self::assertSame(10, $item["age"]);
|
self::assertSame(10, $item["age"]);
|
||||||
self::assertnotnull($row);
|
self::assertnotnull($row);
|
||||||
@ -263,7 +251,7 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
], cl::select($prow, ["name", "age", "done", "notes", "item"]));
|
], cl::select($prow, ["name", "age", "done", "notes", "item"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->charge($channel, ["name" => "first", "age" => 20], function($item, ?array $row, ?array $prow) {
|
$capacitor->charge(["name" => "first", "age" => 20], function($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame("first", $item["name"]);
|
self::assertSame("first", $item["name"]);
|
||||||
self::assertSame(20, $item["age"]);
|
self::assertSame(20, $item["age"]);
|
||||||
self::assertnotnull($row);
|
self::assertnotnull($row);
|
||||||
@ -288,8 +276,8 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
|
|
||||||
function testSetItemNull() {
|
function testSetItemNull() {
|
||||||
# tester le forçage de $îtem à null pour économiser la place
|
# tester le forçage de $îtem à null pour économiser la place
|
||||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
$channel = $capacitor->newChannel(new class extends CapacitorChannel {
|
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||||
const NAME = "set_item_null";
|
const NAME = "set_item_null";
|
||||||
const COLUMN_DEFINITIONS = [
|
const COLUMN_DEFINITIONS = [
|
||||||
"name" => "varchar primary key",
|
"name" => "varchar primary key",
|
||||||
@ -306,8 +294,8 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$capacitor->reset($channel);
|
$capacitor->reset();
|
||||||
$nbModified = $capacitor->charge($channel, ["name" => "first", "age" => 5], function ($item, ?array $row, ?array $prow) {
|
$nbModified = $capacitor->charge(["name" => "first", "age" => 5], function ($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 5,
|
"name" => "first", "age" => 5,
|
||||||
"item" => $item,
|
"item" => $item,
|
||||||
@ -318,7 +306,7 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
sleep(1);
|
sleep(1);
|
||||||
# nb: on met des sleep() pour que la date de modification soit systématiquement différente
|
# nb: on met des sleep() pour que la date de modification soit systématiquement différente
|
||||||
|
|
||||||
$nbModified = $capacitor->charge($channel, ["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) {
|
$nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 10,
|
"name" => "first", "age" => 10,
|
||||||
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
||||||
@ -333,7 +321,7 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
sleep(1);
|
sleep(1);
|
||||||
|
|
||||||
# pas de modification ici
|
# pas de modification ici
|
||||||
$nbModified = $capacitor->charge($channel, ["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) {
|
$nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 10,
|
"name" => "first", "age" => 10,
|
||||||
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
||||||
@ -347,7 +335,7 @@ class SqliteCapacitorTest extends TestCase {
|
|||||||
self::assertSame(0, $nbModified);
|
self::assertSame(0, $nbModified);
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
|
||||||
$nbModified = $capacitor->charge($channel, ["name" => "first", "age" => 20], function ($item, ?array $row, ?array $prow) {
|
$nbModified = $capacitor->charge(["name" => "first", "age" => 20], function ($item, ?array $row, ?array $prow) {
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
"name" => "first", "age" => 20,
|
"name" => "first", "age" => 20,
|
||||||
"item" => $item, "item__sum_" => "001b91982b4e0883b75428c0eb28573a5dc5f7a5",
|
"item" => $item, "item__sum_" => "001b91982b4e0883b75428c0eb28573a5dc5f7a5",
|
Loading…
x
Reference in New Issue
Block a user