modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2025-04-14 18:23:15 +04:00
parent d241ce6561
commit 2a46c12e08
26 changed files with 253 additions and 239 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
/test_*
/.composer.lock.runphp /.composer.lock.runphp
.~lock*# .~lock*#

View File

@ -1,81 +0,0 @@
<?php
require __DIR__.'/vendor/autoload.php';
use nulib\cl;
use nulib\db\Capacitor;
use nulib\db\CapacitorChannel;
use nulib\db\pgsql\Pgsql;
use nulib\db\pgsql\PgsqlStorage;
$pgsql = new Pgsql([
"host" => "pegase-dre.self",
"dbname" => "dre",
"user" => "root",
"password" => "admin",
#"user" => "reader",
#"password" => "reader",
]);
function e($sql) {
global $pgsql;
$v = $pgsql->exec($sql);
$v = var_export($v, true);
echo "'$sql' --> $v\n";
}
function g($sql) {
global $pgsql;
$v = $pgsql->get($sql);
$v = var_export($v, true);
echo "'$sql' --> $v\n";
}
function o($sql) {
global $pgsql;
$r = $pgsql->one($sql);
$r = var_export($r, true);
echo "'$sql' --> $r\n";
}
function a($sql) {
global $pgsql;
$rs = $pgsql->all($sql);
echo "'$sql'\n";
foreach ($rs as $r) {
$r = var_export($r, true);
echo " --> $r\n";
}
}
g("select age from personnes where id=1");
o("select name, age from personnes where id=2");
a("select id, name, age from personnes");
$n = rand();
$pgsql->exec([
"insert",
"into" => "personnes",
"values" => [
"name" => "prout$n",
"age" => $n,
],
]);
class MyChannel extends CapacitorChannel {
const COLUMN_DEFINITIONS = [
"name" => "varchar",
"value" => "int",
];
function getItemValues($item): ?array {
return $item;
}
}
$storage = new PgsqlStorage($pgsql);
$channel = new MyChannel();
new Capacitor($storage, $channel);
$channel->charge([
"name" => "one",
"value" => rand(),
]);
foreach ($channel->all(null) as $row) {
var_export($row);
}

View File

@ -120,10 +120,6 @@ class Capacitor implements ITransactor {
if ($db->inTransaction()) $db->rollback(); if ($db->inTransaction()) $db->rollback();
} }
function getCreateSql(): string {
return $this->storage->_getCreateSql($this->channel);
}
function exists(): bool { function exists(): bool {
return $this->storage->_exists($this->channel); return $this->storage->_exists($this->channel);
} }

View File

