From c5cbf565d9fc7129a4a213b6c4ccf07c72767c90 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 21 Jul 2018 14:43:43 +0200 Subject: [PATCH] DBStructure enhancements (#5437) * Adding DBStructure enhancements - Added DBStructure::rename() - Added DBStructure::existTable() - Added DBStructure::existColumn() (cherry picked from commit 4ae06ec) * Adding `pre_update_1279` method - Added DBStructure::rename() - Added DBStructure::existTable() - Added DBStructure::existColumn() (cherry picked from commit 8496d84) * code standards (cherry picked from commit 551d09b) * simplify to `empty` instead `is_null` (cherry picked from commit ce68835) --- src/Database/DBA.php | 4 + src/Database/DBStructure.php | 135 +++++++++++++++++++++++++ tests/src/Core/Lock/LockTest.php | 3 +- tests/src/Database/DBATest.php | 40 ++++++++ tests/src/Database/DBStructureTest.php | 68 +++++++++++++ 5 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 tests/src/Database/DBATest.php create mode 100644 tests/src/Database/DBStructureTest.php diff --git a/src/Database/DBA.php b/src/Database/DBA.php index 4027f56d4e..51b2c8898e 100644 --- a/src/Database/DBA.php +++ b/src/Database/DBA.php @@ -644,6 +644,10 @@ class DBA $fields = []; + if (empty($condition)) { + return DBStructure::existsTable($table); + } + reset($condition); $first_key = key($condition); if (!is_int($first_key)) { diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index 55188d2b39..c6c8c15662 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -684,6 +684,141 @@ class DBStructure return $sql; } + /** + * Check if a table exists + * + * @param string $table Table name + * + * @return boolean Does the table exist? + */ + public static function existsTable($table) + { + if (empty($table)) { + return false; + } + + $table = DBA::escape($table); + + $sql = "SHOW TABLES LIKE '" . $table . "';"; + + $stmt = DBA::p($sql); + + if (is_bool($stmt)) { + $retval = $stmt; + } else { + $retval = (DBA::num_rows($stmt) > 0); + } + + DBA::close($stmt); + + return $retval; + } + + /** + * Check if the columns of the table exists + * + * @param string $table Table name + * @param array $columns Columns to check ( Syntax: [ $col1, $col2, .. ] ) + * + * @return boolean Does the table exist? + */ + public static function existsColumn($table, $columns = []) { + if (empty($table)) { + return false; + } + + if (is_null($columns) || empty($columns)) { + return self::existsTable($table); + } + + $table = DBA::escape($table); + + foreach ($columns AS $column) { + $sql = "SHOW COLUMNS FROM `" . $table . "` LIKE '" . $column . "';"; + + $stmt = DBA::p($sql); + + if (is_bool($stmt)) { + $retval = $stmt; + } else { + $retval = (DBA::num_rows($stmt) > 0); + } + + DBA::close($stmt); + + if (!$retval) { + return false; + } + } + + return true; + } + + const RENAME_COLUMN = 0; + const RENAME_PRIMARY_KEY = 1; + + /** + * Renames columns or the primary key of a table + * @todo You cannot rename a primary key if "auto increment" is set + * + * @param string $table Table name + * @param array $columns Columns Syntax for Rename: [ $old1 => [ $new1, $type1 ], $old2 => [ $new2, $type2 ], ... ] ) + * Syntax for Primary Key: [ $col1, $col2, ...] ) + * @param int $type The type of renaming (Default is Column) + * + * @return boolean Was the renaming successful? + * + */ + public static function rename($table, $columns, $type = self::RENAME_COLUMN) { + if (empty($table) || empty($columns)) { + return false; + } + + if (!is_array($columns)) { + return false; + } + + $table = DBA::escape($table); + + $sql = "ALTER TABLE `" . $table . "`"; + switch ($type) { + case self::RENAME_COLUMN: + if (!self::existsColumn($table, array_keys($columns))) { + return false; + } + $sql .= implode(',', array_map( + function ($to, $from) { + return " CHANGE `" . $from . "` `" . $to[0] . "` " . $to[1]; + }, + $columns, + array_keys($columns) + )); + break; + case self::RENAME_PRIMARY_KEY: + if (!self::existsColumn($table, $columns)) { + return false; + } + $sql .= " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode('`, `', $columns) . "`)"; + break; + default: + return false; + } + + $sql .= ";"; + + $stmt = DBA::p($sql); + + if (is_bool($stmt)) { + $retval = $stmt; + } else { + $retval = true; + } + + DBA::close($stmt); + + return $retval; + } + public static function definition() { $database = []; diff --git a/tests/src/Core/Lock/LockTest.php b/tests/src/Core/Lock/LockTest.php index 5663a26af1..9698f0bdea 100644 --- a/tests/src/Core/Lock/LockTest.php +++ b/tests/src/Core/Lock/LockTest.php @@ -2,6 +2,7 @@ namespace Friendica\Test\src\Core\Lock; +use Friendica\BaseObject; use Friendica\Core\Config; use Friendica\Test\DatabaseTest; @@ -20,7 +21,7 @@ abstract class LockTest extends DatabaseTest $this->instance = $this->getInstance(); // Reusable App object - $this->app = \Friendica\BaseObject::getApp(); + $this->app = BaseObject::getApp(); // Default config Config::set('config', 'hostname', 'localhost'); diff --git a/tests/src/Database/DBATest.php b/tests/src/Database/DBATest.php new file mode 100644 index 0000000000..17ac55fd7a --- /dev/null +++ b/tests/src/Database/DBATest.php @@ -0,0 +1,40 @@ +app = BaseObject::getApp(); + + // Default config + Config::set('config', 'hostname', 'localhost'); + Config::set('system', 'throttle_limit_day', 100); + Config::set('system', 'throttle_limit_week', 100); + Config::set('system', 'throttle_limit_month', 100); + Config::set('system', 'theme', 'system_theme'); + } + + /** + * @small + */ + public function testExists() { + + $this->assertTrue(DBA::exists('config', [])); + $this->assertFalse(DBA::exists('notable', [])); + + $this->assertTrue(DBA::exists('config', null)); + $this->assertFalse(DBA::exists('notable', null)); + + $this->assertTrue(DBA::exists('config', ['k' => 'hostname'])); + $this->assertFalse(DBA::exists('config', ['k' => 'nonsense'])); + } +} diff --git a/tests/src/Database/DBStructureTest.php b/tests/src/Database/DBStructureTest.php new file mode 100644 index 0000000000..268bf8eedd --- /dev/null +++ b/tests/src/Database/DBStructureTest.php @@ -0,0 +1,68 @@ +app = BaseObject::getApp(); + + // Default config + Config::set('config', 'hostname', 'localhost'); + Config::set('system', 'throttle_limit_day', 100); + Config::set('system', 'throttle_limit_week', 100); + Config::set('system', 'throttle_limit_month', 100); + Config::set('system', 'theme', 'system_theme'); + } + + /** + * @small + */ + public function testExists() { + $this->assertTrue(DBStructure::existsTable('config')); + + $this->assertFalse(DBStructure::existsTable('notatable')); + + $this->assertTrue(DBStructure::existsColumn('config', ['k'])); + $this->assertFalse(DBStructure::existsColumn('config', ['nonsense'])); + $this->assertFalse(DBStructure::existsColumn('config', ['k', 'nonsense'])); + } + + /** + * @small + */ + public function testRename() { + $fromColumn = 'k'; + $toColumn = 'key'; + $fromType = 'varbinary(255) not null'; + $toType = 'varbinary(255) not null comment \'Test To Type\''; + + $this->assertTrue(DBStructure::rename('config', [ $fromColumn => [ $toColumn, $toType ]])); + $this->assertTrue(DBStructure::existsColumn('config', [ $toColumn ])); + $this->assertFalse(DBStructure::existsColumn('config', [ $fromColumn ])); + + $this->assertTrue(DBStructure::rename('config', [ $toColumn => [ $fromColumn, $fromType ]])); + $this->assertTrue(DBStructure::existsColumn('config', [ $fromColumn ])); + $this->assertFalse(DBStructure::existsColumn('config', [ $toColumn ])); + } + + /** + * @small + */ + public function testChangePrimaryKey() { + $oldID = 'client_id'; + $newID = 'pw'; + + $this->assertTrue(DBStructure::rename('clients', [ $newID ], DBStructure::RENAME_PRIMARY_KEY)); + $this->assertTrue(DBStructure::rename('clients', [ $oldID ], DBStructure::RENAME_PRIMARY_KEY)); + } +}