| [ Index ] |
PHP Cross Reference of Joomla 2.5.4 DE |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * @package Joomla.Platform 4 * @subpackage Database 5 * 6 * @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved. 7 * @license GNU General Public License version 2 or later; see LICENSE 8 */ 9 10 defined('JPATH_PLATFORM') or die; 11 12 /** 13 * MySQL import driver. 14 * 15 * @package Joomla.Platform 16 * @subpackage Database 17 * @since 11.1 18 */ 19 class JDatabaseImporterMySQL 20 { 21 /** 22 * @var array An array of cached data. 23 * @since 11.1 24 */ 25 protected $cache = array(); 26 27 /** 28 * The database connector to use for exporting structure and/or data. 29 * 30 * @var JDatabaseMySQL 31 * @since 11.1 32 */ 33 protected $db = null; 34 35 /** 36 * The input source. 37 * 38 * @var mixed 39 * @since 11.1 40 */ 41 protected $from = array(); 42 43 /** 44 * The type of input format (XML). 45 * 46 * @var string 47 * @since 11.1 48 */ 49 protected $asFormat = 'xml'; 50 51 /** 52 * An array of options for the exporter. 53 * 54 * @var JObject 55 * @since 11.1 56 */ 57 protected $options = null; 58 59 /** 60 * Constructor. 61 * 62 * Sets up the default options for the exporter. 63 * 64 * @since 11.1 65 */ 66 public function __construct() 67 { 68 $this->options = new JObject; 69 70 $this->cache = array('columns' => array(), 'keys' => array()); 71 72 // Set up the class defaults: 73 74 // Import with only structure 75 $this->withStructure(); 76 77 // Export as XML. 78 $this->asXml(); 79 80 // Default destination is a string using $output = (string) $exporter; 81 } 82 83 /** 84 * Set the output option for the exporter to XML format. 85 * 86 * @return JDatabaseImporterMySQL Method supports chaining. 87 * 88 * @since 11.1 89 */ 90 public function asXml() 91 { 92 $this->asFormat = 'xml'; 93 94 return $this; 95 } 96 97 /** 98 * Checks if all data and options are in order prior to exporting. 99 * 100 * @return JDatabaseImporterMySQL Method supports chaining. 101 * 102 * @since 11.1 103 * @throws Exception if an error is encountered. 104 */ 105 public function check() 106 { 107 // Check if the db connector has been set. 108 if (!($this->db instanceof JDatabaseMySql)) 109 { 110 throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE'); 111 } 112 113 // Check if the tables have been specified. 114 if (empty($this->from)) 115 { 116 throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED'); 117 } 118 119 return $this; 120 } 121 122 /** 123 * Specifies the data source to import. 124 * 125 * @param mixed $from The data source to import. 126 * 127 * @return JDatabaseImporterMySQL Method supports chaining. 128 * 129 * @since 11.1 130 */ 131 public function from($from) 132 { 133 $this->from = $from; 134 135 return $this; 136 } 137 138 /** 139 * Get the SQL syntax to add a column. 140 * 141 * @param string $table The table name. 142 * @param SimpleXMLElement $field The XML field definition. 143 * 144 * @return string 145 * 146 * @since 11.1 147 */ 148 protected function getAddColumnSQL($table, SimpleXMLElement $field) 149 { 150 $sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD COLUMN ' . $this->getColumnSQL($field); 151 152 return $sql; 153 } 154 155 /** 156 * Get the SQL syntax to add a key. 157 * 158 * @param string $table The table name. 159 * @param array $keys An array of the fields pertaining to this key. 160 * 161 * @return string 162 * 163 * @since 11.1 164 */ 165 protected function getAddKeySQL($table, $keys) 166 { 167 $sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD ' . $this->getKeySQL($keys); 168 169 return $sql; 170 } 171 172 /** 173 * Get alters for table if there is a difference. 174 * 175 * @param SimpleXMLElement $structure The XML structure pf the table. 176 * 177 * @return array 178 * 179 * @since 11.1 180 */ 181 protected function getAlterTableSQL(SimpleXMLElement $structure) 182 { 183 // Initialise variables. 184 $table = $this->getRealTableName($structure['name']); 185 $oldFields = $this->db->getTableColumns($table); 186 $oldKeys = $this->db->getTableKeys($table); 187 $alters = array(); 188 189 // Get the fields and keys from the XML that we are aiming for. 190 $newFields = $structure->xpath('field'); 191 $newKeys = $structure->xpath('key'); 192 193 // Loop through each field in the new structure. 194 foreach ($newFields as $field) 195 { 196 $fName = (string) $field['Field']; 197 198 if (isset($oldFields[$fName])) 199 { 200 // The field exists, check it's the same. 201 $column = $oldFields[$fName]; 202 203 // Test whether there is a change. 204 $change = ((string) $field['Type'] != $column->Type) || ((string) $field['Null'] != $column->Null) 205 || ((string) $field['Default'] != $column->Default) || ((string) $field['Extra'] != $column->Extra); 206 207 if ($change) 208 { 209 $alters[] = $this->getChangeColumnSQL($table, $field); 210 } 211 212 // Unset this field so that what we have left are fields that need to be removed. 213 unset($oldFields[$fName]); 214 } 215 else 216 { 217 // The field is new. 218 $alters[] = $this->getAddColumnSQL($table, $field); 219 } 220 } 221 222 // Any columns left are orphans 223 foreach ($oldFields as $name => $column) 224 { 225 // Delete the column. 226 $alters[] = $this->getDropColumnSQL($table, $name); 227 } 228 229 // Get the lookups for the old and new keys. 230 $oldLookup = $this->getKeyLookup($oldKeys); 231 $newLookup = $this->getKeyLookup($newKeys); 232 233 // Loop through each key in the new structure. 234 foreach ($newLookup as $name => $keys) 235 { 236 // Check if there are keys on this field in the existing table. 237 if (isset($oldLookup[$name])) 238 { 239 $same = true; 240 $newCount = count($newLookup[$name]); 241 $oldCount = count($oldLookup[$name]); 242 243 // There is a key on this field in the old and new tables. Are they the same? 244 if ($newCount == $oldCount) 245 { 246 // Need to loop through each key and do a fine grained check. 247 for ($i = 0; $i < $newCount; $i++) 248 { 249 $same = (((string) $newLookup[$name][$i]['Non_unique'] == $oldLookup[$name][$i]->Non_unique) 250 && ((string) $newLookup[$name][$i]['Column_name'] == $oldLookup[$name][$i]->Column_name) 251 && ((string) $newLookup[$name][$i]['Seq_in_index'] == $oldLookup[$name][$i]->Seq_in_index) 252 && ((string) $newLookup[$name][$i]['Collation'] == $oldLookup[$name][$i]->Collation) 253 && ((string) $newLookup[$name][$i]['Index_type'] == $oldLookup[$name][$i]->Index_type)); 254 255 // Debug. 256 // echo '<pre>'; 257 // echo '<br />Non_unique: '. 258 // ((string) $newLookup[$name][$i]['Non_unique'] == $oldLookup[$name][$i]->Non_unique ? 'Pass' : 'Fail').' '. 259 // (string) $newLookup[$name][$i]['Non_unique'].' vs '.$oldLookup[$name][$i]->Non_unique; 260 // echo '<br />Column_name: '. 261 // ((string) $newLookup[$name][$i]['Column_name'] == $oldLookup[$name][$i]->Column_name ? 'Pass' : 'Fail').' '. 262 // (string) $newLookup[$name][$i]['Column_name'].' vs '.$oldLookup[$name][$i]->Column_name; 263 // echo '<br />Seq_in_index: '. 264 // ((string) $newLookup[$name][$i]['Seq_in_index'] == $oldLookup[$name][$i]->Seq_in_index ? 'Pass' : 'Fail').' '. 265 // (string) $newLookup[$name][$i]['Seq_in_index'].' vs '.$oldLookup[$name][$i]->Seq_in_index; 266 // echo '<br />Collation: '. 267 // ((string) $newLookup[$name][$i]['Collation'] == $oldLookup[$name][$i]->Collation ? 'Pass' : 'Fail').' '. 268 // (string) $newLookup[$name][$i]['Collation'].' vs '.$oldLookup[$name][$i]->Collation; 269 // echo '<br />Index_type: '. 270 // ((string) $newLookup[$name][$i]['Index_type'] == $oldLookup[$name][$i]->Index_type ? 'Pass' : 'Fail').' '. 271 // (string) $newLookup[$name][$i]['Index_type'].' vs '.$oldLookup[$name][$i]->Index_type; 272 // echo '<br />Same = '.($same ? 'true' : 'false'); 273 // echo '</pre>'; 274 275 if (!$same) 276 { 277 // Break out of the loop. No need to check further. 278 break; 279 } 280 } 281 } 282 else 283 { 284 // Count is different, just drop and add. 285 $same = false; 286 } 287 288 if (!$same) 289 { 290 $alters[] = $this->getDropKeySQL($table, $name); 291 $alters[] = $this->getAddKeySQL($table, $keys); 292 } 293 294 // Unset this field so that what we have left are fields that need to be removed. 295 unset($oldLookup[$name]); 296 } 297 else 298 { 299 // This is a new key. 300 $alters[] = $this->getAddKeySQL($table, $keys); 301 } 302 } 303 304 // Any keys left are orphans. 305 foreach ($oldLookup as $name => $keys) 306 { 307 if (strtoupper($name) == 'PRIMARY') 308 { 309 $alters[] = $this->getDropPrimaryKeySQL($table); 310 } 311 else 312 { 313 $alters[] = $this->getDropKeySQL($table, $name); 314 } 315 } 316 317 return $alters; 318 } 319 320 /** 321 * Get the syntax to alter a column. 322 * 323 * @param string $table The name of the database table to alter. 324 * @param SimpleXMLElement $field The XML definition for the field. 325 * 326 * @return string 327 * 328 * @since 11.1 329 */ 330 protected function getChangeColumnSQL($table, SimpleXMLElement $field) 331 { 332 $sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' CHANGE COLUMN ' . $this->db->quoteName((string) $field['Field']) . ' ' 333 . $this->getColumnSQL($field); 334 335 return $sql; 336 } 337 338 /** 339 * Get the SQL syntax for a single column that would be included in a table create or alter statement. 340 * 341 * @param SimpleXMLElement $field The XML field definition. 342 * 343 * @return string 344 * 345 * @since 11.1 346 */ 347 protected function getColumnSQL(SimpleXMLElement $field) 348 { 349 // Initialise variables. 350 // TODO Incorporate into parent class and use $this. 351 $blobs = array('text', 'smalltext', 'mediumtext', 'largetext'); 352 353 $fName = (string) $field['Field']; 354 $fType = (string) $field['Type']; 355 $fNull = (string) $field['Null']; 356 $fDefault = isset($field['Default']) ? (string) $field['Default'] : null; 357 $fExtra = (string) $field['Extra']; 358 359 $sql = $this->db->quoteName($fName) . ' ' . $fType; 360 361 if ($fNull == 'NO') 362 { 363 if (in_array($fType, $blobs) || $fDefault === null) 364 { 365 $sql .= ' NOT NULL'; 366 } 367 else 368 { 369 // TODO Don't quote numeric values. 370 $sql .= ' NOT NULL DEFAULT ' . $this->db->quote($fDefault); 371 } 372 } 373 else 374 { 375 if ($fDefault === null) 376 { 377 $sql .= ' DEFAULT NULL'; 378 } 379 else 380 { 381 // TODO Don't quote numeric values. 382 $sql .= ' DEFAULT ' . $this->db->quote($fDefault); 383 } 384 } 385 386 if ($fExtra) 387 { 388 $sql .= ' ' . strtoupper($fExtra); 389 } 390 391 return $sql; 392 } 393 394 /** 395 * Get the SQL syntax to drop a column. 396 * 397 * @param string $table The table name. 398 * @param string $name The name of the field to drop. 399 * 400 * @return string 401 * 402 * @since 11.1 403 */ 404 protected function getDropColumnSQL($table, $name) 405 { 406 $sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP COLUMN ' . $this->db->quoteName($name); 407 408 return $sql; 409 } 410 411 /** 412 * Get the SQL syntax to drop a key. 413 * 414 * @param string $table The table name. 415 * @param string $name The name of the key to drop. 416 * 417 * @return string 418 * 419 * @since 11.1 420 */ 421 protected function getDropKeySQL($table, $name) 422 { 423 $sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP KEY ' . $this->db->quoteName($name); 424 425 return $sql; 426 } 427 428 /** 429 * Get the SQL syntax to drop a key. 430 * 431 * @param string $table The table name. 432 * 433 * @return string 434 * 435 * @since 11.1 436 */ 437 protected function getDropPrimaryKeySQL($table) 438 { 439 $sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP PRIMARY KEY'; 440 441 return $sql; 442 } 443 444 /** 445 * Get the details list of keys for a table. 446 * 447 * @param array $keys An array of objects that comprise the keys for the table. 448 * 449 * @return array The lookup array. array({key name} => array(object, ...)) 450 * 451 * @since 11.1 452 * @throws Exception 453 */ 454 protected function getKeyLookup($keys) 455 { 456 // First pass, create a lookup of the keys. 457 $lookup = array(); 458 foreach ($keys as $key) 459 { 460 if ($key instanceof SimpleXMLElement) 461 { 462 $kName = (string) $key['Key_name']; 463 } 464 else 465 { 466 $kName = $key->Key_name; 467 } 468 if (empty($lookup[$kName])) 469 { 470 $lookup[$kName] = array(); 471 } 472 $lookup[$kName][] = $key; 473 } 474 475 return $lookup; 476 } 477 478 /** 479 * Get the SQL syntax for a key. 480 * 481 * @param array $columns An array of SimpleXMLElement objects comprising the key. 482 * 483 * @return string 484 * 485 * @since 11.1 486 */ 487 protected function getKeySQL($columns) 488 { 489 // TODO Error checking on array and element types. 490 491 $kNonUnique = (string) $columns[0]['Non_unique']; 492 $kName = (string) $columns[0]['Key_name']; 493 $kColumn = (string) $columns[0]['Column_name']; 494 495 $prefix = ''; 496 if ($kName == 'PRIMARY') 497 { 498 $prefix = 'PRIMARY '; 499 } 500 elseif ($kNonUnique == 0) 501 { 502 $prefix = 'UNIQUE '; 503 } 504 505 $nColumns = count($columns); 506 $kColumns = array(); 507 508 if ($nColumns == 1) 509 { 510 $kColumns[] = $this->db->quoteName($kColumn); 511 } 512 else 513 { 514 foreach ($columns as $column) 515 { 516 $kColumns[] = (string) $column['Column_name']; 517 } 518 } 519 520 $sql = $prefix . 'KEY ' . ($kName != 'PRIMARY' ? $this->db->quoteName($kName) : '') . ' (' . implode(',', $kColumns) . ')'; 521 522 return $sql; 523 } 524 525 /** 526 * Get the real name of the table, converting the prefix wildcard string if present. 527 * 528 * @param string $table The name of the table. 529 * 530 * @return string The real name of the table. 531 * 532 * @since 11.1 533 */ 534 protected function getRealTableName($table) 535 { 536 // TODO Incorporate into parent class and use $this. 537 $prefix = $this->db->getPrefix(); 538 539 // Replace the magic prefix if found. 540 $table = preg_replace('|^#__|', $prefix, $table); 541 542 return $table; 543 } 544 545 /** 546 * Merges the incoming structure definition with the existing structure. 547 * 548 * @return void 549 * 550 * @note Currently only supports XML format. 551 * @since 11.1 552 * @throws Exception on error. 553 * @todo If it's not XML convert to XML first. 554 */ 555 protected function mergeStructure() 556 { 557 // Initialise variables. 558 $prefix = $this->db->getPrefix(); 559 $tables = $this->db->getTableList(); 560 561 if ($this->from instanceof SimpleXMLElement) 562 { 563 $xml = $this->from; 564 } 565 else 566 { 567 $xml = new SimpleXMLElement($this->from); 568 } 569 570 // Get all the table definitions. 571 $xmlTables = $xml->xpath('database/table_structure'); 572 573 foreach ($xmlTables as $table) 574 { 575 // Convert the magic prefix into the real table name. 576 $tableName = (string) $table['name']; 577 $tableName = preg_replace('|^#__|', $prefix, $tableName); 578 579 if (in_array($tableName, $tables)) 580 { 581 // The table already exists. Now check if there is any difference. 582 if ($queries = $this->getAlterTableSQL($xml->database->table_structure)) 583 { 584 // Run the queries to upgrade the data structure. 585 foreach ($queries as $query) 586 { 587 $this->db->setQuery((string) $query); 588 if (!$this->db->query()) 589 { 590 $this->addLog('Fail: ' . $this->db->getQuery()); 591 throw new Exception($this->db->getErrorMsg()); 592 } 593 else 594 { 595 $this->addLog('Pass: ' . $this->db->getQuery()); 596 } 597 } 598 599 } 600 } 601 else 602 { 603 // This is a new table. 604 $sql = $this->xmlToCreate($table); 605 606 $this->db->setQuery((string) $sql); 607 if (!$this->db->query()) 608 { 609 $this->addLog('Fail: ' . $this->db->getQuery()); 610 throw new Exception($this->db->getErrorMsg()); 611 } 612 else 613 { 614 $this->addLog('Pass: ' . $this->db->getQuery()); 615 } 616 } 617 } 618 } 619 620 /** 621 * Sets the database connector to use for exporting structure and/or data from MySQL. 622 * 623 * @param JDatabaseMySQL $db The database connector. 624 * 625 * @return JDatabaseImporterMySQL Method supports chaining. 626 * 627 * @since 11.1 628 */ 629 public function setDbo(JDatabaseMySQL $db) 630 { 631 $this->db = $db; 632 633 return $this; 634 } 635 636 /** 637 * Sets an internal option to merge the structure based on the input data. 638 * 639 * @param boolean $setting True to export the structure, false to not. 640 * 641 * @return JDatabaseImporterMySQL Method supports chaining. 642 * 643 * @since 11.1 644 */ 645 public function withStructure($setting = true) 646 { 647 $this->options->set('with-structure', (boolean) $setting); 648 649 return $this; 650 } 651 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Tue Apr 3 11:40:28 2012 | Cross-referenced by PHPXref 0.7.1 |