[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |