|
eZ Publish
[trunk]
|
00001 <?php 00002 /** 00003 * File containing the eZPostgreSQLDB 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 /*! 00012 \class eZPostgreSQLDB ezpostgresqldb.php 00013 \ingroup eZDB 00014 \brief The eZPostgreSQLDB class provides PostgreSQL database functions. 00015 00016 eZPostgreSQLDB implementes PostgreSQLDB specific database code. 00017 00018 \sa eZDB 00019 */ 00020 class eZPostgreSQLDB extends eZDBInterface 00021 { 00022 /*! 00023 Creates a new eZPostgreSQLDB object and connects to the database. 00024 */ 00025 function eZPostgreSQLDB( $parameters ) 00026 { 00027 $this->eZDBInterface( $parameters ); 00028 00029 if ( !extension_loaded( 'pgsql' ) ) 00030 { 00031 if ( function_exists( 'eZAppendWarningItem' ) ) 00032 { 00033 eZAppendWarningItem( array( 'error' => array( 'type' => 'ezdb', 00034 'number' => eZDBInterface::ERROR_MISSING_EXTENSION ), 00035 'text' => 'PostgreSQL extension was not found, the DB handler will not be initialized.' ) ); 00036 $this->IsConnected = false; 00037 } 00038 eZDebug::writeWarning( 'PostgreSQL extension was not found, the DB handler will not be initialized.', 'eZPostgreSQLDB' ); 00039 return; 00040 } 00041 00042 eZDebug::createAccumulatorGroup( 'postgresql_total', 'Postgresql Total' ); 00043 00044 $ini = eZINI::instance(); 00045 00046 $server = $this->Server; 00047 $port = $this->Port; 00048 $db = $this->DB; 00049 $user = $this->User; 00050 $password = $this->Password; 00051 00052 $connectString = self::connectString( $this->Server, $this->Port, $this->DB, $this->User, $this->Password ); 00053 00054 if ( $ini->variable( "DatabaseSettings", "UsePersistentConnection" ) == "enabled" && function_exists( "pg_pconnect" )) 00055 { 00056 eZDebugSetting::writeDebug( 'kernel-db-postgresql', $ini->variable( "DatabaseSettings", "UsePersistentConnection" ), "using persistent connection" ); 00057 00058 // avoid automatic SQL errors 00059 $oldHandling = eZDebug::setHandleType( eZDebug::HANDLE_EXCEPTION ); 00060 eZDebug::accumulatorStart( 'postgresql_connection', 'postgresql_total', 'Database connection' ); 00061 try { 00062 $this->DBConnection = pg_pconnect( $connectString ); 00063 } catch( ErrorException $e ) {} 00064 eZDebug::accumulatorStop( 'postgresql_connection' ); 00065 eZDebug::setHandleType( $oldHandling ); 00066 00067 $maxAttempts = $this->connectRetryCount(); 00068 $waitTime = $this->connectRetryWaitTime(); 00069 $numAttempts = 1; 00070 while ( $this->DBConnection == false and $numAttempts <= $maxAttempts ) 00071 { 00072 sleep( $waitTime ); 00073 $oldHandling = eZDebug::setHandleType( eZDebug::HANDLE_EXCEPTION ); 00074 eZDebug::accumulatorStart( 'postgresql_connection', 'postgresql_total', 'Database connection' ); 00075 try { 00076 $this->DBConnection = pg_pconnect( $connectString ); 00077 } catch( ErrorException $e ) {} 00078 eZDebug::accumulatorStop( 'postgresql_connection' ); 00079 eZDebug::setHandleType( $oldHandling ); 00080 $numAttempts++; 00081 } 00082 if ( $this->DBConnection ) 00083 { 00084 $this->IsConnected = true; 00085 } 00086 else 00087 { 00088 throw new eZDBNoConnectionException( $server, $this->ErrorMessage, $this->ErrorNumber ); 00089 } 00090 } 00091 else if ( function_exists( "pg_connect" ) ) 00092 { 00093 eZDebugSetting::writeDebug( 'kernel-db-postgresql', "using real connection", "using real connection" ); 00094 00095 $oldHandling = eZDebug::setHandleType( eZDebug::HANDLE_EXCEPTION ); 00096 eZDebug::accumulatorStart( 'postgresql_connection', 'postgresql_total', 'Database connection' ); 00097 try { 00098 $this->DBConnection = pg_connect( $connectString ); 00099 } catch( ErrorException $e ) {} 00100 eZDebug::accumulatorStop( 'postgresql_connection' ); 00101 eZDebug::setHandleType( $oldHandling ); 00102 00103 $maxAttempts = $this->connectRetryCount(); 00104 $waitTime = $this->connectRetryWaitTime(); 00105 $numAttempts = 1; 00106 while ( $this->DBConnection == false and $numAttempts <= $maxAttempts ) 00107 { 00108 sleep( $waitTime ); 00109 $oldHandling = eZDebug::setHandleType( eZDebug::HANDLE_EXCEPTION ); 00110 eZDebug::accumulatorStart( 'postgresql_connection', 'postgresql_total', 'Database connection' ); 00111 try { 00112 $this->DBConnection = pg_connect( $connectString ); 00113 } catch( ErrorException $e ) {} 00114 eZDebug::accumulatorStop( 'postgresql_connection' ); 00115 eZDebug::setHandleType( $oldHandling ); 00116 $numAttempts++; 00117 } 00118 if ( $this->DBConnection ) 00119 { 00120 $this->IsConnected = true; 00121 } 00122 else 00123 { 00124 $this->setError(); 00125 throw new eZDBNoConnectionException( $server, $this->ErrorMessage, $this->ErrorNumber ); 00126 } 00127 } 00128 else 00129 { 00130 $this->IsConnected = false; 00131 eZDebug::writeError( "PostgreSQL support not compiled into PHP, contact your system administrator", "eZPostgreSQLDB" ); 00132 00133 } 00134 } 00135 00136 public static function connectString( $server = null, $port = null, $db = null, $user = null, $password = null ) 00137 { 00138 $connectParams = array(); 00139 if ( $server !== false and $server !== null ) 00140 $connectParams[] = "host='$server'"; 00141 if ( $db !== false and $db !== null ) 00142 $connectParams[] = "dbname='$db'"; 00143 if ( $user !== false and $user !== null ) 00144 $connectParams[] = "user='$user'"; 00145 if ( $password !== false and $password !== null ) 00146 $connectParams[] = "password='$password'"; 00147 if ( $port ) 00148 $connectParams[] = "port='$port'"; 00149 00150 return implode( " ", $connectParams ); 00151 } 00152 00153 function availableDatabases() 00154 { 00155 $query = "SELECT datname FROM pg_database"; 00156 $result = $this->query( $query ); 00157 00158 $databases = array(); 00159 $counter = pg_num_rows( $result ) - 1; 00160 00161 while ( $counter > 0 ) 00162 { 00163 $row = pg_fetch_result( $result, $counter, "datname" ); 00164 $databases[] = $row; 00165 $counter--; 00166 } 00167 00168 pg_free_result( $result ); 00169 00170 return $databases; 00171 } 00172 00173 function databaseName() 00174 { 00175 return 'postgresql'; 00176 } 00177 00178 function bindingType( ) 00179 { 00180 return eZDBInterface::BINDING_NO; 00181 } 00182 00183 function bindVariable( $value, $fieldDef = false ) 00184 { 00185 return $value; 00186 } 00187 00188 function query( $sql, $server = false ) 00189 { 00190 if ( $this->isConnected() ) 00191 { 00192 eZDebug::accumulatorStart( 'postgresql_query', 'postgresql_total', 'Postgresql queries' ); 00193 if ( $this->OutputSQL ) 00194 { 00195 $this->startTimer(); 00196 } 00197 // postgres will by default cast an error if a query fails 00198 // exception handling mode needs to catch this exception and set the $result variable to false 00199 if ( $this->errorHandling == eZDB::ERROR_HANDLING_EXCEPTIONS ) 00200 { 00201 $oldHandling = eZDebug::setHandleType( eZDebug::HANDLE_EXCEPTION ); 00202 try { 00203 $result = pg_query( $this->DBConnection, $sql ); 00204 } catch( ErrorException $e ) { 00205 $result = false; 00206 } 00207 eZDebug::setHandleType( $oldHandling ); 00208 } 00209 else 00210 { 00211 $result = pg_query( $this->DBConnection, $sql ); 00212 } 00213 00214 if ( $this->OutputSQL ) 00215 { 00216 $this->endTimer(); 00217 if ($this->timeTaken() > $this->SlowSQLTimeout) 00218 { 00219 $this->reportQuery( 'eZPostgreSQLDB', $sql, false, $this->timeTaken() ); 00220 } 00221 } 00222 eZDebug::accumulatorStop( 'postgresql_query' ); 00223 00224 if ( !$result ) 00225 { 00226 $this->setError(); 00227 eZDebug::writeError( "Error: error executing query: $sql: {$this->ErrorMessage}", "eZPostgreSQLDB" ); 00228 if ( $this->errorHandling == eZDB::ERROR_HANDLING_EXCEPTIONS ) 00229 { 00230 throw new eZDBException( $this->ErrorMessage, $this->ErrorNumber ); 00231 } 00232 $this->reportError(); 00233 } 00234 } 00235 else 00236 $result = false; 00237 return $result; 00238 } 00239 00240 00241 function arrayQuery( $sql, $params = array(), $server = false ) 00242 { 00243 $retArray = array(); 00244 if ( $this->isConnected() ) 00245 { 00246 $limit = -1; 00247 $offset = 0; 00248 // check for array parameters 00249 if ( is_array( $params ) ) 00250 { 00251 // $params = $min; 00252 00253 00254 $column = false; 00255 if ( isset( $params["limit"] ) and is_numeric( $params["limit"] ) ) 00256 { 00257 $limit = $params["limit"]; 00258 } 00259 00260 if ( isset( $params["offset"] ) and is_numeric( $params["offset"] ) ) 00261 { 00262 $offset = $params["offset"]; 00263 } 00264 if ( isset( $params["column"] ) and ( is_numeric( $params["column"] ) or is_string( $params["column"] ) ) ) 00265 $column = $params["column"]; 00266 } 00267 00268 if ( $limit != -1 ) 00269 { 00270 $sql .= "\nLIMIT $limit"; 00271 } 00272 if ( $offset > 0 ) 00273 { 00274 if ( $limit == -1 ) 00275 $sql .= "\n"; 00276 else 00277 $sql .= " "; 00278 $sql .= "OFFSET $offset"; 00279 } 00280 00281 eZDebug::accumulatorStart( 'postgresql_query', 'postgresql_total', 'Postgresql queries' ); 00282 00283 $result = $this->query( $sql ); 00284 00285 eZDebug::accumulatorStop( 'postgresql_query' ); 00286 00287 if ( $result == false ) 00288 { 00289 return false; 00290 } 00291 00292 if ( pg_numrows( $result ) > 0 ) 00293 { 00294 eZDebug::accumulatorStart( 'postgresql_loop', 'postgresql_total', 'Looping result' ); 00295 if ( !is_string( $column ) ) 00296 { 00297 for($i = 0; $i < pg_numrows($result); $i++) 00298 { 00299 $retArray[$i + $offset] = pg_fetch_array( $result, $i, PGSQL_ASSOC ); 00300 } 00301 } 00302 else 00303 { 00304 for ($i = 0; $i < pg_numrows( $result ); $i++ ) 00305 { 00306 $tmp_row = pg_fetch_array( $result, $i, PGSQL_ASSOC ); 00307 $retArray[$i + $offset] =& $tmp_row[$column]; 00308 } 00309 } 00310 eZDebug::accumulatorStart( 'postgresql_loop' ); 00311 } 00312 pg_free_result( $result ); 00313 } 00314 return $retArray; 00315 } 00316 00317 /*! 00318 \private 00319 */ 00320 function subString( $string, $from, $len = null ) 00321 { 00322 if ( $len == null ) 00323 { 00324 return " substring( $string from $from ) "; 00325 }else 00326 { 00327 return " substring( $string from $from for $len ) "; 00328 } 00329 00330 } 00331 00332 function concatString( $strings = array() ) 00333 { 00334 $str = implode( " || " , $strings ); 00335 return " $str "; 00336 } 00337 00338 function md5( $str ) 00339 { 00340 return " encode(digest( $str, 'md5' ), 'hex' ) "; 00341 } 00342 00343 function supportedRelationTypeMask() 00344 { 00345 return ( eZDBInterface::RELATION_TABLE_BIT | 00346 eZDBInterface::RELATION_SEQUENCE_BIT | 00347 eZDBInterface::RELATION_TRIGGER_BIT | 00348 eZDBInterface::RELATION_VIEW_BIT | 00349 eZDBInterface::RELATION_INDEX_BIT ); 00350 } 00351 00352 function supportedRelationTypes() 00353 { 00354 return array( eZDBInterface::RELATION_TABLE, 00355 eZDBInterface::RELATION_SEQUENCE, 00356 eZDBInterface::RELATION_TRIGGER, 00357 eZDBInterface::RELATION_VIEW, 00358 eZDBInterface::RELATION_INDEX ); 00359 } 00360 00361 /*! 00362 \private 00363 */ 00364 function relationKind( $relationType ) 00365 { 00366 $kind = array( eZDBInterface::RELATION_TABLE => 'r', 00367 eZDBInterface::RELATION_SEQUENCE => 'S', 00368 eZDBInterface::RELATION_TRIGGER => 't', 00369 eZDBInterface::RELATION_VIEW => 'v', 00370 eZDBInterface::RELATION_INDEX => 'i' ); 00371 if ( !isset( $kind[$relationType] ) ) 00372 return false; 00373 return $kind[$relationType]; 00374 } 00375 00376 function relationCounts( $relationMask ) 00377 { 00378 $relationTypes = $this->supportedRelationTypes(); 00379 $relationKinds = array(); 00380 foreach ( $relationTypes as $relationType ) 00381 { 00382 $relationBit = ( 1 << $relationType ); 00383 if ( $relationMask & $relationBit ) 00384 { 00385 $relationKind = $this->relationKind( $relationType ); 00386 if ( $relationKind ) 00387 $relationKinds[] = $relationKind; 00388 } 00389 } 00390 if ( count( $relationKinds ) == 0 ) 00391 return 0; 00392 $count = false; 00393 $relkindText = ''; 00394 $i = 0; 00395 foreach ( $relationKinds as $relationKind ) 00396 { 00397 if ( $i > 0 ) 00398 $relkindText .= ' OR '; 00399 $relkindText .= "relkind='$relationKind'"; 00400 $i++; 00401 } 00402 if ( $this->isConnected() ) 00403 { 00404 $sql = "SELECT COUNT( relname ) as count 00405 FROM pg_catalog.pg_class c 00406 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace 00407 WHERE ( $relkindText ) 00408 AND c.relname !~ '^pg_' 00409 AND pg_catalog.pg_table_is_visible(c.oid)"; 00410 $array = $this->arrayQuery( $sql, array( 'column' => 'count' ) ); 00411 $count = $array[0]; 00412 } 00413 return $count; 00414 } 00415 00416 function relationCount( $relationType = eZDBInterface::RELATION_TABLE ) 00417 { 00418 $count = false; 00419 $relationKind = $this->relationKind( $relationType ); 00420 if ( !$relationKind ) 00421 { 00422 eZDebug::writeError( "Unsupported relation type '$relationType'", __METHOD__ ); 00423 return false; 00424 } 00425 00426 if ( $this->isConnected() ) 00427 { 00428 $sql = "SELECT COUNT( relname ) as count 00429 FROM pg_catalog.pg_class c 00430 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace 00431 WHERE c.relkind = '$relationKind' 00432 AND c.relname !~ '^pg_' 00433 AND pg_catalog.pg_table_is_visible(c.oid)"; 00434 $array = $this->arrayQuery( $sql, array( 'column' => 'count' ) ); 00435 $count = $array[0]; 00436 } 00437 return $count; 00438 } 00439 00440 function relationList( $relationType = eZDBInterface::RELATION_TABLE ) 00441 { 00442 $count = false; 00443 $relationKind = $this->relationKind( $relationType ); 00444 if ( !$relationKind ) 00445 { 00446 eZDebug::writeError( "Unsupported relation type '$relationType'", __METHOD__ ); 00447 return false; 00448 } 00449 00450 $array = array(); 00451 if ( $this->isConnected() ) 00452 { 00453 $sql = "SELECT relname 00454 FROM pg_catalog.pg_class c 00455 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace 00456 WHERE c.relkind = '$relationKind' 00457 AND c.relname !~ '^pg_' 00458 AND pg_catalog.pg_table_is_visible( c.oid )"; 00459 $array = $this->arrayQuery( $sql, array( 'column' => 'relname' ) ); 00460 } 00461 return $array; 00462 } 00463 00464 function eZTableList( $server = eZDBInterface::SERVER_MASTER ) 00465 { 00466 $array = array(); 00467 if ( $this->isConnected() ) 00468 { 00469 foreach ( array( eZDBInterface::RELATION_TABLE, eZDBInterface::RELATION_SEQUENCE ) as $relationType ) 00470 { 00471 $sql = "SELECT relname FROM pg_class WHERE relkind='" . $this->relationKind( $relationType ) . "' AND relname like 'ez%'"; 00472 foreach ( $this->arrayQuery( $sql, array( 'column' => 'relname' ) ) as $result ) 00473 { 00474 $array[$result] = $relationType; 00475 } 00476 } 00477 } 00478 return $array; 00479 } 00480 00481 function relationMatchRegexp( $relationType ) 00482 { 00483 return "#^(ez|tmp_notification_rule_s)#"; 00484 } 00485 00486 function removeRelation( $relationName, $relationType ) 00487 { 00488 $relationTypeName = $this->relationName( $relationType ); 00489 if ( !$relationTypeName ) 00490 { 00491 eZDebug::writeError( "Unsupported relation type '$relationType'", __METHOD__ ); 00492 return false; 00493 } 00494 00495 if ( $this->isConnected() ) 00496 { 00497 $sql = "DROP $relationTypeName $relationName"; 00498 return $this->query( $sql ); 00499 } 00500 return false; 00501 } 00502 00503 function lock( $table ) 00504 { 00505 $this->begin(); 00506 if ( $this->isConnected() ) 00507 { 00508 if ( is_array( $table ) ) 00509 { 00510 $lockQuery = "LOCK TABLE"; 00511 $first = true; 00512 foreach( array_keys( $table ) as $tableKey ) 00513 { 00514 if ( $first == true ) 00515 $first = false; 00516 else 00517 $lockQuery .= ","; 00518 $lockQuery .= " " . $table[$tableKey]['table']; 00519 } 00520 $this->query( $lockQuery ); 00521 } 00522 else 00523 { 00524 $this->query( "LOCK TABLE $table" ); 00525 } 00526 } 00527 } 00528 00529 function unlock() 00530 { 00531 $this->commit(); 00532 } 00533 00534 /*! 00535 The query to start the transaction. 00536 */ 00537 function beginQuery() 00538 { 00539 return $this->query("BEGIN WORK"); 00540 } 00541 00542 /*! 00543 The query to commit the transaction. 00544 */ 00545 function commitQuery() 00546 { 00547 return $this->query( "COMMIT WORK" ); 00548 } 00549 00550 /*! 00551 The query to cancel the transaction. 00552 */ 00553 function rollbackQuery() 00554 { 00555 return pg_query( $this->DBConnection, "ROLLBACK WORK" ); 00556 } 00557 00558 /** 00559 * Returns the last serial ID generated with an auto increment field. 00560 * 00561 * In this case that means the current value of the sequence assigned 00562 * <var>$table</var> 00563 * 00564 * @param string $table 00565 * @param string $column 00566 * @return int The most recent value for the sequence 00567 */ 00568 function lastSerialID( $table = false, $column = 'id' ) 00569 { 00570 if ( $this->isConnected() ) 00571 { 00572 $sql = "SELECT currval( '" . $table . "_s')"; 00573 $result = pg_query( $this->DBConnection, $sql ); 00574 if ( !$result ) 00575 { 00576 eZDebug::writeError( "Error: error executing query: $sql " . pg_last_error( $this->DBConnection ), "eZPostgreSQLDB" ); 00577 } 00578 00579 if ( $result ) 00580 { 00581 $array = pg_fetch_row( $result, 0 ); 00582 $id = (int)$array[0]; 00583 } 00584 } 00585 return $id; 00586 } 00587 00588 function setError( ) 00589 { 00590 if ( $this->DBConnection ) 00591 { 00592 00593 $this->ErrorMessage = pg_last_error( $this->DBConnection ); 00594 if ( $this->ErrorMessage != '' ) 00595 { 00596 $this->ErrorNumber = 1; 00597 } 00598 else 00599 { 00600 $this->ErrorNumber = 0; 00601 } 00602 00603 } 00604 } 00605 00606 function escapeString( $str ) 00607 { 00608 $str = str_replace("\0", '', $str); 00609 $str = pg_escape_string( $str ); 00610 return $str; 00611 } 00612 00613 function close() 00614 { 00615 pg_close( $this->DBConnection ); 00616 } 00617 00618 function createDatabase( $dbName ) 00619 { 00620 if ( $this->DBConnection != false ) 00621 { 00622 $this->query( "CREATE DATABASE $dbName" ); 00623 $this->setError(); 00624 } 00625 } 00626 00627 function removeDatabase( $dbName ) 00628 { 00629 if ( $this->DBConnection != false ) 00630 { 00631 $this->query( "DROP DATABASE $dbName" ); 00632 $this->setError(); 00633 } 00634 } 00635 00636 function isCharsetSupported( $charset ) 00637 { 00638 return true; 00639 } 00640 00641 function databaseServerVersion() 00642 { 00643 if ( $this->isConnected() ) 00644 { 00645 $sql = "SELECT version()"; 00646 $result = pg_query( $this->DBConnection, $sql ); 00647 if ( !$result ) 00648 { 00649 eZDebug::writeError( "Error: error executing query: $sql " . pg_last_error( $this->DBConnection ), "eZPostgreSQLDB" ); 00650 } 00651 00652 if ( $result ) 00653 { 00654 $array = pg_fetch_row( $result, 0 ); 00655 $versionText = $array[0]; 00656 } 00657 list( $dbType, $versionInfo ) = explode( ' ', $versionText ); 00658 $versionArray = explode( '.', $versionInfo ); 00659 return array( 'string' => $versionInfo, 00660 'values' => $versionArray ); 00661 } 00662 return false; 00663 } 00664 00665 /*! 00666 Sets PostgreSQL sequence values to the maximum values used in the corresponding columns. 00667 */ 00668 function correctSequenceValues() 00669 { 00670 if ( $this->isConnected() ) 00671 { 00672 $rows = $this->arrayQuery( "SELECT pg_class.relname AS table, pg_attribute.attname AS column 00673 FROM pg_class,pg_attribute,pg_attrdef 00674 WHERE pg_attrdef.adsrc LIKE 'nextval(%' 00675 AND pg_attrdef.adrelid=pg_attribute.attrelid 00676 AND pg_attrdef.adnum=pg_attribute.attnum 00677 AND pg_attribute.attrelid=pg_class.oid" ); 00678 foreach ( $rows as $row ) 00679 { 00680 $this->query( "SELECT setval('".$row['table']."_s', max(".$row['column'].")) from ".$row['table'] ); 00681 } 00682 return true; 00683 } 00684 return false; 00685 } 00686 00687 /// \privatesection 00688 00689 } 00690 00691 ?>