@ -17,6 +17,8 @@ class CapacitorChannel {
const PRIMARY_KEYS = null; const PRIMARY_KEYS = null;
const MIGRATION = null;
const MANAGE_TRANSACTIONS = true; const MANAGE_TRANSACTIONS = true;
const EACH_COMMIT_THRESHOLD = 100; const EACH_COMMIT_THRESHOLD = 100;
@ -80,6 +82,7 @@ class CapacitorChannel {
} }
$this->columnDefinitions = $columnDefinitions; $this->columnDefinitions = $columnDefinitions;
$this->primaryKeys = $primaryKeys; $this->primaryKeys = $primaryKeys;
$this->migration = cl::withn(static::MIGRATION);
} }
protected string $name; protected string $name;
@ -192,6 +195,12 @@ class CapacitorChannel {
return $this->columnDefinitions; return $this->columnDefinitions;
} }
protected ?array $migration;
function getMigration(): ?array {
return $this->migration;
}
protected ?array $primaryKeys; protected ?array $primaryKeys;
function getPrimaryKeys(): ?array { function getPrimaryKeys(): ?array {

View File

@ -2,6 +2,7 @@
namespace nulib\db; namespace nulib\db;
use nulib\cl; use nulib\cl;
use nulib\db\_private\_migration;
use nulib\db\cache\cache; use nulib\db\cache\cache;
use nulib\php\func; use nulib\php\func;
use nulib\ValueException; use nulib\ValueException;
@ -75,6 +76,10 @@ abstract class CapacitorStorage {
return cl::merge($definitions, $constraints); return cl::merge($definitions, $constraints);
} }
protected function getMigration(CapacitorChannel $channel): ?array {
return $channel->getMigration();
}
/** sérialiser les valeurs qui doivent l'être dans $values */ /** sérialiser les valeurs qui doivent l'être dans $values */
protected function serialize(CapacitorChannel $channel, ?array $values): ?array { protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
if ($values === null) return null; if ($values === null) return null;
@ -137,11 +142,10 @@ abstract class CapacitorStorage {
} }
protected function _createSql(CapacitorChannel $channel): array { protected function _createSql(CapacitorChannel $channel): array {
$cols = $this->ColumnDefinitions($channel);
return [ return [
"create table if not exists", "create table if not exists",
"table" => $channel->getTableName(), "table" => $channel->getTableName(),
"cols" => $cols, "cols" => $this->ColumnDefinitions($channel),
]; ];
} }
@ -156,12 +160,7 @@ $sql;
EOT; EOT;
} }
abstract function _getCreateSql(CapacitorChannel $channel): string; abstract function _getMigration(CapacitorChannel $channel): _migration;
/** obtenir la requête SQL utilisée pour créer la table */
function getCreateSql(?string $channel): string {
return $this->_getCreateSql($this->getChannel($channel));
}
protected function _afterCreate(CapacitorChannel $channel): void { protected function _afterCreate(CapacitorChannel $channel): void {
} }
@ -169,7 +168,7 @@ EOT;
protected function _create(CapacitorChannel $channel): void { protected function _create(CapacitorChannel $channel): void {
$channel->ensureSetup(); $channel->ensureSetup();
if (!$channel->isCreated()) { if (!$channel->isCreated()) {
$this->db()->exec($this->_createSql($channel)); $this->_getMigration($channel)->migrate($this->db());
$this->_afterCreate($channel); $this->_afterCreate($channel);
$channel->setCreated(); $channel->setCreated();
} }

View File

@ -18,16 +18,22 @@ class _create extends _common {
static function parse(array $query, ?array &$bindings=null): string { static function parse(array $query, ?array &$bindings=null): string {
#XXX implémentation minimale #XXX implémentation minimale
$sql = [self::merge_seq($query)]; $tmpsql = self::merge_seq($query);
self::consume('create(?:\s+table)?\b', $tmpsql);
$sql = ["create table"];
if ($tmpsql) $sql[] = $tmpsql;
## préfixe ## préfixe
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix; $prefix = $query["prefix"] ?? null;
if ($prefix !== null) $sql[] = $prefix;
## table ## table
$sql[] = $query["table"]; $table = $query["table"] ?? null;
if ($table !== null) $sql[] = $table;
## columns ## columns
$cols = $query["cols"]; $cols = $query["cols"] ?? null;
if ($cols !== null) {
$index = 0; $index = 0;
foreach ($cols as $col => &$definition) { foreach ($cols as $col => &$definition) {
if ($col === $index) { if ($col === $index) {
@ -37,9 +43,11 @@ class _create extends _common {
} }
}; unset($definition); }; unset($definition);
$sql[] = "(\n ".implode("\n, ", $cols)."\n)"; $sql[] = "(\n ".implode("\n, ", $cols)."\n)";
}
## suffixe ## suffixe
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix; $suffix = $query["suffix"] ?? null;
if ($suffix !== null) $sql[] = $suffix;
## fin de la requête ## fin de la requête
return implode(" ", $sql); return implode(" ", $sql);

View File

@ -17,13 +17,16 @@ class _delete extends _common {
#XXX implémentation minimale #XXX implémentation minimale
$tmpsql = self::merge_seq($query); $tmpsql = self::merge_seq($query);
self::consume('delete(?:\s+from)?\b', $tmpsql); self::consume('delete(?:\s+from)?\b', $tmpsql);
$sql = ["delete from", $tmpsql]; $sql = ["delete from"];
if ($tmpsql) $sql[] = $tmpsql;
## préfixe ## préfixe
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix; $prefix = $query["prefix"] ?? null;
if ($prefix !== null) $sql[] = $prefix;
## table ## table
$sql[] = $query["from"]; $from = $query["from"] ?? null;
if ($from !== null) $sql[] = $from;
## where ## where
$where = $query["where"] ?? null; $where = $query["where"] ?? null;
@ -36,7 +39,8 @@ class _delete extends _common {
} }
## suffixe ## suffixe
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix; $suffix = $query["suffix"] ?? null;
if ($suffix !== null) $sql[] = $suffix;
## fin de la requête ## fin de la requête
return implode(" ", $sql); return implode(" ", $sql);

View File

@ -9,7 +9,7 @@ class _generic extends _common {
]; ];
static function isa(string $sql): bool { static function isa(string $sql): bool {
return preg_match('/^(?:drop\s+table)\b/i', $sql); return preg_match('/^drop\s+table\b/i', $sql);
} }
static function parse(array $query, ?array &$bindings=null): string { static function parse(array $query, ?array &$bindings=null): string {

View File

@ -0,0 +1,45 @@
<?php
namespace nulib\db\_private;
use nulib\db\IDatabase;
use nulib\php\func;
abstract class _migration {
const MIGRATION = null;
function __construct($migrations, string $channel="", ?IDatabase $db=null) {
$this->db = $db;
$this->channel = $channel;
if ($migrations === null) $migrations = static::MIGRATION;
if ($migrations === null) $migrations = [];
elseif (is_string($migrations)) $migrations = [$migrations];
elseif (is_callable($migrations)) $migrations = [$migrations];
elseif (!is_array($migrations)) $migrations = [strval($migrations)];
$this->migrations = $migrations;
}
protected ?IDatabase $db;
protected string $channel;
/** @var callable[]|string[] */
protected $migrations;
abstract function setup(): void;
abstract function beforeMigrate(string $key): bool;
abstract function afterMigrate(string $key): void;
function migrate(?IDatabase $db=null): void {
$db = ($this->db ??= $db);
$this->setup();
foreach ($this->migrations as $key => $migration) {
if (!$this->beforeMigrate($key)) continue;
if (is_string($migration) || !func::is_callable($migration)) {
$db->exec($migration);
} else {
func::with($migration)->bind($this, true)->invoke([$db, $key]);
}
$this->afterMigrate($key);
}
}
}

View File

@ -21,10 +21,12 @@ class _update extends _common {
$sql = [self::merge_seq($query)]; $sql = [self::merge_seq($query)];
## préfixe ## préfixe
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix; $prefix = $query["prefix"] ?? null;
if ($prefix !== null) $sql[] = $prefix;
## table ## table
$sql[] = $query["table"]; $table = $query["table"] ?? null;
if ($table !== null) $sql[] = $table;
## set ## set
self::parse_set_values($query["values"], $setsql, $bindings); self::parse_set_values($query["values"], $setsql, $bindings);
@ -42,7 +44,8 @@ class _update extends _common {
} }
## suffixe ## suffixe
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix; $suffix = $query["suffix"] ?? null;
if ($suffix !== null) $sql[] = $suffix;
## fin de la requête ## fin de la requête
return implode(" ", $sql); return implode(" ", $sql);

View File

@ -23,7 +23,7 @@ class MysqlStorage extends CapacitorStorage {
]; ];
function _getCreateSql(CapacitorChannel $channel): string { function _getCreateSql(CapacitorChannel $channel): string {
$query = new query($this->_createSql($channel)); $query = new _mysqlQuery($this->_createSql($channel));
return self::format_sql($channel, $query->getSql()); return self::format_sql($channel, $query->getSql());
} }

View File

@ -0,0 +1,7 @@
<?php
namespace nulib\db\mysql;
use nulib\db\pdo\_pdoQuery;
class _mysqlQuery extends _pdoQuery {
}

View File

@ -1,5 +0,0 @@
<?php
namespace nulib\db\mysql;
class query extends \nulib\db\pdo\query {
}

View File

@ -22,7 +22,7 @@ class Pdo implements IDatabase {
"dbconn" => $pdo->dbconn, "dbconn" => $pdo->dbconn,
"options" => $pdo->options, "options" => $pdo->options,
"config" => $pdo->config, "config" => $pdo->config,
"migrate" => $pdo->migration, "migration" => $pdo->migration,
], $params)); ], $params));
} else { } else {
return new static($pdo, $params); return new static($pdo, $params);
@ -50,7 +50,7 @@ class Pdo implements IDatabase {
protected const CONFIG = null; protected const CONFIG = null;
protected const MIGRATE = null; protected const MIGRATION = null;
const dbconn_SCHEMA = [ const dbconn_SCHEMA = [
"name" => "string", "name" => "string",
@ -63,7 +63,7 @@ class Pdo implements IDatabase {
"options" => ["?array|callable"], "options" => ["?array|callable"],
"replace_config" => ["?array|callable"], "replace_config" => ["?array|callable"],
"config" => ["?array|callable"], "config" => ["?array|callable"],
"migrate" => ["?array|string|callable"], "migration" => ["?array|string|callable"],
"auto_open" => ["bool", true], "auto_open" => ["bool", true],
]; ];
@ -94,7 +94,7 @@ class Pdo implements IDatabase {
} }
$this->config = $config; $this->config = $config;
# migrations # migrations
$this->migration = $params["migrate"] ?? static::MIGRATE; $this->migration = $params["migration"] ?? static::MIGRATION;
# #
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1]; $defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
if ($params["auto_open"] ?? $defaultAutoOpen) { if ($params["auto_open"] ?? $defaultAutoOpen) {
@ -145,7 +145,7 @@ class Pdo implements IDatabase {
function exec($query, ?array $params=null) { function exec($query, ?array $params=null) {
$db = $this->db(); $db = $this->db();
$query = new query($query, $params); $query = new _pdoQuery($query, $params);
if ($query->_use_stmt($db, $stmt, $sql)) { if ($query->_use_stmt($db, $stmt, $sql)) {
if ($stmt->execute() === false) return false; if ($stmt->execute() === false) return false;
if ($query->isInsert()) return $db->lastInsertId(); if ($query->isInsert()) return $db->lastInsertId();
@ -217,7 +217,7 @@ class Pdo implements IDatabase {
function get($query, ?array $params=null, bool $entireRow=false) { function get($query, ?array $params=null, bool $entireRow=false) {
$db = $this->db(); $db = $this->db();
$query = new query($query, $params); $query = new _pdoQuery($query, $params);
$stmt = null; $stmt = null;
try { try {
/** @var \PDOStatement $stmt */ /** @var \PDOStatement $stmt */
@ -242,7 +242,7 @@ class Pdo implements IDatabase {
function all($query, ?array $params=null, $primaryKeys=null): iterable { function all($query, ?array $params=null, $primaryKeys=null): iterable {
$db = $this->db(); $db = $this->db();
$query = new query($query, $params); $query = new _pdoQuery($query, $params);
$stmt = null; $stmt = null;
try { try {
/** @var \PDOStatement $stmt */ /** @var \PDOStatement $stmt */

View File

@ -5,7 +5,7 @@ use nulib\db\_private\_base;
use nulib\db\_private\Tbindings; use nulib\db\_private\Tbindings;
use nulib\output\msg; use nulib\output\msg;
class query extends _base { class _pdoQuery extends _base {
use Tbindings; use Tbindings;
const DEBUG_QUERIES = false; const DEBUG_QUERIES = false;

View File

@ -21,7 +21,7 @@ class Pgsql implements IDatabase {
"dbconn" => $pgsql->dbconn, "dbconn" => $pgsql->dbconn,
"options" => $pgsql->options, "options" => $pgsql->options,
"config" => $pgsql->config, "config" => $pgsql->config,
"migrate" => $pgsql->migration, "migration" => $pgsql->migration,
], $params)); ], $params));
} else { } else {
return new static($pgsql, $params); return new static($pgsql, $params);
@ -37,14 +37,14 @@ class Pgsql implements IDatabase {
const CONFIG = null; const CONFIG = null;
const MIGRATE = null; const MIGRATION = null;
const params_SCHEMA = [ const params_SCHEMA = [
"dbconn" => ["array"], "dbconn" => ["array"],
"options" => ["?array|callable"], "options" => ["?array|callable"],
"replace_config" => ["?array|callable"], "replace_config" => ["?array|callable"],
"config" => ["?array|callable"], "config" => ["?array|callable"],
"migrate" => ["?array|string|callable"], "migration" => ["?array|string|callable"],
"auto_open" => ["bool", true], "auto_open" => ["bool", true],
]; ];
@ -95,7 +95,7 @@ class Pgsql implements IDatabase {
} }
$this->config = $config; $this->config = $config;
# migrations # migrations
$this->migration = $params["migrate"] ?? static::MIGRATE; $this->migration = $params["migration"] ?? static::MIGRATION;
# #
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1]; $defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
if ($params["auto_open"] ?? $defaultAutoOpen) { if ($params["auto_open"] ?? $defaultAutoOpen) {
@ -183,7 +183,7 @@ class Pgsql implements IDatabase {
function exec($query, ?array $params=null) { function exec($query, ?array $params=null) {
$db = $this->db(); $db = $this->db();
$query = new query($query, $params); $query = new _pgsqlQuery($query, $params);
$result = $query->_exec($db); $result = $query->_exec($db);
$serialSupport = $this->options["serial_support"] ?? true; $serialSupport = $this->options["serial_support"] ?? true;
if ($serialSupport && $query->isInsert()) return $this->getLastSerial(); if ($serialSupport && $query->isInsert()) return $this->getLastSerial();
@ -261,7 +261,7 @@ class Pgsql implements IDatabase {
function get($query, ?array $params=null, bool $entireRow=false) { function get($query, ?array $params=null, bool $entireRow=false) {
$db = $this->db(); $db = $this->db();
$query = new query($query, $params); $query = new _pgsqlQuery($query, $params);
$result = $query->_exec($db); $result = $query->_exec($db);
$row = pg_fetch_assoc($result); $row = pg_fetch_assoc($result);
pg_free_result($result); pg_free_result($result);
@ -277,7 +277,7 @@ class Pgsql implements IDatabase {
function all($query, ?array $params=null, $primaryKeys=null): iterable { function all($query, ?array $params=null, $primaryKeys=null): iterable {
$db = $this->db(); $db = $this->db();
$query = new query($query, $params); $query = new _pgsqlQuery($query, $params);
$result = $query->_exec($db); $result = $query->_exec($db);
$primaryKeys = cl::withn($primaryKeys); $primaryKeys = cl::withn($primaryKeys);
while (($row = pg_fetch_assoc($result)) !== false) { while (($row = pg_fetch_assoc($result)) !== false) {

View File

@ -24,7 +24,7 @@ class PgsqlStorage extends CapacitorStorage {
]; ];
function _getCreateSql(CapacitorChannel $channel): string { function _getCreateSql(CapacitorChannel $channel): string {
$query = new query($this->_createSql($channel)); $query = new _pgsqlQuery($this->_createSql($channel));
return self::format_sql($channel, $query->getSql()); return self::format_sql($channel, $query->getSql());
} }

View File

@ -6,7 +6,7 @@ use nulib\db\_private\_base;
use nulib\db\_private\Tbindings; use nulib\db\_private\Tbindings;
use nulib\output\msg; use nulib\output\msg;
class query extends _base { class _pgsqlQuery extends _base {
use Tbindings; use Tbindings;
const DEBUG_QUERIES = false; const DEBUG_QUERIES = false;

View File

@ -30,7 +30,7 @@ class Sqlite implements IDatabase {
"encryption_key" => $sqlite->encryptionKey, "encryption_key" => $sqlite->encryptionKey,
"allow_wal" => $sqlite->allowWal, "allow_wal" => $sqlite->allowWal,
"config" => $sqlite->config, "config" => $sqlite->config,
"migrate" => $sqlite->migration, "migration" => $sqlite->migration,
], $params)); ], $params));
} elseif (is_array($sqlite)) { } elseif (is_array($sqlite)) {
return new static(null, cl::merge($sqlite, $params)); return new static(null, cl::merge($sqlite, $params));
@ -72,7 +72,7 @@ class Sqlite implements IDatabase {
const CONFIG = null; const CONFIG = null;
const MIGRATE = null; const MIGRATION = null;
const params_SCHEMA = [ const params_SCHEMA = [
"file" => ["string", ""], "file" => ["string", ""],
@ -81,7 +81,7 @@ class Sqlite implements IDatabase {
"allow_wal" => ["?bool"], "allow_wal" => ["?bool"],
"replace_config" => ["?array|callable"], "replace_config" => ["?array|callable"],
"config" => ["?array|callable"], "config" => ["?array|callable"],
"migrate" => ["?array|string|callable"], "migration" => ["?array|string|callable"],
"auto_open" => ["bool", true], "auto_open" => ["bool", true],
]; ];
@ -109,7 +109,7 @@ class Sqlite implements IDatabase {
} }
$this->config = $config; $this->config = $config;
# migrations # migrations
$this->migration = $params["migrate"] ?? static::MIGRATE; $this->migration = $params["migration"] ?? static::MIGRATION;
# #
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1]; $defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
$this->inTransaction = false; $this->inTransaction = false;
@ -150,7 +150,7 @@ class Sqlite implements IDatabase {
if ($this->db === null) { if ($this->db === null) {
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey); $this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
_config::with($this->config)->configure($this); _config::with($this->config)->configure($this);
_migration::with($this->migration)->migrate($this); _sqliteMigration::with($this->migration)->migrate($this);
$this->inTransaction = false; $this->inTransaction = false;
} }
return $this; return $this;
@ -183,7 +183,7 @@ class Sqlite implements IDatabase {
function exec($query, ?array $params=null) { function exec($query, ?array $params=null) {
$db = $this->db(); $db = $this->db();
$query = new query($query, $params); $query = new _sqliteQuery($query, $params);
if ($query->_use_stmt($db, $stmt, $sql)) { if ($query->_use_stmt($db, $stmt, $sql)) {
try { try {
$result = $stmt->execute(); $result = $stmt->execute();
@ -270,7 +270,7 @@ class Sqlite implements IDatabase {
function get($query, ?array $params=null, bool $entireRow=false) { function get($query, ?array $params=null, bool $entireRow=false) {
$db = $this->db(); $db = $this->db();
$query = new query($query, $params); $query = new _sqliteQuery($query, $params);
if ($query->_use_stmt($db, $stmt, $sql)) { if ($query->_use_stmt($db, $stmt, $sql)) {
try { try {
$result = $this->checkResult($stmt->execute()); $result = $this->checkResult($stmt->execute());
@ -315,7 +315,7 @@ class Sqlite implements IDatabase {
function all($query, ?array $params=null, $primaryKeys=null): iterable { function all($query, ?array $params=null, $primaryKeys=null): iterable {
$db = $this->db(); $db = $this->db();
$query = new query($query, $params); $query = new _sqliteQuery($query, $params);
if ($query->_use_stmt($db, $stmt, $sql)) { if ($query->_use_stmt($db, $stmt, $sql)) {
$result = $this->checkResult($stmt->execute()); $result = $this->checkResult($stmt->execute());
return $this->_fetchResult($result, $stmt, $primaryKeys); return $this->_fetchResult($result, $stmt, $primaryKeys);

View File

@ -1,6 +1,7 @@
<?php <?php
namespace nulib\db\sqlite; namespace nulib\db\sqlite;
use nulib\cl;
use nulib\db\CapacitorChannel; use nulib\db\CapacitorChannel;
use nulib\db\CapacitorStorage; use nulib\db\CapacitorStorage;
@ -22,8 +23,14 @@ class SqliteStorage extends CapacitorStorage {
"id_" => "integer primary key autoincrement", "id_" => "integer primary key autoincrement",
]; ];
function _getMigration(CapacitorChannel $channel): _sqliteMigration {
return new _sqliteMigration(cl::merge([
$this->_createSql($channel),
], $channel->getMigration()), $channel->getName());
}
function _getCreateSql(CapacitorChannel $channel): string { function _getCreateSql(CapacitorChannel $channel): string {
$query = new query($this->_createSql($channel)); $query = new _sqliteQuery($this->_createSql($channel));
return self::format_sql($channel, $query->getSql()); return self::format_sql($channel, $query->getSql());
} }
@ -51,8 +58,7 @@ class SqliteStorage extends CapacitorStorage {
# 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
$sqlite->exec([ $sqlite->exec([
"create table if not exists", "create table if not exists _channels",
"table" => "_channels",
"cols" => [ "cols" => [
"name" => "varchar primary key", "name" => "varchar primary key",
"table_name" => "varchar", "table_name" => "varchar",
@ -64,8 +70,7 @@ class SqliteStorage extends CapacitorStorage {
# 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
$sqlite->exec([ $sqlite->exec([
"insert", "insert into _channels",
"into" => "_channels",
"values" => [ "values" => [
"name" => $channel->getName(), "name" => $channel->getName(),
"table_name" => $channel->getTableName(), "table_name" => $channel->getTableName(),
@ -77,9 +82,15 @@ class SqliteStorage extends CapacitorStorage {
} }
protected function _beforeReset(CapacitorChannel $channel): void { protected function _beforeReset(CapacitorChannel $channel): void {
$this->sqlite->exec([ $sqlite = $this->sqlite;
"delete", $sqlite->exec([
"from" => "_channels", "delete from _migration",
"where" => [
"channel" => $channel->getName(),
],
]);
$sqlite->exec([
"delete from _channels",
"where" => [ "where" => [
"name" => $channel->getName(), "name" => $channel->getName(),
], ],

View File

@ -1,54 +0,0 @@
<?php
namespace nulib\db\sqlite;
use nulib\php\func;
class _migration {
static function with($migrations): self {
if ($migrations instanceof static) {
return $migrations;
} elseif ($migrations instanceof self) {
return new static($migrations->migrations);
} else {
return new static($migrations);
}
}
const MIGRATE = null;
function __construct($migrations) {
if ($migrations === null) $migrations = static::MIGRATE;
if ($migrations === null) $migrations = [];
elseif (is_string($migrations)) $migrations = [$migrations];
elseif (is_callable($migrations)) $migrations = [$migrations];
elseif (!is_array($migrations)) $migrations = [strval($migrations)];
$this->migrations = $migrations;
}
/** @var callable[]|string[] */
protected $migrations;
function migrate(Sqlite $sqlite): void {
$sqlite->exec("create table if not exists _migration(key varchar primary key, value varchar not null, done integer default 0)");
foreach ($this->migrations as $key => $migration) {
$exists = $sqlite->get("select 1 from _migration where key = :key and done = 1", [
"key" => $key,
]);
if (!$exists) {
$sqlite->exec("insert or replace into _migration(key, value, done) values(:key, :value, :done)", [
"key" => $key,
"value" => $migration,
"done" => 0,
]);
if (is_string($migration) && !func::is_method($migration)) {
$sqlite->exec($migration);
} else {
func::with($migration)->bind($this, true)->invoke([$sqlite, $key]);
}
$sqlite->exec("update _migration set done = 1 where key = :key", [
"key" => $key,
]);
}
}
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace nulib\db\sqlite;
use nulib\db\_private\_migration;
class _sqliteMigration extends _migration {
static function with($migration): self {
if ($migration instanceof self) return $migration;
else return new static($migration);
}
function setup(): void {
$this->db->exec([
"create table if not exists _migration",
"cols" => [
"channel" => "varchar not null",
"key" => "varchar not null",
"done" => "integer not null default 0",
],
]);
}
function beforeMigrate(string $key): bool {
$db = $this->db;
$migrated = $db->get([
"select 1 from _migration",
"where" => [
"channel" => $this->channel,
"key" => $key,
"done" => 1,
],
]);
if ($migrated) return false;
$db->exec([
"insert or replace into _migration",
"values" => [
"channel" => $this->channel,
"key" => $key,
"done" => 0,
],
]);
return true;
}
function afterMigrate(string $key): void {
$this->db->exec([
"update _migration",
"values" => [
"done" => 1,
],
"where" => [
"channel" => $this->channel,
"key" => $key,
],
]);
}
}

View File

@ -14,7 +14,7 @@ use nulib\ValueException;
use SQLite3; use SQLite3;
use SQLite3Stmt; use SQLite3Stmt;
class query extends _base { class _sqliteQuery extends _base {
use Tbindings; use Tbindings;
const DEBUG_QUERIES = false; const DEBUG_QUERIES = false;

View File

@ -437,7 +437,7 @@ class func {
return new ValueException($reason); return new ValueException($reason);
} }
static function with($func, ?array $args=null, bool $strict=true): self { private static function _with($func, ?array $args=null, bool $strict=true, ?string &$reason=null): ?self {
if (!is_array($func)) { if (!is_array($func)) {
if ($func instanceof Closure) { if ($func instanceof Closure) {
return new self(self::TYPE_CLOSURE, $func, $args); return new self(self::TYPE_CLOSURE, $func, $args);
@ -458,6 +458,12 @@ class func {
} elseif (self::verifix_static($func, $strict, $bound, $reason)) { } elseif (self::verifix_static($func, $strict, $bound, $reason)) {
return new self(self::TYPE_STATIC, $func, $args, $bound, $reason); return new self(self::TYPE_STATIC, $func, $args, $bound, $reason);
} }
return null;
}
static function with($func, ?array $args=null, bool $strict=true): self {
$func = self::_with($func, $args, $strict, $reason);
if ($func !== null) return $func;
throw self::not_a_callable($func, $reason); throw self::not_a_callable($func, $reason);
} }
@ -478,6 +484,13 @@ class func {
} }
} }
static function is_callable($func): bool {
$func = self::_with($func);
if ($func === null) return false;
if (!$func->isBound()) return false;
return $func->type !== self::TYPE_CLASS;
}
static function call($func, ...$args) { static function call($func, ...$args) {
return self::with($func)->invoke($args); return self::with($func)->invoke($args);
} }
@ -638,7 +651,7 @@ class func {
function bind($object, bool $unlessAlreadyBound=false, bool $replace=false): self { function bind($object, bool $unlessAlreadyBound=false, bool $replace=false): self {
if ($this->type !== self::TYPE_METHOD) return $this; if ($this->type !== self::TYPE_METHOD) return $this;
if ($this->object !== null && $unlessAlreadyBound) return $this; if ($this->bound && $unlessAlreadyBound) return $this;
[$c, $f] = $this->func; [$c, $f] = $this->func;
if ($replace) { if ($replace) {

View File

@ -11,7 +11,7 @@ class SqliteTest extends TestCase {
function testMigration() { function testMigration() {
$sqlite = new Sqlite(":memory:", [ $sqlite = new Sqlite(":memory:", [
"migrate" => [ "migration" => [
self::CREATE_PERSON, self::CREATE_PERSON,
self::INSERT_JEPHTE, self::INSERT_JEPHTE,
], ],
@ -49,7 +49,7 @@ class SqliteTest extends TestCase {
} }
function testInsert() { function testInsert() {
$sqlite = new Sqlite(":memory:", [ $sqlite = new Sqlite(":memory:", [
"migrate" => "create table mapping (i integer, s varchar)", "migration" => "create table mapping (i integer, s varchar)",
]); ]);
$sqlite->exec(["insert into mapping", "values" => ["i" => 1, "s" => "un"]]); $sqlite->exec(["insert into mapping", "values" => ["i" => 1, "s" => "un"]]);
$sqlite->exec(["insert mapping", "values" => ["i" => 2, "s" => "deux"]]); $sqlite->exec(["insert mapping", "values" => ["i" => 2, "s" => "deux"]]);
@ -78,7 +78,7 @@ class SqliteTest extends TestCase {
function testSelect() { function testSelect() {
$sqlite = new Sqlite(":memory:", [ $sqlite = new Sqlite(":memory:", [
"migrate" => "create table user (name varchar, amount integer)", "migration" => "create table user (name varchar, amount integer)",
]); ]);
$sqlite->exec(["insert into user", "values" => ["name" => "jclain1", "amount" => 1]]); $sqlite->exec(["insert into user", "values" => ["name" => "jclain1", "amount" => 1]]);
$sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 2]]); $sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 2]]);
@ -130,7 +130,7 @@ class SqliteTest extends TestCase {
function testSelectGroupBy() { function testSelectGroupBy() {
$sqlite = new Sqlite(":memory:", [ $sqlite = new Sqlite(":memory:", [
"migrate" => "create table user (name varchar, amount integer)", "migration" => "create table user (name varchar, amount integer)",
]); ]);
$sqlite->exec(["insert into user", "values" => ["name" => "jclain1", "amount" => 1]]); $sqlite->exec(["insert into user", "values" => ["name" => "jclain1", "amount" => 1]]);
$sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 1]]); $sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 1]]);

View File

@ -6,119 +6,119 @@ use PHPUnit\Framework\TestCase;
class _queryTest extends TestCase { class _queryTest extends TestCase {
function testParseConds(): void { function testParseConds(): void {
$sql = $params = null; $sql = $params = null;
query::parse_conds(null, $sql, $params); _sqliteQuery::parse_conds(null, $sql, $params);
self::assertNull($sql); self::assertNull($sql);
self::assertNull($params); self::assertNull($params);
$sql = $params = null; $sql = $params = null;
query::parse_conds([], $sql, $params); _sqliteQuery::parse_conds([], $sql, $params);
self::assertNull($sql); self::assertNull($sql);
self::assertNull($params); self::assertNull($params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["col" => null], $sql, $params); _sqliteQuery::parse_conds(["col" => null], $sql, $params);
self::assertSame(["col is null"], $sql); self::assertSame(["col is null"], $sql);
self::assertNull($params); self::assertNull($params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["col = 'value'"], $sql, $params); _sqliteQuery::parse_conds(["col = 'value'"], $sql, $params);
self::assertSame(["col = 'value'"], $sql); self::assertSame(["col = 'value'"], $sql);
self::assertNull($params); self::assertNull($params);
$sql = $params = null; $sql = $params = null;
query::parse_conds([["col = 'value'"]], $sql, $params); _sqliteQuery::parse_conds([["col = 'value'"]], $sql, $params);
self::assertSame(["col = 'value'"], $sql); self::assertSame(["col = 'value'"], $sql);
self::assertNull($params); self::assertNull($params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["int" => 42, "string" => "value"], $sql, $params); _sqliteQuery::parse_conds(["int" => 42, "string" => "value"], $sql, $params);
self::assertSame(["(int = :int and string = :string)"], $sql); self::assertSame(["(int = :int and string = :string)"], $sql);
self::assertSame(["int" => 42, "string" => "value"], $params); self::assertSame(["int" => 42, "string" => "value"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["or", "int" => 42, "string" => "value"], $sql, $params); _sqliteQuery::parse_conds(["or", "int" => 42, "string" => "value"], $sql, $params);
self::assertSame(["(int = :int or string = :string)"], $sql); self::assertSame(["(int = :int or string = :string)"], $sql);
self::assertSame(["int" => 42, "string" => "value"], $params); self::assertSame(["int" => 42, "string" => "value"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_conds([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params); _sqliteQuery::parse_conds([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
self::assertSame(["((int = :int and string = :string) and (int = :int2 and string = :string2))"], $sql); self::assertSame(["((int = :int and string = :string) and (int = :int2 and string = :string2))"], $sql);
self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params); self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["int" => ["is null"], "string" => ["<>", "value"]], $sql, $params); _sqliteQuery::parse_conds(["int" => ["is null"], "string" => ["<>", "value"]], $sql, $params);
self::assertSame(["(int is null and string <> :string)"], $sql); self::assertSame(["(int is null and string <> :string)"], $sql);
self::assertSame(["string" => "value"], $params); self::assertSame(["string" => "value"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["col" => ["between", "lower", "upper"]], $sql, $params); _sqliteQuery::parse_conds(["col" => ["between", "lower", "upper"]], $sql, $params);
self::assertSame(["col between :col and :col2"], $sql); self::assertSame(["col between :col and :col2"], $sql);
self::assertSame(["col" => "lower", "col2" => "upper"], $params); self::assertSame(["col" => "lower", "col2" => "upper"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["col" => ["in", "one"]], $sql, $params); _sqliteQuery::parse_conds(["col" => ["in", "one"]], $sql, $params);
self::assertSame(["col in (:col)"], $sql); self::assertSame(["col in (:col)"], $sql);
self::assertSame(["col" => "one"], $params); self::assertSame(["col" => "one"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["col" => ["in", ["one", "two"]]], $sql, $params); _sqliteQuery::parse_conds(["col" => ["in", ["one", "two"]]], $sql, $params);
self::assertSame(["col in (:col, :col2)"], $sql); self::assertSame(["col in (:col, :col2)"], $sql);
self::assertSame(["col" => "one", "col2" => "two"], $params); self::assertSame(["col" => "one", "col2" => "two"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["col" => ["=", ["one", "two"]]], $sql, $params); _sqliteQuery::parse_conds(["col" => ["=", ["one", "two"]]], $sql, $params);
self::assertSame(["col = :col and col = :col2"], $sql); self::assertSame(["col = :col and col = :col2"], $sql);
self::assertSame(["col" => "one", "col2" => "two"], $params); self::assertSame(["col" => "one", "col2" => "two"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["or", "col" => ["=", ["one", "two"]]], $sql, $params); _sqliteQuery::parse_conds(["or", "col" => ["=", ["one", "two"]]], $sql, $params);
self::assertSame(["col = :col or col = :col2"], $sql); self::assertSame(["col = :col or col = :col2"], $sql);
self::assertSame(["col" => "one", "col2" => "two"], $params); self::assertSame(["col" => "one", "col2" => "two"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["col" => ["<>", ["one", "two"]]], $sql, $params); _sqliteQuery::parse_conds(["col" => ["<>", ["one", "two"]]], $sql, $params);
self::assertSame(["col <> :col and col <> :col2"], $sql); self::assertSame(["col <> :col and col <> :col2"], $sql);
self::assertSame(["col" => "one", "col2" => "two"], $params); self::assertSame(["col" => "one", "col2" => "two"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_conds(["or", "col" => ["<>", ["one", "two"]]], $sql, $params); _sqliteQuery::parse_conds(["or", "col" => ["<>", ["one", "two"]]], $sql, $params);
self::assertSame(["col <> :col or col <> :col2"], $sql); self::assertSame(["col <> :col or col <> :col2"], $sql);
self::assertSame(["col" => "one", "col2" => "two"], $params); self::assertSame(["col" => "one", "col2" => "two"], $params);
} }
function testParseValues(): void { function testParseValues(): void {
$sql = $params = null; $sql = $params = null;
query::parse_set_values(null, $sql, $params); _sqliteQuery::parse_set_values(null, $sql, $params);
self::assertNull($sql); self::assertNull($sql);
self::assertNull($params); self::assertNull($params);
$sql = $params = null; $sql = $params = null;
query::parse_set_values([], $sql, $params); _sqliteQuery::parse_set_values([], $sql, $params);
self::assertNull($sql); self::assertNull($sql);
self::assertNull($params); self::assertNull($params);
$sql = $params = null; $sql = $params = null;
query::parse_set_values(["col = 'value'"], $sql, $params); _sqliteQuery::parse_set_values(["col = 'value'"], $sql, $params);
self::assertSame(["col = 'value'"], $sql); self::assertSame(["col = 'value'"], $sql);
self::assertNull($params); self::assertNull($params);
$sql = $params = null; $sql = $params = null;
query::parse_set_values([["col = 'value'"]], $sql, $params); _sqliteQuery::parse_set_values([["col = 'value'"]], $sql, $params);
self::assertSame(["col = 'value'"], $sql); self::assertSame(["col = 'value'"], $sql);
self::assertNull($params); self::assertNull($params);
$sql = $params = null; $sql = $params = null;
query::parse_set_values(["int" => 42, "string" => "value"], $sql, $params); _sqliteQuery::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
self::assertSame(["int = :int", "string = :string"], $sql); self::assertSame(["int = :int", "string = :string"], $sql);
self::assertSame(["int" => 42, "string" => "value"], $params); self::assertSame(["int" => 42, "string" => "value"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_set_values(["int" => 42, "string" => "value"], $sql, $params); _sqliteQuery::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
self::assertSame(["int = :int", "string = :string"], $sql); self::assertSame(["int = :int", "string = :string"], $sql);
self::assertSame(["int" => 42, "string" => "value"], $params); self::assertSame(["int" => 42, "string" => "value"], $params);
$sql = $params = null; $sql = $params = null;
query::parse_set_values([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params); _sqliteQuery::parse_set_values([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
self::assertSame(["int = :int", "string = :string", "int = :int2", "string = :string2"], $sql); self::assertSame(["int = :int", "string = :string", "int = :int2", "string = :string2"], $sql);
self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params); self::assertSame(["int" => 42, "string" => "value", "int2" => 24, "string2" => "eulav"], $params);
} }