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