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