|
eZ Publish
[4.0]
|
00001 <?php 00002 // 00003 // Definition of eZLintSchema class 00004 // 00005 // Created on: <05-Nov-2004 14:03:27 jb> 00006 // 00007 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ## 00008 // SOFTWARE NAME: eZ Publish 00009 // SOFTWARE RELEASE: 4.0.x 00010 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS 00011 // SOFTWARE LICENSE: GNU General Public License v2.0 00012 // NOTICE: > 00013 // This program is free software; you can redistribute it and/or 00014 // modify it under the terms of version 2.0 of the GNU General 00015 // Public License as published by the Free Software Foundation. 00016 // 00017 // This program is distributed in the hope that it will be useful, 00018 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 // GNU General Public License for more details. 00021 // 00022 // You should have received a copy of version 2.0 of the GNU General 00023 // Public License along with this program; if not, write to the Free 00024 // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 00025 // MA 02110-1301, USA. 00026 // 00027 // 00028 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ## 00029 // 00030 00031 /*! 00032 \class eZLintSchema ezlintschema.php 00033 \ingroup eZDbSchema 00034 \brief Provides lint checking of database schemas 00035 00036 Checks a given schema by going trough all tables, fields and indexes 00037 and corrects any mistakes. The result is a new schema which is returned 00038 in schema(). The new schema can then be used to diff against the original 00039 and output the changes. 00040 00041 The current rules apply: 00042 - Table names must not exceed 26 characters, configurable in dbschema.ini (LintChecker/TableLimit) 00043 - Field names must not exceed 30 characters, configurable in dbschema.ini (LintChecker/FieldLimit) 00044 - Index names must not exceed 30 characters, configurable in dbschema.ini (LintChecker/IndexLimit) 00045 - Index names must not be the same as table names 00046 - String fields cannot have NOT NULL and an empty string as DEFAULT value. 00047 - Primary keys must be named PRIMARY 00048 00049 The lint checker works by taking in another DB Schema object as parameter 00050 to the constructor. All calls will be forwarded to this object so it will 00051 work as though it were a real schema. 00052 The exception are the schema(), data() and validate() methods which makes 00053 sure the schema is correct. 00054 00055 To check if the schema has been checked yet call isLintChecked(). 00056 To fetch the DB schema which is checked use otherSchema(). 00057 00058 */ 00059 00060 //include_once( 'lib/ezdbschema/classes/ezdbschemainterface.php' ); 00061 00062 class eZLintSchema extends eZDBSchemaInterface 00063 { 00064 /*! 00065 Initializes the lint checker with a foreign db schema. 00066 00067 \param $db A dummy parameter, pass \c false. 00068 \param $otherSchema The db schema that should be checked 00069 */ 00070 function eZLintSchema( $db, $otherSchema ) 00071 { 00072 $this->eZDBSchemaInterface( $db ); 00073 $this->OtherSchema = $otherSchema; 00074 $this->CorrectSchema = false; 00075 $this->IsLintChecked = false; 00076 } 00077 00078 /*! 00079 Runs the lint checker on the database schema in otherSchema() 00080 and returns the new schema that is correct. 00081 */ 00082 function schema( $params = array() ) 00083 { 00084 if ( $this->IsLintChecked ) 00085 { 00086 return $this->CorrectSchema; 00087 } 00088 00089 $params = array_merge( array( 'meta_data' => false, 00090 'format' => 'generic' ), 00091 $params ); 00092 00093 $this->CorrectSchema = $this->OtherSchema->schema( $params ); 00094 $this->lintCheckSchema( $this->CorrectSchema ); 00095 $this->IsLintChecked = true; 00096 return $this->CorrectSchema; 00097 } 00098 00099 /*! 00100 \reimp 00101 Runs lint checker on all tables, indexes and fields. 00102 */ 00103 function validate() 00104 { 00105 return $this->lintCheckSchema( $this->CorrectSchema ); 00106 } 00107 00108 /*! 00109 \return The schema object which is being lint checked. 00110 */ 00111 function otherSchema() 00112 { 00113 return $this->OtherSchema; 00114 } 00115 00116 /*! 00117 \return \c true if the lint checker has been run on the schema. 00118 */ 00119 function isLintChecked() 00120 { 00121 return $this->IsLintChecked; 00122 } 00123 00124 /*! 00125 \return A modified version of \a $identifier that is guaranteed to be shorter than \a $limit 00126 */ 00127 function shortenIdentifier( $identifier, $limit, $shortenList ) 00128 { 00129 reset( $shortenList ); 00130 // Replace one word at a time until we have a string that is short 00131 // enough, or we run out of replace words 00132 while ( strlen( $identifier ) > $limit and 00133 current( $shortenList ) !== false ) 00134 { 00135 $from = key( $shortenList ); 00136 $to = current( $shortenList ); 00137 next( $shortenList ); 00138 $identifier = str_replace( $from, $to, $identifier ); 00139 } 00140 00141 // It is still to large so we just cut it off 00142 if ( strlen( $identifier ) > $limit ) 00143 { 00144 $identifier = substr( $identifier, 0, $limit ); 00145 } 00146 return $identifier; 00147 } 00148 00149 /*! 00150 \private 00151 Goes trough all tables, fields and indexes and makes sure they have valid names. 00152 \return \c false if something was fixed, \c true otherwise. 00153 */ 00154 function lintCheckSchema( &$schema ) 00155 { 00156 $status = true; 00157 00158 $ini = eZINI::instance( 'dbschema.ini' ); 00159 00160 // A mapping table that maps from a long name to a short name 00161 // This will be used if an identifier/name is too long 00162 $shortenList = $ini->variable( 'LintChecker', 'NameMap' ); 00163 00164 // Limitation on the length of identifiers/names 00165 // Oracle is the database with the most limit (30 characters) so the 00166 // limit values must be equal or lower to that. 00167 $tableNameLimit = (int)$ini->variable( 'LintChecker', 'TableLimit' ); 00168 $fieldNameLimit = (int)$ini->variable( 'LintChecker', 'FieldLimit' ); 00169 $indexNameLimit = (int)$ini->variable( 'LintChecker', 'IndexLimit' ); 00170 00171 // Tables which do not get lint checked, they are currently 00172 // handled with workarounds in the various schema handlers 00173 // Note: The fields and indexes are still checked. 00174 $ignoredTableList = $ini->variable( 'LintChecker', 'IgnoredTables' ); 00175 00176 // Fields which do not get lint checked, they are currently 00177 // handled with workarounds in the various schema handlers 00178 $list = $ini->variable( 'LintChecker', 'IgnoredFields' ); 00179 $ignoredFieldList = array(); 00180 foreach ( $list as $entry ) 00181 { 00182 list( $tableName, $fieldName ) = explode( '.', $entry, 2 ); 00183 if ( !isset( $ignoredFieldList[$tableName] ) ) 00184 $ignoredFieldList[$tableName] = array(); 00185 $ignoredFieldList[$tableName][] = $fieldName; 00186 } 00187 00188 // Fields which do not get lint checked, they are currently 00189 // handled with workarounds in the various schema handlers 00190 $list = $ini->variable( 'LintChecker', 'IgnoredFieldSyntax' ); 00191 $ignoredFieldSyntaxList = array(); 00192 foreach ( $list as $entry ) 00193 { 00194 list( $tableName, $fieldName ) = explode( '.', $entry, 2 ); 00195 if ( !isset( $ignoredFieldList[$tableName] ) ) 00196 $ignoredFieldSyntaxList[$tableName] = array(); 00197 $ignoredFieldSyntaxList[$tableName][] = $fieldName; 00198 } 00199 00200 // Indexes which do not get lint checked, they are currently 00201 // handled with workarounds in the various schema handlers 00202 $ignoredIndexList = $ini->variable( 'LintChecker', 'IgnoredIndexes' ); 00203 00204 $badTables = array(); 00205 foreach ( $schema as $tableName => $tableDef ) 00206 { 00207 // Skip the info structure, this is not a table 00208 if ( $tableName == '_info' ) 00209 continue; 00210 00211 $existingTableName = $tableName; 00212 $tableComments = array(); 00213 00214 // If table is not in ignore list we check the name 00215 if ( !in_array( $tableName, $ignoredTableList ) ) 00216 { 00217 // identifiers must be 30 or less 00218 // for tables we require 26 or less to allow adding suffix or prefix for indexes etc. 00219 if ( strlen( $tableName ) > $tableNameLimit ) 00220 { 00221 $tableComment = "Table names must not exceed $tableNameLimit characters,\n'$tableName' is " . strlen( $tableName ) . " characters,\ndatabases like Oracle will have problems with this."; 00222 $tableName = $this->shortenIdentifier( $tableName, $tableNameLimit, $shortenList ); 00223 $tableComment .= "\nNew name is '$tableName'"; 00224 $tableComments[] = $tableComment; 00225 $status = false; 00226 } 00227 00228 if ( strcmp( $tableName, $existingTableName ) != 0 ) 00229 { 00230 $badTables[] = array( 'from' => $existingTableName, 00231 'to' => $tableName ); 00232 } 00233 } 00234 00235 if ( isset( $tableDef['fields'] ) ) 00236 { 00237 $badFields = array(); 00238 foreach ( $tableDef['fields'] as $fieldName => $fieldDef ) 00239 { 00240 $comments = array(); 00241 $existingFieldName = $fieldName; 00242 00243 // Do we ignore the field name? 00244 if ( !isset( $ignoredFieldList[$existingTableName] ) or 00245 !in_array( $fieldName, $ignoredFieldList[$existingTableName] ) ) 00246 { 00247 00248 // identifiers must be 30 or less 00249 if ( strlen( $fieldName ) > $fieldNameLimit ) 00250 { 00251 $comment = "Field names must not exceed $fieldNameLimit characters,\n'$fieldName' in table '$existingTableName' is " . strlen( $fieldName ) . " characters,\ndatabases like Oracle will have problems with this."; 00252 $fieldName = $this->shortenIdentifier( $fieldName, $fieldNameLimit, $shortenList ); 00253 $comment .= "\nNew name is '$fieldName'"; 00254 $comments[] = $comment; 00255 $status = false; 00256 } 00257 } 00258 00259 if ( !isset( $ignoredFieldSyntaxList[$existingTableName] ) or 00260 !in_array( $fieldName, $ignoredFieldSyntaxList[$existingTableName] ) ) 00261 { 00262 /* Temporarily disabled 00263 if ( in_array( $fieldDef['type'], 00264 array( 'varchar', 'char', 00265 'longtext', 'mediumtext', 'shorttext' ) ) and 00266 isset( $fieldDef['not_null'] ) and 00267 $fieldDef['not_null'] and 00268 $fieldDef['default'] === '' ) 00269 { 00270 $comments[] = "The string type " . $fieldDef['type'] . " ($existingTableName.$fieldName) cannot have NOT NULL defined and an empty string as DEFAULT value\nDatabase like Oracle will have problems with this."; 00271 $status = false; 00272 } 00273 */ 00274 } 00275 00276 if ( strcmp( $existingFieldName, $fieldName ) != 0 ) 00277 { 00278 $badFields[] = array( 'from' => $existingFieldName, 00279 'to' => $fieldName ); 00280 } 00281 00282 if ( count( $comments ) > 0 ) 00283 { 00284 $schema[$existingTableName]['fields'][$existingFieldName]['comments'] = $comments; 00285 foreach ( $comments as $comment ) 00286 { 00287 eZDebug::writeWarning( $comment, 'eZLintSchema::fieldComment' ); 00288 } 00289 } 00290 } 00291 00292 foreach ( $badFields as $badField ) 00293 { 00294 $schema[$existingTableName]['fields'][$badField['to']] = $schema[$existingTableName]['fields'][$badField['from']]; 00295 // unset( $schema[$existingTableName]['fields'][$badField['from']] ); 00296 $schema[$existingTableName]['fields'][$badField['from']]['removed'] = true; 00297 } 00298 } 00299 00300 if ( isset( $tableDef['indexes'] ) ) 00301 { 00302 $badIndexes = array(); 00303 foreach ( $tableDef['indexes'] as $indexName => $indexDef ) 00304 { 00305 // Primary key 00306 if ( $indexDef['type'] == 'primary' ) 00307 continue; 00308 00309 // Do we ignore the index? 00310 if ( in_array( $indexName, $ignoredIndexList ) ) 00311 continue; 00312 00313 $comments = array(); 00314 00315 $existingIndexName = $indexName; 00316 if ( isset( $schema[$indexName] ) ) 00317 { 00318 $comment = "Index named '$indexName' has same name as an existing table,\ndatabases like PostgreSQL and Oracle will have problems with this."; 00319 $indexFieldText = ''; 00320 $i = 0; 00321 foreach ( $indexDef['fields'] as $fieldDef ) 00322 { 00323 if ( $i > 0 ) 00324 $indexFieldText .= '_'; 00325 if ( is_array( $fieldDef ) ) 00326 { 00327 $indexFieldText .= $fieldDef['name']; 00328 } 00329 else 00330 { 00331 $indexFieldText .= $fieldDef; 00332 } 00333 } 00334 $indexName = $indexName . '_' . $indexFieldText . '_i'; 00335 $comment .= "\nNew name is '$indexName'"; 00336 $comments[] = $comment; 00337 $status = false; 00338 } 00339 00340 // Primary indexes must be named PRIMARY 00341 if ( $indexDef['type'] == 'primary' and 00342 $indexName != 'PRIMARY' ) 00343 { 00344 $comment = "Index named '$indexName' which is a primary key must be named PRIMARY."; 00345 $indexName = "PRIMARY"; 00346 $comments[] = $comment; 00347 $status = false; 00348 } 00349 00350 // identifiers must be 30 or less 00351 if ( strlen( $indexName ) > $indexNameLimit ) 00352 { 00353 $comment = "Index names must not exceed $indexNameLimit characters,\n'$indexName' is " . strlen( $indexName ) . " characters,\ndatabases like Oracle will have problems with this."; 00354 $indexName = $this->shortenIdentifier( $indexName, $indexNameLimit, $shortenList ); 00355 $comment .= "\nNew name is '$indexName'"; 00356 $comments[] = $comment; 00357 $status = false; 00358 } 00359 00360 // Check if there are some database specific entries 00361 foreach ( $indexDef['fields'] as $fieldDef ) 00362 { 00363 if ( is_array( $fieldDef ) ) 00364 { 00365 $fieldName = $fieldDef['name']; 00366 foreach ( $fieldDef as $fdName => $fdValue ) 00367 { 00368 if ( preg_match( "#^([a-z0-9]+):#", $fdName, $matches ) ) 00369 { 00370 $dbName = $matches[1]; 00371 $comments[] = "Found database specific entry ($dbName) at index $existingIndexName.$fieldName"; 00372 $status = false; 00373 } 00374 } 00375 } 00376 } 00377 00378 if ( strcmp( $existingIndexName, $indexName ) != 0 ) 00379 { 00380 $badIndexes[] = array( 'from' => $existingIndexName, 00381 'to' => $indexName ); 00382 } 00383 if ( count( $comments ) > 0 ) 00384 { 00385 $schema[$existingTableName]['indexes'][$existingIndexName]['comments'] = $comments; 00386 foreach ( $comments as $comment ) 00387 { 00388 eZDebug::writeWarning( $comment, 'eZLintSchema::indexComment' ); 00389 } 00390 } 00391 } 00392 00393 foreach ( $badIndexes as $badIndex ) 00394 { 00395 $schema[$existingTableName]['indexes'][$badIndex['to']] = $schema[$existingTableName]['indexes'][$badIndex['from']]; 00396 $schema[$existingTableName]['indexes'][$badIndex['from']]['removed'] = true; 00397 } 00398 } 00399 00400 if ( count( $tableComments ) > 0 ) 00401 { 00402 $schema[$existingTableName]['comments'] = $tableComments; 00403 foreach ( $tableComments as $comment ) 00404 { 00405 eZDebug::writeWarning( $comment, 'eZLintSchema::tableComment' ); 00406 } 00407 } 00408 } 00409 foreach ( $badTables as $badTable ) 00410 { 00411 $schema[$badTable['to']] = $schema[$badTable['from']]; 00412 $schema[$badTable['from']]['removed'] = true; 00413 } 00414 return $status; 00415 } 00416 00417 /*! 00418 \reimp 00419 Forwards request to data() on the otherSchema() object. 00420 */ 00421 function data( $schema = false, $tableNameList = false, $params = array() ) 00422 { 00423 return $this->OtherSchema->data( $schema, $tableNameList, $params ); 00424 } 00425 00426 /*! 00427 \reimp 00428 Forwards request to generateSchemaFile() on the otherSchema() object. 00429 */ 00430 function generateSchemaFile( $schema, $params = array() ) 00431 { 00432 return $this->OtherSchema->generateSchemaFile( $schema, $params ); 00433 } 00434 00435 /*! 00436 \reimp 00437 Forwards request to generateUpgradeFile() on the otherSchema() object. 00438 */ 00439 function generateUpgradeFile( $differences, $params = array() ) 00440 { 00441 return $this->OtherSchema->generateUpgradeFile( $differences, $params ); 00442 } 00443 00444 /*! 00445 \reimp 00446 Forwards request to generateDataFile() on the otherSchema() object. 00447 */ 00448 function generateDataFile( $schema, $data, $params ) 00449 { 00450 return $this->OtherSchema->generateDataFile( $schema, $data, $params ); 00451 } 00452 00453 /*! 00454 \reimp 00455 Forwards request to generateTableSchema() on the otherSchema() object. 00456 */ 00457 function generateTableSchema( $table, $tableDef, $params ) 00458 { 00459 return $this->OtherSchema->generateTableSchema( $table, $tableDef, $params ); 00460 } 00461 00462 /*! 00463 \reimp 00464 Forwards request to generateTableInsert() on the otherSchema() object. 00465 */ 00466 function generateTableInsert( $tableName, $tableDef, $dataEntries, $params ) 00467 { 00468 return $this->OtherSchema->generateTableInsert( $tableName, $tableDef, $dataEntries, $params ); 00469 } 00470 00471 /*! 00472 \reimp 00473 Forwards request to generateDropTable() on the otherSchema() object. 00474 */ 00475 function generateDropTable( $table, $params ) 00476 { 00477 return $this->OtherSchema->generateDropTable( $table, $params ); 00478 } 00479 00480 /*! 00481 \reimp 00482 Forwards request to generateAddFieldSql() on the otherSchema() object. 00483 */ 00484 function generateAddFieldSql( $table, $field_name, $added_field, $params ) 00485 { 00486 return $this->OtherSchema->generateAddFieldSql( $table, $field_name, $added_field, $params ); 00487 } 00488 00489 /*! 00490 \reimp 00491 Forwards request to generateAlterFieldSql() on the otherSchema() object. 00492 */ 00493 function generateAlterFieldSql( $table, $field_name, $changed_field, $params ) 00494 { 00495 return $this->OtherSchema->generateAlterFieldSql( $table, $field_name, $changed_field, $params ); 00496 } 00497 00498 /*! 00499 \reimp 00500 Forwards request to generateDropFieldSql() on the otherSchema() object. 00501 */ 00502 function generateDropFieldSql( $table, $field_name, $params ) 00503 { 00504 return $this->OtherSchema->generateDropFieldSql( $table, $field_name, $params ); 00505 } 00506 00507 /*! 00508 \reimp 00509 Forwards request to generateAddIndexSql() on the otherSchema() object. 00510 */ 00511 function generateAddIndexSql( $table, $index_name, $added_index, $params ) 00512 { 00513 return $this->OtherSchema->generateAddIndexSql( $table, $index_name, $added_index, $params ); 00514 } 00515 00516 /*! 00517 \reimp 00518 Forwards request to generateDropIndexSql() on the otherSchema() object. 00519 */ 00520 function generateDropIndexSql( $table, $index_name, $removed_index, $params ) 00521 { 00522 return $this->OtherSchema->generateDropIndexSql( $table, $index_name, $removed_index, $params ); 00523 } 00524 00525 /*! 00526 \reimp 00527 Forwards request to isMultiInsertSupported() on the otherSchema() object. 00528 */ 00529 function isMultiInsertSupported() 00530 { 00531 return $this->OtherSchema->isMultiInsertSupported(); 00532 } 00533 00534 /*! 00535 \reimp 00536 Forwards request to generateDataValueTextSQL() on the otherSchema() object. 00537 */ 00538 function generateDataValueTextSQL( $fieldDef, $value ) 00539 { 00540 return $this->OtherSchema->generateDataValueTextSQL( $fieldDef, $value ); 00541 } 00542 00543 /*! 00544 \reimp 00545 Forwards request to schemaType() on the otherSchema() object. 00546 */ 00547 function schemaType() 00548 { 00549 return $this->OtherSchema->schemaType(); 00550 } 00551 00552 /*! 00553 \reimp 00554 Forwards request to schemaName() on the otherSchema() object. 00555 */ 00556 function schemaName() 00557 { 00558 return $this->OtherSchema->schemaName(); 00559 } 00560 00561 /// \privatesection 00562 /// eZDBSchemaInterface object which should be lint checked 00563 public $OtherSchema; 00564 /// The corrected schema 00565 public $CorrectSchema; 00566 /// Whether the schema has been checked or not 00567 public $IsLintChecked; 00568 } 00569 00570 ?>