[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/ddl/ -> database_manager.php (source)

   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  /**
  18   * Database manager instance is responsible for all database structure modifications.
  19   *
  20   * @package    core_ddl
  21   * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
  22   *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
  23   *             2008 Petr Skoda                   http://skodak.org
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Database manager instance is responsible for all database structure modifications.
  31   *
  32   * It is using db specific generators to find out the correct SQL syntax to do that.
  33   *
  34   * @package    core_ddl
  35   * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
  36   *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
  37   *             2008 Petr Skoda                   http://skodak.org
  38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class database_manager {
  41  
  42      /** @var moodle_database A moodle_database driver specific instance.*/
  43      protected $mdb;
  44  
  45      /** @var sql_generator A driver specific SQL generator instance. Public because XMLDB editor needs to access it.*/
  46      public $generator;
  47  
  48      /**
  49       * Creates a new database manager instance.
  50       * @param moodle_database $mdb A moodle_database driver specific instance.
  51       * @param sql_generator $generator A driver specific SQL generator instance.
  52       */
  53      public function __construct($mdb, $generator) {
  54          $this->mdb       = $mdb;
  55          $this->generator = $generator;
  56      }
  57  
  58      /**
  59       * Releases all resources
  60       */
  61      public function dispose() {
  62          if ($this->generator) {
  63              $this->generator->dispose();
  64              $this->generator = null;
  65          }
  66          $this->mdb = null;
  67      }
  68  
  69      /**
  70       * This function will execute an array of SQL commands.
  71       *
  72       * @param string[] $sqlarr Array of sql statements to execute.
  73       * @throws ddl_change_structure_exception This exception is thrown if any error is found.
  74       */
  75      protected function execute_sql_arr(array $sqlarr) {
  76          $this->mdb->change_database_structure($sqlarr);
  77      }
  78  
  79      /**
  80       * Execute a given sql command string.
  81       *
  82       * @param string $sql The sql string you wish to be executed.
  83       * @throws ddl_change_structure_exception This exception is thrown if any error is found.
  84       */
  85      protected function execute_sql($sql) {
  86          $this->mdb->change_database_structure($sql);
  87      }
  88  
  89      /**
  90       * Given one xmldb_table, check if it exists in DB (true/false).
  91       *
  92       * @param string|xmldb_table $table The table to be searched (string name or xmldb_table instance).
  93       * @return bool True is a table exists, false otherwise.
  94       */
  95      public function table_exists($table) {
  96          if (!is_string($table) and !($table instanceof xmldb_table)) {
  97              throw new ddl_exception('ddlunknownerror', NULL, 'incorrect table parameter!');
  98          }
  99          return $this->generator->table_exists($table);
 100      }
 101  
 102      /**
 103       * Reset a sequence to the id field of a table.
 104       * @param string|xmldb_table $table Name of table.
 105       * @throws ddl_exception thrown upon reset errors.
 106       */
 107      public function reset_sequence($table) {
 108          if (!is_string($table) and !($table instanceof xmldb_table)) {
 109              throw new ddl_exception('ddlunknownerror', NULL, 'incorrect table parameter!');
 110          }
 111  
 112          // Do not test if table exists because it is slow
 113  
 114          if (!$sqlarr = $this->generator->getResetSequenceSQL($table)) {
 115              throw new ddl_exception('ddlunknownerror', null, 'table reset sequence sql not generated');
 116          }
 117  
 118          $this->execute_sql_arr($sqlarr);
 119      }
 120  
 121      /**
 122       * Given one xmldb_field, check if it exists in DB (true/false).
 123       *
 124       * @param string|xmldb_table $table The table to be searched (string name or xmldb_table instance).
 125       * @param string|xmldb_field $field The field to be searched for (string name or xmldb_field instance).
 126       * @return boolean true is exists false otherwise.
 127       * @throws ddl_table_missing_exception
 128       */
 129      public function field_exists($table, $field) {
 130          // Calculate the name of the table
 131          if (is_string($table)) {
 132              $tablename = $table;
 133          } else {
 134              $tablename = $table->getName();
 135          }
 136  
 137          // Check the table exists
 138          if (!$this->table_exists($table)) {
 139              throw new ddl_table_missing_exception($tablename);
 140          }
 141  
 142          if (is_string($field)) {
 143              $fieldname = $field;
 144          } else {
 145              // Calculate the name of the table
 146              $fieldname = $field->getName();
 147          }
 148  
 149          // Get list of fields in table
 150          $columns = $this->mdb->get_columns($tablename);
 151  
 152          $exists = array_key_exists($fieldname,  $columns);
 153  
 154          return $exists;
 155      }
 156  
 157      /**
 158       * Given one xmldb_index, the function returns the name of the index in DB
 159       * of false if it doesn't exist
 160       *
 161       * @param xmldb_table $xmldb_table table to be searched
 162       * @param xmldb_index $xmldb_index the index to be searched
 163       * @param bool $returnall true means return array of all indexes, false means first index only as string
 164       * @return array|string|bool Index name, array of index names or false if no indexes are found.
 165       * @throws ddl_table_missing_exception Thrown when table is not found.
 166       */
 167      public function find_index_name(xmldb_table $xmldb_table, xmldb_index $xmldb_index, $returnall = false) {
 168          // Calculate the name of the table
 169          $tablename = $xmldb_table->getName();
 170  
 171          // Check the table exists
 172          if (!$this->table_exists($xmldb_table)) {
 173              throw new ddl_table_missing_exception($tablename);
 174          }
 175  
 176          // Extract index columns
 177          $indcolumns = $xmldb_index->getFields();
 178  
 179          // Get list of indexes in table
 180          $indexes = $this->mdb->get_indexes($tablename);
 181  
 182          $return = array();
 183  
 184          // Iterate over them looking for columns coincidence
 185          foreach ($indexes as $indexname => $index) {
 186              $columns = $index['columns'];
 187              // Check if index matches queried index
 188              $diferences = array_merge(array_diff($columns, $indcolumns), array_diff($indcolumns, $columns));
 189              // If no differences, we have find the index
 190              if (empty($diferences)) {
 191                  if ($returnall) {
 192                      $return[] = $indexname;
 193                  } else {
 194                      return $indexname;
 195                  }
 196              }
 197          }
 198  
 199          if ($return and $returnall) {
 200              return $return;
 201          }
 202  
 203          // Arriving here, index not found
 204          return false;
 205      }
 206  
 207      /**
 208       * Given one xmldb_index, check if it exists in DB (true/false).
 209       *
 210       * @param xmldb_table $xmldb_table The table to be searched.
 211       * @param xmldb_index $xmldb_index The index to be searched for.
 212       * @return boolean true id index exists, false otherwise.
 213       */
 214      public function index_exists(xmldb_table $xmldb_table, xmldb_index $xmldb_index) {
 215          if (!$this->table_exists($xmldb_table)) {
 216              return false;
 217          }
 218          return ($this->find_index_name($xmldb_table, $xmldb_index) !== false);
 219      }
 220  
 221      /**
 222       * This function IS NOT IMPLEMENTED. ONCE WE'LL BE USING RELATIONAL
 223       * INTEGRITY IT WILL BECOME MORE USEFUL. FOR NOW, JUST CALCULATE "OFFICIAL"
 224       * KEY NAMES WITHOUT ACCESSING TO DB AT ALL.
 225       * Given one xmldb_key, the function returns the name of the key in DB (if exists)
 226       * of false if it doesn't exist
 227       *
 228       * @param xmldb_table $xmldb_table The table to be searched.
 229       * @param xmldb_key $xmldb_key The key to be searched.
 230       * @return string key name if found
 231       */
 232      public function find_key_name(xmldb_table $xmldb_table, xmldb_key $xmldb_key) {
 233  
 234          $keycolumns = $xmldb_key->getFields();
 235  
 236          // Get list of keys in table
 237          // first primaries (we aren't going to use this now, because the MetaPrimaryKeys is awful)
 238              //TODO: To implement when we advance in relational integrity
 239          // then uniques (note that Moodle, for now, shouldn't have any UNIQUE KEY for now, but unique indexes)
 240              //TODO: To implement when we advance in relational integrity (note that AdoDB hasn't any MetaXXX for this.
 241          // then foreign (note that Moodle, for now, shouldn't have any FOREIGN KEY for now, but indexes)
 242              //TODO: To implement when we advance in relational integrity (note that AdoDB has one MetaForeignKeys()
 243              //but it's far from perfect.
 244          // TODO: To create the proper functions inside each generator to retrieve all the needed KEY info (name
 245          //       columns, reftable and refcolumns
 246  
 247          // So all we do is to return the official name of the requested key without any confirmation!)
 248          // One exception, hardcoded primary constraint names
 249          if ($this->generator->primary_key_name && $xmldb_key->getType() == XMLDB_KEY_PRIMARY) {
 250              return $this->generator->primary_key_name;
 251          } else {
 252              // Calculate the name suffix
 253              switch ($xmldb_key->getType()) {
 254                  case XMLDB_KEY_PRIMARY:
 255                      $suffix = 'pk';
 256                      break;
 257                  case XMLDB_KEY_UNIQUE:
 258                      $suffix = 'uk';
 259                      break;
 260                  case XMLDB_KEY_FOREIGN_UNIQUE:
 261                  case XMLDB_KEY_FOREIGN:
 262                      $suffix = 'fk';
 263                      break;
 264              }
 265              // And simply, return the official name
 266              return $this->generator->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), $suffix);
 267          }
 268      }
 269  
 270      /**
 271       * This function will delete all tables found in XMLDB file from db
 272       *
 273       * @param string $file Full path to the XML file to be used.
 274       * @return void
 275       */
 276      public function delete_tables_from_xmldb_file($file) {
 277  
 278          $xmldb_file = new xmldb_file($file);
 279  
 280          if (!$xmldb_file->fileExists()) {
 281              throw new ddl_exception('ddlxmlfileerror', null, 'File does not exist');
 282          }
 283  
 284          $loaded    = $xmldb_file->loadXMLStructure();
 285          $structure = $xmldb_file->getStructure();
 286  
 287          if (!$loaded || !$xmldb_file->isLoaded()) {
 288              // Show info about the error if we can find it
 289              if ($structure) {
 290                  if ($errors = $structure->getAllErrors()) {
 291                      throw new ddl_exception('ddlxmlfileerror', null, 'Errors found in XMLDB file: '. implode (', ', $errors));
 292                  }
 293              }
 294              throw new ddl_exception('ddlxmlfileerror', null, 'not loaded??');
 295          }
 296  
 297          if ($xmldb_tables = $structure->getTables()) {
 298              // Delete in opposite order, this should help with foreign keys in the future.
 299              $xmldb_tables = array_reverse($xmldb_tables);
 300              foreach($xmldb_tables as $table) {
 301                  if ($this->table_exists($table)) {
 302                      $this->drop_table($table);
 303                  }
 304              }
 305          }
 306      }
 307  
 308      /**
 309       * This function will drop the table passed as argument
 310       * and all the associated objects (keys, indexes, constraints, sequences, triggers)
 311       * will be dropped too.
 312       *
 313       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 314       * @return void
 315       */
 316      public function drop_table(xmldb_table $xmldb_table) {
 317          // Check table exists
 318          if (!$this->table_exists($xmldb_table)) {
 319              throw new ddl_table_missing_exception($xmldb_table->getName());
 320          }
 321  
 322          if (!$sqlarr = $this->generator->getDropTableSQL($xmldb_table)) {
 323              throw new ddl_exception('ddlunknownerror', null, 'table drop sql not generated');
 324          }
 325  
 326          $this->execute_sql_arr($sqlarr);
 327      }
 328  
 329      /**
 330       * Load an install.xml file, checking that it exists, and that the structure is OK.
 331       * @param string $file the full path to the XMLDB file.
 332       * @return xmldb_file the loaded file.
 333       */
 334      private function load_xmldb_file($file) {
 335          $xmldb_file = new xmldb_file($file);
 336  
 337          if (!$xmldb_file->fileExists()) {
 338              throw new ddl_exception('ddlxmlfileerror', null, 'File does not exist');
 339          }
 340  
 341          $loaded = $xmldb_file->loadXMLStructure();
 342          if (!$loaded || !$xmldb_file->isLoaded()) {
 343              // Show info about the error if we can find it
 344              if ($structure = $xmldb_file->getStructure()) {
 345                  if ($errors = $structure->getAllErrors()) {
 346                      throw new ddl_exception('ddlxmlfileerror', null, 'Errors found in XMLDB file: '. implode (', ', $errors));
 347                  }
 348              }
 349              throw new ddl_exception('ddlxmlfileerror', null, 'not loaded??');
 350          }
 351  
 352          return $xmldb_file;
 353      }
 354  
 355      /**
 356       * This function will load one entire XMLDB file and call install_from_xmldb_structure.
 357       *
 358       * @param string $file full path to the XML file to be used
 359       * @return void
 360       */
 361      public function install_from_xmldb_file($file) {
 362          $xmldb_file = $this->load_xmldb_file($file);
 363          $xmldb_structure = $xmldb_file->getStructure();
 364          $this->install_from_xmldb_structure($xmldb_structure);
 365      }
 366  
 367      /**
 368       * This function will load one entire XMLDB file and call install_from_xmldb_structure.
 369       *
 370       * @param string $file full path to the XML file to be used
 371       * @param string $tablename the name of the table.
 372       * @param bool $cachestructures boolean to decide if loaded xmldb structures can be safely cached
 373       *             useful for testunits loading the enormous main xml file hundred of times (100x)
 374       */
 375      public function install_one_table_from_xmldb_file($file, $tablename, $cachestructures = false) {
 376  
 377          static $xmldbstructurecache = array(); // To store cached structures
 378          if (!empty($xmldbstructurecache) && array_key_exists($file, $xmldbstructurecache)) {
 379              $xmldb_structure = $xmldbstructurecache[$file];
 380          } else {
 381              $xmldb_file = $this->load_xmldb_file($file);
 382              $xmldb_structure = $xmldb_file->getStructure();
 383              if ($cachestructures) {
 384                  $xmldbstructurecache[$file] = $xmldb_structure;
 385              }
 386          }
 387  
 388          $targettable = $xmldb_structure->getTable($tablename);
 389          if (is_null($targettable)) {
 390              throw new ddl_exception('ddlunknowntable', null, 'The table ' . $tablename . ' is not defined in file ' . $file);
 391          }
 392          $targettable->setNext(NULL);
 393          $targettable->setPrevious(NULL);
 394  
 395          $tempstructure = new xmldb_structure('temp');
 396          $tempstructure->addTable($targettable);
 397          $this->install_from_xmldb_structure($tempstructure);
 398      }
 399  
 400      /**
 401       * This function will generate all the needed SQL statements, specific for each
 402       * RDBMS type and, finally, it will execute all those statements against the DB.
 403       *
 404       * @param stdClass $xmldb_structure xmldb_structure object.
 405       * @return void
 406       */
 407      public function install_from_xmldb_structure($xmldb_structure) {
 408  
 409          if (!$sqlarr = $this->generator->getCreateStructureSQL($xmldb_structure)) {
 410              return; // nothing to do
 411          }
 412          $this->execute_sql_arr($sqlarr);
 413      }
 414  
 415      /**
 416       * This function will create the table passed as argument with all its
 417       * fields/keys/indexes/sequences, everything based in the XMLDB object
 418       *
 419       * @param xmldb_table $xmldb_table Table object (full specs are required).
 420       * @return void
 421       */
 422      public function create_table(xmldb_table $xmldb_table) {
 423          // Check table doesn't exist
 424          if ($this->table_exists($xmldb_table)) {
 425              throw new ddl_exception('ddltablealreadyexists', $xmldb_table->getName());
 426          }
 427  
 428          if (!$sqlarr = $this->generator->getCreateTableSQL($xmldb_table)) {
 429              throw new ddl_exception('ddlunknownerror', null, 'table create sql not generated');
 430          }
 431          $this->execute_sql_arr($sqlarr);
 432      }
 433  
 434      /**
 435       * This function will create the temporary table passed as argument with all its
 436       * fields/keys/indexes/sequences, everything based in the XMLDB object
 437       *
 438       * If table already exists ddl_exception will be thrown, please make sure
 439       * the table name does not collide with existing normal table!
 440       *
 441       * @param xmldb_table $xmldb_table Table object (full specs are required).
 442       * @return void
 443       */
 444      public function create_temp_table(xmldb_table $xmldb_table) {
 445  
 446          // Check table doesn't exist
 447          if ($this->table_exists($xmldb_table)) {
 448              throw new ddl_exception('ddltablealreadyexists', $xmldb_table->getName());
 449          }
 450  
 451          if (!$sqlarr = $this->generator->getCreateTempTableSQL($xmldb_table)) {
 452              throw new ddl_exception('ddlunknownerror', null, 'temp table create sql not generated');
 453          }
 454  
 455          $this->execute_sql_arr($sqlarr);
 456      }
 457  
 458      /**
 459       * This function will drop the temporary table passed as argument with all its
 460       * fields/keys/indexes/sequences, everything based in the XMLDB object
 461       *
 462       * It is recommended to drop temp table when not used anymore.
 463       *
 464       * @deprecated since 2.3, use drop_table() for all table types
 465       * @param xmldb_table $xmldb_table Table object.
 466       * @return void
 467       */
 468      public function drop_temp_table(xmldb_table $xmldb_table) {
 469          debugging('database_manager::drop_temp_table() is deprecated, use database_manager::drop_table() instead');
 470          $this->drop_table($xmldb_table);
 471      }
 472  
 473      /**
 474       * This function will rename the table passed as argument
 475       * Before renaming the index, the function will check it exists
 476       *
 477       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 478       * @param string $newname New name of the index.
 479       * @return void
 480       */
 481      public function rename_table(xmldb_table $xmldb_table, $newname) {
 482          // Check newname isn't empty
 483          if (!$newname) {
 484              throw new ddl_exception('ddlunknownerror', null, 'newname can not be empty');
 485          }
 486  
 487          $check = new xmldb_table($newname);
 488  
 489          // Check table already renamed
 490          if (!$this->table_exists($xmldb_table)) {
 491              if ($this->table_exists($check)) {
 492                  throw new ddl_exception('ddlunknownerror', null, 'table probably already renamed');
 493              } else {
 494                  throw new ddl_table_missing_exception($xmldb_table->getName());
 495              }
 496          }
 497  
 498          // Check new table doesn't exist
 499          if ($this->table_exists($check)) {
 500              throw new ddl_exception('ddltablealreadyexists', $check->getName(), 'can not rename table');
 501          }
 502  
 503          if (!$sqlarr = $this->generator->getRenameTableSQL($xmldb_table, $newname)) {
 504              throw new ddl_exception('ddlunknownerror', null, 'table rename sql not generated');
 505          }
 506  
 507          $this->execute_sql_arr($sqlarr);
 508      }
 509  
 510      /**
 511       * This function will add the field to the table passed as arguments
 512       *
 513       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 514       * @param xmldb_field $xmldb_field Index object (full specs are required).
 515       * @return void
 516       */
 517      public function add_field(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 518           // Check the field doesn't exist
 519          if ($this->field_exists($xmldb_table, $xmldb_field)) {
 520              throw new ddl_exception('ddlfieldalreadyexists', $xmldb_field->getName());
 521          }
 522  
 523          // If NOT NULL and no default given (we ask the generator about the
 524          // *real* default that will be used) check the table is empty
 525          if ($xmldb_field->getNotNull() && $this->generator->getDefaultValue($xmldb_field) === NULL && $this->mdb->count_records($xmldb_table->getName())) {
 526              throw new ddl_exception('ddlunknownerror', null, 'Field ' . $xmldb_table->getName() . '->' . $xmldb_field->getName() .
 527                        ' cannot be added. Not null fields added to non empty tables require default value. Create skipped');
 528          }
 529  
 530          if (!$sqlarr = $this->generator->getAddFieldSQL($xmldb_table, $xmldb_field)) {
 531              throw new ddl_exception('ddlunknownerror', null, 'addfield sql not generated');
 532          }
 533          $this->execute_sql_arr($sqlarr);
 534      }
 535  
 536      /**
 537       * This function will drop the field from the table passed as arguments
 538       *
 539       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 540       * @param xmldb_field $xmldb_field Index object (full specs are required).
 541       * @return void
 542       */
 543      public function drop_field(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 544          if (!$this->table_exists($xmldb_table)) {
 545              throw new ddl_table_missing_exception($xmldb_table->getName());
 546          }
 547          // Check the field exists
 548          if (!$this->field_exists($xmldb_table, $xmldb_field)) {
 549              throw new ddl_field_missing_exception($xmldb_field->getName(), $xmldb_table->getName());
 550          }
 551          // Check for dependencies in the DB before performing any action
 552          $this->check_field_dependencies($xmldb_table, $xmldb_field);
 553  
 554          if (!$sqlarr = $this->generator->getDropFieldSQL($xmldb_table, $xmldb_field)) {
 555              throw new ddl_exception('ddlunknownerror', null, 'drop_field sql not generated');
 556          }
 557  
 558          $this->execute_sql_arr($sqlarr);
 559      }
 560  
 561      /**
 562       * This function will change the type of the field in the table passed as arguments
 563       *
 564       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 565       * @param xmldb_field $xmldb_field Index object (full specs are required).
 566       * @return void
 567       */
 568      public function change_field_type(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 569          if (!$this->table_exists($xmldb_table)) {
 570              throw new ddl_table_missing_exception($xmldb_table->getName());
 571          }
 572          // Check the field exists
 573          if (!$this->field_exists($xmldb_table, $xmldb_field)) {
 574              throw new ddl_field_missing_exception($xmldb_field->getName(), $xmldb_table->getName());
 575          }
 576          // Check for dependencies in the DB before performing any action
 577          $this->check_field_dependencies($xmldb_table, $xmldb_field);
 578  
 579          if (!$sqlarr = $this->generator->getAlterFieldSQL($xmldb_table, $xmldb_field)) {
 580              return; // probably nothing to do
 581          }
 582  
 583          $this->execute_sql_arr($sqlarr);
 584      }
 585  
 586      /**
 587       * This function will change the precision of the field in the table passed as arguments
 588       *
 589       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 590       * @param xmldb_field $xmldb_field Index object (full specs are required).
 591       * @return void
 592       */
 593      public function change_field_precision(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 594          // Just a wrapper over change_field_type. Does exactly the same processing
 595          $this->change_field_type($xmldb_table, $xmldb_field);
 596      }
 597  
 598      /**
 599       * This function will change the unsigned/signed of the field in the table passed as arguments
 600       *
 601       * @deprecated since 2.3, only singed numbers are allowed now, migration is automatic
 602       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 603       * @param xmldb_field $xmldb_field Field object (full specs are required).
 604       * @return void
 605       */
 606      public function change_field_unsigned(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 607          debugging('All unsigned numbers are converted to signed automatically during Moodle upgrade.');
 608          $this->change_field_type($xmldb_table, $xmldb_field);
 609      }
 610  
 611      /**
 612       * This function will change the nullability of the field in the table passed as arguments
 613       *
 614       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 615       * @param xmldb_field $xmldb_field Index object (full specs are required).
 616       * @return void
 617       */
 618      public function change_field_notnull(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 619          // Just a wrapper over change_field_type. Does exactly the same processing
 620          $this->change_field_type($xmldb_table, $xmldb_field);
 621      }
 622  
 623      /**
 624       * This function will change the default of the field in the table passed as arguments
 625       * One null value in the default field means delete the default
 626       *
 627       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 628       * @param xmldb_field $xmldb_field Index object (full specs are required).
 629       * @return void
 630       */
 631      public function change_field_default(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 632          if (!$this->table_exists($xmldb_table)) {
 633              throw new ddl_table_missing_exception($xmldb_table->getName());
 634          }
 635          // Check the field exists
 636          if (!$this->field_exists($xmldb_table, $xmldb_field)) {
 637              throw new ddl_field_missing_exception($xmldb_field->getName(), $xmldb_table->getName());
 638          }
 639          // Check for dependencies in the DB before performing any action
 640          $this->check_field_dependencies($xmldb_table, $xmldb_field);
 641  
 642          if (!$sqlarr = $this->generator->getModifyDefaultSQL($xmldb_table, $xmldb_field)) {
 643              return; //Empty array = nothing to do = no error
 644          }
 645  
 646          $this->execute_sql_arr($sqlarr);
 647      }
 648  
 649      /**
 650       * This function will rename the field in the table passed as arguments
 651       * Before renaming the field, the function will check it exists
 652       *
 653       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 654       * @param xmldb_field $xmldb_field Index object (full specs are required).
 655       * @param string $newname New name of the field.
 656       * @return void
 657       */
 658      public function rename_field(xmldb_table $xmldb_table, xmldb_field $xmldb_field, $newname) {
 659          if (empty($newname)) {
 660              throw new ddl_exception('ddlunknownerror', null, 'newname can not be empty');
 661          }
 662  
 663          if (!$this->table_exists($xmldb_table)) {
 664              throw new ddl_table_missing_exception($xmldb_table->getName());
 665          }
 666  
 667          // Check the field exists
 668          if (!$this->field_exists($xmldb_table, $xmldb_field)) {
 669              throw new ddl_field_missing_exception($xmldb_field->getName(), $xmldb_table->getName());
 670          }
 671  
 672          // Check we have included full field specs
 673          if (!$xmldb_field->getType()) {
 674              throw new ddl_exception('ddlunknownerror', null,
 675                        'Field ' . $xmldb_table->getName() . '->' . $xmldb_field->getName() .
 676                        ' must contain full specs. Rename skipped');
 677          }
 678  
 679          // Check field isn't id. Renaming over that field is not allowed
 680          if ($xmldb_field->getName() == 'id') {
 681              throw new ddl_exception('ddlunknownerror', null,
 682                        'Field ' . $xmldb_table->getName() . '->' . $xmldb_field->getName() .
 683                        ' cannot be renamed. Rename skipped');
 684          }
 685  
 686          if (!$sqlarr = $this->generator->getRenameFieldSQL($xmldb_table, $xmldb_field, $newname)) {
 687              return; //Empty array = nothing to do = no error
 688          }
 689  
 690          $this->execute_sql_arr($sqlarr);
 691      }
 692  
 693      /**
 694       * This function will check, for the given table and field, if there there is any dependency
 695       * preventing the field to be modified. It's used by all the public methods that perform any
 696       * DDL change on fields, throwing one ddl_dependency_exception if dependencies are found.
 697       *
 698       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 699       * @param xmldb_field $xmldb_field Index object (full specs are required).
 700       * @return void
 701       * @throws ddl_dependency_exception|ddl_field_missing_exception|ddl_table_missing_exception if dependency not met.
 702       */
 703      private function check_field_dependencies(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 704  
 705          // Check the table exists
 706          if (!$this->table_exists($xmldb_table)) {
 707              throw new ddl_table_missing_exception($xmldb_table->getName());
 708          }
 709  
 710          // Check the field exists
 711          if (!$this->field_exists($xmldb_table, $xmldb_field)) {
 712              throw new ddl_field_missing_exception($xmldb_field->getName(), $xmldb_table->getName());
 713          }
 714  
 715          // Check the field isn't in use by any index in the table
 716          if ($indexes = $this->mdb->get_indexes($xmldb_table->getName(), false)) {
 717              foreach ($indexes as $indexname => $index) {
 718                  $columns = $index['columns'];
 719                  if (in_array($xmldb_field->getName(), $columns)) {
 720                      throw new ddl_dependency_exception('column', $xmldb_table->getName() . '->' . $xmldb_field->getName(),
 721                                                         'index', $indexname . ' (' . implode(', ', $columns)  . ')');
 722                  }
 723              }
 724          }
 725      }
 726  
 727      /**
 728       * This function will create the key in the table passed as arguments
 729       *
 730       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 731       * @param xmldb_key $xmldb_key Index object (full specs are required).
 732       * @return void
 733       */
 734      public function add_key(xmldb_table $xmldb_table, xmldb_key $xmldb_key) {
 735  
 736          if ($xmldb_key->getType() == XMLDB_KEY_PRIMARY) { // Prevent PRIMARY to be added (only in create table, being serious  :-P)
 737              throw new ddl_exception('ddlunknownerror', null, 'Primary Keys can be added at table create time only');
 738          }
 739  
 740          if (!$sqlarr = $this->generator->getAddKeySQL($xmldb_table, $xmldb_key)) {
 741              return; //Empty array = nothing to do = no error
 742          }
 743  
 744          $this->execute_sql_arr($sqlarr);
 745      }
 746  
 747      /**
 748       * This function will drop the key in the table passed as arguments
 749       *
 750       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 751       * @param xmldb_key $xmldb_key Key object (full specs are required).
 752       * @return void
 753       */
 754      public function drop_key(xmldb_table $xmldb_table, xmldb_key $xmldb_key) {
 755          if ($xmldb_key->getType() == XMLDB_KEY_PRIMARY) { // Prevent PRIMARY to be dropped (only in drop table, being serious  :-P)
 756              throw new ddl_exception('ddlunknownerror', null, 'Primary Keys can be deleted at table drop time only');
 757          }
 758  
 759          if (!$sqlarr = $this->generator->getDropKeySQL($xmldb_table, $xmldb_key)) {
 760              return; //Empty array = nothing to do = no error
 761          }
 762  
 763          $this->execute_sql_arr($sqlarr);
 764      }
 765  
 766      /**
 767       * This function will rename the key in the table passed as arguments
 768       * Experimental. Shouldn't be used at all in normal installation/upgrade!
 769       *
 770       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 771       * @param xmldb_key $xmldb_key key object (full specs are required).
 772       * @param string $newname New name of the key.
 773       * @return void
 774       */
 775      public function rename_key(xmldb_table $xmldb_table, xmldb_key $xmldb_key, $newname) {
 776          debugging('rename_key() is one experimental feature. You must not use it in production!', DEBUG_DEVELOPER);
 777  
 778          // Check newname isn't empty
 779          if (!$newname) {
 780              throw new ddl_exception('ddlunknownerror', null, 'newname can not be empty');
 781          }
 782  
 783          if (!$sqlarr = $this->generator->getRenameKeySQL($xmldb_table, $xmldb_key, $newname)) {
 784              throw new ddl_exception('ddlunknownerror', null, 'Some DBs do not support key renaming (MySQL, PostgreSQL, MsSQL). Rename skipped');
 785          }
 786  
 787          $this->execute_sql_arr($sqlarr);
 788      }
 789  
 790      /**
 791       * This function will create the index in the table passed as arguments
 792       * Before creating the index, the function will check it doesn't exists
 793       *
 794       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 795       * @param xmldb_index $xmldb_intex Index object (full specs are required).
 796       * @return void
 797       */
 798      public function add_index($xmldb_table, $xmldb_intex) {
 799          if (!$this->table_exists($xmldb_table)) {
 800              throw new ddl_table_missing_exception($xmldb_table->getName());
 801          }
 802  
 803          // Check index doesn't exist
 804          if ($this->index_exists($xmldb_table, $xmldb_intex)) {
 805              throw new ddl_exception('ddlunknownerror', null,
 806                        'Index ' . $xmldb_table->getName() . '->' . $xmldb_intex->getName() .
 807                        ' already exists. Create skipped');
 808          }
 809  
 810          if (!$sqlarr = $this->generator->getAddIndexSQL($xmldb_table, $xmldb_intex)) {
 811              throw new ddl_exception('ddlunknownerror', null, 'add_index sql not generated');
 812          }
 813  
 814          $this->execute_sql_arr($sqlarr);
 815      }
 816  
 817      /**
 818       * This function will drop the index in the table passed as arguments
 819       * Before dropping the index, the function will check it exists
 820       *
 821       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 822       * @param xmldb_index $xmldb_intex Index object (full specs are required).
 823       * @return void
 824       */
 825      public function drop_index($xmldb_table, $xmldb_intex) {
 826          if (!$this->table_exists($xmldb_table)) {
 827              throw new ddl_table_missing_exception($xmldb_table->getName());
 828          }
 829  
 830          // Check index exists
 831          if (!$this->index_exists($xmldb_table, $xmldb_intex)) {
 832              throw new ddl_exception('ddlunknownerror', null,
 833                        'Index ' . $xmldb_table->getName() . '->' . $xmldb_intex->getName() .
 834                        ' does not exist. Drop skipped');
 835          }
 836  
 837          if (!$sqlarr = $this->generator->getDropIndexSQL($xmldb_table, $xmldb_intex)) {
 838              throw new ddl_exception('ddlunknownerror', null, 'drop_index sql not generated');
 839          }
 840  
 841          $this->execute_sql_arr($sqlarr);
 842      }
 843  
 844      /**
 845       * This function will rename the index in the table passed as arguments
 846       * Before renaming the index, the function will check it exists
 847       * Experimental. Shouldn't be used at all!
 848       *
 849       * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 850       * @param xmldb_index $xmldb_intex Index object (full specs are required).
 851       * @param string $newname New name of the index.
 852       * @return void
 853       */
 854      public function rename_index($xmldb_table, $xmldb_intex, $newname) {
 855          debugging('rename_index() is one experimental feature. You must not use it in production!', DEBUG_DEVELOPER);
 856  
 857          // Check newname isn't empty
 858          if (!$newname) {
 859              throw new ddl_exception('ddlunknownerror', null, 'newname can not be empty');
 860          }
 861  
 862          // Check index exists
 863          if (!$this->index_exists($xmldb_table, $xmldb_intex)) {
 864              throw new ddl_exception('ddlunknownerror', null,
 865                        'Index ' . $xmldb_table->getName() . '->' . $xmldb_intex->getName() .
 866                        ' does not exist. Rename skipped');
 867          }
 868  
 869          if (!$sqlarr = $this->generator->getRenameIndexSQL($xmldb_table, $xmldb_intex, $newname)) {
 870              throw new ddl_exception('ddlunknownerror', null, 'Some DBs do not support index renaming (MySQL). Rename skipped');
 871          }
 872  
 873          $this->execute_sql_arr($sqlarr);
 874      }
 875  
 876      /**
 877       * Reads the install.xml files for Moodle core and modules and returns an array of
 878       * xmldb_structure object with xmldb_table from these files.
 879       * @return xmldb_structure schema from install.xml files
 880       */
 881      public function get_install_xml_schema() {
 882          global $CFG;
 883          require_once($CFG->libdir.'/adminlib.php');
 884  
 885          $schema = new xmldb_structure('export');
 886          $schema->setVersion($CFG->version);
 887          $dbdirs = get_db_directories();
 888          foreach ($dbdirs as $dbdir) {
 889              $xmldb_file = new xmldb_file($dbdir.'/install.xml');
 890              if (!$xmldb_file->fileExists() or !$xmldb_file->loadXMLStructure()) {
 891                  continue;
 892              }
 893              $structure = $xmldb_file->getStructure();
 894              $tables = $structure->getTables();
 895              foreach ($tables as $table) {
 896                  $table->setPrevious(null);
 897                  $table->setNext(null);
 898                  $schema->addTable($table);
 899              }
 900          }
 901          return $schema;
 902      }
 903  
 904      /**
 905       * Checks the database schema against a schema specified by an xmldb_structure object
 906       * @param xmldb_structure $schema export schema describing all known tables
 907       * @param array $options
 908       * @return array keyed by table name with array of difference messages as values
 909       */
 910      public function check_database_schema(xmldb_structure $schema, array $options = null) {
 911          $alloptions = array(
 912              'extratables' => true,
 913              'missingtables' => true,
 914              'extracolumns' => true,
 915              'missingcolumns' => true,
 916              'changedcolumns' => true,
 917          );
 918  
 919          $typesmap = array(
 920              'I' => XMLDB_TYPE_INTEGER,
 921              'R' => XMLDB_TYPE_INTEGER,
 922              'N' => XMLDB_TYPE_NUMBER,
 923              'F' => XMLDB_TYPE_NUMBER, // Nobody should be using floats!
 924              'C' => XMLDB_TYPE_CHAR,
 925              'X' => XMLDB_TYPE_TEXT,
 926              'B' => XMLDB_TYPE_BINARY,
 927              'T' => XMLDB_TYPE_TIMESTAMP,
 928              'D' => XMLDB_TYPE_DATETIME,
 929          );
 930  
 931          $options = (array)$options;
 932          $options = array_merge($alloptions, $options);
 933  
 934          // Note: the error descriptions are not supposed to be localised,
 935          //       it is intended for developers and skilled admins only.
 936          $errors = array();
 937  
 938          /** @var string[] $dbtables */
 939          $dbtables = $this->mdb->get_tables(false);
 940          /** @var xmldb_table[] $tables */
 941          $tables = $schema->getTables();
 942  
 943          foreach ($tables as $table) {
 944              $tablename = $table->getName();
 945  
 946              if ($options['missingtables']) {
 947                  // Missing tables are a fatal problem.
 948                  if (empty($dbtables[$tablename])) {
 949                      $errors[$tablename][] = "table is missing";
 950                      continue;
 951                  }
 952              }
 953  
 954              /** @var database_column_info[] $dbfields */
 955              $dbfields = $this->mdb->get_columns($tablename, false);
 956              /** @var xmldb_field[] $fields */
 957              $fields = $table->getFields();
 958  
 959              foreach ($fields as $field) {
 960                  $fieldname = $field->getName();
 961                  if (empty($dbfields[$fieldname])) {
 962                      if ($options['missingcolumns']) {
 963                          // Missing columns are a fatal problem.
 964                          $errors[$tablename][] = "column '$fieldname' is missing";
 965                      }
 966                  } else if ($options['changedcolumns']) {
 967                      $dbfield = $dbfields[$fieldname];
 968  
 969                      if (!isset($typesmap[$dbfield->meta_type])) {
 970                          $errors[$tablename][] = "column '$fieldname' has unsupported type '$dbfield->meta_type'";
 971                      } else {
 972                          $dbtype = $typesmap[$dbfield->meta_type];
 973                          $type = $field->getType();
 974                          if ($type == XMLDB_TYPE_FLOAT) {
 975                              $type = XMLDB_TYPE_NUMBER;
 976                          }
 977                          if ($type != $dbtype) {
 978                              if ($expected = array_search($type, $typesmap)) {
 979                                  $errors[$tablename][] = "column '$fieldname' has incorrect type '$dbfield->meta_type', expected '$expected'";
 980                              } else {
 981                                  $errors[$tablename][] = "column '$fieldname' has incorrect type '$dbfield->meta_type'";
 982                              }
 983                          } else {
 984                              if ($field->getNotNull() != $dbfield->not_null) {
 985                                  if ($field->getNotNull()) {
 986                                      $errors[$tablename][] = "column '$fieldname' should be NOT NULL ($dbfield->meta_type)";
 987                                  } else {
 988                                      $errors[$tablename][] = "column '$fieldname' should allow NULL ($dbfield->meta_type)";
 989                                  }
 990                              }
 991                              if ($dbtype == XMLDB_TYPE_TEXT) {
 992                                  // No length check necessary - there is one size only now.
 993  
 994                              } else if ($dbtype == XMLDB_TYPE_NUMBER) {
 995                                  if ($field->getType() == XMLDB_TYPE_FLOAT) {
 996                                      // Do not use floats in any new code, they are deprecated in XMLDB editor!
 997  
 998                                  } else if ($field->getLength() != $dbfield->max_length or $field->getDecimals() != $dbfield->scale) {
 999                                      $size = "({$field->getLength()},{$field->getDecimals()})";
1000                                      $dbsize = "($dbfield->max_length,$dbfield->scale)";
1001                                      $errors[$tablename][] = "column '$fieldname' size is $dbsize, expected $size ($dbfield->meta_type)";
1002                                  }
1003  
1004                              } else if ($dbtype == XMLDB_TYPE_CHAR) {
1005                                  // This is not critical, but they should ideally match.
1006                                  if ($field->getLength() != $dbfield->max_length) {
1007                                      $errors[$tablename][] = "column '$fieldname' length is $dbfield->max_length, expected {$field->getLength()} ($dbfield->meta_type)";
1008                                  }
1009  
1010                              } else if ($dbtype == XMLDB_TYPE_INTEGER) {
1011                                  // Integers may be bigger in some DBs.
1012                                  $length = $field->getLength();
1013                                  if ($length > 18) {
1014                                      // Integers are not supposed to be bigger than 18.
1015                                      $length = 18;
1016                                  }
1017                                  if ($length > $dbfield->max_length) {
1018                                      $errors[$tablename][] = "column '$fieldname' length is $dbfield->max_length, expected at least {$field->getLength()} ($dbfield->meta_type)";
1019                                  }
1020  
1021                              } else if ($dbtype == XMLDB_TYPE_BINARY) {
1022                                  // Ignore binary types.
1023                                  continue;
1024  
1025                              } else if ($dbtype == XMLDB_TYPE_TIMESTAMP) {
1026                                  $errors[$tablename][] = "column '$fieldname' is a timestamp, this type is not supported ($dbfield->meta_type)";
1027                                  continue;
1028  
1029                              } else if ($dbtype == XMLDB_TYPE_DATETIME) {
1030                                  $errors[$tablename][] = "column '$fieldname' is a datetime, this type is not supported ($dbfield->meta_type)";
1031                                  continue;
1032  
1033                              } else {
1034                                  // Report all other unsupported types as problems.
1035                                  $errors[$tablename][] = "column '$fieldname' has unknown type ($dbfield->meta_type)";
1036                                  continue;
1037                              }
1038  
1039                              // Note: The empty string defaults are a bit messy...
1040                              if ($field->getDefault() != $dbfield->default_value) {
1041                                  $default = is_null($field->getDefault()) ? 'NULL' : $field->getDefault();
1042                                  $dbdefault = is_null($dbfield->default_value) ? 'NULL' : $dbfield->default_value;
1043                                  $errors[$tablename][] = "column '$fieldname' has default '$dbdefault', expected '$default' ($dbfield->meta_type)";
1044                              }
1045                          }
1046                      }
1047                  }
1048                  unset($dbfields[$fieldname]);
1049              }
1050  
1051              // Check for extra columns (indicates unsupported hacks) - modify install.xml if you want to pass validation.
1052              foreach ($dbfields as $fieldname => $dbfield) {
1053                  if ($options['extracolumns']) {
1054                      $errors[$tablename][] = "column '$fieldname' is not expected ($dbfield->meta_type)";
1055                  }
1056              }
1057              unset($dbtables[$tablename]);
1058          }
1059  
1060          if ($options['extratables']) {
1061              // Look for unsupported tables - local custom tables should be in /local/xxxx/db/install.xml file.
1062              // If there is no prefix, we can not say if table is ours, sorry.
1063              if ($this->generator->prefix !== '') {
1064                  foreach ($dbtables as $tablename => $unused) {
1065                      if (strpos($tablename, 'pma_') === 0) {
1066                          // Ignore phpmyadmin tables.
1067                          continue;
1068                      }
1069                      if (strpos($tablename, 'test') === 0) {
1070                          // Legacy simple test db tables need to be eventually removed,
1071                          // report them as problems!
1072                          $errors[$tablename][] = "table is not expected (it may be a leftover after Simpletest unit tests)";
1073                      } else {
1074                          $errors[$tablename][] = "table is not expected";
1075                      }
1076                  }
1077              }
1078          }
1079  
1080          return $errors;
1081      }
1082  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1