diff --git a/php/src/db/Capacitor.php b/php/src/db/Capacitor.php index aa5da52..5528a68 100644 --- a/php/src/db/Capacitor.php +++ b/php/src/db/Capacitor.php @@ -39,6 +39,12 @@ class Capacitor implements ITransactor { return $this->getChannel()->getTableName(); } + function getCreateSql(): string { + $storage = $this->storage; + $channel = $this->channel; + return $storage->_getMigration($channel)->getSql(get_class($channel), $this->db()); + } + /** @var CapacitorChannel[] */ protected ?array $subChannels = null; diff --git a/php/src/db/CapacitorChannel.php b/php/src/db/CapacitorChannel.php index b0649ae..c7265bb 100644 --- a/php/src/db/CapacitorChannel.php +++ b/php/src/db/CapacitorChannel.php @@ -66,26 +66,47 @@ class CapacitorChannel implements ITransactor { $columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS); $primaryKeys = cl::withn(static::PRIMARY_KEYS); $migration = cl::withn(static::MIGRATION); + $lastMkey = 1; 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])); - } - } elseif (is_array($def)) { - # tableau: c'est une migration - $def = implode(" ", $def); - if ($def) { - $migration["add_$col"] = "alter table $tableName add column $col $def"; + if (is_array($def)) { + # tableau: c'est une migration + $mkey = null; + $mvalues = null; + $mdefs = $def; + $mindex = 0; + foreach ($mdefs as $mcol => $mdef) { + if ($mindex === 0 && $mcol === 0) { + $mindex++; + $mkey = $mdef; + } elseif ($mcol === $mindex) { + # si définition séquentielle, prendre la migration telle quelle + $mindex++; + $mvalues[] = $mdef; + } elseif ($mdef) { + # mise à jour d'une colonne + $mvalues[] = "alter table $tableName add column $mcol $mdef"; + } else { + # suppression d'une colonne + $mvalues[] = "alter table $tableName drop column $mcol"; + } + } + if ($mvalues !== null) { + if ($mkey === null) $mkey = $lastMkey++; + $migration[$mkey] = $mvalues; + } } else { - $migration["drop_$col"] = "alter table $tableName drop column $col"; + # si définition séquentielle, seules les définitions de clé + # primaires sont supportées + if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) { + $primaryKeys = preg_split('/\s*,\s*/', trim($ms[1])); + } } - } elseif (is_scalar($def)) { + } else { # chaine: c'est une définition $def = strval($def); if (preg_match('/\bprimary\s+key\b/i', $def)) { diff --git a/php/src/db/CapacitorStorage.php b/php/src/db/CapacitorStorage.php index 1773198..e7ff6af 100644 --- a/php/src/db/CapacitorStorage.php +++ b/php/src/db/CapacitorStorage.php @@ -40,6 +40,16 @@ abstract class CapacitorStorage { const SERSUM_DEFINITION = "varchar(40)"; const SERTS_DEFINITION = "datetime"; + protected static function sercol($def): string { + if (!is_string($def)) $def = strval($def); + switch ($def) { + case "serdata": $def = static::SERDATA_DEFINITION; break; + case "sersum": $def = static::SERSUM_DEFINITION; break; + case "serts": $def = static::SERTS_DEFINITION; break; + } + return $def; + } + const COLUMN_DEFINITIONS = [ "item__" => "serdata", "item__sum_" => "sersum", @@ -61,22 +71,29 @@ abstract class CapacitorStorage { $constraints = []; $index = 0; foreach ($tmp as $col => $def) { - switch ($def) { - case "serdata": $def = static::SERDATA_DEFINITION; break; - case "sersum": $def = static::SERSUM_DEFINITION; break; - case "serts": $def = static::SERTS_DEFINITION; break; - } if ($col === $index) { $index++; - $constraints[] = $def; - } elseif (is_array($def)) { - # éventuellement, ignorer les migrations - $def = implode(" ", $def); - if ($def && !$ignoreMigrations) { - $definitions[$col] = $def; + 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::sercol($mdef); + } else { + unset($definitions[$mcol]); + } + } + } + } + } else { + $constraints[] = $def; } - } elseif (is_scalar($def)) { - $definitions[$col] = strval($def); + } else { + $definitions[$col] = self::sercol($def); } } return cl::merge($definitions, $constraints); @@ -155,17 +172,6 @@ abstract class CapacitorStorage { ]; } - protected static function format_sql(CapacitorChannel $channel, string $sql): string { - $class = get_class($channel); - return <<exec([ "drop table if exists", - "table" => _migration::MIGRATION_TABLE + "table" => _migration::MIGRATION_TABLE, ]); $db->exec([ "create table", diff --git a/php/src/db/IDatabase.php b/php/src/db/IDatabase.php index dc71720..1383c9a 100644 --- a/php/src/db/IDatabase.php +++ b/php/src/db/IDatabase.php @@ -2,6 +2,9 @@ namespace nulib\db; interface IDatabase extends ITransactor { + /** obtenir la requête SQL correspondant à $query */ + function getSql($query, ?array $params=null): string; + /** * - si c'est un insert, retourner l'identifiant autogénéré de la ligne * - sinon retourner le nombre de lignes modifiées en cas de succès, ou false diff --git a/php/src/db/_private/_base.php b/php/src/db/_private/_base.php index 5e366e6..2ca42f9 100644 --- a/php/src/db/_private/_base.php +++ b/php/src/db/_private/_base.php @@ -1,8 +1,6 @@ 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 { + if (func::is_callable($migration)) { func::with($migration)->bind($this)->invoke([$db, $name]); + } else { + foreach (cl::with($migration) as $query) { + $db->exec($query); + } } $this->setMigrated($name, true); } } + + protected static function sql_prefix(?string $source=null): string { + $prefix = "-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8\n"; + if ($source !== null) $prefix .= "-- autogénéré à partir de $source\n"; + return $prefix; + } + + function getSql(?string $source=null, ?IDatabase $db=null): string { + $db = ($this->db ??= $db); + $lines = [self::sql_prefix($source)]; + foreach ($this->migrations as $name => $migration) { + $lines[] = "-- $name"; + if (func::is_callable($migration)) { + $lines[] = "-- "; + } else { + foreach (cl::with($migration) as $query) { + $sql = $db->getSql($query); + $lines[] = "$sql;"; + } + } + $lines[] = ""; + } + return implode("\n", $lines); + } } diff --git a/php/src/db/mysql/MysqlStorage.php b/php/src/db/mysql/MysqlStorage.php index 4f27f1a..ace8beb 100644 --- a/php/src/db/mysql/MysqlStorage.php +++ b/php/src/db/mysql/MysqlStorage.php @@ -41,14 +41,10 @@ class MysqlStorage extends CapacitorStorage { ]; 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()); + $migrations = cl::merge([ + "0init" => [$this->_createSql($channel)], + ], $channel->getMigration()); + return new _mysqlMigration($migrations, $channel->getName()); } const CHANNELS_COLS = [ diff --git a/php/src/db/pdo/Pdo.php b/php/src/db/pdo/Pdo.php index 78e38db..f34ff69 100644 --- a/php/src/db/pdo/Pdo.php +++ b/php/src/db/pdo/Pdo.php @@ -1,7 +1,6 @@ getSql(); + } + function open(): self { if ($this->db === null) { $dbconn = $this->dbconn; diff --git a/php/src/db/pgsql/Pgsql.php b/php/src/db/pgsql/Pgsql.php index 34d82dc..dbcbedf 100644 --- a/php/src/db/pgsql/Pgsql.php +++ b/php/src/db/pgsql/Pgsql.php @@ -117,6 +117,11 @@ class Pgsql implements IDatabase { /** @var resource */ protected $db = null; + function getSql($query, ?array $params=null): string { + $query = new _pgsqlQuery($query, $params); + return $query->getSql(); + } + function open(): self { if ($this->db === null) { $dbconn = $this->dbconn; diff --git a/php/src/db/pgsql/PgsqlException.php b/php/src/db/pgsql/PgsqlException.php index afd21c4..f9a500e 100644 --- a/php/src/db/pgsql/PgsqlException.php +++ b/php/src/db/pgsql/PgsqlException.php @@ -3,7 +3,6 @@ namespace nulib\db\pgsql; use Exception; use RuntimeException; -use SQLite3; class PgsqlException extends RuntimeException { static final function last_error($db): self { diff --git a/php/src/db/pgsql/PgsqlStorage.php b/php/src/db/pgsql/PgsqlStorage.php index 0dc93b7..dd89e2a 100644 --- a/php/src/db/pgsql/PgsqlStorage.php +++ b/php/src/db/pgsql/PgsqlStorage.php @@ -42,14 +42,10 @@ class PgsqlStorage extends CapacitorStorage { } 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()); + $migrations = cl::merge([ + "0init" => [$this->_createSql($channel)], + ], $channel->getMigration()); + return new _pgsqlMigration($migrations, $channel->getName()); } protected function _addToChannelsSql(CapacitorChannel $channel): array { diff --git a/php/src/db/sqlite/Sqlite.php b/php/src/db/sqlite/Sqlite.php index fffb8f1..ae4ea99 100644 --- a/php/src/db/sqlite/Sqlite.php +++ b/php/src/db/sqlite/Sqlite.php @@ -146,6 +146,11 @@ class Sqlite implements IDatabase { protected bool $inTransaction; + function getSql($query, ?array $params=null): string { + $query = new _sqliteQuery($query, $params); + return $query->getSql(); + } + function open(): self { if ($this->db === null) { $this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey); diff --git a/php/src/db/sqlite/SqliteStorage.php b/php/src/db/sqlite/SqliteStorage.php index f031a56..4621cb2 100644 --- a/php/src/db/sqlite/SqliteStorage.php +++ b/php/src/db/sqlite/SqliteStorage.php @@ -34,14 +34,10 @@ class SqliteStorage extends CapacitorStorage { } function _getMigration(CapacitorChannel $channel): _sqliteMigration { - return new _sqliteMigration(cl::merge([ - $this->_createSql($channel), - ], $channel->getMigration()), $channel->getName()); - } - - function _getCreateSql(CapacitorChannel $channel): string { - $query = new _sqliteQuery($this->_createSql($channel)); - return self::format_sql($channel, $query->getSql()); + $migrations = cl::merge([ + "0init" => [$this->_createSql($channel)], + ], $channel->getMigration()); + return new _sqliteMigration($migrations, $channel->getName()); } function channelExists(string $name): bool { diff --git a/php/src/db/sqlite/_sqliteQuery.php b/php/src/db/sqlite/_sqliteQuery.php index 14fb1b0..04e6f1c 100644 --- a/php/src/db/sqlite/_sqliteQuery.php +++ b/php/src/db/sqlite/_sqliteQuery.php @@ -2,15 +2,8 @@ namespace nulib\db\sqlite; use nulib\db\_private\_base; -use nulib\db\_private\_create; -use nulib\db\_private\_delete; -use nulib\db\_private\_generic; -use nulib\db\_private\_insert; -use nulib\db\_private\_select; -use nulib\db\_private\_update; use nulib\db\_private\Tbindings; use nulib\output\msg; -use nulib\ValueException; use SQLite3; use SQLite3Stmt; diff --git a/php/src/php/func.php b/php/src/php/func.php index 90a1cff..675d359 100644 --- a/php/src/php/func.php +++ b/php/src/php/func.php @@ -3,7 +3,6 @@ namespace nulib\php; use Closure; use Exception; -use Generator; use nulib\A; use nulib\cl; use nulib\cv; @@ -268,6 +267,8 @@ class func { $reason = "$c::$f: method not found"; return false; } + $method = new ReflectionMethod($c, $f); + if (!$method->isStatic()) return false; } else { $reason = "$c::$f: not bound"; } @@ -402,6 +403,8 @@ class func { $reason = "$c::$f: method not found"; return false; } + $method = new ReflectionMethod($c, $f); + if ($method->isStatic()) return false; } else { $reason = "$c::$f: not bound"; } diff --git a/php/tests/db/sqlite/ChannelMigrationTest.php b/php/tests/db/sqlite/ChannelMigrationTest.php new file mode 100644 index 0000000..1946179 --- /dev/null +++ b/php/tests/db/sqlite/ChannelMigrationTest.php @@ -0,0 +1,75 @@ +charge([ + "name" => $name, + "value" => $value, + "date_cre" => $dateCre, + "date_mod" => $dateMod, + "age" => $age, + ]); + } + } + + function testMigration() { + $storage = new SqliteStorage(__DIR__.'/capacitor.db'); + $data = [ + ["first", "premier", new DateTime(), new DateTime(), 15], + ["second", "deuxieme", new DateTime(), new DateTime(), 15], + ]; + + new Capacitor($storage, $channel = new MyChannel()); + $channel->reset(true); + $this->addData($channel, $data); + + new Capacitor($storage, $channel = new MyChannelV2()); + $this->addData($channel, $data); + + new Capacitor($storage, $channel = new MyChannelV3()); + $this->addData($channel, $data); + + $sql = $channel->getCapacitor()->getCreateSql(); + $class = MyChannelV3::class; + $expected = << "varchar not null primary key", + "value" => "varchar", + ]; + + const VERSION = 1; + + function __construct() { + parent::__construct(); + $this->version = static::VERSION; + } + + protected int $version; + + function getItemValues($item): ?array { + + return [ + "name" => "{$item["name"]}$this->version", + "value" => "{$item["value"]} v$this->version", + "date_cre" => $item["date_cre"] ?? null, + "date_mod" => $item["date_mod"] ?? null, + "age" => $item["age"] ?? null, + ]; + } +} diff --git a/php/tests/db/sqlite/impl/MyChannelV2.php b/php/tests/db/sqlite/impl/MyChannelV2.php new file mode 100644 index 0000000..68f18bf --- /dev/null +++ b/php/tests/db/sqlite/impl/MyChannelV2.php @@ -0,0 +1,14 @@ + "varchar", + "value" => "varchar", + ["dates", + "date_cre" => "datetime", + "date_mod" => "datetime", + ], + ]; +} diff --git a/php/tests/db/sqlite/impl/MyChannelV3.php b/php/tests/db/sqlite/impl/MyChannelV3.php new file mode 100644 index 0000000..e6e2170 --- /dev/null +++ b/php/tests/db/sqlite/impl/MyChannelV3.php @@ -0,0 +1,17 @@ + "varchar", + "value" => "varchar", + ["dates", + "date_cre" => "datetime", + "date_mod" => "datetime", + ], + ["infos", + "age" => "integer", + ], + ]; +} diff --git a/php/tests/php/funcTest.php b/php/tests/php/funcTest.php index 22fa882..f15ce27 100644 --- a/php/tests/php/funcTest.php +++ b/php/tests/php/funcTest.php @@ -562,7 +562,7 @@ namespace nulib\php { true, true, [SC::class, "tstatic"], ], [[SC::class, "tmethod"], - true, true, [SC::class, "tmethod"], + false, true, [SC::class, "tmethod"], true, true, [SC::class, "tmethod"], ], [[SC::class, "->tmethod"],