|
eZ Publish
[trunk]
|
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 ?>