eZ Publish  [trunk]
ezpostgresqldb.php
Go to the documentation of this file.
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 ?>