eZ Publish  [trunk]
ezdbschemainterface.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZDBSchemaInterface class.
00004  *
00005  * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved.
00006  * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
00007  * @version //autogentag//
00008  * @package lib
00009  */
00010 
00011 /*! \defgroup eZDbSchema Database schema abstraction layer */
00012 
00013 /*!
00014   \class eZDBSchemaInterface ezdbschemainterface.php
00015   \ingroup eZDbSchema
00016   \brief This class provide interface for DB schema library
00017 
00018   Schema structure an array with Table structures, each key is the name of the
00019   table.
00020 
00021   Table structure:
00022   - name - Name of table
00023   - fields - Array of Field structures, each key is the field name
00024   - indexes - Array of Index structures, each key is the index name
00025   - removed - Contains whether the table has been removed or not (Optional)
00026   - comments - An array with comment strings (Optional)
00027 
00028   The \c removed entry will only be used when some comments have been added
00029   to the table. That way the comments can be added to the DROP TABLE statements.
00030 
00031   Field structure:
00032   - length - A number which defines the length/size of the type or \c false
00033   - type - String containing the identifier of the Type, see Types below.
00034   - not_null - Is 1 if the type cannot be null, if 0 or not defined it can be null
00035   - default - The default value, the value depends on the type, \c false means no default value.
00036 
00037   Index structure:
00038   - type - What kind of index, see Index Types.
00039   - fields - Array of field names the index is made on, or of sub-arrays where the field name is in the 'name' field
00040 
00041   Index Types:
00042   - primary - A primary key, there can only be one primary key. This key will be named PRIMARY.
00043   - non-unique - A standard index
00044   - unique - A unique index
00045 
00046   Field Types:
00047   - int - Integer, uses \c length to define number of digits.
00048   - float - Float, uses \c length to define number of digits.
00049   - auto_increment - Integer that auto increments (uses sequence+trigger).
00050   - varchar - String with variable length, uses \c length for max length.
00051   - char - String with fixed length, uses \c length.
00052   - longtext - String with 2^32 number of possible characters
00053   - mediumtext - String with 2^24 number of possible characters
00054   - shorttext - String with 2^16 number of possible characters
00055 
00056   When stored as a PHP array the schema structure will be placed in a variable
00057   called $schema. The data structure will be placed in $data.
00058 
00059 */
00060 
00061 class eZDBSchemaInterface
00062 {
00063     /*!
00064      Constructor
00065 
00066      \sa eZDB
00067      */
00068     function eZDBSchemaInterface( $params )
00069     {
00070         $this->DBInstance = $params['instance'];
00071         $this->Schema = false;
00072         $this->Data = false;
00073         if ( isset( $params['schema'] ) )
00074             $this->Schema = $params['schema'];
00075         if ( isset( $params['data'] ) )
00076             $this->Data = $params['data'];
00077     }
00078 
00079     /*!
00080      \pure
00081      Gets SQL db schema definition by analyzing the current DB instance and return
00082      it in array format.
00083      NB: once the schema is generated, it might be cached.
00084 
00085      \param $params supported options are 'meta_data' and 'format'
00086      \return DB schema array
00087     */
00088     function schema( $params = array() )
00089     {
00090     }
00091 
00092     /*!
00093      Fetches the data for all tables and returns an array containing the data.
00094      Empty tables are skipped.
00095      NB: once the data is generated, it might be cached. Use the 'force_read'
00096          parameter to force regeneration of the data
00097 
00098      \param $schema A schema definition array which defines tables to fetch from.
00099                     If \c false it will call schema() to fetch it.
00100      \param $tableNameList An array with tables to include, will further narrow
00101                            tables in \a $schema. Use \c false to fetch all tables.
00102 
00103      \note You shouldn't need to reimplement this method since the default
00104            code will do simple SELECT queries
00105      \sa fetchTableData()
00106     */
00107     function data( $schema = false, $tableNameList = false, $params = array() )
00108     {
00109         $params = array_merge( array( 'meta_data' => false,
00110                                       'format' => 'generic',
00111                                       'offset' => false,
00112                                       'limit' => false,
00113                                       'force_read' => false ),
00114                                $params );
00115 
00116         if ( $this->Data === false || $params['force_read'] )
00117         {
00118             if ( $schema === false )
00119                 $schema = $this->schema( $params );
00120 
00121             // We need to transform schema to local format for data to be fetched correctly.
00122             if ( $schema['_info']['format'] == 'generic' )
00123                 $this->transformSchema( $schema, true );
00124 
00125             $data = array();
00126             foreach ( $schema as $tableName => $tableInfo )
00127             {
00128                 // Skip the information array, this is not a table
00129                 if ( $tableName == '_info' )
00130                     continue;
00131 
00132                 if ( is_array( $tableNameList ) and
00133                      !in_array( $tableName, $tableNameList ) )
00134                     continue;
00135 
00136                 $tableEntry = $this->fetchTableData( $tableInfo, $params['offset'], $params['limit'] );
00137                 if ( count( $tableEntry['rows'] ) > 0 )
00138                     $data[$tableName] = $tableEntry;
00139             }
00140             $this->transformData( $data, $params['format'] == 'local' );
00141             ksort( $data );
00142             $this->Data = $data;
00143         }
00144         else
00145         {
00146             $this->transformData( $this->Data, $params['format'] == 'local' );
00147             $data = $this->Data;
00148         }
00149 
00150         return $data;
00151     }
00152 
00153     /*!
00154      Validates the current schema and returns \c true if it is correct or
00155      \c false if something must be fixed.
00156      \note This should not be reimplemented by normal handlers, only schema
00157            checkers.
00158     */
00159     function validate()
00160     {
00161         return false;
00162     }
00163 
00164     /*!
00165      \protected
00166      Fetches all rows for table defined in \a $tableInfo and returns this structure:
00167      - fields - Array with fields that were fetched from table, the order of the fields
00168                 are the same as the order of the data
00169      - rows - Array with all rows, each row is an indexed array with the data.
00170 
00171      \param $tableInfo Table structure from schema definition.
00172      \param $offset Which offset to start from or \c false to start at top
00173      \param $limit How many rows to fetch or \c false for no limit.
00174 
00175      \note You shouldn't need to reimplement this method since the default
00176            code will do simple SELECT queries
00177      \sa data()
00178     */
00179     function fetchTableData( $tableInfo, $offset = false, $limit = false )
00180     {
00181         if ( count( $tableInfo['fields'] ) == 0 )
00182             return false;
00183 
00184         $tableName = $tableInfo['name'];
00185         $fields = array_keys( $tableInfo['fields'] );
00186         $fieldText = implode( ', ', $fields );
00187         $rows = $this->DBInstance->arrayQuery( "SELECT $fieldText FROM $tableName", array( 'offset' => $offset, 'limit' => $limit ) );
00188         $resultArray = array();
00189         /// @todo scan only once for fields of type char, not once per row
00190         foreach ( $rows as $row )
00191         {
00192             $rowData = array();
00193             foreach ( $tableInfo['fields'] as $fieldName => $field )
00194             {
00195                 if ( $field['type'] == 'char' )
00196                 {
00197                     $rowData[$fieldName] = str_pad( $row[$fieldName], $field['length'], ' ' );
00198                 }
00199                 else
00200                 {
00201                     $rowData[$fieldName] = $row[$fieldName];
00202                 }
00203             }
00204             $resultArray[] = array_values( $rowData );
00205         }
00206         return array( 'fields' => $fields,
00207                       'rows' => $resultArray );
00208     }
00209 
00210     /*!
00211      \pure
00212      Write upgrade sql to file
00213 
00214      \param differences array
00215      \param filename
00216     */
00217     function writeUpgradeFile( $differences, $filename, $params = array() )
00218     {
00219         $params = array_merge( array( 'schema' => true,
00220                                       'data' => false,
00221                                       'allow_multi_insert' => false,
00222                                       'diff_friendly' => false ),
00223                                $params );
00224         $fp = @fopen( $filename, 'w' );
00225         if ( $fp )
00226         {
00227             fputs( $fp, $this->generateUpgradeFile( $differences, $params ) );
00228             fclose( $fp );
00229             return true;
00230         }
00231         else
00232         {
00233             return false;
00234         }
00235     }
00236 
00237     /*!
00238       Write SQL schema definition to file.
00239       The generated schema is always in 'local' format, as 'generic' SQL does not exist.
00240 
00241       \param filename
00242     */
00243     function writeSQLSchemaFile( $filename, $params = array() )
00244     {
00245         $params = array_merge( array( 'schema' => true,
00246                                       'data' => false,
00247                                       'allow_multi_insert' => false,
00248                                       'diff_friendly' => false,
00249                                       'offset' => false,
00250                                       'limit' => false ),
00251                                $params );
00252         $includeSchema = $params['schema'];
00253         $includeData = $params['data'];
00254         $fp = @fopen( $filename, 'w' );
00255         if ( $fp )
00256         {
00257             $schema = $this->schema( $params );
00258             $this->transformSchema( $schema, true );
00259             if ( $includeSchema )
00260             {
00261                 fputs( $fp, $this->generateSchemaFile( $schema, $params ) );
00262             }
00263             if ( $includeData )
00264             {
00265                 $data = $this->data( $schema, false, array( 'offset' => $params['offset'], 'limit' => $params['limit'] ) );
00266                 $this->transformData( $data, true );
00267                 fputs( $fp, $this->generateDataFile( $schema, $data, $params ) );
00268             }
00269             fclose( $fp );
00270             return true;
00271         }
00272         else
00273         {
00274             return false;
00275         }
00276     }
00277 
00278     /*!
00279       Write PHP schema definition to file using PHP serialized format.
00280 
00281       \param filename
00282     */
00283     function writeSerializedSchemaFile( $filename, $params = array() )
00284     {
00285         $params = array_merge( array( 'schema' => true,
00286                                       'data' => false,
00287                                       'offset' => false,
00288                                       'limit' => false ),
00289                                $params );
00290         $includeSchema = $params['schema'];
00291         $includeData = $params['data'];
00292         $fp = @fopen( $filename, 'w' );
00293         if ( $fp )
00294         {
00295             $schema = $this->schema( $params );
00296             if ( $includeSchema and $includeData )
00297             {
00298                 fputs( $fp, serialize( array( 'schema' => $schema,
00299                                               'data' => $this->data( $schema, false, array( 'offset' => $params['offset'], 'limit' => $params['limit'] ) ) ) ) );
00300             }
00301             else if ( $includeSchema )
00302             {
00303                 fputs( $fp, serialize( $schema ) );
00304             }
00305             else if ( $includeData )
00306             {
00307                 fputs( $fp, serialize( $this->data( $schema, false, array( 'offset' => $params['offset'], 'limit' => $params['limit'] ) ) ) );
00308             }
00309             fclose( $fp );
00310             return true;
00311         }
00312         else
00313         {
00314             return false;
00315         }
00316     }
00317 
00318     /*!
00319       Write PHP schema definition to file using PHP array structures.
00320 
00321       \param filename
00322     */
00323     function writeArraySchemaFile( $filename, $params = array() )
00324     {
00325         $params = array_merge( array( 'schema' => true,
00326                                       'data' => false,
00327                                       'offset' => false,
00328                                       'limit' => false ),
00329                                $params );
00330         $includeSchema = $params['schema'];
00331         $includeData = $params['data'];
00332         $fp = @fopen( $filename, 'w' );
00333         if ( $fp )
00334         {
00335             $schema = $this->schema( $params );
00336             fputs( $fp, '<?' . 'php' . "\n" );
00337             if ( $includeSchema )
00338             {
00339                 fputs( $fp, "// This array contains the database schema\n" );
00340                 if ( isset( $schema['_info'] ) )
00341                 {
00342                     // push the _info member at bottom of array
00343                     $info = $schema['_info'];
00344                     unset( $schema['_info'] );
00345                     $schema['_info'] = $info;
00346                 }
00347                 fputs( $fp, '$schema = ' . var_export( $schema, true ) . ";\n" );
00348             }
00349             if ( $includeData )
00350             {
00351                 $data = $this->data( $schema, false, array( 'offset' => $params['offset'], 'limit' => $params['limit'] ) );
00352                 fputs( $fp, "// This array contains the database data\n" );
00353                 if ( isset( $data['_info'] ) )
00354                 {
00355                     // push the _info member at bottom of array
00356                     $info = $data['_info'];
00357                     unset( $data['_info'] );
00358                     $data['_info'] = $info;
00359                 }
00360                 fputs( $fp, '$data = ' . var_export( $data, true ) . ";\n" );
00361             }
00362             fputs( $fp, "\n" . '?>' );
00363             fclose( $fp );
00364             return true;
00365         }
00366         else
00367         {
00368             return false;
00369         }
00370     }
00371 
00372     /*!
00373       Insert PHP schema to the current database instance by running one SQL at a time.
00374 
00375       \param $params Optional parameter which controls what to insert:
00376                      - schema - Whether to insert the schema or not, default is \c true.
00377                      - data - Whether to insert the data or not, default is \c false
00378       \return \c false if the schema could not be inserted, \c true if successful
00379     */
00380     function insertSchema( $params = array() )
00381     {
00382         $params = array_merge( array( 'schema' => true,
00383                                       'data' => false ),
00384                                $params );
00385 
00386         if ( !is_object( $this->DBInstance ) )
00387         {
00388             eZDebug::writeError( "No database instance is available, cannot insert", __METHOD__ );
00389             return false;
00390         }
00391 
00392         $oldOutputSQL = $this->DBInstance->OutputSQL;
00393         $this->DBInstance->OutputSQL = false;
00394 
00395         $includeSchema = $params['schema'];
00396         $includeData = $params['data'];
00397         $params['format'] = 'local';
00398         $schema = $this->schema( $params );
00399         if ( $includeSchema )
00400         {
00401             foreach ( $schema as $tableName => $table )
00402             {
00403                 // Skip the information array, this is not a table
00404                 if ( $tableName == '_info' )
00405                     continue;
00406 
00407                 $sqlList = $this->generateTableSQLList( $tableName, $table, $params, false );
00408                 foreach ( $sqlList as $sql )
00409                 {
00410                     if ( !$this->DBInstance->query( $sql ) )
00411                     {
00412                         eZDebug::writeError( "Failed inserting the SQL:\n$sql" );
00413                         return false;
00414                     }
00415                 }
00416             }
00417         }
00418         if ( $includeData )
00419         {
00420             $data = $this->data( $schema, false, array( 'format' => 'local' ) );
00421 
00422             $this->DBInstance->begin();
00423 
00424             foreach ( $schema as $tableName => $table )
00425             {
00426                 // Skip the information array, this is not a table
00427                 if ( $tableName == '_info' )
00428                     continue;
00429 
00430                 if ( !isset( $data[$tableName] ) )
00431                 {
00432                     continue;
00433                 }
00434 
00435                 $sqlList = $this->generateTableInsertSQLList( $tableName, $table, $data[$tableName], $params, false );
00436                 foreach ( $sqlList as $sql )
00437                 {
00438                     if ( !$this->DBInstance->query( $sql ) )
00439                     {
00440                         eZDebug::writeError( "Failed inserting the SQL:\n$sql" );
00441                         $this->DBInstance->rollback();
00442                         return false;
00443                     }
00444                 }
00445             }
00446 
00447             $this->DBInstance->commit();
00448 
00449             // Update sequences for databases that require this
00450             if ( method_exists( $this->DBInstance, 'correctSequenceValues' ) )
00451             {
00452                 $status = $this->DBInstance->correctSequenceValues();
00453                 if ( !$status )
00454                     return false;
00455             }
00456         }
00457         $this->DBInstance->OutputSQL = $oldOutputSQL;
00458         return true;
00459     }
00460 
00461     /*!
00462      \private
00463      \param schema database schema
00464      \return schema for file output
00465     */
00466     function generateDataFile( $schema, $data, $params )
00467     {
00468         $params = array_merge( array( 'allow_multi_insert' => false,
00469                                       'diff_friendly' => false ),
00470                                $params );
00471         $sql = '';
00472 
00473         $i = 0;
00474         foreach ( $schema as $tableName => $tableDef )
00475         {
00476             // Skip the info structure, this is not a table
00477             if ( $tableName == '_info' )
00478                 continue;
00479 
00480             if ( !isset( $data[$tableName] ) )
00481                 continue;
00482             if ( $i > 0 )
00483                 $sql .= "\n\n";
00484             $dataEntries = $data[$tableName];
00485             $sql .= $this->generateTableInsert( $tableName, $tableDef, $dataEntries, $params );
00486             ++$i;
00487         }
00488 
00489         return $sql;
00490     }
00491 
00492     /*!
00493      \private
00494      \param schema database schema
00495      \return schema for file output
00496     */
00497     function generateSchemaFile( $schema, $params = array() )
00498     {
00499         $sql = '';
00500 
00501         $i = 0;
00502         foreach ( $schema as $table => $tableDef )
00503         {
00504             // Skip the info structure, this is not a table
00505             if ( $table == '_info' )
00506                 continue;
00507 
00508             if ( $i > 0 )
00509                 $sql .= "\n\n";
00510             $sql .= $this->generateTableSchema( $table, $tableDef, $params );
00511             ++$i;
00512         }
00513 
00514         return $sql;
00515     }
00516 
00517     /*!
00518      * \private
00519      */
00520     function generateUpgradeFile( $differences, $params = array() )
00521     {
00522         $params = array_merge( array( 'schema' => true,
00523                                       'data' => false,
00524                                       'allow_multi_insert' => false,
00525                                       'diff_friendly' => false ),
00526                                $params );
00527         $sql = '';
00528 
00529         /* Loop over all 'table_changes' */
00530         if ( isset( $differences['table_changes'] ) )
00531         {
00532             foreach ( $differences['table_changes'] as $table => $table_diff )
00533             {
00534                 if ( isset ( $table_diff['added_fields'] ) )
00535                 {
00536                     foreach ( $table_diff['added_fields'] as $field_name => $added_field )
00537                     {
00538                         $this->appendSQLComments( $added_field, $sql );
00539                         $sql .= $this->generateAddFieldSql( $table, $field_name, $added_field, $params );
00540                     }
00541                 }
00542 
00543                 if ( isset ( $table_diff['changed_fields'] ) )
00544                 {
00545                     foreach ( $table_diff['changed_fields'] as $field_name => $changed_field )
00546                     {
00547                         $changed_field_def = $changed_field['field-def'];
00548                         $diffPrams = array_merge( $params, array( 'different-options' => $changed_field['different-options'] ) );
00549                         $this->appendSQLComments( $changed_field_def, $sql );
00550                         $sql .= $this->generateAlterFieldSql( $table, $field_name, $changed_field_def, $diffPrams );
00551                         unset( $diffPrams );
00552                     }
00553                 }
00554                 if ( isset ( $table_diff['removed_fields'] ) )
00555                 {
00556                     foreach ( $table_diff['removed_fields'] as $field_name => $removed_field)
00557                     {
00558                         $this->appendSQLComments( $removed_field, $sql );
00559                         $sql .= $this->generateDropFieldSql( $table, $field_name, $params );
00560                     }
00561                 }
00562 
00563                 if ( isset ( $table_diff['removed_indexes'] ) )
00564                 {
00565                     foreach ( $table_diff['removed_indexes'] as $index_name => $removed_index)
00566                     {
00567                         $this->appendSQLComments( $removed_index, $sql );
00568                         $sql .= $this->generateDropIndexSql( $table, $index_name, $removed_index, $params );
00569                     }
00570                 }
00571                 if ( isset ( $table_diff['added_indexes'] ) )
00572                 {
00573                     foreach ( $table_diff['added_indexes'] as $index_name => $added_index)
00574                     {
00575                         $this->appendSQLComments( $added_index, $sql );
00576                         $sql .= $this->generateAddIndexSql( $table, $index_name, $added_index, $params );
00577                     }
00578                 }
00579 
00580                 if ( isset ( $table_diff['changed_indexes'] ) )
00581                 {
00582                     foreach ( $table_diff['changed_indexes'] as $index_name => $changed_index )
00583                     {
00584                         //eZDebug::writeDebug( $changed_index, "changed index $index_name" );
00585                         $this->appendSQLComments( $changed_index, $sql );
00586                         $sql .= $this->generateDropIndexSql( $table, $index_name, $changed_index, $params );
00587                         $sql .= $this->generateAddIndexSql( $table, $index_name, $changed_index, $params );
00588                         //eZDebug::writeDebug( 'qqq' );
00589                     }
00590                 }
00591             }
00592         }
00593         if ( isset( $differences['new_tables'] ) )
00594         {
00595             foreach ( $differences['new_tables'] as $table => $table_def )
00596             {
00597                 $this->appendSQLComments( $table_def, $sql );
00598                 $sql .= $this->generateTableSchema( $table, $table_def, $params );
00599             }
00600         }
00601         if ( isset( $differences['removed_tables'] ) )
00602         {
00603             foreach ( $differences['removed_tables'] as $table => $table_def )
00604             {
00605                 $this->appendSQLComments( $table_def, $sql );
00606                 $sql .= $this->generateDropTable( $table, $params );
00607             }
00608         }
00609         return $sql;
00610     }
00611 
00612     /*!
00613      \pure
00614      \protected
00615      Generates the necessary SQLs to create the table and returns them all in an array.
00616 
00617      \param $tableName The table name
00618      \param $table The table structure, see class definition for more details
00619      \param $params An associative array with optional parameters which controls the output of SQLs
00620      \param $separateTypes If \c true then the returned array must be an associative array
00621                            containing the SQL arrays split into multiple groups.
00622                            The groups are:
00623                            - sequences - List of sequences
00624                            - tables - List of tables
00625                            - trigger - List of triggers
00626                            - indexes - List of indexes
00627                            - constraints - List of constraints/primary keys
00628                            - other - Other SQLs that doesn't fit into the above
00629                            .
00630                            Each group can be omitted and will be run in order.
00631 
00632      \note Each SQL in the array will be without a semi-colon
00633      \sa generateTableSchema()
00634      */
00635     function generateTableSQLList( $tableName, $table, $params, $separateTypes )
00636     {
00637         return false;
00638     }
00639 
00640     /*!
00641      \pure
00642      \protected
00643      Generates the necessary SQLs to create the table and returns them all in a string.
00644 
00645      \param $tableName The table name
00646      \param $table The table structure, see class definition for more details
00647 
00648      \note The SQLs will be ended with a semi-colon.
00649      \sa generateTableSQLList()
00650     */
00651     function generateTableSchema( $tableName, $table, $params )
00652     {
00653         return false;
00654     }
00655 
00656     /*!
00657      \protected
00658 
00659      \note Calls generateTableInsertSQLList and joins the SQLs to a string
00660     */
00661     function generateTableInsert( $tableName, $tableDef, $dataEntries, $params )
00662     {
00663         return join( "\n", $this->generateTableInsertSQLList( $tableName, $tableDef, $dataEntries, $params, true ) );
00664     }
00665 
00666     /*!
00667      \protected
00668     */
00669     function generateTableInsertSQLList( $tableName, $tableDef, $dataEntries, $params, $withClosure = true )
00670     {
00671         $diffFriendly = isset( $params['diff_friendly'] ) ? $params['diff_friendly'] : false;
00672         $multiInsert = ( isset( $params['allow_multi_insert'] ) and $params['allow_multi_insert'] ) ? $this->isMultiInsertSupported() : false;
00673 
00674         // Make sure we don't generate SQL when there are no rows
00675         if ( count( $dataEntries['rows'] ) == 0 )
00676             return '';
00677 
00678         $sqlList = array();
00679         $sql = '';
00680         $defText = '';
00681         $entryIndex = 0;
00682         foreach ( $dataEntries['fields'] as $fieldName )
00683         {
00684             if ( !isset( $tableDef['fields'][$fieldName] ) )
00685                 continue;
00686             if ( $entryIndex == 0 )
00687             {
00688                 if ( $diffFriendly )
00689                 {
00690                     $defText .= "  ";
00691                 }
00692             }
00693             else
00694             {
00695                 if ( $diffFriendly )
00696                 {
00697                     $defText .= ",\n  ";
00698                 }
00699                 else
00700                 {
00701                     $defText .= ", ";
00702                 }
00703             }
00704             $defText .= $fieldName;
00705             ++$entryIndex;
00706         }
00707 
00708         if ( $multiInsert )
00709         {
00710             if ( $diffFriendly )
00711             {
00712                 $sql .= "INSERT INTO $tableName (\n  $defText\n)\nVALUES\n";
00713             }
00714             else
00715             {
00716                 $sql .= "INSERT INTO $tableName ($defText) VALUES ";
00717             }
00718         }
00719         $insertIndex = 0;
00720         foreach ( $dataEntries['rows'] as $row )
00721         {
00722             if ( $multiInsert and $insertIndex > 0 )
00723             {
00724                 if ( $diffFriendly )
00725                     $sql .= "\n,\n";
00726                 else
00727                     $sql .= ", ";
00728             }
00729             $dataText = '';
00730             $entryIndex = 0;
00731             foreach ( $dataEntries['fields'] as $fieldName )
00732             {
00733                 if ( !isset( $tableDef['fields'][$fieldName] ) )
00734                     continue;
00735                 if ( $entryIndex == 0 )
00736                 {
00737                     if ( $diffFriendly )
00738                     {
00739                         $dataText .= "  ";
00740                     }
00741                 }
00742                 else
00743                 {
00744                     if ( $diffFriendly )
00745                     {
00746                         $dataText .= ",\n  ";
00747                     }
00748                     else
00749                     {
00750                         $dataText .= ",";
00751                     }
00752                 }
00753                 $dataText .= $this->generateDataValueTextSQL( $tableDef['fields'][$fieldName], $row[$entryIndex] );
00754                 ++$entryIndex;
00755             }
00756             if ( $multiInsert )
00757             {
00758                 if ( $diffFriendly )
00759                 {
00760                     $sql .= "(\n  $dataText\n)";
00761                 }
00762                 else
00763                 {
00764                     $sql .= "($dataText)";
00765                 }
00766                 ++$insertIndex;
00767             }
00768             else
00769             {
00770                 if ( $diffFriendly )
00771                 {
00772                     $sqlList[] = "INSERT INTO $tableName (\n$defText\n) VALUES (\n$dataText\n)" . ( $withClosure ? ";" : "" );
00773                 }
00774                 else
00775                 {
00776                     $sqlList[] = "INSERT INTO $tableName ($defText) VALUES ($dataText)" . ( $withClosure ? ";" : "" );
00777                 }
00778             }
00779         }
00780         if ( $multiInsert )
00781         {
00782             if ( $withClosure )
00783                 $sql .= "\n;";
00784             $sqlList[] = $sql;
00785         }
00786         return $sqlList;
00787     }
00788 
00789     /*!
00790      \protected
00791     */
00792     function generateDataValueTextSQL( $fieldDef, $value )
00793     {
00794         if ( $fieldDef['type'] == 'auto_increment' or
00795              $fieldDef['type'] == 'int' or
00796              $fieldDef['type'] == 'float' )
00797         {
00798             if ( $value === null or
00799                  $value === false )
00800                 return "NULL";
00801             $value = (int)$value;
00802             $value = (string)$value;
00803             return $value;
00804         }
00805         else if ( is_string( $value ) )
00806         {
00807             return "'" . $this->escapeSQLString( $value ) . "'";
00808         }
00809         else
00810         {
00811             if ( $value === null or
00812                  $value === false )
00813                 return "NULL";
00814             return (string)$value;
00815         }
00816     }
00817 
00818     /*!
00819      \pure
00820      This escapes the string according to the current database type and returns it.
00821      \note The default just returns the value so it must be reimplemented.
00822     */
00823     function escapeSQLString( $value )
00824     {
00825         return $value;
00826     }
00827 
00828     /*!
00829      \pure
00830      \protected
00831      */
00832     function generateAlterFieldSql( $table_name, $field_name, $def, $params )
00833     {
00834     }
00835 
00836     /*!
00837      \pure
00838      \protected
00839      */
00840     function generateAddFieldSql( $table_name, $field_name, $def, $params )
00841     {
00842     }
00843 
00844     /*!
00845      \private
00846     */
00847     function generateDropFieldSql( $table_name, $field_name, $params )
00848     {
00849         $sql = "ALTER TABLE $table_name DROP COLUMN $field_name";
00850 
00851         return $sql . ";\n";
00852     }
00853 
00854     /*!
00855      Appends any comments found in \a $def to SQL text \a $sql as SQL comments.
00856      \return \c true if any comments were added.
00857     */
00858     function appendSQLComments( $def, &$sql )
00859     {
00860         if ( isset( $def['comments'] ) )
00861         {
00862             if ( count( $def['comments'] ) > 0 )
00863                 $sql .= "\n";
00864             foreach ( $def['comments'] as $comment )
00865             {
00866                 $commentLines = explode( "\n", $comment );
00867                 foreach ( $commentLines as $commentLine )
00868                 {
00869                     $sql .= '-- ' . $commentLine . "\n";
00870                 }
00871             }
00872             return true;
00873         }
00874         return false;
00875     }
00876 
00877     /*!
00878      \protected
00879      \return \c true if the schema system supports multi inserts.
00880              The default is to return \c false.
00881     */
00882     function isMultiInsertSupported()
00883     {
00884         return false;
00885     }
00886 
00887     /*!
00888      \pure
00889 
00890      \return Identifier for schema type as string.
00891      Examples: 'mysql', 'postgresql', 'oracle'
00892      \sa schemaName()
00893     */
00894     function schemaType()
00895     {
00896     }
00897 
00898     /*!
00899      \pure
00900 
00901      \return Displayable name for schema type as string.
00902      Examples: 'MySQL', 'PostgreSQL', 'Oracle'
00903      \sa schemaType()
00904     */
00905     function schemaName()
00906     {
00907     }
00908 
00909     /*!
00910      \private
00911      \static
00912      \return array of transformation rules on success, false otherwise
00913      */
00914     function loadSchemaTransformationRules( $schemaType )
00915     {
00916         $ini = eZINI::instance( 'dbschema.ini' );
00917 
00918         if ( !$ini )
00919         {
00920             eZDebug::writeError( "Error loading $schemaType schema transformation rules" );
00921             return false;
00922         }
00923 
00924         $transformationRules = array();
00925 
00926         if ( $ini->hasVariable( $schemaType, 'ColumnNameTranslation' ) )
00927             $transformationRules['column-name'] = $ini->variable( $schemaType, 'ColumnNameTranslation' );
00928 
00929         if ( $ini->hasVariable( $schemaType, 'ColumnTypeTranslation' ) )
00930         {
00931             $transformationRules['column-type'] = $ini->variable( $schemaType, 'ColumnTypeTranslation' );
00932 
00933             // substitute values like "type1;type2" with an appropriate arrays
00934             if ( is_array( $transformationRules['column-type'] ) )
00935             {
00936                 foreach ( $transformationRules['column-type'] as $key => $val )
00937                 {
00938                     $types = explode( ';', $val );
00939                     $transformationRules['column-type'][$key] = $types;
00940                 }
00941             }
00942         }
00943 
00944         $indexTranslations = array();
00945         if ( $ini->hasVariable( $schemaType, 'IndexTranslation' ) )
00946         {
00947             $translations = $ini->variable( $schemaType, 'IndexTranslation' );
00948             foreach ( $translations as $combinedName => $translation )
00949             {
00950                 list( $tableName, $indexName ) = explode( '.', $combinedName );
00951                 $indexTranslations[$tableName][$indexName] = array();
00952                 $fields = explode( ';', $translation );
00953                 foreach ( $fields as $field )
00954                 {
00955                     $entries = explode( '.', $field );
00956                     $fieldName = $entries[0];
00957                     $fieldData = array();
00958                     for ( $i = 1; $i < count( $entries ); ++$i )
00959                     {
00960                         list( $metaName, $metaValue ) = explode( '/', $entries[$i], 2 );
00961                         if ( is_numeric( $metaValue ) )
00962                             $metaValue = (int)$metaValue;
00963                         $fieldData[$metaName] = $metaValue;
00964                     }
00965                     $indexTranslations[$tableName][$indexName][$fieldName] = $fieldData;
00966                 }
00967             }
00968         }
00969         $transformationRules['index-field'] = $indexTranslations;
00970 
00971         $tableTranslations = array();
00972         if ( $ini->hasVariable( $schemaType, 'TableOptionTranslation' ) )
00973         {
00974             $translations = $ini->variable( $schemaType, 'TableOptionTranslation' );
00975             foreach ( $translations as $tableName => $optionTexts )
00976             {
00977                 $tableTranslations[$tableName] = array();
00978                 $options = explode( ';', $optionTexts );
00979                 $optionData = array();
00980                 foreach ( $options as $option )
00981                 {
00982                     list( $metaName, $metaValue ) = explode( '/', $option, 2 );
00983                     if ( is_numeric( $metaValue ) )
00984                         $metaValue = (int)$metaValue;
00985                     $optionData[$metaName] = $metaValue;
00986                     $tableTranslations[$tableName] = $optionData;
00987                 }
00988             }
00989         }
00990         $transformationRules['table-option'] = $tableTranslations;
00991 
00992         if ( $ini->hasVariable( $schemaType, 'IndexNameTranslation' ) )
00993         {
00994             $tmpIdxNameTranslations = $ini->variable( $schemaType, 'IndexNameTranslation' );
00995 
00996             if ( is_array( $tmpIdxNameTranslations ) )
00997             {
00998                 foreach ( $tmpIdxNameTranslations as $key => $val )
00999                 {
01000                     list( $tableName, $genericIdxName ) = explode( '.', $key );
01001                     $localIdxName = $val;
01002                     if ( !$tableName || !$genericIdxName || !$localIdxName )
01003                     {
01004                         eZDebug::writeWarning( "Malformed index name translation rule: $key => $val" );
01005                         continue;
01006                     }
01007                     $transformationRules['index-name'][] = array( $tableName, $genericIdxName, $localIdxName );
01008 
01009                 }
01010             }
01011             unset( $tmpIdxNameTranslations );
01012         }
01013 
01014         if ( $ini->hasVariable( $schemaType, 'ColumnOptionTranslations' ) )
01015         {
01016             $transformationRules['column-option'] = array();
01017             foreach( $ini->variable( $schemaType, 'ColumnOptionTranslations' ) as $key => $val )
01018             {
01019                 list( $tableName, $colName ) = explode( '.', $key );
01020                 $colOptOverride = $val;
01021                 if ( !$tableName || !$colName || !$colOptOverride )
01022                 {
01023                     eZDebug::writeWarning( "Malformed column option translation rule: $key => $val" );
01024                     continue;
01025                 }
01026                 $transformationRules['column-option'][] = array( $tableName, $colName, $colOptOverride );
01027             }
01028         }
01029 
01030         // prevent PHP warnings when cycling through the rules
01031         foreach ( array( 'column-name', 'column-type', 'column-option', 'index-name' ) as $rulesType )
01032         {
01033             if( !isset( $transformationRules[$rulesType] ) )
01034                 $transformationRules[$rulesType] = array();
01035         }
01036 
01037         return $transformationRules;
01038     }
01039 
01040     /*!
01041     \protected
01042     \return true on success, false otherwise
01043 
01044     Transforms database schema to the given direction, applying the transformation rules.
01045     */
01046     function transformSchema( &$schema, /* bool */ $toLocal )
01047     {
01048         // Check if it is already in correct format
01049         if ( isset( $schema['_info']['format'] ) )
01050         {
01051             if ( $schema['_info']['format'] == ( $toLocal ? 'local' : 'generic' ) )
01052                 return true;
01053         }
01054 
01055         // Set the new format it will get
01056         $schema['_info']['format'] = $toLocal ? 'local' : 'generic';
01057 
01058         // load the schema transformation rules
01059         $schemaType = $this->schemaType();
01060         $schemaTransformationRules = eZDBSchemaInterface::loadSchemaTransformationRules( $schemaType );
01061         if ( $schemaTransformationRules === false )
01062             return false;
01063 
01064         // transform column names
01065         foreach ( $schemaTransformationRules['column-name'] as $key => $val )
01066         {
01067             list( $tableName, $genericColName ) = explode( '.', $key );
01068             $localColName = $val;
01069 
01070             if ( $toLocal )
01071             {
01072                 $searchColName = $genericColName;
01073                 $replacementColName = $localColName;
01074             }
01075             else
01076             {
01077                 $searchColName = $localColName;
01078                 $replacementColName = $genericColName;
01079             }
01080 
01081             if ( !isset( $schema[$tableName] ) )
01082                 continue;
01083 
01084             // transform column names in tables
01085             if ( isset( $schema[$tableName]['fields'][$searchColName] )  )
01086             {
01087                 $schema[$tableName]['fields'][$replacementColName] = $schema[$tableName]['fields'][$searchColName];
01088                 unset( $schema[$tableName]['fields'][$searchColName] );
01089                 ksort( $schema[$tableName]['fields'] );
01090                 eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
01091                                             "transformed table column name $tableName.$searchColName to $replacementColName" );
01092             }
01093 
01094 
01095             // transform column names in indexes
01096             foreach ( $schema[$tableName]['indexes'] as $indexName => $indexSchema )
01097             {
01098                 if ( ( $key = array_search( $searchColName, $indexSchema['fields'] ) ) !== false )
01099                 {
01100                     $schema[$tableName]['indexes'][$indexName]['fields'][$key] = $replacementColName;
01101                     eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
01102                                                 "transformed index columnn name $indexName.$searchColName to $replacementColName" );
01103                 }
01104             }
01105         }
01106 
01107         // tranform column types
01108         foreach ( $schemaTransformationRules['column-type'] as $key => $val )
01109         {
01110             list( $tableName, $colName ) = explode( '.', $key );
01111             list( $genericType, $localType ) = $val;
01112 
01113             if ( !isset( $schema[$tableName] ) )
01114                 continue;
01115 
01116             preg_match( '/(\w+)\((\d+)\)/', $localType, $matches );
01117             $localLength = ( count($matches) == 3 ) ? $matches[2] : null;
01118             if ( count($matches) == 3 )
01119                 $localType = $matches[1];
01120 
01121             preg_match( '/(\w+)\((\d+)\)/', $genericType, $matches );
01122             $genericLength = ( count($matches) == 3 ) ? $matches[2] : null;
01123             if ( count($matches) == 3 )
01124                 $genericType = $matches[1];
01125 
01126             if ( !isset( $schema[$tableName]['fields'][$colName] ) )
01127                 continue;
01128 
01129             $fieldSchema = $schema[$tableName]['fields'][$colName];
01130 
01131             if ( $toLocal )
01132             {
01133                 $searchType        = $genericType;
01134                 $searchLength      = $genericLength;
01135                 $replacementType   = $localType;
01136                 $replacementLength = $localLength;
01137             }
01138             else // to generic
01139             {
01140                 $searchType        = $localType;
01141                 $searchLength      = $localLength;
01142                 $replacementType   = $genericType;
01143                 $replacementLength = $genericLength;
01144             }
01145 
01146             $fieldSchema['type'] = $replacementType;
01147             if ( $replacementLength !== null )
01148             {
01149                 $fieldSchema['length'] = $replacementLength;
01150             }
01151             else
01152             {
01153                 unset( $fieldSchema['length'] );
01154             }
01155 
01156             eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
01157                                         "transformed table column type $schemaType:$tableName.$colName from $searchType to $replacementType" );
01158 
01159             $schema[$tableName]['fields'][$colName] = $fieldSchema;
01160         }
01161 
01162         // Find indexes that needs to be fixed
01163         foreach ( $schemaTransformationRules['index-field'] as $tableName => $indexes )
01164         {
01165             foreach ( $indexes as $indexName => $fields )
01166             {
01167                 if ( !isset( $schema[$tableName]['indexes'][$indexName]['fields'] ) )
01168                     continue;
01169 
01170                 $newFields = array();
01171                 foreach ( $schema[$tableName]['indexes'][$indexName]['fields'] as $indexField )
01172                 {
01173                     if ( !is_array( $indexField ) )
01174                     {
01175                         $indexField = array( 'name' => $indexField );
01176                     }
01177                     $fieldName = $indexField['name'];
01178                     if ( isset( $fields[$fieldName] ) )
01179                     {
01180                         if ( $toLocal )
01181                         {
01182                             $indexField = array_merge( $indexField,
01183                                                        $fields[$fieldName] );
01184                         }
01185                         else
01186                         {
01187                             foreach ( $fields[$fieldName] as $removeName => $removeValue )
01188                             {
01189                                 unset( $indexField[$removeName] );
01190                             }
01191                         }
01192                     }
01193 
01194                     // Check if we have any entries other than 'name', if not we skip the array definition
01195                     if ( count( array_diff( array_keys( $indexField ), array( 'name' ) ) ) == 0 )
01196                     {
01197                         $indexField = $indexField['name'];
01198                     }
01199                     $newFields[] = $indexField;
01200                 }
01201                 $schema[$tableName]['indexes'][$indexName]['fields'] = $newFields;
01202             }
01203         }
01204 
01205         // Find tables that needs to fix their options
01206         foreach ( $schemaTransformationRules['table-option'] as $tableName => $options )
01207         {
01208             if ( !isset( $schema[$tableName] ) )
01209                 continue;
01210 
01211             if ( !isset( $schema[$tableName]['options'] ) )
01212             {
01213                 if ( $toLocal )
01214                     $schema[$tableName]['options'] = $options;
01215             }
01216             else
01217             {
01218                 if ( $toLocal )
01219                 {
01220                     $schema[$tableName]['options'] = array_merge( $schema[$tableName]['options'], $options );
01221                 }
01222                 else
01223                 {
01224                     foreach ( $options as $optionName => $optionValue )
01225                     {
01226                         unset( $schema[$tableName]['options'][$optionName] );
01227                     }
01228                 }
01229             }
01230         }
01231 
01232         // Transform index names
01233         foreach ( $schemaTransformationRules['index-name'] as $idxTransRule )
01234         {
01235             list( $tableName, $genericIdxName, $localIdxName ) = $idxTransRule;
01236 
01237             if ( $toLocal )
01238             {
01239                 $searchIdxName      = $genericIdxName;
01240                 $replacementIdxName = $localIdxName;
01241             }
01242             else
01243             {
01244                 $searchIdxName      = $localIdxName;
01245                 $replacementIdxName = $genericIdxName;
01246             }
01247 
01248             if ( !isset( $schema[$tableName] ) )
01249                 continue;
01250 
01251             if ( isset( $schema[$tableName]['indexes'][$searchIdxName] )  )
01252             {
01253                 eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
01254                                             "replaced $tableName.$searchIdxName => $replacementIdxName" );
01255                 $schema[$tableName]['indexes'][$replacementIdxName] = $schema[$tableName]['indexes'][$searchIdxName];
01256                 unset( $schema[$tableName]['indexes'][$searchIdxName] );
01257                 ksort( $schema[$tableName]['indexes'] );
01258             }
01259 
01260         }
01261 
01262         // Transform table column options
01263         foreach ( $schemaTransformationRules['column-option'] as $colOptTransRule )
01264         {
01265             list( $tableName, $colName, $colOptOverride ) = $colOptTransRule;
01266 
01267             if ( !isset( $schema[$tableName] ) || !isset( $schema[$tableName]['fields'][$colName] ) )
01268                 continue;
01269 
01270             $fieldSchema = $schema[$tableName]['fields'][$colName];
01271 
01272             switch ( $colOptOverride )
01273             {
01274                 case 'null':
01275                 {
01276                     if ( $toLocal )
01277                     {
01278                         // remove "NOT NULL" requirement
01279                         unset( $fieldSchema['not_null'] );
01280                         eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
01281                                                     "transformed table column option: $schemaType:$tableName.$colName set to NULL" );
01282                     }
01283                     else
01284                     {
01285                         // add "NOT NULL" requirement
01286                         $fieldSchema['not_null'] = '1';
01287                         eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
01288                                                     "transformed table column option: $schemaType:$tableName.$colName set to NOT NULL" );
01289                     }
01290 
01291                     // FIXME: ugly hack preserving keys order in the field schema array, just to be diff-friendly
01292                     {
01293                         $tmp = $fieldSchema['default'];
01294                         unset( $fieldSchema['default'] );
01295                         $fieldSchema['default'] = $tmp;
01296                     }
01297                 } break;
01298                 default:
01299                 {
01300                     eZDebug::writeWarning( "Column option override '$colOptOverride' is not supported" );
01301                 } break;
01302             }
01303 
01304             $schema[$tableName]['fields'][$colName] = $fieldSchema;
01305         }
01306 
01307 
01308         return true;
01309     }
01310 
01311     /*!
01312     \protected
01313     \return true on success, false otherwise
01314 
01315     Transforms database data to the given direction, applying the transformation rules.
01316     */
01317     function transformData( &$data, /* bool */ $toLocal )
01318     {
01319         // Check if it is already in correct format
01320         if ( isset( $data['_info']['format'] ) )
01321         {
01322             if ( $data['_info']['format'] == ( $toLocal ? 'local' : 'generic' ) )
01323                 return true;
01324         }
01325 
01326         // Set the new format it will get
01327         $data['_info']['format'] = $toLocal ? 'local' : 'generic';
01328 
01329         // load the schema transformation rules
01330         $schemaType = $this->schemaType();
01331         $schemaTransformationRules = eZDBSchemaInterface::loadSchemaTransformationRules( $schemaType );
01332         if ( $schemaTransformationRules === false )
01333             return false;
01334 
01335         // transform column names
01336         foreach ( $schemaTransformationRules['column-name'] as $key => $val )
01337         {
01338             list( $tableName, $genericColName ) = explode( '.', $key );
01339             $localColName = $val;
01340 
01341             if ( $toLocal )
01342             {
01343                 $searchColName = $genericColName;
01344                 $replacementColName = $localColName;
01345             }
01346             else
01347             {
01348                 $searchColName = $localColName;
01349                 $replacementColName = $genericColName;
01350             }
01351 
01352             if ( !isset( $data[$tableName] ) )
01353                 continue;
01354 
01355             // transform column names in tables
01356             $fieldsData = $data[$tableName]['fields'];
01357             foreach ( $fieldsData as $key => $fieldName )
01358             {
01359                 if ( $searchColName == $fieldName )
01360                 {
01361                     $data[$tableName]['fields'][$key] = $replacementColName;
01362                     eZDebugSetting::writeDebug( 'lib-dbschema-data-transformation', '',
01363                                                 "transformed table column name $tableName.$searchColName to $replacementColName" );
01364                 }
01365             }
01366         }
01367 
01368         return true;
01369     }
01370 
01371     /// eZDB instance
01372     public $DBInstance;
01373     public $Schema;
01374     public $Data;
01375 }
01376 
01377 ?>