diff --git a/migrations/m250707_103609_tree.php b/migrations/m250707_103609_tree.php new file mode 100644 index 0000000..43e0688 --- /dev/null +++ b/migrations/m250707_103609_tree.php @@ -0,0 +1,43 @@ +createTable( + '{{%tree}}', + [ + 'id' => $this->db->driverName !== 'oci' ? $this->primaryKey()->notNull() : $rawPrimaryKey, + 'name' => $this->db->driverName === 'oci' ? $this->string()->notNull() : $this->text()->notNull(), + 'lft' => $this->integer()->notNull(), + 'rgt' => $this->integer()->notNull(), + 'depth' => $this->integer()->notNull(), + ], + ); + + $this->createIndex('idx_tree_lft', '{{%tree}}', 'lft'); + $this->createIndex('idx_tree_rgt', '{{%tree}}', 'rgt'); + $this->createIndex('idx_tree_depth', '{{%tree}}', 'depth'); + $this->createIndex('idx_tree_lft_rgt', '{{%tree}}', ['lft', 'rgt']); + + if ($this->db->driverName !== 'sqlite') { + $this->addCommentOnTable('{{%tree}}', 'Nested sets tree structure for hierarchical data'); + $this->addCommentOnColumn('{{%tree}}', 'id', 'Primary key of the tree node'); + $this->addCommentOnColumn('{{%tree}}', 'name', 'Name of the tree node'); + $this->addCommentOnColumn('{{%tree}}', 'lft', 'Left boundary of nested set'); + $this->addCommentOnColumn('{{%tree}}', 'rgt', 'Right boundary of nested set'); + $this->addCommentOnColumn('{{%tree}}', 'depth', 'Node depth in the tree hierarchy'); + } + } + + public function safeDown() + { + $this->dropTable('{{%tree}}'); + } +} diff --git a/migrations/m250707_104009_multiple_tree.php b/migrations/m250707_104009_multiple_tree.php new file mode 100644 index 0000000..cfaf25b --- /dev/null +++ b/migrations/m250707_104009_multiple_tree.php @@ -0,0 +1,44 @@ +createTable( + '{{%multiple_tree}}', + [ + 'id' => $this->db->driverName !== 'oci' ? $this->primaryKey()->notNull() : $rawPrimaryKey, + 'tree' => $this->integer()->null(), + 'name' => $this->db->driverName === 'oci' ? $this->string()->notNull() : $this->text()->notNull(), + 'lft' => $this->integer()->notNull(), + 'rgt' => $this->integer()->notNull(), + 'depth' => $this->integer()->notNull(), + ], + ); + + $this->createIndex('idx_multiple_tree_tree', '{{%multiple_tree}}', 'tree'); + $this->createIndex('idx_multiple_tree_lft', '{{%multiple_tree}}', 'lft'); + $this->createIndex('idx_multiple_tree_rgt', '{{%multiple_tree}}', 'rgt'); + $this->createIndex('idx_multiple_tree_depth', '{{%multiple_tree}}', 'depth'); + $this->createIndex('idx_multiple_tree_tree_lft_rgt', '{{%multiple_tree}}', ['tree', 'lft', 'rgt']); + + if ($this->db->driverName !== 'sqlite') { + $this->addCommentOnTable('{{%multiple_tree}}', 'Multiple nested sets tree structure for hierarchical data'); + $this->addCommentOnColumn('{{%multiple_tree}}', 'tree', 'Tree identifier for multiple trees support'); + $this->addCommentOnColumn('{{%multiple_tree}}', 'lft', 'Left boundary of nested set'); + $this->addCommentOnColumn('{{%multiple_tree}}', 'rgt', 'Right boundary of nested set'); + $this->addCommentOnColumn('{{%multiple_tree}}', 'depth', 'Node depth in the tree hierarchy'); + } + } + + public function safeDown() + { + $this->dropTable('{{%multiple_tree}}'); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 521ee5d..bac0ea2 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,8 +9,9 @@ use Yii; use yii\base\InvalidArgumentException; use yii\console\Application; -use yii\db\{ActiveQuery, ActiveRecord, Connection, SchemaBuilderTrait}; +use yii\db\{ActiveQuery, ActiveRecord, Connection, Query, SchemaBuilderTrait}; use yii2\extensions\nestedsets\tests\support\model\{MultipleTree, Tree}; +use yii2\extensions\nestedsets\tests\support\stub\EchoMigrateController; use function array_merge; use function array_values; @@ -169,44 +170,24 @@ protected function buildFlatXMLDataSet(array $dataSet): string protected function createDatabase(): void { $command = $this->getDb()->createCommand(); + $dropTables = [ + 'migration', + 'multiple_tree', + 'tree', + ]; - if ($this->getDb()->getTableSchema('tree', true) !== null) { - $command->dropTable('tree')->execute(); + try { + $this->runMigrate('down', ['all']); + } catch (RuntimeException) { } - if ($this->getDb()->getTableSchema('multiple_tree', true) !== null) { - $command->dropTable('multiple_tree')->execute(); + foreach ($dropTables as $table) { + if ($this->getDb()->getTableSchema($table, true) !== null) { + $command->dropTable($table)->execute(); + } } - $primaryKey = $this->driverName === 'oci' - ? 'NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY' - : $this->primaryKey()->notNull(); - $name = $this->driverName === 'oci' - ? $this->string()->notNull() - : $this->text()->notNull(); - - $command->createTable( - 'tree', - [ - 'id' => $primaryKey, - 'name' => $name, - 'lft' => $this->integer()->notNull(), - 'rgt' => $this->integer()->notNull(), - 'depth' => $this->integer()->notNull(), - ], - )->execute(); - - $command->createTable( - 'multiple_tree', - [ - 'id' => $primaryKey, - 'tree' => $this->integer(), - 'name' => $name, - 'lft' => $this->integer()->notNull(), - 'rgt' => $this->integer()->notNull(), - 'depth' => $this->integer()->notNull(), - ], - )->execute(); + $this->runMigrate('up'); } /** @@ -405,6 +386,34 @@ protected function replaceQuotes(string $sql): string }; } + /** + * @phpstan-param array $params + */ + protected function runMigrate(string $action, array $params = []): mixed + { + $migrate = new EchoMigrateController( + 'migrate', + Yii::$app, + [ + 'migrationPath' => dirname(__DIR__) . '/migrations', + 'interactive' => false, + ], + ); + + ob_start(); + ob_implicit_flush(false); + + $result = $migrate->run($action, $params); + + $capture = ob_get_clean(); + + if (is_int($result) && $result !== 0) { + throw new RuntimeException("Migration '{$action}' failed with code {$result}.\nOutput: {$capture}"); + } + + return $result; + } + /** * Applies database updates to tree nodes. * diff --git a/tests/support/stub/EchoMigrateController.php b/tests/support/stub/EchoMigrateController.php new file mode 100644 index 0000000..8cfeaf6 --- /dev/null +++ b/tests/support/stub/EchoMigrateController.php @@ -0,0 +1,17 @@ +