From 146461a184df5f08f10acc30913e42285f69484f Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 15 Apr 2025 12:12:03 +0400 Subject: [PATCH] modifs.mineures sans commentaires --- php/src/db/CapacitorChannel.php | 20 +++++++-- php/src/db/CapacitorStorage.php | 60 +++++++++++++++++++++++-- php/src/db/_private/_create.php | 3 +- php/src/db/_private/_migration.php | 43 ++++++++++++++---- php/src/db/mysql/MysqlStorage.php | 29 +++++++++--- php/src/db/mysql/_mysqlMigration.php | 31 +++++++++++++ php/src/db/mysql/_mysqlQuery.php | 1 + php/src/db/pgsql/PgsqlStorage.php | 50 +++++++-------------- php/src/db/pgsql/_pgsqlMigration.php | 24 ++++++++++ php/src/db/sqlite/SqliteStorage.php | 61 ++++++++------------------ php/src/db/sqlite/_sqliteMigration.php | 44 +++---------------- 11 files changed, 227 insertions(+), 139 deletions(-) create mode 100644 php/src/db/mysql/_mysqlMigration.php create mode 100644 php/src/db/pgsql/_pgsqlMigration.php diff --git a/php/src/db/CapacitorChannel.php b/php/src/db/CapacitorChannel.php index a17e679..dbeebe7 100644 --- a/php/src/db/CapacitorChannel.php +++ b/php/src/db/CapacitorChannel.php @@ -65,15 +65,29 @@ class CapacitorChannel { $this->created = false; $columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS); $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; foreach ($columnDefinitions as $col => $def) { if ($col === $index) { + # si définition séquentielle, seules les définitions de clé + # primaires sont supportées $index++; if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) { $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)) { $primaryKeys[] = $col; } @@ -82,7 +96,7 @@ class CapacitorChannel { } $this->columnDefinitions = $columnDefinitions; $this->primaryKeys = $primaryKeys; - $this->migration = cl::withn(static::MIGRATION); + $this->migration = $migration; } protected string $name; diff --git a/php/src/db/CapacitorStorage.php b/php/src/db/CapacitorStorage.php index e2bc4ac..66acb17 100644 --- a/php/src/db/CapacitorStorage.php +++ b/php/src/db/CapacitorStorage.php @@ -47,7 +47,7 @@ abstract class CapacitorStorage { "modified_" => "serts", ]; - protected function ColumnDefinitions(CapacitorChannel $channel): array { + protected function ColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array { $definitions = []; if ($channel->getPrimaryKeys() === null) { $definitions[] = static::PRIMARY_KEY_DEFINITION; @@ -69,8 +69,14 @@ abstract class CapacitorStorage { if ($col === $index) { $index++; $constraints[] = $def; - } else { - $definitions[$col] = $def; + } elseif (is_array($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); @@ -145,7 +151,7 @@ abstract class CapacitorStorage { return [ "create table if not exists", "table" => $channel->getTableName(), - "cols" => $this->ColumnDefinitions($channel), + "cols" => $this->ColumnDefinitions($channel, true), ]; } @@ -162,7 +168,37 @@ EOT; 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 { + $db = $this->db(); + $db->exec($this->_createChannelsSql()); + $db->exec($this->_addToChannelsSql($channel)); } protected function _create(CapacitorChannel $channel): void { @@ -191,6 +227,22 @@ EOT; } 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é */ diff --git a/php/src/db/_private/_create.php b/php/src/db/_private/_create.php index 29e83eb..afb90c5 100644 --- a/php/src/db/_private/_create.php +++ b/php/src/db/_private/_create.php @@ -11,9 +11,8 @@ class _create extends _common { ]; static function isa(string $sql): bool { - //return preg_match("/^create(?:\s+table)?\b/i", $sql); #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 { diff --git a/php/src/db/_private/_migration.php b/php/src/db/_private/_migration.php index 8ad75a6..5f33acb 100644 --- a/php/src/db/_private/_migration.php +++ b/php/src/db/_private/_migration.php @@ -5,6 +5,14 @@ use nulib\db\IDatabase; use nulib\php\func; 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; function __construct($migrations, string $channel="", ?IDatabase $db=null) { @@ -25,21 +33,40 @@ abstract class _migration { /** @var callable[]|string[] */ protected $migrations; - abstract function setup(): void; - abstract function beforeMigrate(string $key): bool; - abstract function afterMigrate(string $key): void; + function ensureTable(): void { + $this->db->exec([ + "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 { $db = ($this->db ??= $db); - $this->setup(); - foreach ($this->migrations as $key => $migration) { - if (!$this->beforeMigrate($key)) continue; + $this->ensureTable(); + foreach ($this->migrations as $name => $migration) { + if ($this->isMigrated($name)) continue; + $this->setMigrated($name, false); if (is_string($migration) || !func::is_callable($migration)) { $db->exec($migration); } 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); } } } diff --git a/php/src/db/mysql/MysqlStorage.php b/php/src/db/mysql/MysqlStorage.php index ced2c6f..9f23ec4 100644 --- a/php/src/db/mysql/MysqlStorage.php +++ b/php/src/db/mysql/MysqlStorage.php @@ -1,6 +1,7 @@ mysql = Mysql::with($mysql); + $this->db = Mysql::with($mysql); } - protected Mysql $mysql; + protected Mysql $db; function db(): Mysql { - return $this->mysql; + return $this->db; } const PRIMARY_KEY_DEFINITION = [ "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 { $query = new _mysqlQuery($this->_createSql($channel)); 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 { - $mysql = $this->mysql; + $mysql = $this->db; $tableName = $mysql->get([ "select table_name from information_schema.tables", "where" => [ @@ -40,6 +59,6 @@ class MysqlStorage extends CapacitorStorage { } function close(): void { - $this->mysql->close(); + $this->db->close(); } } diff --git a/php/src/db/mysql/_mysqlMigration.php b/php/src/db/mysql/_mysqlMigration.php new file mode 100644 index 0000000..ab107de --- /dev/null +++ b/php/src/db/mysql/_mysqlMigration.php @@ -0,0 +1,31 @@ + "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", + ]); + } +} diff --git a/php/src/db/mysql/_mysqlQuery.php b/php/src/db/mysql/_mysqlQuery.php index 83cfa1b..b207161 100644 --- a/php/src/db/mysql/_mysqlQuery.php +++ b/php/src/db/mysql/_mysqlQuery.php @@ -4,4 +4,5 @@ namespace nulib\db\mysql; use nulib\db\pdo\_pdoQuery; class _mysqlQuery extends _pdoQuery { + const DEBUG_QUERIES = false; } diff --git a/php/src/db/pgsql/PgsqlStorage.php b/php/src/db/pgsql/PgsqlStorage.php index 419e043..e7560da 100644 --- a/php/src/db/pgsql/PgsqlStorage.php +++ b/php/src/db/pgsql/PgsqlStorage.php @@ -1,6 +1,7 @@ pgsql = Pgsql::with($pgsql); + $this->db = Pgsql::with($pgsql); } - protected Pgsql $pgsql; + protected Pgsql $db; function db(): Pgsql { - return $this->pgsql; + return $this->db; } const PRIMARY_KEY_DEFINITION = [ "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 { $query = new _pgsqlQuery($this->_createSql($channel)); return self::format_sql($channel, $query->getSql()); } - protected function _afterCreate(CapacitorChannel $channel): void { - $db = $this->pgsql; - $db->exec([ - "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(), - ], + protected function _addToChannelsSql(CapacitorChannel $channel): array { + return cl::merge(parent::_addToChannelsSql($channel), [ + "suffix" => "on conflict do nothing", ]); } @@ -69,7 +49,7 @@ class PgsqlStorage extends CapacitorStorage { } else { $schemaName = "public"; } - return null !== $this->pgsql->get([ + return null !== $this->db->get([ "select tablename from pg_tables", "where" => [ "schemaname" => $schemaName, @@ -79,6 +59,6 @@ class PgsqlStorage extends CapacitorStorage { } function close(): void { - $this->pgsql->close(); + $this->db->close(); } } diff --git a/php/src/db/pgsql/_pgsqlMigration.php b/php/src/db/pgsql/_pgsqlMigration.php new file mode 100644 index 0000000..2a37c4b --- /dev/null +++ b/php/src/db/pgsql/_pgsqlMigration.php @@ -0,0 +1,24 @@ +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", + ]); + } +} diff --git a/php/src/db/sqlite/SqliteStorage.php b/php/src/db/sqlite/SqliteStorage.php index 3ba97d6..b63cac9 100644 --- a/php/src/db/sqlite/SqliteStorage.php +++ b/php/src/db/sqlite/SqliteStorage.php @@ -10,13 +10,13 @@ use nulib\db\CapacitorStorage; */ class SqliteStorage extends CapacitorStorage { function __construct($sqlite) { - $this->sqlite = Sqlite::with($sqlite); + $this->db = Sqlite::with($sqlite); } - protected Sqlite $sqlite; + protected Sqlite $db; function db(): Sqlite { - return $this->sqlite; + return $this->db; } const PRIMARY_KEY_DEFINITION = [ @@ -35,7 +35,7 @@ class SqliteStorage extends CapacitorStorage { } 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, # mais le nom sqlite_master est toujours valable pour le moment "select name from sqlite_master ", @@ -45,63 +45,38 @@ class SqliteStorage extends CapacitorStorage { } function channelExists(string $name): bool { - $name = $this->sqlite->get([ - "select name from _channels", + return null !== $this->db->get([ + "select name", + "from" => static::CHANNELS_TABLE, "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 { - $sqlite = $this->sqlite; - if (!$this->tableExists("_channels")) { + $db = $this->db; + if (!$this->tableExists(static::CHANNELS_TABLE)) { # ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un # verrou en écriture - $sqlite->exec([ - "create table if not exists _channels", - "cols" => [ - "name" => "varchar primary key", - "table_name" => "varchar", - "class" => "varchar", - ], - ]); + $db->exec($this->_createChannelsSql()); } if (!$this->channelExists($channel->getName())) { # ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un # verrou en écriture - $sqlite->exec([ - "insert into _channels", - "values" => [ - "name" => $channel->getName(), - "table_name" => $channel->getTableName(), - "class" => get_class($channel), - ], - "suffix" => "on conflict do nothing", - ]); + $this->_addToChannelsSql($channel); } } - 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 { return $this->tableExists($channel->getTableName()); } function close(): void { - $this->sqlite->close(); + $this->db->close(); } } diff --git a/php/src/db/sqlite/_sqliteMigration.php b/php/src/db/sqlite/_sqliteMigration.php index 1fdca37..182c71c 100644 --- a/php/src/db/sqlite/_sqliteMigration.php +++ b/php/src/db/sqlite/_sqliteMigration.php @@ -9,48 +9,14 @@ class _sqliteMigration extends _migration { else return new static($migration); } - function setup(): void { + protected function setMigrated(string $name, bool $done): 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", + "insert or replace", + "into" => static::MIGRATION_TABLE, "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, + "name" => $name, + "done" => $done? 1: 0, ], ]); }