PageRenderTime 25ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/db/upgradelib.php

https://github.com/telematika/moodle
PHP | 272 lines | 213 code | 17 blank | 42 comment | 17 complexity | 52a4a76621f184671c0baa15e5817468 MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Upgrade helper functions
  18. *
  19. * This file is used for special upgrade functions - for example groups and gradebook.
  20. * These functions must use SQL and database related functions only- no other Moodle API,
  21. * because it might depend on db structures that are not yet present during upgrade.
  22. * (Do not use functions from accesslib.php, grades classes or group functions at all!)
  23. *
  24. * @package core_install
  25. * @category upgrade
  26. * @copyright 2007 Petr Skoda (http://skodak.org)
  27. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28. */
  29. defined('MOODLE_INTERNAL') || die();
  30. /**
  31. * Returns all non-view and non-temp tables with sane names.
  32. * Prints list of non-supported tables using $OUTPUT->notification()
  33. *
  34. * @return array
  35. */
  36. function upgrade_mysql_get_supported_tables() {
  37. global $OUTPUT, $DB;
  38. $tables = array();
  39. $patprefix = str_replace('_', '\\_', $DB->get_prefix());
  40. $pregprefix = preg_quote($DB->get_prefix(), '/');
  41. $sql = "SHOW FULL TABLES LIKE '$patprefix%'";
  42. $rs = $DB->get_recordset_sql($sql);
  43. foreach ($rs as $record) {
  44. $record = array_change_key_case((array)$record, CASE_LOWER);
  45. $type = $record['table_type'];
  46. unset($record['table_type']);
  47. $fullname = array_shift($record);
  48. if ($pregprefix === '') {
  49. $name = $fullname;
  50. } else {
  51. $count = null;
  52. $name = preg_replace("/^$pregprefix/", '', $fullname, -1, $count);
  53. if ($count !== 1) {
  54. continue;
  55. }
  56. }
  57. if (!preg_match("/^[a-z][a-z0-9_]*$/", $name)) {
  58. echo $OUTPUT->notification("Database table with invalid name '$fullname' detected, skipping.", 'notifyproblem');
  59. continue;
  60. }
  61. if ($type === 'VIEW') {
  62. echo $OUTPUT->notification("Unsupported database table view '$fullname' detected, skipping.", 'notifyproblem');
  63. continue;
  64. }
  65. $tables[$name] = $name;
  66. }
  67. $rs->close();
  68. return $tables;
  69. }
  70. /**
  71. * Remove all signed numbers from current database and change
  72. * text fields to long texts - mysql only.
  73. */
  74. function upgrade_mysql_fix_unsigned_and_lob_columns() {
  75. // We are not using standard API for changes of column
  76. // because everything 'signed'-related will be removed soon.
  77. // If anybody already has numbers higher than signed limit the execution stops
  78. // and tables must be fixed manually before continuing upgrade.
  79. global $DB;
  80. if ($DB->get_dbfamily() !== 'mysql') {
  81. return;
  82. }
  83. $pbar = new progress_bar('mysqlconvertunsignedlobs', 500, true);
  84. $prefix = $DB->get_prefix();
  85. $tables = upgrade_mysql_get_supported_tables();
  86. $tablecount = count($tables);
  87. $i = 0;
  88. foreach ($tables as $table) {
  89. $i++;
  90. $changes = array();
  91. $sql = "SHOW COLUMNS FROM `{{$table}}`";
  92. $rs = $DB->get_recordset_sql($sql);
  93. foreach ($rs as $column) {
  94. $column = (object)array_change_key_case((array)$column, CASE_LOWER);
  95. if (stripos($column->type, 'unsigned') !== false) {
  96. $maxvalue = 0;
  97. if (preg_match('/^int/i', $column->type)) {
  98. $maxvalue = 2147483647;
  99. } else if (preg_match('/^medium/i', $column->type)) {
  100. $maxvalue = 8388607;
  101. } else if (preg_match('/^smallint/i', $column->type)) {
  102. $maxvalue = 32767;
  103. } else if (preg_match('/^tinyint/i', $column->type)) {
  104. $maxvalue = 127;
  105. }
  106. if ($maxvalue) {
  107. // Make sure nobody is abusing our integer ranges - moodle int sizes are in digits, not bytes!!!
  108. $invalidcount = $DB->get_field_sql("SELECT COUNT('x') FROM `{{$table}}` WHERE `$column->field` > :maxnumber", array('maxnumber'=>$maxvalue));
  109. if ($invalidcount) {
  110. throw new moodle_exception('notlocalisederrormessage', 'error', new moodle_url('/admin/'), "Database table '{$table}'' contains unsigned column '{$column->field}' with $invalidcount values that are out of allowed range, upgrade can not continue.");
  111. }
  112. }
  113. $type = preg_replace('/unsigned/i', 'signed', $column->type);
  114. $notnull = ($column->null === 'NO') ? 'NOT NULL' : 'NULL';
  115. $default = (!is_null($column->default) and $column->default !== '') ? "DEFAULT '$column->default'" : '';
  116. $autoinc = (stripos($column->extra, 'auto_increment') !== false) ? 'AUTO_INCREMENT' : '';
  117. // Primary and unique not necessary here, change_database_structure does not add prefix.
  118. $changes[] = "MODIFY COLUMN `$column->field` $type $notnull $default $autoinc";
  119. } else if ($column->type === 'tinytext' or $column->type === 'mediumtext' or $column->type === 'text') {
  120. $notnull = ($column->null === 'NO') ? 'NOT NULL' : 'NULL';
  121. $default = (!is_null($column->default) and $column->default !== '') ? "DEFAULT '$column->default'" : '';
  122. // Primary, unique and inc are not supported for texts.
  123. $changes[] = "MODIFY COLUMN `$column->field` LONGTEXT $notnull $default";
  124. } else if ($column->type === 'tinyblob' or $column->type === 'mediumblob' or $column->type === 'blob') {
  125. $notnull = ($column->null === 'NO') ? 'NOT NULL' : 'NULL';
  126. $default = (!is_null($column->default) and $column->default !== '') ? "DEFAULT '$column->default'" : '';
  127. // Primary, unique and inc are not supported for blobs.
  128. $changes[] = "MODIFY COLUMN `$column->field` LONGBLOB $notnull $default";
  129. }
  130. }
  131. $rs->close();
  132. if ($changes) {
  133. // Set appropriate timeout - 1 minute per thousand of records should be enough, min 60 minutes just in case.
  134. $count = $DB->count_records($table, array());
  135. $timeout = ($count/1000)*60;
  136. $timeout = ($timeout < 60*60) ? 60*60 : (int)$timeout;
  137. upgrade_set_timeout($timeout);
  138. $sql = "ALTER TABLE `{$prefix}$table` ".implode(', ', $changes);
  139. $DB->change_database_structure($sql);
  140. }
  141. $pbar->update($i, $tablecount, "Converted unsigned/lob columns in MySQL database - $i/$tablecount.");
  142. }
  143. }
  144. /**
  145. * Migrate NTEXT to NVARCHAR(MAX).
  146. */
  147. function upgrade_mssql_nvarcharmax() {
  148. global $DB;
  149. if ($DB->get_dbfamily() !== 'mssql') {
  150. return;
  151. }
  152. $pbar = new progress_bar('mssqlconvertntext', 500, true);
  153. $prefix = $DB->get_prefix();
  154. $tables = $DB->get_tables(false);
  155. $tablecount = count($tables);
  156. $i = 0;
  157. foreach ($tables as $table) {
  158. $i++;
  159. $columns = array();
  160. $sql = "SELECT column_name
  161. FROM INFORMATION_SCHEMA.COLUMNS
  162. WHERE table_name = '{{$table}}' AND UPPER(data_type) = 'NTEXT'";
  163. $rs = $DB->get_recordset_sql($sql);
  164. foreach ($rs as $column) {
  165. $columns[] = $column->column_name;
  166. }
  167. $rs->close();
  168. if ($columns) {
  169. // Set appropriate timeout - 1 minute per thousand of records should be enough, min 60 minutes just in case.
  170. $count = $DB->count_records($table, array());
  171. $timeout = ($count/1000)*60;
  172. $timeout = ($timeout < 60*60) ? 60*60 : (int)$timeout;
  173. upgrade_set_timeout($timeout);
  174. $updates = array();
  175. foreach ($columns as $column) {
  176. // Change the definition.
  177. $sql = "ALTER TABLE {$prefix}$table ALTER COLUMN $column NVARCHAR(MAX)";
  178. $DB->change_database_structure($sql);
  179. $updates[] = "$column = $column";
  180. }
  181. // Now force the migration of text data to new optimised storage.
  182. $sql = "UPDATE {{$table}} SET ".implode(', ', $updates);
  183. $DB->execute($sql);
  184. }
  185. $pbar->update($i, $tablecount, "Converted NTEXT to NVARCHAR(MAX) columns in MS SQL Server database - $i/$tablecount.");
  186. }
  187. }
  188. /**
  189. * Migrate IMAGE to VARBINARY(MAX).
  190. */
  191. function upgrade_mssql_varbinarymax() {
  192. global $DB;
  193. if ($DB->get_dbfamily() !== 'mssql') {
  194. return;
  195. }
  196. $pbar = new progress_bar('mssqlconvertimage', 500, true);
  197. $prefix = $DB->get_prefix();
  198. $tables = $DB->get_tables(false);
  199. $tablecount = count($tables);
  200. $i = 0;
  201. foreach ($tables as $table) {
  202. $i++;
  203. $columns = array();
  204. $sql = "SELECT column_name
  205. FROM INFORMATION_SCHEMA.COLUMNS
  206. WHERE table_name = '{{$table}}' AND UPPER(data_type) = 'IMAGE'";
  207. $rs = $DB->get_recordset_sql($sql);
  208. foreach ($rs as $column) {
  209. $columns[] = $column->column_name;
  210. }
  211. $rs->close();
  212. if ($columns) {
  213. // Set appropriate timeout - 1 minute per thousand of records should be enough, min 60 minutes just in case.
  214. $count = $DB->count_records($table, array());
  215. $timeout = ($count/1000)*60;
  216. $timeout = ($timeout < 60*60) ? 60*60 : (int)$timeout;
  217. upgrade_set_timeout($timeout);
  218. foreach ($columns as $column) {
  219. // Change the definition.
  220. $sql = "ALTER TABLE {$prefix}$table ALTER COLUMN $column VARBINARY(MAX)";
  221. $DB->change_database_structure($sql);
  222. }
  223. // Binary columns should not be used, do not waste time optimising the storage.
  224. }
  225. $pbar->update($i, $tablecount, "Converted IMAGE to VARBINARY(MAX) columns in MS SQL Server database - $i/$tablecount.");
  226. }
  227. }