eZ Publish  [4.0]
ezdbinterface.php
Go to the documentation of this file.
00001 <?php
00002 //
00003 // $Id$
00004 //
00005 // Definition of eZDBInterface class
00006 //
00007 // Created on: <12-Feb-2002 15:54:17 bf>
00008 //
00009 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00010 // SOFTWARE NAME: eZ Publish
00011 // SOFTWARE RELEASE: 4.0.x
00012 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS
00013 // SOFTWARE LICENSE: GNU General Public License v2.0
00014 // NOTICE: >
00015 //   This program is free software; you can redistribute it and/or
00016 //   modify it under the terms of version 2.0  of the GNU General
00017 //   Public License as published by the Free Software Foundation.
00018 //
00019 //   This program is distributed in the hope that it will be useful,
00020 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
00021 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00022 //   GNU General Public License for more details.
00023 //
00024 //   You should have received a copy of version 2.0 of the GNU General
00025 //   Public License along with this program; if not, write to the Free
00026 //   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00027 //   MA 02110-1301, USA.
00028 //
00029 //
00030 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00031 //
00032 
00033 /*!
00034   \class eZDBInterface ezdbinterface.php
00035   \ingroup eZDB
00036   \brief The eZDBInterface defines the interface for all database implementations
00037 
00038   \sa eZDB
00039 */
00040 
00041 require_once( "lib/ezutils/classes/ezdebug.php" );
00042 //include_once( "lib/ezutils/classes/ezini.php" );
00043 
00044 class eZDBInterface
00045 {
00046     const BINDING_NO = 0;
00047     const BINDING_NAME = 1;
00048     const BINDING_ORDERED = 2;
00049 
00050     const RELATION_TABLE = 0;
00051     const RELATION_SEQUENCE = 1;
00052     const RELATION_TRIGGER = 2;
00053     const RELATION_VIEW = 3;
00054     const RELATION_INDEX = 4;
00055 
00056     const RELATION_TABLE_BIT = 1;
00057     const RELATION_SEQUENCE_BIT = 2;
00058     const RELATION_TRIGGER_BIT = 4;
00059     const RELATION_VIEW_BIT = 8;
00060     const RELATION_INDEX_BIT = 16;
00061 
00062     const RELATION_NONE = 0;
00063     const RELATION_MASK = 31;
00064 
00065     const ERROR_MISSING_EXTENSION = 1;
00066 
00067     const SERVER_MASTER = 1;
00068     const SERVER_SLAVE = 2;
00069 
00070     /*!
00071       Create a new eZDBInterface object and connects to the database backend.
00072     */
00073     function eZDBInterface( $parameters )
00074     {
00075         $server = $parameters['server'];
00076         $port = $parameters['port'];
00077         $user = $parameters['user'];
00078         $password = $parameters['password'];
00079         $db = $parameters['database'];
00080         $useSlaveServer = $parameters['use_slave_server'];
00081         $slaveServer = $parameters['slave_server'];
00082         $slavePort = $parameters['slave_port'];
00083         $slaveUser = $parameters['slave_user'];
00084         $slavePassword = $parameters['slave_password'];
00085         $slaveDB =  $parameters['slave_database'];
00086         $socketPath = $parameters['socket'];
00087         $charset = $parameters['charset'];
00088         $isInternalCharset = $parameters['is_internal_charset'];
00089         $builtinEncoding = $parameters['builtin_encoding'];
00090         $connectRetries = $parameters['connect_retries'];
00091 
00092         if ( $parameters['use_persistent_connection'] == 'enabled' )
00093         {
00094             $this->UsePersistentConnection = true;
00095         }
00096 
00097         $this->DB = $db;
00098         $this->Server = $server;
00099         $this->Port = $port;
00100         $this->SocketPath = $socketPath;
00101         $this->User = $user;
00102         $this->Password = $password;
00103         $this->UseSlaveServer = $useSlaveServer;
00104         $this->SlaveDB = $slaveDB;
00105         $this->SlaveServer = $slaveServer;
00106         $this->SlavePort = $slavePort;
00107         $this->SlaveUser = $slaveUser;
00108         $this->SlavePassword = $slavePassword;
00109         $this->Charset = $charset;
00110         $this->IsInternalCharset = $isInternalCharset;
00111         $this->UseBuiltinEncoding = $builtinEncoding;
00112         $this->ConnectRetries = $connectRetries;
00113         $this->DBConnection = false;
00114         $this->DBWriteConnection = false;
00115         $this->TransactionCounter = 0;
00116         $this->TransactionIsValid = false;
00117         $this->TransactionStackTree = false;
00118 
00119         $this->OutputTextCodec = null;
00120         $this->InputTextCodec = null;
00121 /*
00122         This is pseudocode, there is no such function as
00123         mysql_supports_charset() of course
00124         if ( $this->UseBuiltinEncoding and mysql_supports_charset( $charset ) )
00125         {
00126             mysql_session_set_charset( $charset );
00127         }
00128         else
00129 */
00130         {
00131             //include_once( "lib/ezi18n/classes/eztextcodec.php" );
00132             $tmpOutputTextCodec = eZTextCodec::instance( $charset, false, false );
00133             $tmpInputTextCodec = eZTextCodec::instance( false, $charset, false );
00134             unset( $this->OutputTextCodec );
00135             unset( $this->InputTextCodec );
00136             $this->OutputTextCodec = null;
00137             $this->InputTextCodec = null;
00138 
00139             if ( $tmpOutputTextCodec && $tmpInputTextCodec )
00140             {
00141                 if ( $tmpOutputTextCodec->conversionRequired() && $tmpInputTextCodec->conversionRequired() )
00142                 {
00143                     $this->OutputTextCodec =& $tmpOutputTextCodec;
00144                     $this->InputTextCodec =& $tmpInputTextCodec;
00145                 }
00146             }
00147         }
00148 
00149         $this->OutputSQL = false;
00150         $this->SlowSQLTimeout = 0;
00151         $ini = eZINI::instance();
00152         if ( ( $ini->variable( "DatabaseSettings", "SQLOutput" ) == "enabled" ) and
00153              ( $ini->variable( "DebugSettings", "DebugOutput" ) == "enabled" ) )
00154         {
00155             $this->OutputSQL = true;
00156 
00157             $this->SlowSQLTimeout = (int) $ini->variable( "DatabaseSettings", "SlowQueriesOutput" );
00158         }
00159         if ( $ini->variable( "DatabaseSettings", "DebugTransactions" ) == "enabled" )
00160         {
00161             // Setting it to an array turns on the debugging
00162             $this->TransactionStackTree = array();
00163         }
00164 
00165         $this->QueryAnalysisOutput = false;
00166         if ( $ini->variable( "DatabaseSettings", "QueryAnalysisOutput" ) == "enabled" )
00167         {
00168             $this->QueryAnalysisOutput = true;
00169         }
00170 
00171         $this->IsConnected = false;
00172         $this->NumQueries = 0;
00173         $this->StartTime = false;
00174         $this->EndTime = false;
00175         $this->TimeTaken = false;
00176 
00177         $this->AttributeVariableMap =
00178         array(
00179             'database_name' => 'DB',
00180             'database_server' => 'Server',
00181             'database_port' => 'Port',
00182             'database_socket_path' => 'SocketPath',
00183             'database_user' => 'User',
00184             'use_slave_server' => 'UseSlaveServer',
00185             'slave_database_name' => 'SlaveDB',
00186             'slave_database_server' => 'SlaveServer',
00187             'slave_database_port' => 'SlavePort',
00188             'slave_database_user' => 'SlaveUser',
00189             'charset' => 'Charset',
00190             'is_internal_charset' => 'IsInternalCharset',
00191             'use_builting_encoding' => 'UseBuiltinEncoding',
00192             'retry_count' => 'ConnectRetries' );
00193     }
00194 
00195     /*!
00196      \return the available attributes for this database handler.
00197     */
00198     function attributes()
00199     {
00200         return array_keys( $this->AttributeVariableMap );
00201     }
00202 
00203     /*!
00204      \return \c true if the attribute \a $name exists for this database handler.
00205     */
00206     function hasAttribute( $name )
00207     {
00208         if ( isset( $this->AttributeVariableMap[$name] ) )
00209         {
00210             return true;
00211         }
00212         return false;
00213     }
00214 
00215     /*!
00216      \return the value of the attribute \a $name if it exists, otherwise \c null.
00217     */
00218     function attribute( $name )
00219     {
00220         if ( isset( $this->AttributeVariableMap[$name] ) )
00221         {
00222             $memberVariable = $this->AttributeVariableMap[$name];
00223             return $this->$memberVariable;
00224         }
00225         else
00226         {
00227             eZDebug::writeError( "Attribute '$name' does not exist", 'eZDBInterface::attribute' );
00228             return null;
00229         }
00230     }
00231 
00232     /*!
00233       Checks if the requested character set matches the one used in the database.
00234 
00235       \return \c true if it matches or \c false if it differs.
00236       \param[out] $currentCharset The charset that the database uses,
00237                                   will only be set if the match fails.
00238                                   Note: This will be specific to the database.
00239 
00240       \note The default is to always return \c true, see the specific database handler
00241             for more information.
00242     */
00243     function checkCharset( $charset, &$currentCharset )
00244     {
00245         return true;
00246     }
00247 
00248     /*!
00249      \private
00250      Prepare the sql file so we can create the database.
00251      \param $fd    The file descriptor
00252      \param $buffer Reference to string buffer for SQL queries.
00253     */
00254     function prepareSqlQuery( &$fd, &$buffer )
00255     {
00256 
00257         $sqlQueryArray = array();
00258         while( count( $sqlQueryArray ) == 0 && !feof( $fd ) )
00259         {
00260             $buffer  .= fread( $fd, 4096 );
00261             if ( $buffer )
00262             {
00263                 // Fix SQL file by deleting all comments and newlines
00264 //            eZDebug::writeDebug( $buffer, "read data" );
00265                 $sqlQuery = preg_replace( array( "/^#.*\n" . "/m",
00266                                                  "#^/\*.*\*/\n" . "#m",
00267                                                  "/^--.*\n" . "/m",
00268                                                  "/\n|\r\n|\r/m" ),
00269                                           array( "",
00270                                                  "",
00271                                                  "",
00272                                                  "\n" ),
00273                                           $buffer );
00274 //            eZDebug::writeDebug( $sqlQuery, "read data" );
00275 
00276                 // Split the query into an array
00277                 $sqlQueryArray = preg_split( "/;\n/m", $sqlQuery );
00278 
00279                 if ( preg_match( '/;\n/m', $sqlQueryArray[ count( $sqlQueryArray ) -1 ] ) )
00280                 {
00281                     $buffer = '';
00282                 }
00283                 else
00284                 {
00285                     $buffer = $sqlQueryArray[ count( $sqlQueryArray ) -1 ];
00286                     array_splice( $sqlQueryArray, count( $sqlQueryArray ) -1 , 1 );
00287                 }
00288             }
00289             else
00290             {
00291                 return $sqlQueryArray;
00292 
00293             }
00294         }
00295         return $sqlQueryArray;
00296     }
00297 
00298     /*!
00299      Inserts the SQL file \a $sqlFile found in the path \a $path into
00300      the currently connected database.
00301      \return \c true if succesful.
00302     */
00303     function insertFile( $path, $sqlFile, $usePathType = true )
00304     {
00305         $type = $this->databaseName();
00306 
00307         //include_once( 'lib/ezfile/classes/ezdir.php' );
00308         if ( $usePathType )
00309             $sqlFileName = eZDir::path( array( $path, $type, $sqlFile ) );
00310         else
00311             $sqlFileName = eZDir::path( array( $path, $sqlFile ) );
00312         $sqlFileHandler = fopen( $sqlFileName, 'rb' );
00313         $buffer = '';
00314         $done = false;
00315         while ( count( ( $sqlArray = $this->prepareSqlQuery( $sqlFileHandler, $buffer ) ) ) > 0 )
00316         {
00317             // Turn unneccessary SQL debug output off
00318             $oldOutputSQL = $this->OutputSQL;
00319             $this->OutputSQL = false;
00320             if ( $sqlArray && is_array( $sqlArray ) )
00321             {
00322                 $done = true;
00323                 foreach( $sqlArray as $singleQuery )
00324                 {
00325                     $singleQuery = preg_replace( "/\n|\r\n|\r/", " ", $singleQuery );
00326                     if ( preg_match( "#^ */(.+)$#", $singleQuery, $matches ) )
00327                     {
00328                         $singleQuery = $matches[1];
00329                     }
00330                     if ( trim( $singleQuery ) != "" )
00331                     {
00332 //                    eZDebug::writeDebug( $singleQuery );
00333                         $this->query( trim( $singleQuery ) );
00334                         if ( $this->errorNumber() )
00335                         {
00336                             return false;
00337                         }
00338                     }
00339                 }
00340 
00341             }
00342             $this->OutputSQL = $oldOutputSQL;
00343         }
00344         return $done;
00345 
00346     }
00347 
00348     /*!
00349      \private
00350      Writes a debug notice with query information.
00351     */
00352     function reportQuery( $class, $sql, $numRows, $timeTaken )
00353     {
00354         $rowText = '';
00355         if ( $numRows !== false ) $rowText = "$numRows rows, ";
00356 
00357         $backgroundClass = ($this->TransactionCounter > 0  ? "debugtransaction transactionlevel-$this->TransactionCounter" : "");
00358         eZDebug::writeNotice( "$sql", "$class::query($rowText" . number_format( $timeTaken, 3 ) . " ms) query number per page:" . $this->NumQueries++, $backgroundClass );
00359     }
00360 
00361     /*!
00362      Enabled or disables sql output.
00363     */
00364     function setIsSQLOutputEnabled( $enabled )
00365     {
00366         $this->OutputSQL = $enabled;
00367     }
00368 
00369     /*!
00370      \private
00371      Records the current micro time. End the timer with endTimer() and
00372      fetch the result with timeTaken();
00373     */
00374     function startTimer()
00375     {
00376         $this->StartTime = microtime( true );
00377     }
00378 
00379     /*!
00380      \private
00381      Stops the current timer and calculates the time taken.
00382      \sa startTimer, timeTaken
00383     */
00384     function endTimer()
00385     {
00386         $this->EndTime = microtime( true );
00387         // Calculate time taken in ms
00388         $this->TimeTaken = $this->EndTime - $this->StartTime;
00389         $this->TimeTaken *= 1000.0;
00390     }
00391 
00392     /*!
00393      \private
00394      \return the micro time when the timer was start or false if no timer.
00395     */
00396     function startTime()
00397     {
00398         return $this->StartTime;
00399     }
00400 
00401     /*!
00402      \private
00403      \return the micro time when the timer was ended or false if no timer.
00404     */
00405     function endTime()
00406     {
00407         return $this->EndTime;
00408     }
00409 
00410     /*!
00411      \private
00412      \return the number of milliseconds the last operation took or false if no value.
00413     */
00414     function timeTaken()
00415     {
00416         return $this->TimeTaken;
00417     }
00418 
00419     /*!
00420      \pure
00421      Returns the name of driver, this is used to determine the name of the database type.
00422      For instance multiple implementations of the MySQL database will all return \c 'mysql'.
00423     */
00424     function databaseName()
00425     {
00426         return '';
00427     }
00428 
00429     /*!
00430      \return the socket path for the database or \c false if no socket path was defined.
00431     */
00432     function socketPath()
00433     {
00434         return $this->SocketPath;
00435     }
00436 
00437     /*!
00438      \return the number of times the db handler should try to reconnect if it fails.
00439     */
00440     function connectRetryCount()
00441     {
00442         return $this->ConnectRetries;
00443     }
00444 
00445     /*!
00446      \return the number of seconds the db handler should wait before rereconnecting.
00447      \note Currently returns 3 seconds.
00448     */
00449     function connectRetryWaitTime()
00450     {
00451         return 3;
00452     }
00453 
00454     /*!
00455      \pure
00456      \return a mask of the relation type it supports.
00457     */
00458     function supportedRelationTypeMask()
00459     {
00460         return eZDBInterface::RELATION_NONE;
00461     }
00462 
00463     /*!
00464      \pure
00465      \return if the short column names should be used insted of default ones
00466     */
00467     function useShortNames()
00468     {
00469         return false;
00470     }
00471 
00472     /*!
00473      \pure
00474      \return an array of the relation types.
00475     */
00476     function supportedRelationTypes()
00477     {
00478         return array();
00479     }
00480 
00481     /*!
00482      \pure
00483      \return a sql-expression(string) to get substring.
00484     */
00485     function subString( $string, $from, $len = null )
00486     {
00487         return '';
00488     }
00489 
00490     /*!
00491      \pure
00492      \return a sql-expression(string) to concatenate strings.
00493     */
00494     function concatString( $strings = array() )
00495     {
00496         return '';
00497     }
00498 
00499     /*!
00500      \pure
00501      \return a sql-expression(string) to generate a md5 sum of the string.
00502     */
00503     function md5( $str )
00504     {
00505         return '';
00506     }
00507 
00508     /*!
00509      \pure
00510      \return a sql-expression(string) to generate a bit and  of the argument.
00511     */
00512     function bitAnd( $arg1, $arg2 )
00513     {
00514         return '(' . $arg1 . ' & ' . $arg2 . ' ) ';
00515     }
00516 
00517     /*!
00518      \pure
00519      \return a sql-expression(string) to generate a bit and  of the argument.
00520     */
00521     function bitOr( $arg1, $arg2 )
00522     {
00523         return '( ' . $arg1 . ' | ' . $arg2 . ' ) ';
00524     }
00525 
00526     /*!
00527      Checks if the version number of the server is equal or larger than \a $minVersion.
00528      Will also check if the database type is correct if \a $name is set.
00529 
00530      \param $minVersion A string denoting the min. required version.
00531      \param $name The name of the database type it requires or \c false if it does not matter.
00532      \return \c true if the server fulfills the requirements.
00533     */
00534     function hasRequiredServerVersion( $minVersion, $name = false )
00535     {
00536         if ( $name !== false and
00537              $name != $this->databaseName() )
00538             return false;
00539 
00540         $version = $this->databaseServerVersion();
00541         $version = $version['string'];
00542         return version_compare( $version, $minVersion ) >= 0;
00543     }
00544 
00545     /*!
00546      \virtual
00547      \return the version of the database server or \c false if no version could be retrieved/
00548     */
00549     function databaseServerVersion()
00550     {
00551     }
00552 
00553     /*!
00554      \pure
00555      \return the version of the database client or \c false if no version could be retrieved/
00556     */
00557     function databaseClientVersion()
00558     {
00559     }
00560 
00561     /*!
00562      \return \c true if the charset \a $charset is supported by the connected database.
00563     */
00564     function isCharsetSupported( $charset )
00565     {
00566         return false;
00567     }
00568 
00569     /*!
00570      Returns the charset which the database is encoded in.
00571      \sa usesBuiltinEncoding
00572     */
00573     function charset()
00574     {
00575         return $this->Charset;
00576     }
00577 
00578     /*!
00579      Returns true if the database handles encoding itself, if not
00580      all queries and returned data must be decoded yourselves.
00581      \note This functionality might be removed in the future
00582     */
00583     function usesBuiltinEncoding()
00584     {
00585         return $this->UseBuiltinEncoding;
00586     }
00587 
00588     /*!
00589       \pure
00590        Returns type of binding used in database plugin.
00591     */
00592     function bindingType( )
00593     {
00594     }
00595 
00596     /*!
00597       \pure
00598        Binds variable.
00599     */
00600     function bindVariable( $value, $fieldDef = false )
00601     {
00602     }
00603 
00604     /*!
00605       \pure
00606       Execute a query on the global MySQL database link.  If it returns an error,
00607       the script is halted and the attempted SQL query and MySQL error message are printed.
00608 
00609       \param $sql SQL query to execute.
00610     */
00611     function query( $sql, $server = false )
00612     {
00613     }
00614 
00615     /*!
00616       \pure
00617       Executes an SQL query and returns the result as an array of accociative arrays.
00618 
00619       \param $sql SQL query to execute.
00620       \param $params Associative array containing extra parameters, can contain:
00621              - offset - The offset of the query.
00622              - limit - The limit of the query.
00623              - column - Limit returned row arrays to only contain this column.
00624       \param $server Which server to execute the query on, either eZDBInterface::SERVER_MASTER or eZDBInterface::SERVER_SLAVE
00625 
00626       An example would be:
00627       \code
00628       $db->arrayQuery( 'SELECT * FROM eztable', array( 'limit' => 10, 'offset' => 5 ) );
00629       \endcode
00630     */
00631     function arrayQuery( $sql, $params = array(), $server = false )
00632     {
00633     }
00634 
00635     /*!
00636       \pure
00637       Locks a table
00638     */
00639     function lock( $table )
00640     {
00641     }
00642 
00643     /*!
00644       \pure
00645       Releases table locks.
00646     */
00647     function unlock()
00648     {
00649     }
00650 
00651     /*!
00652       Begin a new transaction. If we are already in transaction then we omit
00653       this new transaction and its matching commit or rollback.
00654     */
00655     function begin()
00656     {
00657         $ini = eZINI::instance();
00658         if ($ini->variable( "DatabaseSettings", "Transactions" ) == "enabled")
00659         {
00660             if ( $this->TransactionCounter > 0 )
00661             {
00662                 if ( is_array( $this->TransactionStackTree ) )
00663                 {
00664                     // Make a new sub-level for debugging
00665                     $bt = debug_backtrace();
00666                     $subLevels =& $this->TransactionStackTree['sub_levels'];
00667                     for ( $i = 1; $i < $this->TransactionCounter; ++$i )
00668                     {
00669                         $subLevels =& $subLevels[count( $subLevels ) - 1]['sub_levels'];
00670                     }
00671                     // Next entry will be at the end
00672                     $subLevels[count( $subLevels )] = array( 'level' => $this->TransactionCounter,
00673                                                              'trace' => $bt,
00674                                                              'sub_levels' => array() );
00675                 }
00676                 ++$this->TransactionCounter;
00677                 return false;
00678             }
00679             else
00680             {
00681                 if ( is_array( $this->TransactionStackTree ) )
00682                 {
00683                     // Start new stack tree for debugging
00684                     $bt = debug_backtrace();
00685                     $this->TransactionStackTree = array( 'level' => $this->TransactionCounter,
00686                                                          'trace' => $bt,
00687                                                          'sub_levels' => array() );
00688                 }
00689             }
00690             $this->TransactionIsValid = true;
00691 
00692             if ( $this->isConnected() )
00693             {
00694                 $oldRecordError = $this->RecordError;
00695                 // Turn off error handling while we begin
00696                 $this->RecordError = false;
00697                 $this->beginQuery();
00698                 $this->RecordError = $oldRecordError;
00699 
00700                 // We update the transaction counter after the query, otherwise we
00701                 // mess up the debug background highlighting.
00702                 ++$this->TransactionCounter;
00703             }
00704         }
00705         return true;
00706     }
00707 
00708     /*!
00709       \virtual
00710       The query to start a transaction.
00711       This function must be reimplemented in the subclasses.
00712     */
00713      function beginQuery()
00714     {
00715         return false;
00716     }
00717 
00718     /*!
00719       Commits the current transaction. If this is not the outermost it will not commit
00720       to the database immediately but instead decrease the transaction counter.
00721 
00722       If the current transaction had any errors in it the transaction will be rollbacked
00723       instead of commited. This ensures that the database is in a valid state.
00724       Also the PHP execution will be stopped.
00725 
00726       \return \c true if the transaction was successful, \c false otherwise.
00727     */
00728     function commit()
00729     {
00730         $ini = eZINI::instance();
00731         if ($ini->variable( "DatabaseSettings", "Transactions" ) == "enabled")
00732         {
00733             if ( $this->TransactionCounter <= 0 )
00734             {
00735                 eZDebug::writeError( 'No transaction in progress, cannot commit', 'eZDBInterface::commit' );
00736                 return false;
00737             }
00738 
00739             --$this->TransactionCounter;
00740             if ( $this->TransactionCounter == 0 )
00741             {
00742                 if ( is_array( $this->TransactionStackTree ) )
00743                 {
00744                     // Reset the stack debug tree since the top commit was done
00745                     $this->TransactionStackTree = array();
00746                 }
00747                 if ( $this->isConnected() )
00748                 {
00749                     // Check if we have encountered any problems, if so we have to rollback
00750                     if ( !$this->TransactionIsValid )
00751                     {
00752                         $oldRecordError = $this->RecordError;
00753                         // Turn off error handling while we rollback
00754                         $this->RecordError = false;
00755                         $this->rollbackQuery();
00756                         $this->RecordError = $oldRecordError;
00757 
00758                         return false;
00759                     }
00760                     else
00761                     {
00762                         $oldRecordError = $this->RecordError;
00763                         // Turn off error handling while we commit
00764                         $this->RecordError = false;
00765                         $this->commitQuery();
00766                         $this->RecordError = $oldRecordError;
00767                     }
00768                 }
00769             }
00770             else
00771             {
00772                 if ( is_array( $this->TransactionStackTree ) )
00773                 {
00774                     // Close the last open nested transaction
00775                     $bt = debug_backtrace();
00776                     // Store commit trace
00777                     $subLevels =& $this->TransactionStackTree['sub_levels'];
00778                     for ( $i = 1; $i < $this->TransactionCounter; ++$i )
00779                     {
00780                         $subLevels =& $subLevels[count( $subLevels ) - 1]['sub_levels'];
00781                     }
00782                     // Find last entry and add the commit trace
00783                     $subLevels[count( $subLevels ) - 1]['commit_trace'] = $bt;
00784                 }
00785             }
00786         }
00787         return true;
00788     }
00789 
00790     /*!
00791       \virtual
00792       The query to commit the transaction.
00793       This function must be reimplemented in the subclasses.
00794     */
00795     function commitQuery()
00796     {
00797         return false;
00798     }
00799 
00800     /*!
00801       Cancels the transaction.
00802     */
00803     function rollback()
00804     {
00805         if ( is_array( $this->TransactionStackTree ) )
00806         {
00807             // All transactions were rollbacked, reset the tree.
00808             $this->TransactionStackTree = array();
00809         }
00810         $ini = eZINI::instance();
00811         if ($ini->variable( "DatabaseSettings", "Transactions" ) == "enabled")
00812         {
00813             if ( $this->TransactionCounter <= 0 )
00814             {
00815                 eZDebug::writeError( 'No transaction in progress, cannot rollback', 'eZDBInterface::rollback' );
00816                 return false;
00817             }
00818             // Reset the transaction counter
00819             $this->TransactionCounter = 0;
00820             if ( $this->isConnected() )
00821             {
00822                 $oldRecordError = $this->RecordError;
00823                 // Turn off error handling while we rollback
00824                 $this->RecordError = false;
00825                 $this->rollbackQuery();
00826                 $this->RecordError = $oldRecordError;
00827             }
00828         }
00829         return true;
00830     }
00831 
00832     /*!
00833       Goes through the transaction stack tree $this->TransactionStackTree and
00834       generates the text output for it and returns it.
00835       \returns The generated string or false if it is disabled.
00836     */
00837     function generateFailedTransactionStack()
00838     {
00839         if ( !$this->TransactionStackTree )
00840         {
00841             return false;
00842         }
00843         return $this->generateFailedTransactionStackEntry( $this->TransactionStackTree, 0 );
00844     }
00845 
00846     /*!
00847      \private
00848      Recursive helper function for generating stack tree output.
00849      \returns The generated string
00850      */
00851     function generateFailedTransactionStackEntry( $stack, $indentCount )
00852     {
00853         $stackText = '';
00854         $indent = '';
00855         if ( $indentCount > 0 )
00856         {
00857             $indent = str_repeat( " ", $indentCount*4 );
00858         }
00859         $stackText .= $indent . "Level " . $stack['level'] . "\n" . $indent . "{" . $indent . "\n";
00860         $stackText .= $indent . "  Began at:\n";
00861         for ( $i = 0; $i < 2 && $i < count( $stack['trace'] ); ++$i )
00862         {
00863             $indent2 = str_repeat( "  ", $i + 1 );
00864             if ( $i > 0 )
00865             {
00866                 $indent2 .= "->";
00867             }
00868             $stackText .= $indent . $indent2 . $this->generateTraceEntry( $stack['trace'][$i] );
00869             $stackText .= "\n";
00870         }
00871         foreach ( $stack['sub_levels'] as $subStack )
00872         {
00873             $stackText .= $this->generateFailedTransactionStackEntry( $subStack, $indentCount + 1 );
00874         }
00875         if ( isset( $stack['commit_trace'] ) )
00876         {
00877             $stackText .= $indent . "  And commited at:\n";
00878             for ( $i = 0; $i < 2 && $i < count( $stack['commit_trace'] ); ++$i )
00879             {
00880                 $indent2 = str_repeat( "  ", $i + 1 );
00881                 if ( $i > 0 )
00882                 {
00883                     $indent2 .= "->";
00884                 }
00885                 $stackText .= $indent . $indent2 . $this->generateTraceEntry( $stack['commit_trace'][$i] );
00886                 $stackText .= "\n";
00887             }
00888         }
00889         $stackText .= $indent . "}" . "\n";
00890         return $stackText;
00891     }
00892 
00893     /*!
00894      \private
00895      Helper function for generating output for one stack-trace entry.
00896      \returns The generated string
00897      */
00898     function generateTraceEntry( $entry )
00899     {
00900         if ( isset( $entry['file'] ) )
00901         {
00902             $stackText = $entry['file'];
00903         }
00904         else
00905         {
00906             $stackText = "???";
00907         }
00908         $stackText .= ":";
00909         if ( isset( $entry['line'] ) )
00910         {
00911             $stackText .= $entry['line'];
00912         }
00913         else
00914         {
00915             $stackText .= "???";
00916         }
00917         $stackText .= " ";
00918         if ( isset( $entry['class'] ) )
00919         {
00920             $stackText .= $entry['class'];
00921         }
00922         else
00923         {
00924             $stackText .= "???";
00925         }
00926         $stackText .= "::";
00927         if ( isset( $entry['function'] ) )
00928         {
00929             $stackText .= $entry['function'];
00930         }
00931         else
00932         {
00933             $stackText .= "???";
00934         }
00935         return $stackText;
00936     }
00937 
00938     /*!
00939       \virtual
00940       The query to cancel the transaction.
00941       This function must be reimplemented in the subclasses.
00942     */
00943     function rollbackQuery()
00944     {
00945         return false;
00946     }
00947 
00948     /*!
00949       Invalidates the current transaction, see commit() for more details on this.
00950       \return \c true if it was invalidated or \c false if there is no transaction to invalidate.
00951 
00952       \sa isTransactionValid()
00953     */
00954     function invalidateTransaction()
00955     {
00956         if ( $this->TransactionCounter <= 0 )
00957             return false;
00958         $this->TransactionIsValid = false;
00959         return true;
00960     }
00961 
00962     /*!
00963      \protected
00964      This is called whenever an error occurs in one of the database handlers.
00965      If a transaction is active it will be invalidated as well.
00966     */
00967     function reportError()
00968     {
00969         // If we have a running transaction we must mark as invalid
00970         // in which case a call to commit() will perform a rollback
00971         if ( $this->TransactionCounter > 0 )
00972         {
00973             $this->invalidateTransaction();
00974 
00975             // This is the unique ID for this incidence which will also be placed in the error logs.
00976             $transID = 'TRANSID-' . md5( time() . mt_rand() );
00977 
00978             eZDebug::writeError( 'Transaction in progress failed due to DB error, transaction was rollbacked. Transaction ID is ' . $transID . '.', 'eZDBInterface::commit ' . $transID );
00979 
00980             $oldRecordError = $this->RecordError;
00981             // Turn off error handling while we rollback
00982             $this->RecordError = false;
00983             $this->rollbackQuery();
00984             $this->RecordError = $oldRecordError;
00985 
00986             // Stop execution immediately while allowing other systems (session etc.) to cleanup
00987             require_once( 'lib/ezutils/classes/ezexecution.php' );
00988             eZExecution::cleanup();
00989             eZExecution::setCleanExit();
00990 
00991             // Give some feedback, and also possibly show the debug output
00992             eZDebug::setHandleType( eZDebug::HANDLE_NONE );
00993 
00994             $ini = eZINI::instance();
00995             $adminEmail = $ini->variable( 'MailSettings', 'AdminEmail' );
00996             //include_once( 'lib/ezutils/classes/ezsys.php' );
00997 
00998             if ( !eZSys::isShellExecution() )
00999             {
01000                 $site = eZSys::serverVariable( 'HTTP_HOST' );
01001                 $uri = eZSys::serverVariable( 'REQUEST_URI' );
01002 
01003                 print( "<div class=\"fatal-error\" style=\"" );
01004                 print( 'margin: 0.5em 0 1em 0; ' .
01005                        'padding: 0.25em 1em 0.75em 1em;' .
01006                        'border: 4px solid #000000;' .
01007                        'background-color: #f8f8f4;' .
01008                        'border-color: #f95038;" >' );
01009                 print( "<b>Fatal error</b>: A database transaction in eZ Publish failed.<br/>" );
01010                 print( "<p>" );
01011                 print( "The current execution was stopped to prevent further problems.<br/>\n" .
01012                        "You should contact the <a href=\"mailto:$adminEmail?subject=Transaction failed on $site and URI $uri with ID $transID\">System Administrator</a> of this site with the information on this page.<br/>\n" .
01013                        "The current transaction ID is <b>$transID</b> and has been logged.<br/>\n" .
01014                        "Please include the transaction ID and the current URL when contacting the system administrator.<br/>\n" );
01015                 print( "</p>" );
01016                 print( "</div>" );
01017 
01018                 $templateResult = null;
01019                 if ( function_exists( 'eZDisplayResult' ) )
01020                 {
01021                     eZDisplayResult( $templateResult );
01022                 }
01023             }
01024             else
01025             {
01026                 fputs( STDERR,"Fatal error: A database transaction in eZ Publish failed.\n" );
01027                 fputs( STDERR, "\n" );
01028                 fputs( STDERR, "The current execution was stopped to prevent further problems.\n" .
01029                        "You should contact the System Administrator ($adminEmail) of this site.\n" .
01030                        "The current transaction ID is $transID and has been logged.\n" .
01031                        "Please include the transaction ID and the name of the current script when contacting the system administrator.\n" );
01032                 fputs( STDERR, "\n" );
01033 
01034                 fputs( STDERR, eZDebug::printReport( false, false, true ) );
01035             }
01036 
01037             // PHP execution stops here
01038             exit( 1 );
01039         }
01040     }
01041 
01042     /*!
01043       \return \c true if the current or last running transaction was valid,
01044               \c false otherwise.
01045       \sa invalidateTransaction()
01046     */
01047     function isTransactionValid()
01048     {
01049         return $this->TransactionIsValid;
01050     }
01051 
01052     /*!
01053      \return The current transaction counter.
01054 
01055      0 means no transactions, 1 or higher means 1 or more transactions
01056      are running and a negative value means something is wrong.
01057     */
01058     function transactionCounter()
01059     {
01060         return $this->TransactionCounter;
01061     }
01062 
01063     /*!
01064       \pure
01065       \return the relation count for all relation types in the mask \a $relationMask.
01066     */
01067     function relationCounts( $relationMask )
01068     {
01069     }
01070 
01071     /*!
01072       \pure
01073       \return the number of relation objects in the database for the relation type \a $relationType.
01074     */
01075     function relationCount( $relationType = eZDBInterface::RELATION_TABLE )
01076     {
01077     }
01078 
01079     /*!
01080      \pure
01081      \return existing ez publish tables in database
01082     */
01083     function eZTableList( $server = self::SERVER_MASTER )
01084     {
01085     }
01086 
01087     /*!
01088       \pure
01089       \return the relation names in the database as an array for the relation type \a $relationType.
01090     */
01091     function relationList( $relationType = eZDBInterface::RELATION_TABLE )
01092     {
01093     }
01094 
01095     /*!
01096       \pure
01097       Tries to remove the relation type \a $relationType named \a $relationName
01098       \return \c true if successful
01099     */
01100     function removeRelation( $relationName, $relationType )
01101     {
01102         return false;
01103     }
01104 
01105     /*!
01106      \protected
01107      \return the name of the relation type which is usable in SQL or false if unknown type.
01108      \note This function can be used by som database handlers which can operate on relation types using SQL.
01109     */
01110     function relationName( $relationType )
01111     {
01112         $names = array( eZDBInterface::RELATION_TABLE => 'TABLE',
01113                         eZDBInterface::RELATION_SEQUENCE => 'SEQUENCE',
01114                         eZDBInterface::RELATION_TRIGGER => 'TRIGGER',
01115                         eZDBInterface::RELATION_VIEW => 'VIEW',
01116                         eZDBInterface::RELATION_INDEX => 'INDEX' );
01117         if ( !isset( $names[$relationType] ) )
01118             return false;
01119         return $names[$relationType];
01120     }
01121 
01122     /*!
01123      \pure
01124      \return A regexp (PCRE) that can be used to filter out certain relation elements.
01125              If no special regexp is provided it will return \c false.
01126      \param $relationType The type which needs to be filtered, this allows one regexp per type.
01127 
01128      An example, will only match tables that start with 'ez'.
01129      \code
01130      return "#^ez#";
01131      \endcode
01132 
01133      \note This function is currently used by the eZDBTool class to remove relation elements
01134            of a specific kind (Most likely eZ Publish related elements).
01135     */
01136     function relationMatchRegexp( $relationType )
01137     {
01138         return false;
01139     }
01140 
01141     /*!
01142       Casts elements of \a $pieces to type \a $type and returns them as string separated by \a $glue.
01143 
01144       \param $glue The separator.
01145              $pieces The array containing the elements.
01146              $type The type to cast to.
01147 
01148       Example:
01149       \code
01150       implodeWithTypeCast( ',', $myArray, 'int' )
01151       \endcode
01152 
01153     */
01154     function implodeWithTypeCast( $glue, &$pieces, $type )
01155     {
01156         $str = '';
01157         if ( !is_array( $pieces ) )
01158             return $str;
01159 
01160         foreach( $pieces as $piece )
01161         {
01162             settype( $piece, $type );
01163             $str .= $piece.$glue;
01164         }
01165         $str = rtrim( $str, $glue );
01166         return $str;
01167     }
01168 
01169     /*!
01170       \pure
01171       Returns the last serial ID generated with an auto increment field.
01172     */
01173     function lastSerialID( $table = false, $column = false )
01174     {
01175     }
01176 
01177     /*!
01178       \pure
01179       Will escape a string so it's ready to be inserted in the database.
01180     */
01181     function escapeString( $str )
01182     {
01183         return $str;
01184     }
01185 
01186     /*!
01187       \pure
01188       Will close the database connection.
01189     */
01190     function close()
01191     {
01192     }
01193 
01194     /*!
01195       \protected
01196       Returns true if we're connected to the database backend.
01197     */
01198     function isConnected()
01199     {
01200         return $this->IsConnected;
01201     }
01202 
01203     /*!
01204       \pure
01205       Create a new database
01206     */
01207     function createDatabase( $dbName )
01208     {
01209     }
01210 
01211     /*!
01212       Create a new temporary table
01213     */
01214     function createTempTable( $createTableQuery = '', $server = self::SERVER_SLAVE )
01215     {
01216         $this->query( $createTableQuery, $server );
01217     }
01218 
01219     /*!
01220       Drop temporary table
01221     */
01222     function dropTempTable( $dropTableQuery = '', $server = self::SERVER_SLAVE )
01223     {
01224         $this->query( $dropTableQuery, $server );
01225     }
01226 
01227     /*!
01228       Drop temporary table list
01229     */
01230     function dropTempTableList( $tableList, $server = self::SERVER_SLAVE )
01231     {
01232         foreach( $tableList as $tableName )
01233             $this->dropTempTable( "DROP TABLE $tableName", $server );
01234     }
01235 
01236     /*!
01237       \pure
01238       Sets the error message and error message number
01239     */
01240     function setError()
01241     {
01242     }
01243 
01244     /*!
01245       Returns the error message
01246     */
01247     function errorMessage()
01248     {
01249         return $this->ErrorMessage;
01250     }
01251 
01252     /*!
01253       Returns the error number
01254     */
01255     function errorNumber()
01256     {
01257         return $this->ErrorNumber;
01258     }
01259 
01260     /*!
01261       Return alvailable databases in database.
01262 
01263       \return array of available databases,
01264               null of none available
01265               false if listing databases not supported by database
01266     */
01267     function availableDatabases()
01268     {
01269         return false;
01270     }
01271 
01272     /*!
01273      Generate unique table name basing on the given pattern.
01274      If the pattern contains a (%) character then the character
01275      is replaced with a part providing uniqueness (e.g. random number).
01276     */
01277     function generateUniqueTempTableName( $pattern, $randomizeIndex = false, $server = self::SERVER_SLAVE )
01278     {
01279         $tableList = array_keys( $this->eZTableList( $server ) );
01280         if ( $randomizeIndex === false )
01281         {
01282             $randomizeIndex = rand( 10, 1000 );
01283         }
01284         do
01285         {
01286             $uniqueTempTableName = str_replace( '%', $randomizeIndex, $pattern );
01287             $randomizeIndex++;
01288         } while ( in_array( $uniqueTempTableName, $tableList ) );
01289 
01290         return $uniqueTempTableName;
01291     }
01292 
01293     /*!
01294       Get database version number
01295 
01296       \return version number
01297               false if not supported
01298     */
01299     function version()
01300     {
01301         return false;
01302     }
01303 
01304     /*!
01305      \static
01306 
01307      This function can be used to create a SQL IN statement to be used in a WHERE clause:
01308 
01309      WHERE columnName IN ( element1, element2, ... )
01310 
01311      By default, the elements that can be submitted as an anonymous array (or an integer value
01312      in case of a single element) will just be imploded. Drivers for databases with a limitation
01313      of the elements within an IN statement have to reimplement this function. It is also possible
01314      to negate the "IN" to a "NOT IN" by using the last parameter of this function.
01315 
01316      Usage:
01317 
01318      $db =& eZDb::instance();
01319      $db->generateSQLINStatement( array( 2, 5, 43, ) );
01320 
01321      \param $elements   Elements that should (not) be matched by the IN statment as an integer or anonymous array
01322      \param $columnName Column name of the database table the IN statement should be created for
01323      \param $not        Will generate a "NOT IN" ( if set to \c true ) statement instead of an "IN" ( if set to
01324                         \c false , default )
01325      \param $unique
01326      \param $type      The type to cast the array elements to
01327 
01328      \return A string with the correct IN statement like for example
01329              "columnName IN ( element1, element2 )"
01330      */
01331     function generateSQLINStatement( $elements, $columnName = '', $not = false, $unique = true, $type = false )
01332     {
01333         $result    = '';
01334         $statement = $columnName . ' IN';
01335         if ( $not === true )
01336         {
01337             $statement = $columnName . ' NOT IN';
01338         }
01339 
01340         if ( !is_array( $elements ) )
01341         {
01342             $elements = array( $elements );
01343         }
01344 
01345         $impString = $type ? $this->implodeWithTypeCast( ', ', $elements, $type ) : implode( ', ', $elements );
01346         $result = $statement . ' ( ' . $impString . ' )';
01347 
01348         return $result;
01349     }
01350 
01351     function supportsDefaultValuesInsertion()
01352     {
01353         return true;
01354     }
01355 
01356     /// \protectedsection
01357     /// Contains the current server
01358     public $Server;
01359     /// Contains the current port
01360     public $Port;
01361     /// The socket path, used by MySQL
01362     public $SocketPath;
01363     /// The current database name
01364     public $DB;
01365     /// The current connection, \c false if not connection has been made
01366     public $DBConnection;
01367     /// Contains the write database connection if used
01368     public $DBWriteConnection;
01369     /// Stores the database connection user
01370     public $User;
01371     /// Stores the database connection password
01372     public $Password;
01373     /// The charset used for the current database
01374     public $Charset;
01375     /// The number of times to retry a connection if it fails
01376     public $ConnectRetries;
01377     /// Instance of a textcodec which handles text conversion, may not be set if no builtin encoding is used
01378     public $OutputTextCodec;
01379     public $InputTextCodec;
01380 
01381     /// True if a builtin encoder is to be used, this means that all input/output text is converted
01382     public $UseBuiltinEncoding;
01383     /// Setting if SQL queries should be sent to debug output
01384     public $OutputSQL;
01385     /// Contains true if we're connected to the database backend
01386     public $IsConnected = false;
01387     /// Contains number of queries sended to DB
01388     public $NumQueries = 0;
01389     /// The start time of the timer
01390     public $StartTime;
01391     /// The end time of the tiemr
01392     public $EndTime;
01393     /// The total number of milliseconds the timer took
01394     public $TimeTaken;
01395     /// The database error message of the last executed function
01396     public $ErrorMessage;
01397     /// The database error message number of the last executed function
01398     public $ErrorNumber = 0;
01399     /// If true then ErrorMessage and ErrorNumber get filled
01400     public $RecordError = true;
01401     /// If true then the database connection should be persistent
01402     public $UsePersistentConnection = false;
01403     /// Contains true if slave servers are enabled
01404     public $UserSlaveServer;
01405     /// The slave database name
01406     public $SlaveDB;
01407     /// The slave server name
01408     public $SlaveServer;
01409     /// The slave server port
01410     public $SlavePort;
01411     /// The slave database user
01412     public $SlaveUser;
01413     /// The slave database user password
01414     public $SlavePassword;
01415     /// The transaction counter, 0 means no transaction
01416     public $TransactionCounter;
01417     /// Flag which tells if a transaction is considered valid or not
01418     /// A transaction will be made invalid if SQL errors occur
01419     public $TransactionIsValid;
01420 }
01421 
01422 ?>