[ Index ]

PHP Cross Reference of Joomla 2.5.4 DE

title

Body

[close]

/libraries/joomla/database/database/ -> mysqlimporter.php (source)

   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  }


Generated: Tue Apr 3 11:40:28 2012 Cross-referenced by PHPXref 0.7.1