eZ Publish  [4.0]
ezmysqlschema.php
Go to the documentation of this file.
00001 <?php
00002 //
00003 // Created on: <30-Jan-2004 10:14:58 dr>
00004 //
00005 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00006 // SOFTWARE NAME: eZ Publish
00007 // SOFTWARE RELEASE: 4.0.x
00008 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS
00009 // SOFTWARE LICENSE: GNU General Public License v2.0
00010 // NOTICE: >
00011 //   This program is free software; you can redistribute it and/or
00012 //   modify it under the terms of version 2.0  of the GNU General
00013 //   Public License as published by the Free Software Foundation.
00014 //
00015 //   This program is distributed in the hope that it will be useful,
00016 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
00017 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018 //   GNU General Public License for more details.
00019 //
00020 //   You should have received a copy of version 2.0 of the GNU General
00021 //   Public License along with this program; if not, write to the Free
00022 //   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00023 //   MA 02110-1301, USA.
00024 //
00025 //
00026 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00027 //
00028 
00029 /*!
00030   \class eZMysqlSchema ezmysqlschema.php
00031   \ingroup eZDbSchema
00032   \brief Handles schemas for MySQL
00033 
00034 */
00035 
00036 //include_once( 'lib/ezdbschema/classes/ezdbschemainterface.php' );
00037 
00038 class eZMysqlSchema extends eZDBSchemaInterface
00039 {
00040 
00041     /*!
00042      \reimp
00043      Constructor
00044 
00045      \param db instance
00046     */
00047     function eZMysqlSchema( $params )
00048     {
00049         $this->eZDBSchemaInterface( $params );
00050     }
00051 
00052     /*!
00053      \reimp
00054     */
00055     function schema( $params = array() )
00056     {
00057         $params = array_merge( array( 'meta_data' => false,
00058                                       'format' => 'generic' ),
00059                                $params );
00060         $schema = array();
00061 
00062         if ( $this->Schema === false )
00063         {
00064             $tableArray = $this->DBInstance->arrayQuery( "SHOW TABLES" );
00065 
00066             foreach( $tableArray as $tableNameArray )
00067             {
00068                 $table_name = current( $tableNameArray );
00069                 if ( !isset( $params['table_include'] ) or
00070                      ( is_array( $params['table_include'] ) and
00071                        in_array( $table_name, $params['table_include'] ) ) )
00072                 {
00073                     $schema_table['name'] = $table_name;
00074                     $schema_table['fields'] = $this->fetchTableFields( $table_name, $params );
00075                     $schema_table['indexes'] = $this->fetchTableIndexes( $table_name, $params );
00076 
00077                     $schema[$table_name] = $schema_table;
00078                 }
00079             }
00080             $this->transformSchema( $schema, $params['format'] == 'local' );
00081             ksort( $schema );
00082             $this->Schema = $schema;
00083         }
00084         else
00085         {
00086             $this->transformSchema( $this->Schema, $params['format'] == 'local' );
00087             $schema = $this->Schema;
00088         }
00089         return $schema;
00090     }
00091 
00092     /*!
00093      \private
00094 
00095      \param table name
00096      */
00097     function fetchTableFields( $table, $params )
00098     {
00099         $fields = array();
00100 
00101         $resultArray = $this->DBInstance->arrayQuery( "DESCRIBE $table" );
00102 
00103         foreach( $resultArray as $row )
00104         {
00105             $field = array();
00106             $field['type'] = $this->parseType ( $row['Type'], $field['length'] );
00107             if ( !$field['length'] )
00108             {
00109                 unset( $field['length'] );
00110             }
00111             $field['not_null'] = 0;
00112             if ( $row['Null'] != 'YES' )
00113             {
00114                 $field['not_null'] = '1';
00115             }
00116             $field['default'] = false;
00117             if ( !$field['not_null'] )
00118             {
00119                 if ( $row['Default'] === null )
00120                     $field['default'] = null;
00121                 else
00122                     $field['default'] = (string)$row['Default'];
00123             }
00124             else
00125             {
00126                 $field['default'] = (string)$row['Default'];
00127             }
00128 
00129             $numericTypes = array( 'float', 'int' );
00130             $blobTypes = array( 'tinytext', 'text', 'mediumtext', 'longtext' );
00131             $charTypes = array( 'varchar', 'char' );
00132             if ( in_array( $field['type'], $charTypes ) )
00133             {
00134                 if ( !$field['not_null'] )
00135                 {
00136                     if ( $field['default'] === null )
00137                     {
00138                         $field['default'] = null;
00139                     }
00140                     else if ( $field['default'] === false )
00141                     {
00142                         $field['default'] = '';
00143                     }
00144                 }
00145             }
00146             else if ( in_array( $field['type'], $numericTypes ) )
00147             {
00148                 if ( $field['default'] === false )
00149                 {
00150                     if ( $field['not_null'] )
00151                     {
00152                         $field['default'] = 0;
00153                     }
00154                 }
00155                 else if ( $field['type'] == 'int' )
00156                 {
00157                     if ( $field['not_null'] or
00158                          is_numeric( $field['default'] ) )
00159                         $field['default'] = (int)$field['default'];
00160                 }
00161                 else if ( $field['type'] == 'float' or
00162                           is_numeric( $field['default'] ) )
00163                 {
00164                     if ( $field['not_null'] or
00165                          is_numeric( $field['default'] ) )
00166                         $field['default'] = (float)$field['default'];
00167                 }
00168             }
00169             else if ( in_array( $field['type'], $blobTypes ) )
00170             {
00171                 // We do not want default for blobs.
00172                 $field['default'] = false;
00173             }
00174 
00175             if ( strpos ( $row['Extra'], 'auto_increment' ) !== false )
00176             {
00177                 unset( $field['length'] );
00178                 $field['not_null'] = 0;
00179                 $field['default'] = false;
00180                 $field['type'] = 'auto_increment';
00181             }
00182 
00183             if ( !$field['not_null'] )
00184                 unset( $field['not_null'] );
00185 
00186             $fields[$row['Field']] = $field;
00187         }
00188         ksort( $fields );
00189 
00190         return $fields;
00191     }
00192 
00193     /*!
00194      * \private
00195      */
00196     function fetchTableIndexes( $table, $params )
00197     {
00198         $metaData = false;
00199         if ( isset( $params['meta_data'] ) )
00200         {
00201             $metaData = $params['meta_data'];
00202         }
00203 
00204         $indexes = array();
00205 
00206         $resultArray = $this->DBInstance->arrayQuery( "SHOW INDEX FROM $table" );
00207 
00208         foreach( $resultArray as $row )
00209         {
00210             $kn = $row['Key_name'];
00211 
00212             if ( $kn == 'PRIMARY' )
00213             {
00214                 $indexes[$kn]['type'] = 'primary';
00215             }
00216             else
00217             {
00218                 $indexes[$kn]['type'] = $row['Non_unique'] ? 'non-unique' : 'unique';
00219             }
00220 
00221             $indexFieldDef = array( 'name' => $row['Column_name'] );
00222 
00223             // Include length if one is defined
00224             if ( $row['Sub_part'] )
00225             {
00226                 $indexFieldDef['mysql:length'] = (int)$row['Sub_part'];
00227             }
00228 
00229             // Check if we have any entries other than 'name', if not we skip the array definition
00230             if ( count( array_diff( array_keys( $indexFieldDef ), array( 'name' ) ) ) == 0 )
00231             {
00232                 $indexFieldDef = $indexFieldDef['name'];
00233             }
00234             $indexes[$kn]['fields'][$row['Seq_in_index'] - 1] = $indexFieldDef;
00235         }
00236         ksort( $indexes );
00237 
00238         return $indexes;
00239     }
00240 
00241     function parseType( $type_info, &$length_info )
00242     {
00243         preg_match ( "@([a-z ]*)(\(([0-9]*|[0-9]*,[0-9]*)\))?@", $type_info, $matches );
00244         if ( isset( $matches[3] ) )
00245         {
00246             $length_info = $matches[3];
00247             if ( is_numeric( $length_info ) )
00248                 $length_info = (int)$length_info;
00249         }
00250         return $matches[1];
00251     }
00252 
00253     /*!
00254      * \private
00255      */
00256     function generateAddIndexSql( $table_name, $index_name, $def, $params, $isEmbedded = false )
00257     {
00258         $diffFriendly = isset( $params['diff_friendly'] ) ? $params['diff_friendly'] : false;
00259         // If the output should compatible with existing MySQL dumps
00260         $mysqlCompatible = isset( $params['compatible_sql'] ) ? $params['compatible_sql'] : false;
00261         $sql = '';
00262 
00263         // Will be set to true when primary key is inside CREATE TABLE
00264         if ( !$isEmbedded )
00265         {
00266             $sql .= "ALTER TABLE $table_name ADD";
00267             $sql .= " ";
00268         }
00269 
00270         switch ( $def['type'] )
00271         {
00272             case 'primary':
00273             {
00274                 $sql .= 'PRIMARY KEY';
00275                 if ( $mysqlCompatible )
00276                     $sql .= " ";
00277             } break;
00278 
00279             case 'non-unique':
00280             {
00281                 if ( $isEmbedded )
00282                 {
00283                     $sql .= "KEY $index_name";
00284                 }
00285                 else
00286                 {
00287                     $sql .= "INDEX $index_name";
00288                 }
00289             } break;
00290 
00291             case 'unique':
00292             {
00293                 if ( $isEmbedded )
00294                 {
00295                     $sql .= "UNIQUE KEY $index_name";
00296                 }
00297                 else
00298                 {
00299                     $sql .= "UNIQUE $index_name";
00300                 }
00301             } break;
00302         }
00303 
00304         $sql .= ( $diffFriendly ? " (\n    " : ( $mysqlCompatible ? " (" : " ( " ) );
00305         $fields = $def['fields'];
00306         $i = 0;
00307         foreach ( $fields as $fieldDef )
00308         {
00309             if ( $i > 0 )
00310             {
00311                 $sql .= $diffFriendly ? ",\n    " : ( $mysqlCompatible ? ',' : ', ' );
00312             }
00313             if ( is_array( $fieldDef ) )
00314             {
00315                 $sql .= $fieldDef['name'];
00316                 if ( isset( $fieldDef['mysql:length'] ) )
00317                 {
00318                     if ( $diffFriendly )
00319                     {
00320                         $sql .= "(\n";
00321                         $sql .= "    " . str_repeat( ' ', strlen( $fieldDef['name'] ) );
00322                     }
00323                     else
00324                     {
00325                         $sql .= $mysqlCompatible ? "(" : "( ";
00326                     }
00327                     $sql .= $fieldDef['mysql:length'];
00328                     if ( $diffFriendly )
00329                     {
00330                         $sql .= ")";
00331                     }
00332                     else
00333                     {
00334                         $sql .= $mysqlCompatible ? ")" : " )";
00335                     }
00336                 }
00337             }
00338             else
00339             {
00340                 $sql .= $fieldDef;
00341             }
00342             ++$i;
00343         }
00344         $sql .= ( $diffFriendly ? "\n)" : ( $mysqlCompatible ? ")" : " )" ) );
00345 
00346         if ( !$isEmbedded )
00347         {
00348             return $sql . ";\n";
00349         }
00350         return $sql;
00351     }
00352 
00353     /*!
00354      * \private
00355      */
00356     function generateDropIndexSql( $table_name, $index_name, $def, $params )
00357     {
00358         $sql = '';
00359         $sql .= "ALTER TABLE $table_name DROP ";
00360 
00361         if ( $def['type'] == 'primary' )
00362         {
00363             $sql .= 'PRIMARY KEY';
00364         }
00365         else
00366         {
00367             $sql .= "INDEX $index_name";
00368         }
00369         return $sql . ";\n";
00370     }
00371 
00372     /*!
00373      * \private
00374      */
00375     function generateFieldDef( $field_name, $def, &$skip_primary, $params = null )
00376     {
00377         $diffFriendly = isset( $params['diff_friendly'] ) ? $params['diff_friendly'] : false;
00378         // If the output should compatible with existing MySQL dumps
00379         $mysqlCompatible = isset( $params['compatible_sql'] ) ? $params['compatible_sql'] : false;
00380 
00381         $sql_def = $field_name . ' ';
00382         $defaultText = $mysqlCompatible ? "default" : "DEFAULT";
00383 
00384         if ( $def['type'] != 'auto_increment' )
00385         {
00386             $defList = array();
00387             $type = $def['type'];
00388             if ( isset( $def['length'] ) )
00389             {
00390                 $type .= "({$def['length']})";
00391             }
00392             $defList[] = $type;
00393             if ( isset( $def['not_null'] ) && ( $def['not_null'] ) )
00394             {
00395                 $defList[] = 'NOT NULL';
00396             }
00397             if ( array_key_exists( 'default', $def ) )
00398             {
00399                 if ( $def['default'] === null )
00400                 {
00401                     $defList[] = "$defaultText NULL";
00402                 }
00403                 else if ( $def['default'] !== false )
00404                 {
00405                     $defList[] = "$defaultText '{$def['default']}'";
00406                 }
00407             }
00408             else if ( $def['type'] == 'varchar' )
00409             {
00410                 $defList[] = "$defaultText ''";
00411             }
00412             $sql_def .= join( $diffFriendly ? "\n    " : " ", $defList );
00413             $skip_primary = false;
00414         }
00415         else
00416         {
00417             $incrementText = $mysqlCompatible ? "auto_increment" : "AUTO_INCREMENT";
00418             if ( $diffFriendly )
00419             {
00420                 $sql_def .= "int(11)\n    NOT NULL\n    $incrementText";
00421             }
00422             else
00423             {
00424                 $sql_def .= "int(11) NOT NULL $incrementText";
00425             }
00426             $skip_primary = true;
00427         }
00428         return $sql_def;
00429     }
00430 
00431     /*!
00432      * \private
00433      */
00434     function generateAddFieldSql( $table_name, $field_name, $def, $params )
00435     {
00436         $sql = "ALTER TABLE $table_name ADD COLUMN ";
00437         $sql .= eZMysqlSchema::generateFieldDef ( $field_name, $def, $dummy );
00438 
00439         return $sql . ";\n";
00440     }
00441 
00442     /*!
00443      * \private
00444      */
00445     function generateAlterFieldSql( $table_name, $field_name, $def, $params )
00446     {
00447         $sql = "ALTER TABLE $table_name CHANGE COLUMN $field_name ";
00448         $sql .= eZMysqlSchema::generateFieldDef ( $field_name, $def, $dummy );
00449 
00450         return $sql . ";\n";
00451     }
00452 
00453     /*!
00454      \reimp
00455      \note Calls generateTableSQL() with \a $asArray set to \c false
00456     */
00457     function generateTableSchema( $tableName, $table, $params )
00458     {
00459         return $this->generateTableSQL( $tableName, $table, $params, false, false );
00460     }
00461 
00462     /*!
00463      \reimp
00464      \note Calls generateTableSQL() with \a $asArray set to \c true
00465     */
00466     function generateTableSQLList( $tableName, $table, $params, $separateTypes )
00467     {
00468         return $this->generateTableSQL( $tableName, $table, $params, true, $separateTypes );
00469     }
00470 
00471     /*!
00472      \private
00473 
00474      \param $asArray If \c true all SQLs are return in an array,
00475                      if not they are returned as a string.
00476      \note When returned as array the SQLs will not have a semi-colon to end the statement
00477     */
00478     function generateTableSQL( $tableName, $tableDef, $params, $asArray, $separateTypes = false )
00479     {
00480         $diffFriendly = isset( $params['diff_friendly'] ) ? $params['diff_friendly'] : false;
00481         $mysqlCompatible = isset( $params['compatible_sql'] ) ? $params['compatible_sql'] : false;
00482 
00483         if ( $asArray )
00484         {
00485             if ( $separateTypes )
00486             {
00487                 $sqlList = array( 'tables' => array() );
00488             }
00489             else
00490             {
00491                 $sqlList = array();
00492             }
00493         }
00494 
00495         $sql = '';
00496         $skip_pk = false;
00497         $sql_fields = array();
00498         $sql .= "CREATE TABLE $tableName (\n";
00499 
00500         $fields = $tableDef['fields'];
00501 
00502         foreach ( $fields as $field_name => $field_def )
00503         {
00504             $sql_fields[] = '  ' . eZMysqlSchema::generateFieldDef( $field_name, $field_def, $skip_pk_flag, $params );
00505             if ( $skip_pk_flag )
00506             {
00507                 $skip_pk = true;
00508             }
00509         }
00510 
00511         // Make sure the order is as defined by 'offset'
00512         $indexes = $tableDef['indexes'];
00513 
00514         // We need to add all keys in table definition
00515         foreach ( $indexes as $index_name => $index_def )
00516         {
00517             $sql_fields[] = ( $diffFriendly ? '' : '  ' ) . eZMysqlSchema::generateAddIndexSql( $tableName, $index_name, $index_def, $params, true );
00518         }
00519         $sql .= join( ",\n", $sql_fields );
00520         $sql .= "\n)";
00521 
00522         // Add some extra table options if they are required
00523         $extraOptions = array();
00524         if ( isset( $params['table_type'] ) and $params['table_type'] )
00525         {
00526             $typeName = $this->tableStorageTypeName( $params['table_type'] );
00527             if ( $typeName )
00528             {
00529                 $extraOptions[] = "ENGINE=" . $typeName;
00530             }
00531         }
00532         if ( isset( $params['table_charset'] ) and $params['table_charset'] )
00533         {
00534             $charsetName = $this->tableCharsetName( $params['table_charset'] );
00535             if ( $charsetName )
00536             {
00537                 $extraOptions[] = "DEFAULT CHARACTER SET " . $charsetName;
00538             }
00539         }
00540         if ( isset( $tableDef['options'] ) )
00541         {
00542             foreach( $tableDef['options'] as $optionType => $optionValue )
00543             {
00544                 $optionText = $this->generateTableOption( $tableName, $tableDef, $optionType, $optionValue, $params );
00545                 if ( $optionText )
00546                     $extraOptions[] = $optionText;
00547             }
00548         }
00549 
00550         if ( count( $extraOptions ) > 0 )
00551         {
00552             $sql .= " " . implode( $diffFriendly ? "\n" : " ", $extraOptions );
00553         }
00554 
00555         if ( $asArray )
00556         {
00557             if ( $separateTypes )
00558             {
00559                 $sqlList['tables'][] = $sql;
00560             }
00561             else
00562             {
00563                 $sqlList[] = $sql;
00564             }
00565         }
00566         else
00567         {
00568             $sql .= ";\n";
00569 
00570             if ( $mysqlCompatible )
00571             {
00572                 $sql .= "\n\n\n";
00573             }
00574         }
00575 
00576         return $asArray ? $sqlList : $sql;
00577     }
00578 
00579     /*!
00580      Detects known options and generates the MySQL SQL code for it.
00581      \return The SQL code as a string or \c false if not known.
00582      \param $optionType The type of option, the supported ones are:
00583                         - delay_key_write - If \a $optionValue is true then adds DELAY_KEY_WRITE=1
00584     */
00585     function generateTableOption( $tableName, $tableDef, $optionType, $optionValue, $params )
00586     {
00587         switch ( $optionType )
00588         {
00589             case 'mysql:delay_key_write':
00590             {
00591                 if ( $optionValue )
00592                     return 'DELAY_KEY_WRITE=1';
00593             } break;
00594         }
00595         return false;
00596     }
00597 
00598     /*!
00599       \return The name of the charset \a $charset in a format MySQL understands.
00600     */
00601     function tableCharsetName( $charset )
00602     {
00603         //include_once( 'lib/ezi18n/classes/ezcharsetinfo.php' );
00604         $charset = eZCharsetInfo::realCharsetCode( $charset );
00605         // Convert charset names into something MySQL will understand
00606         $charsetMapping = array( 'iso-8859-1' => 'latin1',
00607                                  'iso-8859-2' => 'latin2',
00608                                  'iso-8859-8' => 'hebrew',
00609                                  'iso-8859-7' => 'greek',
00610                                  'iso-8859-9' => 'latin5',
00611                                  'iso-8859-13' => 'latin7',
00612                                  'windows-1250' => 'cp1250',
00613                                  'windows-1251' => 'cp1251',
00614                                  'windows-1256' => 'cp1256',
00615                                  'windows-1257' => 'cp1257',
00616                                  'utf-8' => 'utf8',
00617                                  'koi8-r' => 'koi8r',
00618                                  'koi8-u' => 'koi8u' );
00619         $charset = strtolower( $charset );
00620         if ( isset( $charsetMapping ) )
00621             return $charsetMapping[$charset];
00622         return $charset;
00623     }
00624 
00625     /*!
00626       \return The name of storage type \a $type or \c false if not supported.
00627 
00628       \note Currently supports \c bdb, \c myisam and \c innodb.
00629 
00630       See http://dev.mysql.com/doc/mysql/en/CREATE_TABLE.html for overview of the types MySQL supports
00631     */
00632     function tableStorageTypeName( $type )
00633     {
00634         $type = strtolower( $type );
00635         switch ( $type )
00636         {
00637             case 'bdb':
00638             {
00639                 return 'BDB';
00640             }
00641 
00642             case 'myisam':
00643             {
00644                 return 'MyISAM';
00645             }
00646 
00647             case 'innodb':
00648             {
00649                 return 'InnoDB';
00650             }
00651         }
00652         return false;
00653     }
00654 
00655     /*!
00656      * \private
00657      */
00658     function generateDropTable( $table, $params )
00659     {
00660         return "DROP TABLE $table;\n";
00661     }
00662 
00663     /*!
00664      \reimp
00665      MySQL 3.22.5 and higher support multi-insert queries so if the current
00666      database has sufficient version we return \c true.
00667      If no database is connected we return \true.
00668     */
00669     function isMultiInsertSupported()
00670     {
00671         if ( $this->DBInstance instanceof eZDBInterface )
00672         {
00673             $versionInfo = $this->DBInstance->databaseServerVersion();
00674 
00675             // We require MySQL 3.22.5 to use multi-insert queries
00676             // http://dev.mysql.com/doc/mysql/en/INSERT.html
00677             return ( version_compare( $versionInfo['string'], '3.22.5' ) >= 0 );
00678         }
00679         return true;
00680     }
00681 
00682     /*!
00683      \reimp
00684     */
00685     function escapeSQLString( $value )
00686     {
00687         if ( $this->DBInstance instanceof eZDBInterface )
00688         {
00689             return $this->DBInstance->escapeString( $value );
00690         }
00691 
00692         return $value;
00693     }
00694 
00695     /*!
00696      \reimp
00697     */
00698     function schemaType()
00699     {
00700         return 'mysql';
00701     }
00702 
00703     /*!
00704      \reimp
00705     */
00706     function schemaName()
00707     {
00708         return 'MySQL';
00709     }
00710 
00711 }
00712 ?>