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