modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2025-04-15 12:12:03 +04:00
parent 2a46c12e08
commit 146461a184
11 changed files with 227 additions and 139 deletions

View File

@ -65,15 +65,29 @@ class CapacitorChannel {
$this->created = false; $this->created = false;
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS); $columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
$primaryKeys = cl::withn(static::PRIMARY_KEYS); $primaryKeys = cl::withn(static::PRIMARY_KEYS);
if ($primaryKeys === null && $columnDefinitions !== null) { $migration = cl::withn(static::MIGRATION);
if ($columnDefinitions !== null) {
# mettre à jour la liste des clés primaires et des migrations
$index = 0; $index = 0;
foreach ($columnDefinitions as $col => $def) { foreach ($columnDefinitions as $col => $def) {
if ($col === $index) { if ($col === $index) {
# si définition séquentielle, seules les définitions de clé
# primaires sont supportées
$index++; $index++;
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) { if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
$primaryKeys = preg_split('/\s*,\s*/', trim($ms[1])); $primaryKeys = preg_split('/\s*,\s*/', trim($ms[1]));
} }
} else { } elseif (is_array($def)) {
# tableau: c'est une migration
$def = implode(" ", $def);
if ($def) {
$migration["add_$col"] = "alter table $tableName add column $col $def";
} else {
$migration["drop_$col"] = "alter table $tableName drop column $col";
}
} elseif (is_scalar($def)) {
# chaine: c'est une définition
$def = strval($def);
if (preg_match('/\bprimary\s+key\b/i', $def)) { if (preg_match('/\bprimary\s+key\b/i', $def)) {
$primaryKeys[] = $col; $primaryKeys[] = $col;
} }
@ -82,7 +96,7 @@ class CapacitorChannel {
} }
$this->columnDefinitions = $columnDefinitions; $this->columnDefinitions = $columnDefinitions;
$this->primaryKeys = $primaryKeys; $this->primaryKeys = $primaryKeys;
$this->migration = cl::withn(static::MIGRATION); $this->migration = $migration;
} }
protected string $name; protected string $name;

View File

@ -47,7 +47,7 @@ abstract class CapacitorStorage {
"modified_" => "serts", "modified_" => "serts",
]; ];
protected function ColumnDefinitions(CapacitorChannel $channel): array { protected function ColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
$definitions = []; $definitions = [];
if ($channel->getPrimaryKeys() === null) { if ($channel->getPrimaryKeys() === null) {
$definitions[] = static::PRIMARY_KEY_DEFINITION; $definitions[] = static::PRIMARY_KEY_DEFINITION;
@ -69,8 +69,14 @@ abstract class CapacitorStorage {
if ($col === $index) { if ($col === $index) {
$index++; $index++;
$constraints[] = $def; $constraints[] = $def;
} else { } elseif (is_array($def)) {
$definitions[$col] = $def; # éventuellement, ignorer les migrations
$def = implode(" ", $def);
if ($def && !$ignoreMigrations) {
$definitions[$col] = $def;
}
} elseif (is_scalar($def)) {
$definitions[$col] = strval($def);
} }
} }
return cl::merge($definitions, $constraints); return cl::merge($definitions, $constraints);
@ -145,7 +151,7 @@ abstract class CapacitorStorage {
return [ return [
"create table if not exists", "create table if not exists",
"table" => $channel->getTableName(), "table" => $channel->getTableName(),
"cols" => $this->ColumnDefinitions($channel), "cols" => $this->ColumnDefinitions($channel, true),
]; ];
} }
@ -162,7 +168,37 @@ EOT;
abstract function _getMigration(CapacitorChannel $channel): _migration; abstract function _getMigration(CapacitorChannel $channel): _migration;
const CHANNELS_TABLE = "_channels";
const CHANNELS_COLS = [
"name" => "varchar primary key",
"table_name" => "varchar",
"class_name" => "varchar",
];
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 { protected function _afterCreate(CapacitorChannel $channel): void {
$db = $this->db();
$db->exec($this->_createChannelsSql());
$db->exec($this->_addToChannelsSql($channel));
} }
protected function _create(CapacitorChannel $channel): void { protected function _create(CapacitorChannel $channel): void {
@ -191,6 +227,22 @@ EOT;
} }
protected function _beforeReset(CapacitorChannel $channel): void { 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é */ /** supprimer le canal spécifié */

View File

@ -11,9 +11,8 @@ class _create extends _common {
]; ];
static function isa(string $sql): bool { static function isa(string $sql): bool {
//return preg_match("/^create(?:\s+table)?\b/i", $sql);
#XXX implémentation minimale #XXX implémentation minimale
return preg_match("/^create\s+table\b/i", $sql); return preg_match("/^create(?:\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

@ -5,6 +5,14 @@ use nulib\db\IDatabase;
use nulib\php\func; use nulib\php\func;
abstract class _migration { abstract class _migration {
const MIGRATION_TABLE = "_migration";
const MIGRATION_COLS = [
"channel" => "varchar not null",
"name" => "varchar not null",
"done" => "integer not null default 0",
"primary key (channel, name)",
];
const MIGRATION = null; const MIGRATION = null;
function __construct($migrations, string $channel="", ?IDatabase $db=null) { function __construct($migrations, string $channel="", ?IDatabase $db=null) {
@ -25,21 +33,40 @@ abstract class _migration {
/** @var callable[]|string[] */ /** @var callable[]|string[] */
protected $migrations; protected $migrations;
abstract function setup(): void; function ensureTable(): void {
abstract function beforeMigrate(string $key): bool; $this->db->exec([
abstract function afterMigrate(string $key): void; "create table if not exists",
"table" => static::MIGRATION_TABLE,
"cols" => static::MIGRATION_COLS,
]);
}
protected function isMigrated(string $name): bool {
return boolval($this->db->get([
"select 1",
"from" => static::MIGRATION_TABLE,
"where" => [
"channel" => $this->channel,
"name" => $name,
"done" => 1,
],
]));
}
abstract protected function setMigrated(string $name, bool $done): void;
function migrate(?IDatabase $db=null): void { function migrate(?IDatabase $db=null): void {
$db = ($this->db ??= $db); $db = ($this->db ??= $db);
$this->setup(); $this->ensureTable();
foreach ($this->migrations as $key => $migration) { foreach ($this->migrations as $name => $migration) {
if (!$this->beforeMigrate($key)) continue; if ($this->isMigrated($name)) continue;
$this->setMigrated($name, false);
if (is_string($migration) || !func::is_callable($migration)) { if (is_string($migration) || !func::is_callable($migration)) {
$db->exec($migration); $db->exec($migration);
} else { } else {
func::with($migration)->bind($this, true)->invoke([$db, $key]); func::with($migration)->bind($this, true)->invoke([$db, $name]);
} }
$this->afterMigrate($key); $this->setMigrated($name, true);
} }
} }
} }

View File

@ -1,6 +1,7 @@
<?php <?php
namespace nulib\db\mysql; namespace nulib\db\mysql;
use nulib\cl;
use nulib\db\CapacitorChannel; use nulib\db\CapacitorChannel;
use nulib\db\CapacitorStorage; use nulib\db\CapacitorStorage;
@ -9,26 +10,44 @@ use nulib\db\CapacitorStorage;
*/ */
class MysqlStorage extends CapacitorStorage { class MysqlStorage extends CapacitorStorage {
function __construct($mysql) { function __construct($mysql) {
$this->mysql = Mysql::with($mysql); $this->db = Mysql::with($mysql);
} }
protected Mysql $mysql; protected Mysql $db;
function db(): Mysql { function db(): Mysql {
return $this->mysql; return $this->db;
} }
const PRIMARY_KEY_DEFINITION = [ const PRIMARY_KEY_DEFINITION = [
"id_" => "integer primary key auto_increment", "id_" => "integer primary key auto_increment",
]; ];
function _getMigration(CapacitorChannel $channel): _mysqlMigration {
return new _mysqlMigration(cl::merge([
$this->_createSql($channel),
], $channel->getMigration()), $channel->getName());
}
function _getCreateSql(CapacitorChannel $channel): string { function _getCreateSql(CapacitorChannel $channel): string {
$query = new _mysqlQuery($this->_createSql($channel)); $query = new _mysqlQuery($this->_createSql($channel));
return self::format_sql($channel, $query->getSql()); return self::format_sql($channel, $query->getSql());
} }
const CHANNELS_COLS = [
"name" => "varchar(255) primary key",
"table_name" => "varchar(64)",
"class_name" => "varchar(255)",
];
protected function _addToChannelsSql(CapacitorChannel $channel): array {
return cl::merge(parent::_addToChannelsSql($channel), [
"suffix" => "on duplicate key update name = name",
]);
}
function _exists(CapacitorChannel $channel): bool { function _exists(CapacitorChannel $channel): bool {
$mysql = $this->mysql; $mysql = $this->db;
$tableName = $mysql->get([ $tableName = $mysql->get([
"select table_name from information_schema.tables", "select table_name from information_schema.tables",
"where" => [ "where" => [
@ -40,6 +59,6 @@ class MysqlStorage extends CapacitorStorage {
} }
function close(): void { function close(): void {
$this->mysql->close(); $this->db->close();
} }
} }

View File

@ -0,0 +1,31 @@
<?php
namespace nulib\db\mysql;
use nulib\db\_private\_migration;
class _mysqlMigration extends _migration {
const MIGRATION_COLS = [
"channel" => "varchar(64) not null",
"name" => "varchar(64) not null",
"done" => "integer not null default 0",
"primary key (channel, name)",
];
static function with($migration): self {
if ($migration instanceof self) return $migration;
else return new static($migration);
}
protected function setMigrated(string $name, bool $done): void {
$this->db->exec([
"insert",
"into" => static::MIGRATION_TABLE,
"values" => [
"channel" => $this->channel,
"name" => $name,
"done" => $done? 1: 0,
],
"suffix" => "on duplicate key update done = :done",
]);
}
}

View File

@ -4,4 +4,5 @@ namespace nulib\db\mysql;
use nulib\db\pdo\_pdoQuery; use nulib\db\pdo\_pdoQuery;
class _mysqlQuery extends _pdoQuery { class _mysqlQuery extends _pdoQuery {
const DEBUG_QUERIES = false;
} }

View File

@ -1,6 +1,7 @@
<?php <?php
namespace nulib\db\pgsql; namespace nulib\db\pgsql;
use nulib\cl;
use nulib\db\CapacitorChannel; use nulib\db\CapacitorChannel;
use nulib\db\CapacitorStorage; use nulib\db\CapacitorStorage;
@ -10,54 +11,33 @@ class PgsqlStorage extends CapacitorStorage {
const SERTS_DEFINITION = "timestamp"; const SERTS_DEFINITION = "timestamp";
function __construct($pgsql) { function __construct($pgsql) {
$this->pgsql = Pgsql::with($pgsql); $this->db = Pgsql::with($pgsql);
} }
protected Pgsql $pgsql; protected Pgsql $db;
function db(): Pgsql { function db(): Pgsql {
return $this->pgsql; return $this->db;
} }
const PRIMARY_KEY_DEFINITION = [ const PRIMARY_KEY_DEFINITION = [
"id_" => "serial primary key", "id_" => "serial primary key",
]; ];
function _getMigration(CapacitorChannel $channel): _pgsqlMigration {
return new _pgsqlMigration(cl::merge([
$this->_createSql($channel),
], $channel->getMigration()), $channel->getName());
}
function _getCreateSql(CapacitorChannel $channel): string { function _getCreateSql(CapacitorChannel $channel): string {
$query = new _pgsqlQuery($this->_createSql($channel)); $query = new _pgsqlQuery($this->_createSql($channel));
return self::format_sql($channel, $query->getSql()); return self::format_sql($channel, $query->getSql());
} }
protected function _afterCreate(CapacitorChannel $channel): void { protected function _addToChannelsSql(CapacitorChannel $channel): array {
$db = $this->pgsql; return cl::merge(parent::_addToChannelsSql($channel), [
$db->exec([ "suffix" => "on conflict do nothing",
"create table if not exists",
"table" => "_channels",
"cols" => [
"name" => "varchar primary key",
"table_name" => "varchar",
"class" => "varchar",
],
]);
$db->exec([
"insert",
"into" => "_channels",
"values" => [
"name" => $channel->getName(),
"table_name" => $channel->getTableName(),
"class" => get_class($channel),
],
"suffix" => "on conflict (name) do nothing",
]);
}
protected function _beforeReset(CapacitorChannel $channel): void {
$this->pgsql->exec([
"delete",
"from" => "_channels",
"where" => [
"name" => $channel->getName(),
],
]); ]);
} }
@ -69,7 +49,7 @@ class PgsqlStorage extends CapacitorStorage {
} else { } else {
$schemaName = "public"; $schemaName = "public";
} }
return null !== $this->pgsql->get([ return null !== $this->db->get([
"select tablename from pg_tables", "select tablename from pg_tables",
"where" => [ "where" => [
"schemaname" => $schemaName, "schemaname" => $schemaName,
@ -79,6 +59,6 @@ class PgsqlStorage extends CapacitorStorage {
} }
function close(): void { function close(): void {
$this->pgsql->close(); $this->db->close();
} }
} }

View File

@ -0,0 +1,24 @@
<?php
namespace nulib\db\pgsql;
use nulib\db\_private\_migration;
class _pgsqlMigration extends _migration {
static function with($migration): self {
if ($migration instanceof self) return $migration;
else return new static($migration);
}
protected function setMigrated(string $name, bool $done): void {
$this->db->exec([
"insert",
"into" => static::MIGRATION_TABLE,
"values" => [
"channel" => $this->channel,
"name" => $name,
"done" => $done? 1: 0,
],
"suffix" => "on conflict (channel, name) do update set done = :done",
]);
}
}

View File

@ -10,13 +10,13 @@ use nulib\db\CapacitorStorage;
*/ */
class SqliteStorage extends CapacitorStorage { class SqliteStorage extends CapacitorStorage {
function __construct($sqlite) { function __construct($sqlite) {
$this->sqlite = Sqlite::with($sqlite); $this->db = Sqlite::with($sqlite);
} }
protected Sqlite $sqlite; protected Sqlite $db;
function db(): Sqlite { function db(): Sqlite {
return $this->sqlite; return $this->db;
} }
const PRIMARY_KEY_DEFINITION = [ const PRIMARY_KEY_DEFINITION = [
@ -35,7 +35,7 @@ class SqliteStorage extends CapacitorStorage {
} }
function tableExists(string $tableName): bool { function tableExists(string $tableName): bool {
$name = $this->sqlite->get([ $name = $this->db->get([
# depuis la version 3.33.0 le nom officiel de la table est sqlite_schema, # depuis la version 3.33.0 le nom officiel de la table est sqlite_schema,
# mais le nom sqlite_master est toujours valable pour le moment # mais le nom sqlite_master est toujours valable pour le moment
"select name from sqlite_master ", "select name from sqlite_master ",
@ -45,63 +45,38 @@ class SqliteStorage extends CapacitorStorage {
} }
function channelExists(string $name): bool { function channelExists(string $name): bool {
$name = $this->sqlite->get([ return null !== $this->db->get([
"select name from _channels", "select name",
"from" => static::CHANNELS_TABLE,
"where" => ["name" => $name], "where" => ["name" => $name],
]); ]);
return $name !== null; }
protected function _addToChannelsSql(CapacitorChannel $channel): array {
return cl::merge(parent::_createChannelsSql(), [
"suffix" => "on conflict ignore",
]);
} }
protected function _afterCreate(CapacitorChannel $channel): void { protected function _afterCreate(CapacitorChannel $channel): void {
$sqlite = $this->sqlite; $db = $this->db;
if (!$this->tableExists("_channels")) { 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
$sqlite->exec([ $db->exec($this->_createChannelsSql());
"create table if not exists _channels",
"cols" => [
"name" => "varchar primary key",
"table_name" => "varchar",
"class" => "varchar",
],
]);
} }
if (!$this->channelExists($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
$sqlite->exec([ $this->_addToChannelsSql($channel);
"insert into _channels",
"values" => [
"name" => $channel->getName(),
"table_name" => $channel->getTableName(),
"class" => get_class($channel),
],
"suffix" => "on conflict do nothing",
]);
} }
} }
protected function _beforeReset(CapacitorChannel $channel): void {
$sqlite = $this->sqlite;
$sqlite->exec([
"delete from _migration",
"where" => [
"channel" => $channel->getName(),
],
]);
$sqlite->exec([
"delete from _channels",
"where" => [
"name" => $channel->getName(),
],
]);
}
function _exists(CapacitorChannel $channel): bool { function _exists(CapacitorChannel $channel): bool {
return $this->tableExists($channel->getTableName()); return $this->tableExists($channel->getTableName());
} }
function close(): void { function close(): void {
$this->sqlite->close(); $this->db->close();
} }
} }

View File

@ -9,48 +9,14 @@ class _sqliteMigration extends _migration {
else return new static($migration); else return new static($migration);
} }
function setup(): void { protected function setMigrated(string $name, bool $done): void {
$this->db->exec([ $this->db->exec([
"create table if not exists _migration", "insert or replace",
"cols" => [ "into" => static::MIGRATION_TABLE,
"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" => [ "values" => [
"channel" => $this->channel, "channel" => $this->channel,
"key" => $key, "name" => $name,
"done" => 0, "done" => $done? 1: 0,
],
]);
return true;
}
function afterMigrate(string $key): void {
$this->db->exec([
"update _migration",
"values" => [
"done" => 1,
],
"where" => [
"channel" => $this->channel,
"key" => $key,
], ],
]); ]);
} }