[ Index ]

PHP Cross Reference of Unnamed Project

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1