X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FDatabase%2FDBStructure.php;h=abbac4e781795a700b10c52c95cbecf0abc0aeb2;hb=3de1fc3097cc7a1e38bf1a0c3cae5ae4ec8067eb;hp=f8fc1651c140ece8dffd8385f65097bdf1561638;hpb=389685e09944e3823effd2e820ab2fd95d8b4b32;p=friendica.git diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index f8fc1651c1..abbac4e781 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -2,18 +2,17 @@ /** * @file src/Database/DBStructure.php */ + namespace Friendica\Database; use Exception; -use Friendica\Core\Addon; use Friendica\Core\Config; +use Friendica\Core\Hook; use Friendica\Core\L10n; +use Friendica\Core\Logger; use Friendica\Util\DateTimeFormat; -require_once 'boot.php'; require_once 'include/dba.php'; -require_once 'include/enotify.php'; -require_once 'include/text.php'; /** * @brief This class contain functions for the database management @@ -22,21 +21,36 @@ require_once 'include/text.php'; */ class DBStructure { + const UPDATE_NOT_CHECKED = 0; // Database check wasn't executed before + const UPDATE_SUCCESSFUL = 1; // Database check was successful + const UPDATE_FAILED = 2; // Database check failed + + const RENAME_COLUMN = 0; + const RENAME_PRIMARY_KEY = 1; + + /** + * Database structure definition loaded from config/dbstructure.config.php + * + * @var array + */ + private static $definition = []; + /* * Converts all tables from MyISAM to InnoDB */ - public static function convertToInnoDB() { + public static function convertToInnoDB() + { $r = q("SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `engine` = 'MyISAM' AND `table_schema` = '%s'", DBA::escape(DBA::databaseName())); if (!DBA::isResult($r)) { - echo L10n::t('There are no tables on MyISAM.')."\n"; + echo L10n::t('There are no tables on MyISAM.') . "\n"; return; } foreach ($r AS $table) { $sql = sprintf("ALTER TABLE `%s` engine=InnoDB;", DBA::escape($table['TABLE_NAME'])); - echo $sql."\n"; + echo $sql . "\n"; $result = DBA::e($sql); if (!DBA::isResult($result)) { @@ -45,176 +59,213 @@ class DBStructure } } - /* - * send the email and do what is needed to do on update fails + /** + * @brief Print out database error messages + * + * @param string $message Message to be added to the error message * - * @param update_id (int) number of failed update - * @param error_message (str) error message + * @return string Error message */ - public static function updateFail($update_id, $error_message) { - $a = get_app(); + private static function printUpdateError($message) + { + echo L10n::t("\nError %d occurred during database update:\n%s\n", + DBA::errorNo(), DBA::errorMessage()); - //send the administrators an e-mail - $admin_mail_list = "'".implode("','", array_map(['Friendica\Database\DBA', 'escape'], explode(",", str_replace(" ", "", Config::get('config', 'admin_email')))))."'"; - $adminlist = q("SELECT uid, language, email FROM user WHERE email IN (%s)", - $admin_mail_list - ); + return L10n::t('Errors encountered performing database changes: ') . $message . EOL; + } - // No valid result? - if (!DBA::isResult($adminlist)) { - logger(sprintf('Cannot notify administrators about update_id=%d, error_message=%s', $update_id, $error_message), LOGGER_INFO); + public static function printStructure($basePath) + { + $database = self::definition($basePath, false); - // Don't continue - return; - } + echo "-- ------------------------------------------\n"; + echo "-- " . FRIENDICA_PLATFORM . " " . FRIENDICA_VERSION . " (" . FRIENDICA_CODENAME, ")\n"; + echo "-- DB_UPDATE_VERSION " . DB_UPDATE_VERSION . "\n"; + echo "-- ------------------------------------------\n\n\n"; + foreach ($database AS $name => $structure) { + echo "--\n"; + echo "-- TABLE $name\n"; + echo "--\n"; + self::createTable($name, $structure, true, false); - // every admin could had different language - foreach ($adminlist as $admin) { - $lang = (($admin['language'])?$admin['language']:'en'); - L10n::pushLang($lang); - - $preamble = deindent(L10n::t(" - The friendica developers released update %s recently, - but when I tried to install it, something went terribly wrong. - This needs to be fixed soon and I can't do it alone. Please contact a - friendica developer if you can not help me on your own. My database might be invalid.")); - $body = L10n::t("The error message is\n[pre]%s[/pre]"); - $preamble = sprintf($preamble, $update_id); - $body = sprintf($body, $error_message); - - notification([ - 'uid' => $admin['uid'], - 'type' => SYSTEM_EMAIL, - 'to_email' => $admin['email'], - 'preamble' => $preamble, - 'body' => $body, - 'language' => $lang] - ); - } - - //try the logger - logger("CRITICAL: Database structure update failed: ".$error_message); + echo "\n"; + } } + /** + * Loads the database structure definition from the config/dbstructure.config.php file. + * On first pass, defines DB_UPDATE_VERSION constant. + * + * @see config/dbstructure.config.php + * @param boolean $with_addons_structure Whether to tack on addons additional tables + * @param string $basePath The base path of this application + * @return array + * @throws Exception + */ + public static function definition($basePath, $with_addons_structure = true) + { + if (!self::$definition) { - private static function tableStructure($table) { - $structures = q("DESCRIBE `%s`", $table); + $filename = $basePath . '/config/dbstructure.config.php'; - $full_columns = q("SHOW FULL COLUMNS FROM `%s`", $table); + if (!is_readable($filename)) { + throw new Exception('Missing database structure config file config/dbstructure.config.php'); + } - $indexes = q("SHOW INDEX FROM `%s`", $table); + $definition = require $filename; - $table_status = q("SHOW TABLE STATUS WHERE `name` = '%s'", $table); + if (!$definition) { + throw new Exception('Corrupted database structure config file config/dbstructure.config.php'); + } - if (DBA::isResult($table_status)) { - $table_status = $table_status[0]; + self::$definition = $definition; } else { - $table_status = []; + $definition = self::$definition; } - $fielddata = []; - $indexdata = []; - - if (DBA::isResult($indexes)) { - foreach ($indexes AS $index) { - if ($index['Key_name'] != 'PRIMARY' && $index['Non_unique'] == '0' && !isset($indexdata[$index["Key_name"]])) { - $indexdata[$index["Key_name"]] = ['UNIQUE']; - } + if ($with_addons_structure) { + Hook::callAll('dbstructure_definition', $definition); + } - $column = $index["Column_name"]; + return $definition; + } - if ($index["Sub_part"] != "") { - $column .= "(".$index["Sub_part"].")"; - } + private static function createTable($name, $structure, $verbose, $action) + { + $r = true; - $indexdata[$index["Key_name"]][] = $column; + $engine = ""; + $comment = ""; + $sql_rows = []; + $primary_keys = []; + foreach ($structure["fields"] AS $fieldname => $field) { + $sql_rows[] = "`" . DBA::escape($fieldname) . "` " . self::FieldCommand($field); + if (!empty($field['primary'])) { + $primary_keys[] = $fieldname; } } - if (DBA::isResult($structures)) { - foreach ($structures AS $field) { - // Replace the default size values so that we don't have to define them - $search = ['tinyint(1)', 'tinyint(3) unsigned', 'tinyint(4)', 'smallint(5) unsigned', 'smallint(6)', 'mediumint(8) unsigned', 'mediumint(9)', 'bigint(20)', 'int(10) unsigned', 'int(11)']; - $replace = ['boolean', 'tinyint unsigned', 'tinyint', 'smallint unsigned', 'smallint', 'mediumint unsigned', 'mediumint', 'bigint', 'int unsigned', 'int']; - $field["Type"] = str_replace($search, $replace, $field["Type"]); - $fielddata[$field["Field"]]["type"] = $field["Type"]; - if ($field["Null"] == "NO") { - $fielddata[$field["Field"]]["not null"] = true; + if (!empty($structure["indexes"])) { + foreach ($structure["indexes"] AS $indexname => $fieldnames) { + $sql_index = self::createIndex($indexname, $fieldnames, ""); + if (!is_null($sql_index)) { + $sql_rows[] = $sql_index; } + } + } - if (isset($field["Default"])) { - $fielddata[$field["Field"]]["default"] = $field["Default"]; - } + if (isset($structure["engine"])) { + $engine = " ENGINE=" . $structure["engine"]; + } - if ($field["Extra"] != "") { - $fielddata[$field["Field"]]["extra"] = $field["Extra"]; - } + if (isset($structure["comment"])) { + $comment = " COMMENT='" . DBA::escape($structure["comment"]) . "'"; + } - if ($field["Key"] == "PRI") { - $fielddata[$field["Field"]]["primary"] = true; - } - } + $sql = implode(",\n\t", $sql_rows); + + $sql = sprintf("CREATE TABLE IF NOT EXISTS `%s` (\n\t", DBA::escape($name)) . $sql . + "\n)" . $engine . " DEFAULT COLLATE utf8mb4_general_ci" . $comment; + if ($verbose) { + echo $sql . ";\n"; } - if (DBA::isResult($full_columns)) { - foreach ($full_columns AS $column) { - $fielddata[$column["Field"]]["Collation"] = $column["Collation"]; - $fielddata[$column["Field"]]["comment"] = $column["Comment"]; - } + + if ($action) { + $r = DBA::e($sql); } - return ["fields" => $fielddata, "indexes" => $indexdata, "table_status" => $table_status]; + return $r; } - public static function printStructure() { - $database = self::definition(); + private static function FieldCommand($parameters, $create = true) + { + $fieldstruct = $parameters["type"]; - echo "-- ------------------------------------------\n"; - echo "-- ".FRIENDICA_PLATFORM." ".FRIENDICA_VERSION." (".FRIENDICA_CODENAME,")\n"; - echo "-- DB_UPDATE_VERSION ".DB_UPDATE_VERSION."\n"; - echo "-- ------------------------------------------\n\n\n"; - foreach ($database AS $name => $structure) { - echo "--\n"; - echo "-- TABLE $name\n"; - echo "--\n"; - self::createTable($name, $structure, true, false); + if (isset($parameters["Collation"])) { + $fieldstruct .= " COLLATE " . $parameters["Collation"]; + } - echo "\n"; + if (isset($parameters["not null"])) { + $fieldstruct .= " NOT NULL"; } + + if (isset($parameters["default"])) { + if (strpos(strtolower($parameters["type"]), "int") !== false) { + $fieldstruct .= " DEFAULT " . $parameters["default"]; + } else { + $fieldstruct .= " DEFAULT '" . $parameters["default"] . "'"; + } + } + if (isset($parameters["extra"])) { + $fieldstruct .= " " . $parameters["extra"]; + } + + if (isset($parameters["comment"])) { + $fieldstruct .= " COMMENT '" . DBA::escape($parameters["comment"]) . "'"; + } + + /*if (($parameters["primary"] != "") && $create) + $fieldstruct .= " PRIMARY KEY";*/ + + return ($fieldstruct); } - /** - * @brief Print out database error messages - * - * @param string $message Message to be added to the error message - * - * @return string Error message - */ - private static function printUpdateError($message) { - echo L10n::t("\nError %d occurred during database update:\n%s\n", - DBA::errorNo(), DBA::errorMessage()); + private static function createIndex($indexname, $fieldnames, $method = "ADD") + { + $method = strtoupper(trim($method)); + if ($method != "" && $method != "ADD") { + throw new Exception("Invalid parameter 'method' in self::createIndex(): '$method'"); + } - return L10n::t('Errors encountered performing database changes: ').$message.EOL; + if (in_array($fieldnames[0], ["UNIQUE", "FULLTEXT"])) { + $index_type = array_shift($fieldnames); + $method .= " " . $index_type; + } + + $names = ""; + foreach ($fieldnames AS $fieldname) { + if ($names != "") { + $names .= ","; + } + + if (preg_match('|(.+)\((\d+)\)|', $fieldname, $matches)) { + $names .= "`" . DBA::escape($matches[1]) . "`(" . intval($matches[2]) . ")"; + } else { + $names .= "`" . DBA::escape($fieldname) . "`"; + } + } + + if ($indexname == "PRIMARY") { + return sprintf("%s PRIMARY KEY(%s)", $method, $names); + } + + + $sql = sprintf("%s INDEX `%s` (%s)", $method, DBA::escape($indexname), $names); + return ($sql); } /** * Updates DB structure and returns eventual errors messages * - * @param bool $verbose - * @param bool $action Whether to actually apply the update - * @param bool $install Is this the initial update during the installation? - * @param array $tables An array of the database tables - * @param array $definition An array of the definition tables + * @param string $basePath The base path of this application + * @param bool $verbose + * @param bool $action Whether to actually apply the update + * @param bool $install Is this the initial update during the installation? + * @param array $tables An array of the database tables + * @param array $definition An array of the definition tables * @return string Empty string if the update is successful, error messages otherwise + * @throws Exception */ - public static function update($verbose, $action, $install = false, array $tables = null, array $definition = null) { + public static function update($basePath, $verbose, $action, $install = false, array $tables = null, array $definition = null) + { if ($action && !$install) { Config::set('system', 'maintenance', 1); - Config::set('system', 'maintenance_reason', L10n::t('%s: Database update', DateTimeFormat::utcNow().' '.date('e'))); + Config::set('system', 'maintenance_reason', L10n::t('%s: Database update', DateTimeFormat::utcNow() . ' ' . date('e'))); } $errors = ''; - logger('updating structure', LOGGER_DEBUG); + Logger::log('updating structure', Logger::DEBUG); // Get the current structure $database = []; @@ -227,14 +278,14 @@ class DBStructure foreach ($tables AS $table) { $table = current($table); - logger(sprintf('updating structure for table %s ...', $table), LOGGER_DEBUG); + Logger::log(sprintf('updating structure for table %s ...', $table), Logger::DEBUG); $database[$table] = self::tableStructure($table); } } // Get the definition if (is_null($definition)) { - $definition = self::definition(); + $definition = self::definition($basePath); } // MySQL >= 5.7.4 doesn't support the IGNORE keyword in ALTER TABLE statements @@ -261,16 +312,16 @@ class DBStructure } else { foreach ($structure["indexes"] AS $indexname => $fieldnames) { if (isset($database[$name]["indexes"][$indexname])) { - $current_index_definition = implode(",",$database[$name]["indexes"][$indexname]); + $current_index_definition = implode(",", $database[$name]["indexes"][$indexname]); } else { $current_index_definition = "__NOT_SET__"; } - $new_index_definition = implode(",",$fieldnames); + $new_index_definition = implode(",", $fieldnames); if ($current_index_definition != $new_index_definition) { if ($fieldnames[0] == "UNIQUE") { $is_unique = true; if ($ignore == "") { - $temp_name = "temp-".$name; + $temp_name = "temp-" . $name; } } } @@ -282,29 +333,29 @@ class DBStructure * and index name doesn't start with "local_" */ foreach ($database[$name]["indexes"] as $indexname => $fieldnames) { - $current_index_definition = implode(",",$fieldnames); + $current_index_definition = implode(",", $fieldnames); if (isset($structure["indexes"][$indexname])) { - $new_index_definition = implode(",",$structure["indexes"][$indexname]); + $new_index_definition = implode(",", $structure["indexes"][$indexname]); } else { $new_index_definition = "__NOT_SET__"; } if ($current_index_definition != $new_index_definition && substr($indexname, 0, 6) != 'local_') { - $sql2=self::dropIndex($indexname); + $sql2 = self::dropIndex($indexname); if ($sql3 == "") { - $sql3 = "ALTER".$ignore." TABLE `".$temp_name."` ".$sql2; + $sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2; } else { - $sql3 .= ", ".$sql2; + $sql3 .= ", " . $sql2; } } } // Compare the field structure field by field foreach ($structure["fields"] AS $fieldname => $parameters) { if (!isset($database[$name]["fields"][$fieldname])) { - $sql2=self::addTableField($fieldname, $parameters); + $sql2 = self::addTableField($fieldname, $parameters); if ($sql3 == "") { - $sql3 = "ALTER" . $ignore . " TABLE `".$temp_name."` ".$sql2; + $sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2; } else { - $sql3 .= ", ".$sql2; + $sql3 .= ", " . $sql2; } } else { // Compare the field definition @@ -329,9 +380,9 @@ class DBStructure if ($current_field_definition != $new_field_definition) { $sql2 = self::modifyTableField($fieldname, $parameters); if ($sql3 == "") { - $sql3 = "ALTER" . $ignore . " TABLE `".$temp_name."` ".$sql2; + $sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2; } else { - $sql3 .= ", ".$sql2; + $sql3 .= ", " . $sql2; } } } @@ -346,48 +397,47 @@ class DBStructure if (!$is_new_table) { foreach ($structure["indexes"] AS $indexname => $fieldnames) { if (isset($database[$name]["indexes"][$indexname])) { - $current_index_definition = implode(",",$database[$name]["indexes"][$indexname]); + $current_index_definition = implode(",", $database[$name]["indexes"][$indexname]); } else { $current_index_definition = "__NOT_SET__"; } - $new_index_definition = implode(",",$fieldnames); + $new_index_definition = implode(",", $fieldnames); if ($current_index_definition != $new_index_definition) { $sql2 = self::createIndex($indexname, $fieldnames); // Fetch the "group by" fields for unique indexes - if ($fieldnames[0] == "UNIQUE") { - $group_by = self::groupBy($indexname, $fieldnames); - } + $group_by = self::groupBy($fieldnames); if ($sql2 != "") { if ($sql3 == "") { - $sql3 = "ALTER" . $ignore . " TABLE `".$temp_name."` ".$sql2; + $sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2; } else { - $sql3 .= ", ".$sql2; + $sql3 .= ", " . $sql2; } } } } if (isset($database[$name]["table_status"]["Comment"])) { - if ($database[$name]["table_status"]["Comment"] != $structure['comment']) { - $sql2 = "COMMENT = '".DBA::escape($structure['comment'])."'"; + $structurecomment = defaults($structure, "comment", ""); + if ($database[$name]["table_status"]["Comment"] != $structurecomment) { + $sql2 = "COMMENT = '" . DBA::escape($structurecomment) . "'"; if ($sql3 == "") { - $sql3 = "ALTER" . $ignore . " TABLE `".$temp_name."` ".$sql2; + $sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2; } else { - $sql3 .= ", ".$sql2; + $sql3 .= ", " . $sql2; } } } if (isset($database[$name]["table_status"]["Engine"]) && isset($structure['engine'])) { if ($database[$name]["table_status"]["Engine"] != $structure['engine']) { - $sql2 = "ENGINE = '".DBA::escape($structure['engine'])."'"; + $sql2 = "ENGINE = '" . DBA::escape($structure['engine']) . "'"; if ($sql3 == "") { - $sql3 = "ALTER" . $ignore . " TABLE `".$temp_name."` ".$sql2; + $sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2; } else { - $sql3 .= ", ".$sql2; + $sql3 .= ", " . $sql2; } } } @@ -397,9 +447,9 @@ class DBStructure $sql2 = "DEFAULT COLLATE utf8mb4_general_ci"; if ($sql3 == "") { - $sql3 = "ALTER" . $ignore . " TABLE `".$temp_name."` ".$sql2; + $sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2; } else { - $sql3 .= ", ".$sql2; + $sql3 .= ", " . $sql2; } } } @@ -424,9 +474,9 @@ class DBStructure if ($field_definition['Collation'] != $parameters['Collation']) { $sql2 = self::modifyTableField($fieldname, $parameters); if (($sql3 == "") || (substr($sql3, -2, 2) == "; ")) { - $sql3 .= "ALTER" . $ignore . " TABLE `".$temp_name."` ".$sql2; + $sql3 .= "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2; } else { - $sql3 .= ", ".$sql2; + $sql3 .= ", " . $sql2; } } } @@ -451,27 +501,27 @@ class DBStructure if ($ignore != "") { echo "SET session old_alter_table=1;\n"; } else { - echo "DROP TABLE IF EXISTS `".$temp_name."`;\n"; - echo "CREATE TABLE `".$temp_name."` LIKE `".$name."`;\n"; + echo "DROP TABLE IF EXISTS `" . $temp_name . "`;\n"; + echo "CREATE TABLE `" . $temp_name . "` LIKE `" . $name . "`;\n"; } } - echo $sql3."\n"; + echo $sql3 . "\n"; if ($is_unique && ($temp_name != $name)) { if ($ignore != "") { echo "SET session old_alter_table=0;\n"; } else { - echo "INSERT INTO `".$temp_name."` SELECT ".DBA::anyValueFallback($field_list)." FROM `".$name."`".$group_by.";\n"; - echo "DROP TABLE `".$name."`;\n"; - echo "RENAME TABLE `".$temp_name."` TO `".$name."`;\n"; + echo "INSERT INTO `" . $temp_name . "` SELECT " . DBA::anyValueFallback($field_list) . " FROM `" . $name . "`" . $group_by . ";\n"; + echo "DROP TABLE `" . $name . "`;\n"; + echo "RENAME TABLE `" . $temp_name . "` TO `" . $name . "`;\n"; } } } if ($action) { if (!$install) { - Config::set('system', 'maintenance_reason', L10n::t('%s: updating %s table.', DateTimeFormat::utcNow().' '.date('e'), $name)); + Config::set('system', 'maintenance_reason', L10n::t('%s: updating %s table.', DateTimeFormat::utcNow() . ' ' . date('e'), $name)); } // Ensure index conversion to unique removes duplicates @@ -479,13 +529,13 @@ class DBStructure if ($ignore != "") { DBA::e("SET session old_alter_table=1;"); } else { - $r = DBA::e("DROP TABLE IF EXISTS `".$temp_name."`;"); + $r = DBA::e("DROP TABLE IF EXISTS `" . $temp_name . "`;"); if (!DBA::isResult($r)) { $errors .= self::printUpdateError($sql3); return $errors; } - $r = DBA::e("CREATE TABLE `".$temp_name."` LIKE `".$name."`;"); + $r = DBA::e("CREATE TABLE `" . $temp_name . "` LIKE `" . $name . "`;"); if (!DBA::isResult($r)) { $errors .= self::printUpdateError($sql3); return $errors; @@ -501,17 +551,17 @@ class DBStructure if ($ignore != "") { DBA::e("SET session old_alter_table=0;"); } else { - $r = DBA::e("INSERT INTO `".$temp_name."` SELECT ".$field_list." FROM `".$name."`".$group_by.";"); + $r = DBA::e("INSERT INTO `" . $temp_name . "` SELECT " . $field_list . " FROM `" . $name . "`" . $group_by . ";"); if (!DBA::isResult($r)) { $errors .= self::printUpdateError($sql3); return $errors; } - $r = DBA::e("DROP TABLE `".$name."`;"); + $r = DBA::e("DROP TABLE `" . $name . "`;"); if (!DBA::isResult($r)) { $errors .= self::printUpdateError($sql3); return $errors; } - $r = DBA::e("RENAME TABLE `".$temp_name."` TO `".$name."`;"); + $r = DBA::e("RENAME TABLE `" . $temp_name . "` TO `" . $name . "`;"); if (!DBA::isResult($r)) { $errors .= self::printUpdateError($sql3); return $errors; @@ -527,142 +577,114 @@ class DBStructure Config::set('system', 'maintenance_reason', ''); if ($errors) { - Config::set('system', 'dbupdate', DB_UPDATE_FAILED); + Config::set('system', 'dbupdate', self::UPDATE_FAILED); } else { - Config::set('system', 'dbupdate', DB_UPDATE_SUCCESSFUL); + Config::set('system', 'dbupdate', self::UPDATE_SUCCESSFUL); } } return $errors; } - private static function FieldCommand($parameters, $create = true) { - $fieldstruct = $parameters["type"]; + private static function tableStructure($table) + { + $structures = q("DESCRIBE `%s`", $table); - if (isset($parameters["Collation"])) { - $fieldstruct .= " COLLATE ".$parameters["Collation"]; - } + $full_columns = q("SHOW FULL COLUMNS FROM `%s`", $table); - if (isset($parameters["not null"])) { - $fieldstruct .= " NOT NULL"; - } + $indexes = q("SHOW INDEX FROM `%s`", $table); - if (isset($parameters["default"])) { - if (strpos(strtolower($parameters["type"]),"int")!==false) { - $fieldstruct .= " DEFAULT ".$parameters["default"]; - } else { - $fieldstruct .= " DEFAULT '".$parameters["default"]."'"; - } - } - if (isset($parameters["extra"])) { - $fieldstruct .= " ".$parameters["extra"]; - } + $table_status = q("SHOW TABLE STATUS WHERE `name` = '%s'", $table); - if (isset($parameters["comment"])) { - $fieldstruct .= " COMMENT '".DBA::escape($parameters["comment"])."'"; + if (DBA::isResult($table_status)) { + $table_status = $table_status[0]; + } else { + $table_status = []; } - /*if (($parameters["primary"] != "") && $create) - $fieldstruct .= " PRIMARY KEY";*/ + $fielddata = []; + $indexdata = []; - return($fieldstruct); - } + if (DBA::isResult($indexes)) { + foreach ($indexes AS $index) { + if ($index["Key_name"] != "PRIMARY" && $index["Non_unique"] == "0" && !isset($indexdata[$index["Key_name"]])) { + $indexdata[$index["Key_name"]] = ["UNIQUE"]; + } - private static function createTable($name, $structure, $verbose, $action) { - $r = true; + if ($index["Index_type"] == "FULLTEXT" && !isset($indexdata[$index["Key_name"]])) { + $indexdata[$index["Key_name"]] = ["FULLTEXT"]; + } - $engine = ""; - $comment = ""; - $sql_rows = []; - $primary_keys = []; - foreach ($structure["fields"] AS $fieldname => $field) { - $sql_rows[] = "`".DBA::escape($fieldname)."` ".self::FieldCommand($field); - if (x($field,'primary') && $field['primary']!='') { - $primary_keys[] = $fieldname; - } - } + $column = $index["Column_name"]; - if (!empty($structure["indexes"])) { - foreach ($structure["indexes"] AS $indexname => $fieldnames) { - $sql_index = self::createIndex($indexname, $fieldnames, ""); - if (!is_null($sql_index)) { - $sql_rows[] = $sql_index; + if ($index["Sub_part"] != "") { + $column .= "(" . $index["Sub_part"] . ")"; } + + $indexdata[$index["Key_name"]][] = $column; } } + if (DBA::isResult($structures)) { + foreach ($structures AS $field) { + // Replace the default size values so that we don't have to define them + $search = ['tinyint(1)', 'tinyint(3) unsigned', 'tinyint(4)', 'smallint(5) unsigned', 'smallint(6)', 'mediumint(8) unsigned', 'mediumint(9)', 'bigint(20)', 'int(10) unsigned', 'int(11)']; + $replace = ['boolean', 'tinyint unsigned', 'tinyint', 'smallint unsigned', 'smallint', 'mediumint unsigned', 'mediumint', 'bigint', 'int unsigned', 'int']; + $field["Type"] = str_replace($search, $replace, $field["Type"]); - if (isset($structure["engine"])) { - $engine = " ENGINE=" . $structure["engine"]; - } + $fielddata[$field["Field"]]["type"] = $field["Type"]; + if ($field["Null"] == "NO") { + $fielddata[$field["Field"]]["not null"] = true; + } - if (isset($structure["comment"])) { - $comment = " COMMENT='" . DBA::escape($structure["comment"]) . "'"; - } + if (isset($field["Default"])) { + $fielddata[$field["Field"]]["default"] = $field["Default"]; + } - $sql = implode(",\n\t", $sql_rows); + if ($field["Extra"] != "") { + $fielddata[$field["Field"]]["extra"] = $field["Extra"]; + } - $sql = sprintf("CREATE TABLE IF NOT EXISTS `%s` (\n\t", DBA::escape($name)).$sql. - "\n)" . $engine . " DEFAULT COLLATE utf8mb4_general_ci" . $comment; - if ($verbose) { - echo $sql.";\n"; + if ($field["Key"] == "PRI") { + $fielddata[$field["Field"]]["primary"] = true; + } + } } - - if ($action) { - $r = DBA::e($sql); + if (DBA::isResult($full_columns)) { + foreach ($full_columns AS $column) { + $fielddata[$column["Field"]]["Collation"] = $column["Collation"]; + $fielddata[$column["Field"]]["comment"] = $column["Comment"]; + } } - return $r; - } - - private static function addTableField($fieldname, $parameters) { - $sql = sprintf("ADD `%s` %s", DBA::escape($fieldname), self::FieldCommand($parameters)); - return($sql); - } - - private static function modifyTableField($fieldname, $parameters) { - $sql = sprintf("MODIFY `%s` %s", DBA::escape($fieldname), self::FieldCommand($parameters, false)); - return($sql); + return ["fields" => $fielddata, "indexes" => $indexdata, "table_status" => $table_status]; } - private static function dropIndex($indexname) { + private static function dropIndex($indexname) + { $sql = sprintf("DROP INDEX `%s`", DBA::escape($indexname)); - return($sql); + return ($sql); } - private static function createIndex($indexname, $fieldnames, $method = "ADD") { - $method = strtoupper(trim($method)); - if ($method!="" && $method!="ADD") { - throw new Exception("Invalid parameter 'method' in self::createIndex(): '$method'"); - } - - if ($fieldnames[0] == "UNIQUE") { - array_shift($fieldnames); - $method .= ' UNIQUE'; - } - - $names = ""; - foreach ($fieldnames AS $fieldname) { - if ($names != "") { - $names .= ","; - } - - if (preg_match('|(.+)\((\d+)\)|', $fieldname, $matches)) { - $names .= "`".DBA::escape($matches[1])."`(".intval($matches[2]).")"; - } else { - $names .= "`".DBA::escape($fieldname)."`"; - } - } - - if ($indexname == "PRIMARY") { - return sprintf("%s PRIMARY KEY(%s)", $method, $names); - } - + private static function addTableField($fieldname, $parameters) + { + $sql = sprintf("ADD `%s` %s", DBA::escape($fieldname), self::FieldCommand($parameters)); + return ($sql); + } - $sql = sprintf("%s INDEX `%s` (%s)", $method, DBA::escape($indexname), $names); - return($sql); + private static function modifyTableField($fieldname, $parameters) + { + $sql = sprintf("MODIFY `%s` %s", DBA::escape($fieldname), self::FieldCommand($parameters, false)); + return ($sql); } - private static function groupBy($indexname, $fieldnames) { + /** + * Constructs a GROUP BY clause from a UNIQUE index definition. + * + * @param array $fieldnames + * @return string + */ + private static function groupBy(array $fieldnames) + { if ($fieldnames[0] != "UNIQUE") { return ""; } @@ -676,9 +698,9 @@ class DBStructure } if (preg_match('|(.+)\((\d+)\)|', $fieldname, $matches)) { - $names .= "`".DBA::escape($matches[1])."`"; + $names .= "`" . DBA::escape($matches[1]) . "`"; } else { - $names .= "`".DBA::escape($fieldname)."`"; + $names .= "`" . DBA::escape($fieldname) . "`"; } } @@ -687,28 +709,62 @@ class DBStructure } /** - * Check if a table exists + * Renames columns or the primary key of a table * - * @param string $table Table name + * @todo You cannot rename a primary key if "auto increment" is set * - * @return boolean Does the table exist? + * @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? + * @throws Exception */ - public static function existsTable($table) + public static function rename($table, $columns, $type = self::RENAME_COLUMN) { - if (empty($table)) { + if (empty($table) || empty($columns)) { + return false; + } + + if (!is_array($columns)) { return false; } $table = DBA::escape($table); - $sql = "SHOW TABLES LIKE '" . $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 = (DBA::numRows($stmt) > 0); + $retval = true; } DBA::close($stmt); @@ -717,14 +773,16 @@ class DBStructure } /** - * Check if the columns of the table exists + * 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? + * @throws Exception */ - public static function existsColumn($table, $columns = []) { + public static function existsColumn($table, $columns = []) + { if (empty($table)) { return false; } @@ -756,64 +814,30 @@ class DBStructure 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) + * Check if a table exists * - * @return boolean Was the renaming successful? + * @param string $table Table name * + * @return boolean Does the table exist? + * @throws Exception */ - public static function rename($table, $columns, $type = self::RENAME_COLUMN) { - if (empty($table) || empty($columns)) { - return false; - } - - if (!is_array($columns)) { + public static function existsTable($table) + { + if (empty($table)) { 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 .= ";"; + $sql = "SHOW TABLES LIKE '" . $table . "';"; $stmt = DBA::p($sql); if (is_bool($stmt)) { $retval = $stmt; } else { - $retval = true; + $retval = (DBA::numRows($stmt) > 0); } DBA::close($stmt); @@ -822,43 +846,16 @@ class DBStructure } /** - * Loads the database structure definition from the /config/dbstructure.json file + * Returns the columns of a table * - * Expected format: - * "table_name": { - * "comment": "meaningful table comment", - * "fields": { - * "field_name1": {"type": "int unsigned", "not null": "1", "extra": "auto_increment", "primary": "1", "comment": "meaningful field comment"}, - * "field_name2": {"type": "varchar(50)", "not null": "1", "default": "", "comment": "meaningful field comment"}, - * }, - * "indexes": { - * "PRIMARY": ["field_name1"], - * "name": ["UNIQUE", "field_name2"] - * } - * } + * @param string $table Table name * - * @return array + * @return array An array of the table columns * @throws Exception */ - public static function definition() { - $a = \Friendica\BaseObject::getApp(); - - $filename = $a->getBasePath() . '/config/dbstructure.json'; - - if (!is_readable($filename)) { - throw new Exception('Missing database structure config file config/dbstructure.json'); - } - - $json = file_get_contents($filename); - - $database = json_decode($json, true); - - if (!$database) { - throw new Exception('Corrupted database structure config file config/dbstructure.json'); - } - - Addon::callHooks('dbstructure_definition', $database); - - return $database; + public static function getColumns($table) + { + $stmtColumns = DBA::p("SHOW COLUMNS FROM `" . $table . "`"); + return DBA::toArray($stmtColumns); } }