eZ Publish  [trunk]
ezmysqlidb.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZMySQLiDB 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 eZMySQLiDB eZMySQLiDB.php
00013   \ingroup eZDB
00014   \brief The eZMySQLiDB class provides MySQL implementation of the database interface.
00015 
00016   eZMySQLiDB is the MySQL implementation of eZDB.
00017   \sa eZDB
00018 */
00019 
00020 class eZMySQLiDB extends eZDBInterface
00021 {
00022     const RELATION_FOREIGN_KEY = 5;
00023     const RELATION_FOREIGN_KEY_BIT = 32;
00024 
00025     /*!
00026       Create a new eZMySQLiDB object and connects to the database backend.
00027     */
00028     function eZMySQLiDB( $parameters )
00029     {
00030         $this->eZDBInterface( $parameters );
00031 
00032         if ( !extension_loaded( 'mysqli' ) )
00033         {
00034             if ( function_exists( 'eZAppendWarningItem' ) )
00035             {
00036                 eZAppendWarningItem( array( 'error' => array( 'type' => 'ezdb',
00037                                                               'number' => eZDBInterface::ERROR_MISSING_EXTENSION ),
00038                                             'text' => 'MySQLi extension was not found, the DB handler will not be initialized.' ) );
00039                 $this->IsConnected = false;
00040             }
00041             eZDebug::writeWarning( 'MySQLi extension was not found, the DB handler will not be initialized.', 'eZMySQLiDB' );
00042             return;
00043         }
00044 
00045         eZDebug::createAccumulatorGroup( 'mysqli_total', 'Mysql Total' );
00046 
00047         /// Connect to master server
00048         if ( !$this->DBWriteConnection )
00049         {
00050             $connection = $this->connect( $this->Server, $this->DB, $this->User, $this->Password, $this->SocketPath, $this->Charset, $this->Port );
00051             if ( $this->IsConnected )
00052             {
00053                 $this->DBWriteConnection = $connection;
00054             }
00055         }
00056 
00057         // Connect to slave
00058         if ( !$this->DBConnection )
00059         {
00060             if ( $this->UseSlaveServer === true )
00061             {
00062                 $connection = $this->connect( $this->SlaveServer, $this->SlaveDB, $this->SlaveUser, $this->SlavePassword, $this->SocketPath, $this->Charset, $this->SlavePort );
00063             }
00064             else
00065             {
00066                 $connection = $this->DBWriteConnection;
00067             }
00068 
00069             if ( $connection && $this->DBWriteConnection )
00070             {
00071                 $this->DBConnection = $connection;
00072                 $this->IsConnected = true;
00073             }
00074         }
00075 
00076         // Initialize TempTableList
00077         $this->TempTableList = array();
00078     }
00079 
00080     /*!
00081      \private
00082      Opens a new connection to a MySQL database and returns the connection
00083     */
00084     function connect( $server, $db, $user, $password, $socketPath, $charset = null, $port = false )
00085     {
00086         $connection = false;
00087 
00088         if ( $socketPath !== false )
00089         {
00090             ini_set( "mysqli.default_socket", $socketPath );
00091         }
00092 
00093         if ( $this->UsePersistentConnection == true )
00094         {
00095             // Only supported on PHP 5.3 (mysqlnd)
00096             if ( version_compare( PHP_VERSION, '5.3' ) > 0 )
00097                 $this->Server = 'p:' . $this->Server;
00098             else
00099                 eZDebug::writeWarning( 'mysqli only supports persistent connections when using php 5.3 and higher', __METHOD__ );
00100         }
00101 
00102         $oldHandling = eZDebug::setHandleType( eZDebug::HANDLE_EXCEPTION );
00103         eZDebug::accumulatorStart( 'mysqli_connection', 'mysqli_total', 'Database connection' );
00104         try {
00105             $connection = mysqli_connect( $server, $user, $password, null, (int)$port, $socketPath );
00106         } catch( ErrorException $e ) {}
00107         eZDebug::accumulatorStop( 'mysqli_connection' );
00108         eZDebug::setHandleType( $oldHandling );
00109 
00110         $maxAttempts = $this->connectRetryCount();
00111         $waitTime = $this->connectRetryWaitTime();
00112         $numAttempts = 1;
00113         while ( !$connection && $numAttempts <= $maxAttempts )
00114         {
00115             sleep( $waitTime );
00116 
00117             $oldHandling = eZDebug::setHandleType( eZDebug::HANDLE_EXCEPTION );
00118             eZDebug::accumulatorStart( 'mysqli_connection', 'mysqli_total', 'Database connection' );
00119             try {
00120                 $connection = mysqli_connect( $this->Server, $this->User, $this->Password, null, (int)$this->Port, $this->SocketPath );
00121             } catch( ErrorException $e ) {}
00122             eZDebug::accumulatorStop( 'mysqli_connection' );
00123             eZDebug::setHandleType( $oldHandling );
00124 
00125             $numAttempts++;
00126         }
00127         $this->setError();
00128 
00129         $this->IsConnected = true;
00130 
00131         if ( !$connection )
00132         {
00133             eZDebug::writeError( "Connection error: Couldn't connect to database server. Please try again later or inform the system administrator.\n{$this->ErrorMessage}", __CLASS__ );
00134             $this->IsConnected = false;
00135             throw new eZDBNoConnectionException( $server, $this->ErrorMessage, $this->ErrorNumber );
00136         }
00137 
00138         if ( $this->IsConnected && $db != null )
00139         {
00140             $ret = mysqli_select_db( $connection, $db );
00141             if ( !$ret )
00142             {
00143                 $this->setError( $connection );
00144                 eZDebug::writeError( "Connection error: Couldn't select the database. Please try again later or inform the system administrator.\n{$this->ErrorMessage}", __CLASS__ );
00145                 $this->IsConnected = false;
00146             }
00147         }
00148 
00149         if ( $charset !== null )
00150         {
00151             $originalCharset = $charset;
00152             $charset = eZCharsetInfo::realCharsetCode( $charset );
00153         }
00154 
00155         if ( $this->IsConnected and $charset !== null )
00156         {
00157             $status = mysqli_set_charset( $connection, eZMySQLCharset::mapTo( $charset ) );
00158             if ( !$status )
00159             {
00160                 $this->setError();
00161                 eZDebug::writeWarning( "Connection warning: " . mysqli_errno( $connection ) . ": " . mysqli_error( $connection ), "eZMySQLiDB" );
00162             }
00163         }
00164 
00165         return $connection;
00166     }
00167 
00168     function databaseName()
00169     {
00170         return 'mysql';
00171     }
00172 
00173     function bindingType( )
00174     {
00175         return eZDBInterface::BINDING_NO;
00176     }
00177 
00178     function bindVariable( $value, $fieldDef = false )
00179     {
00180         return $value;
00181     }
00182 
00183     /*!
00184       Checks if the requested character set matches the one used in the database.
00185 
00186       \return \c true if it matches or \c false if it differs.
00187       \param[out] $currentCharset The charset that the database uses.
00188                                   will only be set if the match fails.
00189                                   Note: This will be specific to the database.
00190     */
00191     function checkCharset( $charset, &$currentCharset )
00192     {
00193         // If we don't have a database yet we shouldn't check it
00194         if ( !$this->DB )
00195             return true;
00196 
00197         $versionInfo = $this->databaseServerVersion();
00198 
00199         if ( is_array( $charset ) )
00200         {
00201             $realCharset = array();
00202             foreach ( $charset as $charsetItem )
00203                 $realCharset[] = eZCharsetInfo::realCharsetCode( $charsetItem );
00204         }
00205         else
00206             $realCharset = eZCharsetInfo::realCharsetCode( $charset );
00207 
00208         return $this->checkCharsetPriv( $realCharset, $currentCharset );
00209     }
00210 
00211     /*!
00212      \private
00213     */
00214     function checkCharsetPriv( $charset, &$currentCharset )
00215     {
00216         $query = "SHOW CREATE DATABASE `{$this->DB}`";
00217         $status = mysqli_query( $this->DBConnection, $query );
00218         $this->reportQuery( __CLASS__, $query, false, false );
00219         if ( !$status )
00220         {
00221             $this->setError();
00222             eZDebug::writeWarning( "Connection warning: " . mysqli_errno( $this->DBConnection ) . ": " . mysqli_error( $this->DBConnection ), "eZMySQLiDB" );
00223             return false;
00224         }
00225 
00226         $numRows = mysqli_num_rows( $status );
00227         if ( $numRows == 0 )
00228             return false;
00229 
00230         for ( $i = 0; $i < $numRows; ++$i )
00231         {
00232             $tmpRow = mysqli_fetch_array( $status, MYSQLI_ASSOC );
00233             if ( $tmpRow['Database'] == $this->DB )
00234             {
00235                 $createText = $tmpRow['Create Database'];
00236                 if ( preg_match( '#DEFAULT CHARACTER SET ([a-zA-Z0-9_-]+)#', $createText, $matches ) )
00237                 {
00238                     $currentCharset = $matches[1];
00239                     $currentCharset = eZCharsetInfo::realCharsetCode( $currentCharset );
00240                     // Convert charset names into something MySQL will understand
00241                     $unmappedCurrentCharset = eZMySQLCharset::mapFrom( $currentCharset );
00242 
00243                     if ( is_array( $charset ) )
00244                     {
00245                         if ( in_array( $unmappedCurrentCharset, $charset ) )
00246                         {
00247                             return $unmappedCurrentCharset;
00248                         }
00249                     }
00250                     else if ( $unmappedCurrentCharset == $charset )
00251                     {
00252                         return true;
00253                     }
00254                     return false;
00255                 }
00256                 break;
00257             }
00258         }
00259         return true;
00260     }
00261 
00262     function query( $sql, $server = false )
00263     {
00264         if ( $this->IsConnected )
00265         {
00266             eZDebug::accumulatorStart( 'mysqli_query', 'mysqli_total', 'Mysqli_queries' );
00267             $orig_sql = $sql;
00268             // The converted sql should not be output
00269             if ( $this->InputTextCodec )
00270             {
00271                 eZDebug::accumulatorStart( 'mysqli_conversion', 'mysqli_total', 'String conversion in mysqli' );
00272                 $sql = $this->InputTextCodec->convertString( $sql );
00273                 eZDebug::accumulatorStop( 'mysqli_conversion' );
00274             }
00275 
00276             if ( $this->OutputSQL )
00277             {
00278                 $this->startTimer();
00279             }
00280 
00281             $sql = trim( $sql );
00282 
00283             // Check if we need to use the master or slave server by default
00284             if ( $server === false )
00285             {
00286                 $server = strncasecmp( $sql, 'select', 6 ) === 0 && $this->TransactionCounter == 0 ?
00287                     eZDBInterface::SERVER_SLAVE : eZDBInterface::SERVER_MASTER;
00288             }
00289 
00290             $connection = ( $server == eZDBInterface::SERVER_SLAVE ) ? $this->DBConnection : $this->DBWriteConnection;
00291 
00292             $analysisText = false;
00293             // If query analysis is enable we need to run the query
00294             // with an EXPLAIN in front of it
00295             // Then we build a human-readable table out of the result
00296             if ( $this->QueryAnalysisOutput )
00297             {
00298                 $analysisResult = mysqli_query( $connection, 'EXPLAIN ' . $sql );
00299                 if ( $analysisResult )
00300                 {
00301                     $numRows = mysqli_num_rows( $analysisResult );
00302                     $rows = array();
00303                     if ( $numRows > 0 )
00304                     {
00305                         for ( $i = 0; $i < $numRows; ++$i )
00306                         {
00307                             if ( $this->InputTextCodec )
00308                             {
00309                                 $tmpRow = mysqli_fetch_array( $analysisResult, MYSQLI_ASSOC );
00310                                 $convRow = array();
00311                                 foreach( $tmpRow as $key => $row )
00312                                 {
00313                                     $convRow[$key] = $this->OutputTextCodec->convertString( $row );
00314                                 }
00315                                 $rows[$i] = $convRow;
00316                             }
00317                             else
00318                                 $rows[$i] = mysqli_fetch_array( $analysisResult, MYSQLI_ASSOC );
00319                         }
00320                     }
00321 
00322                     // Figure out all columns and their maximum display size
00323                     $columns = array();
00324                     foreach ( $rows as $row )
00325                     {
00326                         foreach ( $row as $col => $data )
00327                         {
00328                             if ( !isset( $columns[$col] ) )
00329                                 $columns[$col] = array( 'name' => $col,
00330                                                         'size' => strlen( $col ) );
00331                             $columns[$col]['size'] = max( $columns[$col]['size'], strlen( $data ) );
00332                         }
00333                     }
00334 
00335                     $delimiterLine = array();
00336                     $colLine = array();
00337                     // Generate the column line and the vertical delimiter
00338                     // The look of the table is taken from the MySQL CLI client
00339                     // It looks like this:
00340                     // +-------+-------+
00341                     // | col_a | col_b |
00342                     // +-------+-------+
00343                     // | txt   |    42 |
00344                     // +-------+-------+
00345                     foreach ( $columns as $col )
00346                     {
00347                         $delimiterLine[] = str_repeat( '-', $col['size'] + 2 );
00348                         $colLine[] = ' ' . str_pad( $col['name'], $col['size'], ' ', STR_PAD_RIGHT ) . ' ';
00349                     }
00350                     $delimiterLine = '+' . join( '+', $delimiterLine ) . "+\n";
00351                     $analysisText = $delimiterLine;
00352                     $analysisText .= '|' . join( '|', $colLine ) . "|\n";
00353                     $analysisText .= $delimiterLine;
00354 
00355                     // Go trough all data and pad them to create the table correctly
00356                     foreach ( $rows as $row )
00357                     {
00358                         $rowLine = array();
00359                         foreach ( $columns as $col )
00360                         {
00361                             $name = $col['name'];
00362                             $size = $col['size'];
00363                             $data = isset( $row[$name] ) ? $row[$name] : '';
00364                             // Align numerical values to the right (ie. pad left)
00365                             $rowLine[] = ' ' . str_pad( $data, $size, ' ',
00366                                                         is_numeric( $data ) ? STR_PAD_LEFT : STR_PAD_RIGHT ) . ' ';
00367                         }
00368                         $analysisText .= '|' . join( '|', $rowLine ) . "|\n";
00369                         $analysisText .= $delimiterLine;
00370                     }
00371 
00372                     // Reduce memory usage
00373                     unset( $rows, $delimiterLine, $colLine, $columns );
00374                 }
00375             }
00376 
00377             $result = mysqli_query( $connection, $sql );
00378 
00379             if ( $this->RecordError and !$result )
00380                 $this->setError();
00381 
00382             if ( $this->OutputSQL )
00383             {
00384                 $this->endTimer();
00385 
00386                 if ($this->timeTaken() > $this->SlowSQLTimeout)
00387                 {
00388                     $num_rows = mysqli_affected_rows( $connection );
00389                     $text = $sql;
00390 
00391                     // If we have some analysis text we append this to the SQL output
00392                     if ( $analysisText !== false )
00393                         $text = "EXPLAIN\n" . $text . "\n\nANALYSIS:\n" . $analysisText;
00394 
00395                     $this->reportQuery( __CLASS__ . '[' . $connection->host_info . ( $server == eZDBInterface::SERVER_MASTER ? ', on master' : '' ) . ']', $text, $num_rows, $this->timeTaken() );
00396                 }
00397             }
00398             eZDebug::accumulatorStop( 'mysqli_query' );
00399             if ( $result )
00400             {
00401                 return $result;
00402             }
00403             else
00404             {
00405                 $errorMessage = 'Query error (' . mysqli_errno( $connection ) . '): ' . mysqli_error( $connection ) . '. Query: ' . $sql;
00406                 eZDebug::writeError( $errorMessage, __CLASS__  );
00407                 $oldRecordError = $this->RecordError;
00408                 // Turn off error handling while we unlock
00409                 $this->RecordError = false;
00410                 mysqli_query( $connection, 'UNLOCK TABLES' );
00411                 $this->RecordError = $oldRecordError;
00412 
00413                 $this->reportError();
00414 
00415                 // This is to behave the same way as other RDBMS PHP API as PostgreSQL
00416                 // functions which throws an error with a failing request.
00417                 if ( $this->errorHandling == eZDB::ERROR_HANDLING_STANDARD )
00418                 {
00419                     trigger_error( "mysqli_query(): $errorMessage", E_USER_ERROR );
00420                 }
00421                 else
00422                 {
00423                     throw new eZDBException( $this->ErrorMessage, $this->ErrorNumber );
00424                 }
00425 
00426                 return false;
00427             }
00428         }
00429         else
00430         {
00431             eZDebug::writeError( "Trying to do a query without being connected to a database!", __CLASS__ );
00432         }
00433 
00434 
00435     }
00436 
00437     function arrayQuery( $sql, $params = array(), $server = false )
00438     {
00439         $retArray = array();
00440         if ( $this->IsConnected )
00441         {
00442             $limit = false;
00443             $offset = 0;
00444             $column = false;
00445             // check for array parameters
00446             if ( is_array( $params ) )
00447             {
00448                 if ( isset( $params["limit"] ) and is_numeric( $params["limit"] ) )
00449                     $limit = $params["limit"];
00450 
00451                 if ( isset( $params["offset"] ) and is_numeric( $params["offset"] ) )
00452                     $offset = $params["offset"];
00453 
00454                 if ( isset( $params["column"] ) and ( is_numeric( $params["column"] ) or is_string( $params["column"] ) ) )
00455                     $column = $params["column"];
00456             }
00457 
00458             if ( $limit !== false and is_numeric( $limit ) )
00459             {
00460                 $sql .= "\nLIMIT $offset, $limit ";
00461             }
00462             else if ( $offset !== false and is_numeric( $offset ) and $offset > 0 )
00463             {
00464                 $sql .= "\nLIMIT $offset, 18446744073709551615"; // 2^64-1
00465             }
00466             $result = $this->query( $sql, $server );
00467 
00468             if ( $result == false )
00469             {
00470                 $this->reportQuery( __CLASS__, $sql, false, false );
00471                 return false;
00472             }
00473 
00474             $numRows = mysqli_num_rows( $result );
00475             if ( $numRows > 0 )
00476             {
00477                 if ( !is_string( $column ) )
00478                 {
00479                     eZDebug::accumulatorStart( 'mysqli_loop', 'mysqli_total', 'Looping result' );
00480                     for ( $i=0; $i < $numRows; $i++ )
00481                     {
00482                         if ( $this->InputTextCodec )
00483                         {
00484                             $tmpRow = mysqli_fetch_array( $result, MYSQLI_ASSOC );
00485                             $convRow = array();
00486                             foreach( $tmpRow as $key => $row )
00487                             {
00488                                 eZDebug::accumulatorStart( 'mysqli_conversion', 'mysqli_total', 'String conversion in mysqli' );
00489                                 $convRow[$key] = $this->OutputTextCodec->convertString( $row );
00490                                 eZDebug::accumulatorStop( 'mysqli_conversion' );
00491                             }
00492                             $retArray[$i + $offset] = $convRow;
00493                         }
00494                         else
00495                             $retArray[$i + $offset] = mysqli_fetch_array( $result, MYSQLI_ASSOC );
00496                     }
00497                     eZDebug::accumulatorStop( 'mysqli_loop' );
00498 
00499                 }
00500                 else
00501                 {
00502                     eZDebug::accumulatorStart( 'mysqli_loop', 'mysqli_total', 'Looping result' );
00503                     for ( $i=0; $i < $numRows; $i++ )
00504                     {
00505                         $tmp_row = mysqli_fetch_array( $result, MYSQLI_ASSOC );
00506                         if ( $this->InputTextCodec )
00507                         {
00508                             eZDebug::accumulatorStart( 'mysqli_conversion', 'mysqli_total', 'String conversion in mysqli' );
00509                             $retArray[$i + $offset] = $this->OutputTextCodec->convertString( $tmp_row[$column] );
00510                             eZDebug::accumulatorStop( 'mysqli_conversion' );
00511                         }
00512                         else
00513                             $retArray[$i + $offset] =& $tmp_row[$column];
00514                     }
00515                     eZDebug::accumulatorStop( 'mysqli_loop' );
00516                 }
00517             }
00518         }
00519         return $retArray;
00520     }
00521 
00522     function subString( $string, $from, $len = null )
00523     {
00524         if ( $len == null )
00525         {
00526             return " substring( $string from $from ) ";
00527         }else
00528         {
00529             return " substring( $string from $from for $len ) ";
00530         }
00531     }
00532 
00533     function concatString( $strings = array() )
00534     {
00535         $str = implode( "," , $strings );
00536         return " concat( $str  ) ";
00537     }
00538 
00539     function md5( $str )
00540     {
00541         return " MD5( $str ) ";
00542     }
00543 
00544     function bitAnd( $arg1, $arg2 )
00545     {
00546         return 'cast(' . $arg1 . ' & ' . $arg2 . ' AS SIGNED ) ';
00547     }
00548 
00549     function bitOr( $arg1, $arg2 )
00550     {
00551         return 'cast( ' . $arg1 . ' | ' . $arg2 . ' AS SIGNED ) ';
00552     }
00553 
00554     function supportedRelationTypeMask()
00555     {
00556         return eZDBInterface::RELATION_TABLE_BIT | self::RELATION_FOREIGN_KEY_BIT;
00557     }
00558 
00559     function supportedRelationTypes()
00560     {
00561         return array( self::RELATION_FOREIGN_KEY, eZDBInterface::RELATION_TABLE );
00562     }
00563 
00564     function relationCounts( $relationMask )
00565     {
00566         if ( $relationMask & eZDBInterface::RELATION_TABLE_BIT )
00567             return $this->relationCount();
00568         else
00569             return 0;
00570     }
00571 
00572     function relationCount( $relationType = eZDBInterface::RELATION_TABLE )
00573     {
00574         if ( !in_array( $relationType, $this->supportedRelationTypes() ) )
00575         {
00576             eZDebug::writeError( "Unsupported relation type '$relationType'", __METHOD__ );
00577             return false;
00578         }
00579         $count = false;
00580         if ( $this->IsConnected )
00581         {
00582             switch ( $relationType )
00583             {
00584                 case eZDBInterface::RELATION_TABLE:
00585                 {
00586                     $query = 'SHOW TABLES from `' . $this->DB .'`';
00587                     $result = mysqli_query( $this->DBConnection, $query );
00588                     $this->reportQuery( __CLASS__, $query, false, false );
00589                     $count = mysqli_num_rows( $result );
00590                     mysqli_free_result( $result );
00591                 } break;
00592 
00593                 case self::RELATION_FOREIGN_KEY:
00594                 {
00595                     $count = count( $this->relationList( self::RELATION_FOREIGN_KEY ) );
00596                 } break;
00597             }
00598         }
00599         return $count;
00600     }
00601 
00602     function relationList( $relationType = eZDBInterface::RELATION_TABLE )
00603     {
00604         if ( !in_array( $relationType, $this->supportedRelationTypes() ) )
00605         {
00606             eZDebug::writeError( "Unsupported relation type '$relationType'", __METHOD__ );
00607             return false;
00608         }
00609         if ( $this->IsConnected )
00610         {
00611             switch ( $relationType )
00612             {
00613                 case eZDBInterface::RELATION_TABLE:
00614                 {
00615                     $tables = array();
00616                     $query = 'SHOW TABLES from `' . $this->DB .'`';
00617                     $result = mysqli_query( $this->DBConnection, $query );
00618                     $this->reportQuery( __CLASS__, $query, false, false );
00619                     while( $row = mysqli_fetch_row( $result ) )
00620                     {
00621                         $tables[] = $row[0];
00622                     }
00623                     mysqli_free_result( $result );
00624                     return $tables;
00625                 } break;
00626 
00627                 case self::RELATION_FOREIGN_KEY:
00628                 {
00629                     /**
00630                      * Ideally, we would have queried information_schema.KEY_COLUMN_USAGE
00631                      * However, a known bug causes queries on this table to potentially be VERY slow (http://bugs.mysql.com/bug.php?id=19588)
00632                      *
00633                      * The query would look like this:
00634                      * SELECT table_name AS from_table, column_name AS from_column, referenced_table_name AS to_table,
00635                      *        referenced_column_name AS to_column
00636                      * FROM information_schema.KEY_COLUMN_USAGE
00637                      * WHERE REFERENCED_TABLE_SCHEMA = '{$this->DB}'
00638                      *   AND REFERENCED_TABLE_NAME is not null;
00639                      *
00640                      * Result as of MySQL 5.1.48 / August 2010:
00641                      *
00642                      * +---------------+-------------+----------+-----------+
00643                      * | from_table    | from_column | to_table | to_column |
00644                      * +---------------+-------------+----------+-----------+
00645                      * | ezdbfile_data | name_hash   | ezdbfile | name_hash |
00646                      * +---------------+-------------+----------+-----------+
00647                      * 1 row in set (12.56 sec)
00648                      *
00649                      * The only way out right now is to parse SHOW CREATE TABLE for each table and extract CONSTRAINT lines
00650                      */
00651 
00652                     $foreignKeys = array();
00653                     foreach( $this->relationList( eZDBInterface::RELATION_TABLE ) as $table )
00654                     {
00655                         $query = "SHOW CREATE TABLE $table";
00656                         $result = mysqli_query( $this->DBConnection, $query );
00657                         $this->reportQuery( __CLASS__, $query, false, false );
00658                         if ( mysqli_num_rows( $result ) === 1 )
00659                         {
00660                             $row = mysqli_fetch_row( $result );
00661                             if ( strpos( $row[1], "CONSTRAINT" ) !== false )
00662                             {
00663                                 if ( preg_match_all( '#CONSTRAINT [`"]([^`"]+)[`"] FOREIGN KEY \([`"].*[`"]\) REFERENCES [`"]([^`"]+)[`"] \([`"].*[`"]\)#', $row[1], $matches, PREG_PATTERN_ORDER ) )
00664                                 {
00665                                     // $foreignKeys[] = array( 'table' => $table, 'keys' => $matches[1] );
00666                                     foreach( $matches[1] as $fkMatch )
00667                                     {
00668                                         $foreignKeys[] = array( 'table' => $table, 'fk' => $fkMatch );
00669                                     }
00670                                 }
00671                             }
00672                         }
00673                     }
00674                     return $foreignKeys;
00675                 }
00676             }
00677         }
00678     }
00679 
00680     function eZTableList( $server = eZDBInterface::SERVER_MASTER )
00681     {
00682         $tables = array();
00683         if ( $this->IsConnected )
00684         {
00685             if ( $this->UseSlaveServer && $server == eZDBInterface::SERVER_SLAVE )
00686             {
00687                 $connection = $this->DBConnection;
00688                 $db = $this->SlaveDB;
00689             }
00690             else
00691             {
00692                 $connection = $this->DBWriteConnection;
00693                 $db = $this->DB;
00694             }
00695 
00696             $query = 'SHOW TABLES from `' . $db .'`';
00697             $result = mysqli_query( $connection, $query );
00698             $this->reportQuery( __CLASS__, $query, false, false );
00699             while( $row = mysqli_fetch_row( $result ) )
00700             {
00701                 $tableName = $row[0];
00702                 if ( substr( $tableName, 0, 2 ) == 'ez' )
00703                 {
00704                     $tables[$tableName] = eZDBInterface::RELATION_TABLE;
00705                 }
00706             }
00707             mysqli_free_result( $result );
00708         }
00709         return $tables;
00710     }
00711 
00712     function relationMatchRegexp( $relationType )
00713     {
00714         return "#^ez#";
00715     }
00716 
00717     function removeRelation( $relationName, $relationType )
00718     {
00719         $relationTypeName = $this->relationName( $relationType );
00720         if ( !$relationTypeName )
00721         {
00722             eZDebug::writeError( "Unknown relation type '$relationType'", __METHOD__ );
00723             return false;
00724         }
00725 
00726         if ( $this->IsConnected )
00727         {
00728             switch ( $relationType )
00729             {
00730                 case self::RELATION_FOREIGN_KEY:
00731                 {
00732                     $sql = "ALTER TABLE {$relationName['table']} DROP FOREIGN KEY {$relationName['fk']}";
00733                     $this->query( $sql );
00734                     return true;
00735                 } break;
00736 
00737                 default:
00738                 {
00739                     $sql = "DROP $relationTypeName $relationName";
00740                     return $this->query( $sql );
00741                 }
00742             }
00743         }
00744         return false;
00745     }
00746 
00747     /**
00748      * Local eZDBInterface::relationName() override to support the foreign keys type relation
00749      * @param $relationType
00750      * @return string|false
00751      */
00752     public function relationName( $relationType )
00753     {
00754         if ( $relationType == self::RELATION_FOREIGN_KEY )
00755             return 'FOREIGN_KEY';
00756         else
00757             return parent::relationName( $relationType );
00758     }
00759 
00760     function lock( $table )
00761     {
00762         if ( $this->IsConnected )
00763         {
00764             if ( is_array( $table ) )
00765             {
00766                 $lockQuery = "LOCK TABLES";
00767                 $first = true;
00768                 foreach( array_keys( $table ) as $tableKey )
00769                 {
00770                     if ( $first == true )
00771                         $first = false;
00772                     else
00773                         $lockQuery .= ",";
00774                     $lockQuery .= " " . $table[$tableKey]['table'] . " WRITE";
00775                 }
00776                 $this->query( $lockQuery );
00777             }
00778             else
00779             {
00780                 $this->query( "LOCK TABLES $table WRITE" );
00781             }
00782         }
00783     }
00784 
00785     function unlock()
00786     {
00787         if ( $this->IsConnected )
00788         {
00789             $this->query( "UNLOCK TABLES" );
00790         }
00791     }
00792 
00793     /*!
00794      The query to start the transaction.
00795     */
00796     function beginQuery()
00797     {
00798         return $this->query("BEGIN WORK");
00799     }
00800 
00801     /*!
00802      The query to commit the transaction.
00803     */
00804     function commitQuery()
00805     {
00806         return $this->query( "COMMIT" );
00807     }
00808 
00809     /*!
00810      The query to cancel the transaction.
00811     */
00812     function rollbackQuery()
00813     {
00814         return mysqli_query( $this->DBWriteConnection, "ROLLBACK" );
00815     }
00816 
00817     function lastSerialID( $table = false, $column = false )
00818     {
00819         if ( $this->IsConnected )
00820         {
00821             $id = mysqli_insert_id( $this->DBWriteConnection );
00822             return $id;
00823         }
00824         else
00825             return false;
00826     }
00827 
00828     function escapeString( $str )
00829     {
00830         if ( $this->IsConnected )
00831         {
00832             return mysqli_real_escape_string( $this->DBConnection, $str );
00833         }
00834         else
00835         {
00836             eZDebug::writeDebug( 'escapeString called before connection is made', __METHOD__ );
00837             return mysql_escape_string( $str );
00838         }
00839     }
00840 
00841     function close()
00842     {
00843         if ( $this->IsConnected )
00844         {
00845             if ( $this->UseSlaveServer === true )
00846                 mysqli_close( $this->DBConnection );
00847             mysqli_close( $this->DBWriteConnection );
00848         }
00849     }
00850 
00851     function createDatabase( $dbName )
00852     {
00853         if ( $this->IsConnected )
00854         {
00855             $this->query( "CREATE DATABASE $dbName" );
00856             $this->setError();
00857         }
00858     }
00859 
00860     function removeDatabase( $dbName )
00861     {
00862         if ( $this->IsConnected )
00863         {
00864             $this->query( "DROP DATABASE $dbName" );
00865             $this->setError();
00866         }
00867     }
00868 
00869     /**
00870      * Sets the internal error messages & number
00871      * @param MySQLi $connection database connection handle, overrides the current one if given
00872      */
00873     function setError( $connection = false )
00874     {
00875         if ( $this->IsConnected )
00876         {
00877             if ( $connection === false )
00878                 $connection = $this->DBConnection;
00879 
00880             if ( $connection instanceof MySQLi )
00881             {
00882                 $this->ErrorMessage = mysqli_error( $connection );
00883                 $this->ErrorNumber = mysqli_errno( $connection );
00884             }
00885         }
00886         else
00887         {
00888             $this->ErrorMessage = mysqli_connect_error();
00889             $this->ErrorNumber = mysqli_connect_errno();
00890         }
00891     }
00892 
00893     function availableDatabases()
00894     {
00895         $databaseArray = mysqli_query( $this->DBConnection, 'SHOW DATABASES' );
00896 
00897         if ( $this->errorNumber() != 0 )
00898         {
00899             return null;
00900         }
00901 
00902         $databases = array();
00903 
00904         $numRows = mysqli_num_rows( $databaseArray );
00905         if ( count( $numRows ) == 0 )
00906         {
00907             return false;
00908         }
00909 
00910         while ( $row = mysqli_fetch_row( $databaseArray ) )
00911         {
00912             // we don't allow "mysql" or "information_schema" database to be shown anywhere
00913             $curDB = $row[0];
00914             if ( strcasecmp( $curDB, 'mysql' ) != 0 && strcasecmp( $curDB, 'information_schema' ) != 0 )
00915             {
00916                 $databases[] = $curDB;
00917             }
00918         }
00919         return $databases;
00920     }
00921 
00922     function databaseServerVersion()
00923     {
00924         if ( $this->IsConnected )
00925         {
00926             $versionInfo = mysqli_get_server_info( $this->DBConnection );
00927 
00928             $versionArray = explode( '.', $versionInfo );
00929 
00930             return array( 'string' => $versionInfo,
00931                           'values' => $versionArray );
00932         }
00933 
00934         return false;
00935     }
00936 
00937     function databaseClientVersion()
00938     {
00939         $versionInfo = mysqli_get_client_info();
00940 
00941         $versionArray = explode( '.', $versionInfo );
00942 
00943         return array( 'string' => $versionInfo,
00944                       'values' => $versionArray );
00945     }
00946 
00947     function isCharsetSupported( $charset )
00948     {
00949         return true;
00950     }
00951 
00952     function supportsDefaultValuesInsertion()
00953     {
00954         return false;
00955     }
00956 
00957     function dropTempTable( $dropTableQuery = '', $server = self::SERVER_SLAVE )
00958     {
00959         $dropTableQuery = str_ireplace( 'DROP TABLE', 'DROP TEMPORARY TABLE', $dropTableQuery );
00960         parent::dropTempTable( $dropTableQuery, $server );
00961     }
00962 
00963     protected $TempTableList;
00964 
00965     /// \privatesection
00966 }
00967 
00968 ?>