eZ Publish  [4.0]
mysql.php
Go to the documentation of this file.
00001 <?php
00002 //
00003 // Definition of eZDBFileHandlerMysqlBackend class
00004 //
00005 // Created on: <19-Apr-2006 16:15:17 vs>
00006 //
00007 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00008 // SOFTWARE NAME: eZ Publish
00009 // SOFTWARE RELEASE: 4.0.x
00010 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS
00011 // SOFTWARE LICENSE: GNU General Public License v2.0
00012 // NOTICE: >
00013 //   This program is free software; you can redistribute it and/or
00014 //   modify it under the terms of version 2.0  of the GNU General
00015 //   Public License as published by the Free Software Foundation.
00016 //
00017 //   This program is distributed in the hope that it will be useful,
00018 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020 //   GNU General Public License for more details.
00021 //
00022 //   You should have received a copy of version 2.0 of the GNU General
00023 //   Public License along with this program; if not, write to the Free
00024 //   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00025 //   MA 02110-1301, USA.
00026 //
00027 //
00028 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00029 //
00030 
00031 /*! \file ezdbfilehandlermysqlbackend.php
00032 */
00033 
00034 define( 'TABLE_METADATA',     'ezdbfile' );
00035 define( 'TABLE_DATA',         'ezdbfile_data' );
00036 
00037 /*
00038 CREATE TABLE ezdbfile (
00039   datatype      VARCHAR(60)   NOT NULL DEFAULT 'application/octet-stream',
00040   name          TEXT          NOT NULL,
00041   name_trunk    TEXT          NOT NULL,
00042   name_hash     VARCHAR(34)   NOT NULL DEFAULT '',
00043   scope         VARCHAR(20)   NOT NULL DEFAULT '',
00044   size          BIGINT(20)    UNSIGNED NOT NULL,
00045   mtime         INT(11)       NOT NULL DEFAULT '0',
00046   expired       BOOL          NOT NULL DEFAULT '0',
00047   PRIMARY KEY (name_hash),
00048   INDEX ezdbfile_name (name(250)),
00049   INDEX ezdbfile_name_trunk (name_trunk(250)),
00050   INDEX ezdbfile_mtime (mtime),
00051   INDEX ezdbfile_expired_name (expired, name(250))
00052 ) ENGINE=InnoDB;
00053 
00054 
00055 CREATE TABLE ezdbfile_data (
00056   name_hash VARCHAR(34)   NOT NULL DEFAULT '',
00057   offset    INT(11) UNSIGNED NOT NULL,
00058   filedata  BLOB          NOT NULL,
00059   PRIMARY KEY (name_hash, offset),
00060   CONSTRAINT ezdbfile_fk1 FOREIGN KEY (name_hash) REFERENCES ezdbfile (name_hash) ON DELETE CASCADE
00061 ) ENGINE=InnoDB;
00062  */
00063 
00064 require_once( 'lib/ezutils/classes/ezdebugsetting.php' );
00065 require_once( 'lib/ezutils/classes/ezdebug.php' );
00066 
00067 class eZDBFileHandlerMysqlBackend
00068 {
00069     function _connect( $newLink = false )
00070     {
00071         if ( !isset( $GLOBALS['eZDBFileHandlerMysqlBackend_dbparams'] ) )
00072         {
00073             $siteINI = eZINI::instance( 'site.ini' );
00074             $fileINI = eZINI::instance( 'file.ini' );
00075 
00076             $params['host']       = $fileINI->variable( 'ClusteringSettings', 'DBHost' );
00077             $params['port']       = $fileINI->variable( 'ClusteringSettings', 'DBPort' );
00078             $params['socket']     = $fileINI->variable( 'ClusteringSettings', 'DBSocket' );
00079             $params['dbname']     = $fileINI->variable( 'ClusteringSettings', 'DBName' );
00080             $params['user']       = $fileINI->variable( 'ClusteringSettings', 'DBUser' );
00081             $params['pass']       = $fileINI->variable( 'ClusteringSettings', 'DBPassword' );
00082             $params['chunk_size'] = $fileINI->variable( 'ClusteringSettings', 'DBChunkSize' );
00083 
00084             $params['max_connect_tries'] = $fileINI->variable( 'ClusteringSettings', 'DBConnectRetries' );
00085             $params['max_execute_tries'] = $fileINI->variable( 'ClusteringSettings', 'DBExecuteRetries' );
00086 
00087             $params['sql_output'] = $siteINI->variable( "DatabaseSettings", "SQLOutput" ) == "enabled";
00088 
00089             $GLOBALS['eZDBFileHandlerMysqlBackend_dbparams'] = $params;
00090         }
00091         else
00092             $params = $GLOBALS['eZDBFileHandlerMysqlBackend_dbparams'];
00093         $this->dbparams = $params;
00094 
00095         $serverString = $params['host'];
00096         if ( $params['socket'] )
00097             $serverString .= ':' . $params['socket'];
00098         elseif ( $params['port'] )
00099             $serverString .= ':' . $params['port'];
00100 
00101         $maxTries = $params['max_connect_tries'];
00102         $tries = 0;
00103         while ( $tries < $maxTries )
00104         {
00105             if ( $this->db = mysql_connect( $serverString, $params['user'], $params['pass'], $newLink ) )
00106                 break;
00107             ++$tries;
00108         }
00109         if ( !$this->db )
00110             return $this->_die( "Unable to connect to storage server" );
00111 
00112         if ( !mysql_select_db( $params['dbname'], $this->db ) )
00113             return $this->_die( "Unable to select database {$params['dbname']}" );
00114     }
00115 
00116     function _copy( $srcFilePath, $dstFilePath, $fname = false )
00117     {
00118         if ( $fname )
00119             $fname .= "::_copy($srcFilePath, $dstFilePath)";
00120         else
00121             $fname = "_copy($srcFilePath, $dstFilePath)";
00122 
00123         // fetch source file metadata
00124         $metaData = $this->_fetchMetadata( $srcFilePath, $fname );
00125         if ( !$metaData ) // if source file does not exist then do nothing.
00126             return false;
00127         return $this->_protect( array( $this, "_copyInner" ), $fname,
00128                                 $srcFilePath, $dstFilePath, $fname, $metaData );
00129     }
00130 
00131     function _copyInner( $srcFilePath, $dstFilePath, $fname, $metaData )
00132     {
00133         $this->_delete( $dstFilePath, true, $fname );
00134 
00135         $datatype        = $metaData['datatype'];
00136         $filePathHash    = md5( $dstFilePath );
00137         $scope           = $metaData['scope'];
00138         $contentLength   = $metaData['size'];
00139         $fileMTime       = $metaData['mtime'];
00140         $nameTrunk       = self::nameTrunk( $dstFilePath, $scope );
00141 
00142         // Copy file metadata.
00143         if ( $this->_insertUpdate( TABLE_METADATA,
00144                                    array( 'datatype'=> $datatype,
00145                                           'name' => $dstFilePath,
00146                                           'name_trunk' => $nameTrunk,
00147                                           'name_hash' => $filePathHash,
00148                                           'scope' => $scope,
00149                                           'size' => $contentLength,
00150                                           'mtime' => $fileMTime,
00151                                           'expired' => ($fileMTime < 0) ? 1 : 0 ),
00152                                    "datatype=VALUES(datatype), scope=VALUES(scope), size=VALUES(size), mtime=VALUES(mtime), expired=VALUES(expired)",
00153                                    $fname ) === false )
00154         {
00155             return $this->_fail( $srcFilePath, "Failed to insert file metadata on copying." );
00156         }
00157 
00158         // Copy file data.
00159 
00160         $sql = "SELECT filedata, offset FROM " . TABLE_DATA . " WHERE name_hash=" . $this->_md5( $srcFilePath ) . " ORDER BY offset";
00161         if ( !$res = $this->_query( $sql, $fname ) )
00162         {
00163             eZDebug::writeError( "Failed to fetch source file '$srcFilePath' data on copying.", __METHOD__ );
00164             return false;
00165         }
00166 
00167         $offset = 0;
00168         while ( $row = mysql_fetch_row( $res ) )
00169         {
00170             // make the data mysql insert safe.
00171             $binarydata = $row[0];
00172             $expectedOffset = $row[1];
00173             if ( $expectedOffset != $offset )
00174             {
00175                 eZDebug::writeError( "The fetched offset value '$expectedOffset' does not match the computed one for the file '$srcFilePath', aborting copy.",
00176                                      __METHOD__ );
00177                 return false;
00178             }
00179 
00180             if ( $this->_insertUpdate( TABLE_DATA,
00181                                        array( 'name_hash' => $filePathHash,
00182                                               'offset' => $offset,
00183                                               'filedata' => $binarydata ),
00184                                        "filedata=VALUES(filedata)",
00185                                        $fname ) === false )
00186             {
00187                 return $this->_fail( "Failed to insert data row while copying file." );
00188             }
00189             $offset += strlen( $binarydata );
00190         }
00191         if ( $offset != $contentLength )
00192         {
00193             eZDebug::writeError( "The size of the fetched data '$offset' does not match the expected size '$contentLength' for the file '$srcFilePath', aborting copy.",
00194                                  __METHOD__ );
00195             return false;
00196         }
00197 
00198         // Get rid of unused/old offset data.
00199         $result = $this->_cleanupFiledata( $dstFilePath, $contentLength, $fname );
00200         if ( $this->_isFailure( $result ) )
00201             return $result;
00202 
00203         return true;
00204     }
00205 
00206     /*!
00207      Purges meta-data and file-data for the file entry named $filePath from the database.
00208      */
00209     function _purge( $filePath, $onlyExpired = false, $expiry = false, $fname = false )
00210     {
00211         if ( $fname )
00212             $fname .= "::_purge($filePath)";
00213         else
00214             $fname = "_purge($filePath)";
00215         $sql = "DELETE FROM " . TABLE_METADATA . " WHERE name_hash=" . $this->_md5( $filePath );
00216         if ( $expiry !== false )
00217             $sql .= " AND mtime < " . (int)$expiry;
00218         elseif ( $onlyExpired )
00219             $sql .= " AND expired = 1";
00220         if ( !$this->_query( $sql, $fname ) )
00221             return $this->_fail( "Purging file metadata for $filePath failed" );
00222         return true;
00223     }
00224 
00225     /*!
00226      Purges meta-data and file-data for the matching files.
00227      Matching is done by passing the string $like to the LIKE statement in the SQL.
00228      */
00229     function _purgeByLike( $like, $onlyExpired = false, $limit = 50, $expiry = false, $fname = false )
00230     {
00231         if ( $fname )
00232             $fname .= "::_purgeByLike($like, $onlyExpired)";
00233         else
00234             $fname = "_purgeByLike($like, $onlyExpired)";
00235         $sql = "DELETE FROM " . TABLE_METADATA . " WHERE name LIKE " . $this->_quote( $like );
00236         if ( $expiry !== false )
00237             $sql .= " AND mtime < " . (int)$expiry;
00238         elseif ( $onlyExpired )
00239             $sql .= " AND expired = 1";
00240         if ( $limit )
00241             $sql .= " LIMIT $limit";
00242         if ( !$this->_query( $sql, $fname ) )
00243             return $this->_fail( "Purging file metadata by like statement $like failed" );
00244         return mysql_affected_rows( $this->db );
00245     }
00246 
00247     function _delete( $filePath, $insideOfTransaction = false, $fname = false )
00248     {
00249         if ( $fname )
00250             $fname .= "::_delete($filePath)";
00251         else
00252             $fname = "_delete($filePath)";
00253         if ( $insideOfTransaction )
00254         {
00255             $res = $this->_deleteInner( $filePath, $fname );
00256             if ( !$res || $res instanceof eZMySQLBackendError )
00257             {
00258                 $this->_handleErrorType( $res );
00259             }
00260         }
00261         else
00262             return $this->_protect( array( $this, '_deleteInner' ), $fname,
00263                                     $filePath, $insideOfTransaction, $fname );
00264     }
00265 
00266     function _deleteInner( $filePath, $fname )
00267     {
00268         if ( !$this->_query( "UPDATE " . TABLE_METADATA . " SET mtime=-ABS(mtime), expired=1 WHERE name_hash=" . $this->_md5( $filePath ), $fname ) )
00269             return $this->_fail( "Deleting file $filePath failed" );
00270         return true;
00271     }
00272 
00273     function _deleteByLike( $like, $fname = false )
00274     {
00275         if ( $fname )
00276             $fname .= "::_deleteByLike($like)";
00277         else
00278             $fname = "_deleteByLike($like)";
00279         return $this->_protect( array( $this, '_deleteByLikeInner' ), $fname,
00280                                 $like, $fname );
00281     }
00282 
00283     function _deleteByLikeInner( $like, $fname )
00284     {
00285         $sql = "UPDATE " . TABLE_METADATA . " SET mtime=-ABS(mtime), expired=1\nWHERE name like ". $this->_quote( $like );
00286         if ( !$res = $this->_query( $sql, $fname ) )
00287         {
00288             return $this->_fail( "Failed to delete files by like: '$like'" );
00289         }
00290         return true;
00291     }
00292 
00293     function _deleteByRegex( $regex, $fname = false )
00294     {
00295         if ( $fname )
00296             $fname .= "::_deleteByRegex($regex)";
00297         else
00298             $fname = "_deleteByRegex($regex)";
00299         return $this->_protect( array( $this, '_deleteByRegexInner' ), $fname,
00300                                 $regex, $fname );
00301     }
00302 
00303     function _deleteByRegexInner( $regex, $fname )
00304     {
00305         $sql = "UPDATE " . TABLE_METADATA . " SET mtime=-ABS(mtime), expired=1\nWHERE name REGEXP " . $this->_quote( $regex );
00306         if ( !$res = $this->_query( $sql, $fname ) )
00307         {
00308             return $this->_fail( "Failed to delete files by regex: '$regex'" );
00309         }
00310         return true;
00311     }
00312 
00313     function _deleteByWildcard( $wildcard, $fname = false )
00314     {
00315         if ( $fname )
00316             $fname .= "::_deleteByWildcard($wildcard)";
00317         else
00318             $fname = "_deleteByWildcard($wildcard)";
00319         return $this->_protect( array( $this, '_deleteByWildcardInner' ), $fname,
00320                                 $wildcard, $fname );
00321     }
00322 
00323     function _deleteByWildcardInner( $wildcard, $fname )
00324     {
00325         // Convert wildcard to regexp.
00326         $regex = '^' . mysql_real_escape_string( $wildcard ) . '$';
00327 
00328         $regex = str_replace( array( '.'  ),
00329                               array( '\.' ),
00330                               $regex );
00331 
00332         $regex = str_replace( array( '?', '*',  '{', '}', ',' ),
00333                               array( '.', '.*', '(', ')', '|' ),
00334                               $regex );
00335 
00336         $sql = "UPDATE " . TABLE_METADATA . " SET mtime=-ABS(mtime), expired=1\nWHERE name REGEXP '$regex'";
00337         if ( !$res = $this->_query( $sql, $fname ) )
00338         {
00339             return $this->_fail( "Failed to delete files by wildcard: '$wildcard'" );
00340         }
00341         return true;
00342     }
00343 
00344     function _deleteByDirList( $dirList, $commonPath, $commonSuffix, $fname = false )
00345     {
00346         if ( $fname )
00347             $fname .= "::_deleteByDirList($dirList, $commonPath, $commonSuffix)";
00348         else
00349             $fname = "_deleteByDirList($dirList, $commonPath, $commonSuffix)";
00350         return $this->_protect( array( $this, '_deleteByDirListInner' ), $fname,
00351                                 $dirList, $commonPath, $commonSuffix, $fname );
00352     }
00353 
00354     function _deleteByDirListInner( $dirList, $commonPath, $commonSuffix, $fname )
00355     {
00356         foreach ( $dirList as $dirItem )
00357         {
00358             if ( strstr( $commonPath, '/cache/content' ) !== false or strstr( $commonPath, '/cache/template-block' ) !== false )
00359             {
00360                 $where = "WHERE name_trunk = '$commonPath/$dirItem/$commonSuffix'";
00361             }
00362             else
00363             {
00364                 $where = "WHERE name LIKE '$commonPath/$dirItem/$commonSuffix%'";
00365             }
00366             $sql = "UPDATE " . TABLE_METADATA . " SET mtime=-ABS(mtime), expired=1\n$where";
00367             if ( !$res = $this->_query( $sql, $fname ) )
00368             {
00369                 eZDebug::writeError( "Failed to delete files in dir: '$commonPath/$dirItem/$commonSuffix%'", __METHOD__ );
00370             }
00371         }
00372         return true;
00373     }
00374 
00375 
00376     function _exists( $filePath, $fname = false )
00377     {
00378         if ( $fname )
00379             $fname .= "::_exists($filePath)";
00380         else
00381             $fname = "_exists($filePath)";
00382         $row = $this->_selectOneRow( "SELECT name, mtime FROM " . TABLE_METADATA . " WHERE name_hash=" . $this->_md5( $filePath ),
00383                                      $fname, "Failed to check file '$filePath' existance: ", true );
00384         if ( $row === false )
00385             return false;
00386         $rc = $row[1] >= 0;
00387         return $rc;
00388     }
00389 
00390     function __mkdir_p( $dir )
00391     {
00392         // create parent directories
00393         $dirElements = explode( '/', $dir );
00394         if ( count( $dirElements ) == 0 )
00395             return true;
00396 
00397         $result = true;
00398         $currentDir = $dirElements[0];
00399 
00400         if ( $currentDir != '' && !file_exists( $currentDir ) && !eZDir::mkdir( $currentDir, false ))
00401             return false;
00402 
00403         for ( $i = 1; $i < count( $dirElements ); ++$i )
00404         {
00405             $dirElement = $dirElements[$i];
00406             if ( strlen( $dirElement ) == 0 )
00407                 continue;
00408 
00409             $currentDir .= '/' . $dirElement;
00410 
00411             if ( !file_exists( $currentDir ) && !eZDir::mkdir( $currentDir, false ) )
00412                 return false;
00413 
00414             $result = true;
00415         }
00416 
00417         return $result;
00418     }
00419 
00420     function _fetch( $filePath, $uniqueName = false )
00421     {
00422         $metaData = $this->_fetchMetadata( $filePath );
00423         if ( !$metaData )
00424         {
00425             eZDebug::writeError( "File '$filePath' does not exist while trying to fetch.", __METHOD__ );
00426             return false;
00427         }
00428         $contentLength = $metaData['size'];
00429 
00430         $sql = "SELECT filedata, offset FROM " . TABLE_DATA . " WHERE name_hash=" . $this->_md5( $filePath ) . " ORDER BY offset";
00431         if ( !$res = $this->_query( $sql, "_fetch($filePath)" ) )
00432         {
00433             eZDebug::writeError( "Failed to fetch file data for file '$filePath'.", __METHOD__ );
00434             return false;
00435         }
00436 
00437         if( !mysql_num_rows( $res ) )
00438         {
00439             eZDebug::writeError( "No rows in file '$filePath' being fetched.", __METHOD__ );
00440             mysql_free_result( $res );
00441             return false;
00442         }
00443 
00444         // create temporary file
00445         if ( strrpos( $filePath, '.' ) > 0 )
00446             $tmpFilePath = substr_replace( $filePath, getmypid().'tmp', strrpos( $filePath, '.' ), 0  );
00447         else
00448             $tmpFilePath = $filePath . '.' . getmypid().'tmp';
00449         $this->__mkdir_p( dirname( $tmpFilePath ) );
00450 
00451         if ( !( $fp = fopen( $tmpFilePath, 'wb' ) ) )
00452         {
00453             eZDebug::writeError( "Cannot write to '$tmpFilePath' while fetching file.", __METHOD__ );
00454             return false;
00455         }
00456 
00457         $offset = 0;
00458         while ( $row = mysql_fetch_row( $res ) )
00459         {
00460             $expectedOffset = $row[1];
00461             if ( $expectedOffset != $offset )
00462             {
00463                 eZDebug::writeError( "The fetched offset value '$expectedOffset' does not match the computed one for the file '$filePath', aborting fetch.", __METHOD__ );
00464                 fclose( $fp );
00465                 @unlink( $filePath );
00466                 return false;
00467             }
00468             fwrite( $fp, $row[0] );
00469             $offset += strlen( $row[0] );
00470         }
00471         if ( $offset != $contentLength )
00472         {
00473             eZDebug::writeError( "The size of the fetched data '$offset' does not match the expected size '$contentLength' for the file '$filePath', aborting fetch.", __METHOD__ );
00474             fclose( $fp );
00475             @unlink( $filePath );
00476             return false;
00477         }
00478 
00479         fclose( $fp );
00480 
00481         // Make sure all data is written correctly
00482         clearstatcache();
00483         $tmpSize = filesize( $tmpFilePath );
00484         if ( $tmpSize != $metaData['size'] )
00485         {
00486             eZDebug::writeError( "Size ($tmpSize) of written data for file '$tmpFilePath' does not match expected size " . $metaData['size'], __METHOD__ );
00487             return false;
00488         }
00489 
00490         if ( ! $uniqueName === true )
00491         {
00492             //include_once( 'lib/ezfile/classes/ezfile.php' );
00493             eZFile::rename( $tmpFilePath, $filePath );
00494         }
00495         else
00496         {
00497             $filePath = $tmpFilePath;
00498         }
00499         mysql_free_result( $res );
00500 
00501         return $filePath;
00502     }
00503 
00504     function _fetchContents( $filePath, $fname = false )
00505     {
00506         if ( $fname )
00507             $fname .= "::_fetchContents($filePath)";
00508         else
00509             $fname = "_fetchContents($filePath)";
00510         $metaData = $this->_fetchMetadata( $filePath, $fname );
00511         if ( !$metaData )
00512         {
00513             eZDebug::writeError( "File '$filePath' does not exist while trying to fetch its contents.", __METHOD__ );
00514             return false;
00515         }
00516         $contentLength = $metaData['size'];
00517 
00518 //        $fileID = $metaData['id'];
00519         $sql = "SELECT filedata, offset FROM " . TABLE_DATA . " WHERE name_hash=" . $this->_md5( $filePath ) . " ORDER BY offset";
00520         if ( !$res = $this->_query( $sql, $fname ) )
00521         {
00522             eZDebug::writeError( "Failed to fetch file data for the file '$filePath'.", __METHOD__ );
00523             return false;
00524         }
00525 
00526         $contents = '';
00527         $offset   = 0;
00528         while ( $row = mysql_fetch_row( $res ) )
00529         {
00530             $expectedOffset = $row[1];
00531             if ( $expectedOffset != $offset )
00532             {
00533                 eZDebug::writeError( "The fetched offset value '$expectedOffset' does not match the computed one for the file '$filePath', aborting.", __METHOD__ );
00534                 return false;
00535             }
00536             $contents .= $row[0];
00537             $offset += strlen( $row[0] );
00538         }
00539         if ( $offset != $contentLength )
00540         {
00541             eZDebug::writeError( "The size of the fetched data '$offset' does not match the expected size '$contentLength' for the file '$filePath', aborting.", __METHOD__ );
00542             return false;
00543         }
00544 
00545         mysql_free_result( $res );
00546         return $contents;
00547     }
00548 
00549     /**
00550      * \return file metadata, or false if the file does not exist in database.
00551      */
00552     function _fetchMetadata( $filePath, $fname = false )
00553     {
00554         if ( $fname )
00555             $fname .= "::_fetchMetadata($filePath)";
00556         else
00557             $fname = "_fetchMetadata($filePath)";
00558         $sql = "SELECT * FROM " . TABLE_METADATA . " WHERE name_hash=" . $this->_md5( $filePath );
00559         $row = $this->_selectOneAssoc( $sql, $fname,
00560                                        "Failed to retrieve file metadata: $filePath",
00561                                        true );
00562         return $row;
00563     }
00564 
00565     function _linkCopy( $srcPath, $dstPath, $fname = false )
00566     {
00567         if ( $fname )
00568             $fname .= "::_linkCopy($srcPath,$dstPath)";
00569         else
00570             $fname = "_linkCopy($srcPath,$dstPath)";
00571         return $this->_copy( $srcPath, $dstPath, $fname );
00572     }
00573 
00574     /**
00575      * \deprecated This function should not be used since it cannot handle reading errors.
00576      *             For the PHP 5 port this should be removed.
00577      */
00578     function _passThrough( $filePath, $fname = false )
00579     {
00580         if ( $fname )
00581             $fname .= "::_passThrough($filePath)";
00582         else
00583             $fname = "_passThrough($filePath)";
00584 
00585         $metaData = $this->_fetchMetadata( $filePath, $fname );
00586         if ( !$metaData )
00587             return false;
00588 
00589         $sql = "SELECT filedata FROM " . TABLE_DATA . " WHERE name_hash=" . $this->md5( $filePath ) . " ORDER BY offset";
00590         if ( !$res = $this->_query( $sql, $fname ) )
00591         {
00592             eZDebug::writeError( "Failed to fetch file data for file '$filePath'.", __METHOD__ );
00593             return false;
00594         }
00595 
00596         while ( $row = mysql_fetch_row( $res ) )
00597             echo $row[0];
00598 
00599         return true;
00600     }
00601 
00602     function _rename( $srcFilePath, $dstFilePath )
00603     {
00604         if ( strcmp( $srcFilePath, $dstFilePath ) == 0 )
00605             return;
00606 
00607         // fetch source file metadata
00608         $metaData = $this->_fetchMetadata( $srcFilePath );
00609         if ( !$metaData ) // if source file does not exist then do nothing.
00610             return false;
00611 
00612         $this->_query( 'BEGIN');
00613 
00614         $srcFilePathStr  = mysql_real_escape_string( $srcFilePath );
00615         $dstFilePathStr  = mysql_real_escape_string( $dstFilePath );
00616         $dstNameTrunkStr = mysql_real_escape_string( self::nameTrunk( $dstFilePath, $metaData['scope'] ) );
00617 
00618 //        $srcFilePathHash = mysql_real_escape_string( $metaData['name_hash'] );
00619 //        $dstFilePathHash = mysql_real_escape_string( md5( $dstFilePath ) );
00620 
00621         // Mark entry for update to lock it
00622         $sql = "SELECT * FROM " . TABLE_METADATA . " WHERE name_hash=MD5('$srcFilePathStr') FOR UPDATE";
00623         if ( !$this->_query( $sql, "_rename($srcFilePath, $dstFilePath)" ) )
00624         {
00625             eZDebug::writeError( "Failed locking file '$srcFilePath'", __METHOD__ );
00626             $this->_query( 'ROLLBACK');
00627             return false;
00628         }
00629 
00630         if ( $this->_exists( $dstFilePath ) )
00631             $this->_delete( $dstFilePath, true );
00632 
00633         // Create a new meta-data entry for the new file to make foreign keys happy.
00634         $sql = "INSERT INTO " . TABLE_METADATA . " (name, name_trunk, name_hash, datatype, scope, size, mtime, expired) SELECT '$dstFilePathStr' AS name, '$dstNameTrunkStr' as name_trunk, MD5('$dstFilePathStr') AS name_hash, datatype, scope, size, mtime, expired FROM " . TABLE_METADATA . " WHERE name_hash=MD5('$srcFilePathStr')";
00635         if ( !$this->_query( $sql, "_rename($srcFilePath, $dstFilePath)" ) )
00636         {
00637             eZDebug::writeError( "Failed making new file entry '$dstFilePath'", __METHOD__ );
00638             $this->_query( 'ROLLBACK');
00639             return false;
00640         }
00641 
00642         // Update data chunks to refer to the new file entry.
00643         $sql = "UPDATE " . TABLE_DATA . " SET name_hash=MD5('$dstFilePathStr') WHERE name_hash=MD5('$srcFilePathStr')";
00644         if ( !$this->_query( $sql, "_rename($srcFilePath, $dstFilePath)" ) )
00645         {
00646             eZDebug::writeError( "Failed renaming file '$srcFilePath' to '$dstFilePath'", __METHOD__ );
00647             $this->_query( 'ROLLBACK');
00648             return false;
00649         }
00650 
00651         // Remove old entry
00652         $sql = "DELETE FROM " . TABLE_METADATA . " WHERE name_hash=MD5('$srcFilePathStr')";
00653         if ( !$this->_query( $sql, "_rename($srcFilePath, $dstFilePath)" ) )
00654         {
00655             eZDebug::writeError( "Failed removing old file '$srcFilePath'", __METHOD__ );
00656             $this->_query( 'ROLLBACK');
00657             return false;
00658         }
00659 
00660         $this->_query( 'COMMIT');
00661 
00662         return true;
00663     }
00664 
00665     function _store( $filePath, $datatype, $scope, $fname = false )
00666     {
00667         if ( !is_readable( $filePath ) )
00668         {
00669             eZDebug::writeError( "Unable to store file '$filePath' since it is not readable.", __METHOD__ );
00670             return;
00671         }
00672         if ( $fname )
00673             $fname .= "::_store($filePath, $datatype, $scope)";
00674         else
00675             $fname = "_store($filePath, $datatype, $scope)";
00676 
00677         $this->_protect( array( $this, '_storeInner' ), $fname,
00678                          $filePath, $datatype, $scope, $fname );
00679     }
00680 
00681     function _storeInner( $filePath, $datatype, $scope, $fname )
00682     {
00683         // Insert file metadata.
00684         clearstatcache();
00685         $fileMTime = filemtime( $filePath );
00686         $contentLength = filesize( $filePath );
00687         $filePathHash = md5( $filePath );
00688         $nameTrunk = self::nameTrunk( $filePath, $scope );
00689 
00690         if ( $this->_insertUpdate( TABLE_METADATA,
00691                                    array( 'datatype' => $datatype,
00692                                           'name' => $filePath,
00693                                           'name_trunk' => $nameTrunk,
00694                                           'name_hash' => $filePathHash,
00695                                           'scope' => $scope,
00696                                           'size' => $contentLength,
00697                                           'mtime' => $fileMTime,
00698                                           'expired' => ($fileMTime < 0) ? 1 : 0 ),
00699                                    "datatype=VALUES(datatype), scope=VALUES(scope), size=VALUES(size), mtime=VALUES(mtime), expired=VALUES(expired)",
00700                                    $fname ) === false )
00701         {
00702             return $this->_fail( "Failed to insert file metadata while storing. Possible race condition" );
00703         }
00704 
00705         // Insert file contents.
00706         if ( !$fp = @fopen( $filePath, 'rb' ) )
00707         {
00708             return $this->_fail( "Cannot read '$filePath'.", __METHOD__ );
00709         }
00710 
00711         $chunkSize = $this->dbparams['chunk_size'];
00712         $offset = 0;
00713         while ( !feof( $fp ) )
00714         {
00715             // make the data mysql insert safe.
00716             $binarydata = fread( $fp, $chunkSize );
00717 
00718             if ( $this->_insertUpdate( TABLE_DATA,
00719                                        array( 'name_hash' => $filePathHash,
00720                                               'offset' => $offset,
00721                                               'filedata' => $binarydata ),
00722                                        "filedata=VALUES(filedata)",
00723                                        $fname ) === false )
00724             {
00725                 return $this->_fail( "Failed to insert file data row while storing. Possible race condition", __METHOD__ );
00726             }
00727             $offset += strlen( $binarydata );
00728         }
00729         fclose( $fp );
00730 
00731         // Get rid of unused/old offset data.
00732         $result = $this->_cleanupFiledata( $filePath, $contentLength, $fname );
00733         if ( $this->_isFailure( $result ) )
00734             return $result;
00735 
00736         return true;
00737     }
00738 
00739     function _storeContents( $filePath, $contents, $scope, $datatype, $mtime = false, $fname = false )
00740     {
00741         if ( $fname )
00742             $fname .= "::_storeContents($filePath, ..., $scope, $datatype)";
00743         else
00744             $fname = "_storeContents($filePath, ..., $scope, $datatype)";
00745 
00746         $this->_protect( array( $this, '_storeContentsInner' ), $fname,
00747                          $filePath, $contents, $scope, $datatype, $mtime, $fname );
00748     }
00749 
00750     function _storeContentsInner( $filePath, $contents, $scope, $datatype, $curTime, $fname )
00751     {
00752         // Insert file metadata.
00753         $contentLength = strlen( $contents );
00754         $filePathHash = md5( $filePath );
00755         $nameTrunk = self::nameTrunk( $filePath, $scope );
00756         if ( $curTime === false )
00757             $curTime = time();
00758 
00759         if ( $this->_insertUpdate( TABLE_METADATA,
00760                                     array( 'datatype' => $datatype,
00761                                            'name' => $filePath,
00762                                            'name_trunk' => $nameTrunk,
00763                                            'name_hash' => $filePathHash,
00764                                            'scope' => $scope,
00765                                            'size' => $contentLength,
00766                                            'mtime' => $curTime,
00767                                            'expired' => ($curTime < 0) ? 1 : 0 ),
00768                                     "datatype=VALUES(datatype), name_trunk='$nameTrunk', scope=VALUES(scope), size=VALUES(size), mtime=VALUES(mtime), expired=VALUES(expired)",
00769                                    $fname ) === false )
00770         {
00771             return $this->_fail( "Failed to insert file metadata while storing contents. Possible race condition" );
00772         }
00773 
00774         // Insert file contents.
00775         $chunkSize = $this->dbparams['chunk_size'];
00776         for ( $pos = 0; $pos < $contentLength; $pos += $chunkSize )
00777         {
00778             $chunk = substr( $contents, $pos, $chunkSize );
00779 
00780             if ( $this->_insertUpdate( TABLE_DATA,
00781                                        array( 'name_hash' => $filePathHash,
00782                                               'offset'   => $pos,
00783                                               'filedata' => $chunk ),
00784                                        "filedata=VALUES(filedata)",
00785                                        $fname ) === false )
00786             {
00787                 return $this->_fail( "Failed to insert file data row while storing contents. Possible race condition" );
00788             }
00789         }
00790 
00791         // Get rid of unused/old offset data.
00792         $result = $this->_cleanupFiledata( $filePath, $contentLength, $fname );
00793         if ( $this->_isFailure( $result ) )
00794             return $result;
00795 
00796         return true;
00797     }
00798 
00799     function _getFileList( $skipBinaryFiles, $skipImages )
00800     {
00801         $query = 'SELECT name FROM ' . TABLE_METADATA;
00802 
00803         // omit some file types if needed
00804         $filters = array();
00805         if ( $skipBinaryFiles )
00806             $filters[] = "'binaryfile'";
00807         if ( $skipImages )
00808             $filters[] = "'image'";
00809         if ( $filters )
00810             $query .= ' WHERE scope NOT IN (' . join( ', ', $filters ) . ')';
00811 
00812         $rslt = $this->_query( $query, "_getFileList($skipBinaryFiles, $skipImages)" );
00813         if ( !$rslt )
00814         {
00815             eZDebug::writeDebug( 'Unable to get file list', __METHOD__ );
00816             return false;
00817         }
00818 
00819         $filePathList = array();
00820         while ( $row = mysql_fetch_row( $rslt ) )
00821             $filePathList[] = $row[0];
00822 
00823         return $filePathList;
00824     }
00825 
00826 //////////////////////////////////////
00827 //         Helper methods
00828 //////////////////////////////////////
00829 
00830     function _die( $msg, $sql = null )
00831     {
00832         if ( $this->db )
00833         {
00834             eZDebug::writeError( $sql, "$msg" . mysql_error( $this->db ) );
00835         }
00836         else
00837         {
00838             eZDebug::writeError( $sql, "$msg: " . mysql_error() );
00839         }
00840     }
00841 
00842     /*!
00843     Performs an insert of the given items in $array.
00844 
00845     \param $table Name of table to execute insert on.
00846     \param $array Associative array with data to insert, the keys are the field names and the values will be quoted according to type.
00847     \param $fname Name of caller.
00848      */
00849      function _insert( $table, $array, $fname )
00850     {
00851         $keys = array_keys( $array );
00852         $query = "INSERT INTO $table (" . join( ", ", $keys ) . ") VALUES (" . $this->_sqlList( $array ) . ")";
00853         $res = $this->_query( $query, $fname );
00854         if ( !$res )
00855         {
00856             return false;
00857         }
00858         return mysql_insert_id( $this->db );
00859     }
00860 
00861     /*!
00862     Performs an insert of the given items in $array, if entry specified already exists the $update SQL is executed
00863     to update the entry.
00864 
00865     \param $table Name of table to execute insert on.
00866     \param $array Associative array with data to insert, the keys are the field names and the values will be quoted according to type.
00867     \param $update Partial update SQL which is executed when entry exists.
00868     \param $fname Name of caller.
00869      */
00870     function _insertUpdate( $table, $array, $update, $fname, $reportError = true )
00871     {
00872         $keys = array_keys( $array );
00873         $query = "INSERT INTO $table (" . join( ", ", $keys ) . ") VALUES (" . $this->_sqlList( $array ) . ")\nON DUPLICATE KEY UPDATE $update";
00874         $res = $this->_query( $query, $fname, $reportError );
00875         if ( !$res )
00876         {
00877             return false;
00878         }
00879         return mysql_insert_id( $this->db );
00880     }
00881 
00882     /*!
00883      Formats a list of entries as an SQL list which is separated by commas.
00884      Each entry in the list is quoted using _quote().
00885      */
00886     function _sqlList( $array )
00887     {
00888         $text = "";
00889         $sep = "";
00890         foreach ( $array as $e )
00891         {
00892             $text .= $sep;
00893             $text .= $this->_quote( $e );
00894             $sep = ", ";
00895         }
00896         return $text;
00897     }
00898 
00899     /*!
00900      Common select method for doing a SELECT query which is passed in $query and
00901      fetching one row from the result.
00902      If there are more than one row it will fail and exit, if 0 it returns false.
00903      The returned row is a numerical array.
00904 
00905      \param $fname The function name that started the query, should contain relevant arguments in the text.
00906      \param $error Sent to _error() in case of errors
00907      \param $debug If true it will display the fetched row in addition to the SQL.
00908      */
00909     function _selectOneRow( $query, $fname, $error = false, $debug = false )
00910     {
00911         return $this->_selectOne( $query, $fname, $error, $debug, "mysql_fetch_row" );
00912     }
00913 
00914     /*!
00915      Common select method for doing a SELECT query which is passed in $query and
00916      fetching one row from the result.
00917      If there are more than one row it will fail and exit, if 0 it returns false.
00918      The returned row is an associative array.
00919 
00920      \param $fname The function name that started the query, should contain relevant arguments in the text.
00921      \param $error Sent to _error() in case of errors
00922      \param $debug If true it will display the fetched row in addition to the SQL.
00923      */
00924     function _selectOneAssoc( $query, $fname, $error = false, $debug = false )
00925     {
00926         return $this->_selectOne( $query, $fname, $error, $debug, "mysql_fetch_assoc" );
00927     }
00928 
00929     /*!
00930      Common select method for doing a SELECT query which is passed in $query and
00931      fetching one row from the result.
00932      If there are more than one row it will fail and exit, if 0 it returns false.
00933 
00934      \param $fname The function name that started the query, should contain relevant arguments in the text.
00935      \param $error Sent to _error() in case of errors
00936      \param $debug If true it will display the fetched row in addition to the SQL.
00937      \param $fetchCall The callback to fetch the row.
00938      */
00939     function _selectOne( $query, $fname, $error = false, $debug = false, $fetchCall )
00940     {
00941         eZDebug::accumulatorStart( 'mysql_cluster_query', 'mysql_cluster_total', 'Mysql_cluster_queries' );
00942         $time = microtime( true );
00943 
00944         $res = mysql_query( $query, $this->db );
00945         if ( !$res )
00946         {
00947             $this->_error( $query, $fname, $error );
00948             eZDebug::accumulatorStop( 'mysql_cluster_query' );
00949             return false;
00950         }
00951 
00952         $nRows = mysql_num_rows( $res );
00953         if ( $nRows > 1 )
00954         {
00955             eZDebug::writeError( 'Duplicate entries found', $fname );
00956             eZDebug::accumulatorStop( 'mysql_cluster_query' );
00957             // For PHP 5 throw an exception.
00958         }
00959 
00960         $row = $fetchCall( $res );
00961         mysql_free_result( $res );
00962         if ( $debug )
00963             $query = "SQL for _selectOneAssoc:\n" . $query . "\n\nRESULT:\n" . var_export( $row, true );
00964 
00965         $time = microtime( true ) - $time;
00966         eZDebug::accumulatorStop( 'mysql_cluster_query' );
00967 
00968         $this->_report( $query, $fname, $time );
00969         return $row;
00970     }
00971 
00972     /*!
00973       Starts a new transaction by executing a BEGIN call.
00974       If a transaction is already started nothing is executed.
00975      */
00976     function _begin( $fname = false )
00977     {
00978         if ( $fname )
00979             $fname .= "::_begin";
00980         else
00981             $fname = "_begin";
00982         $this->transactionCount++;
00983         if ( $this->transactionCount == 1 )
00984             $this->_query( "BEGIN", $fname );
00985     }
00986 
00987     /*!
00988       Stops a current transaction and commits the changes by executing a COMMIT call.
00989       If the current transaction is a sub-transaction nothing is executed.
00990      */
00991     function _commit( $fname = false )
00992     {
00993         if ( $fname )
00994             $fname .= "::_commit";
00995         else
00996             $fname = "_commit";
00997         $this->transactionCount--;
00998         if ( $this->transactionCount <= 0 )
00999             $this->_query( "COMMIT", $fname );
01000     }
01001 
01002     /*!
01003       Stops a current transaction and discards all changes by executing a ROLLBACK call.
01004       If the current transaction is a sub-transaction nothing is executed.
01005      */
01006     function _rollback( $fname = false )
01007     {
01008         if ( $fname )
01009             $fname .= "::_rollback";
01010         else
01011             $fname = "_rollback";
01012         $this->transactionCount--;
01013         if ( $this->transactionCount <= 0 )
01014             $this->_query( "ROLLBACK", $fname );
01015     }
01016 
01017     /*!
01018      Frees a previously open shared-lock by performing a rollback on the current transaction.
01019 
01020      Note: There is not checking to see if a lock is started, and if
01021            locking was done in an existing transaction nothing will happen.
01022      */
01023     function _freeSharedLock( $fname = false )
01024     {
01025         if ( $fname )
01026             $fname .= "::_freeSharedLock";
01027         else
01028             $fname = "_freeSharedLock";
01029         if ( $this->transactionCount <= 1 )
01030             $this->_query( "ROLLBACK", $fname );
01031         $this->transactionCount--;
01032     }
01033 
01034     /*!
01035      Frees a previously open exclusive-lock by commiting the current transaction.
01036 
01037      Note: There is not checking to see if a lock is started, and if
01038            locking was done in an existing transaction nothing will happen.
01039      */
01040     function _freeExclusiveLock( $fname = false )
01041     {
01042         if ( $fname )
01043             $fname .= "::_freeExclusiveLock";
01044         else
01045             $fname = "_freeExclusiveLock";
01046         if ( $this->transactionCount <= 1 )
01047             $this->_query( "COMMIT", $fname );
01048         $this->transactionCount--;
01049     }
01050 
01051     /*!
01052      Locks the file entry for exclusive write access.
01053 
01054      The locking is performed by trying to insert the entry with mtime
01055      set to -1, which means that file is not to be used. If it exists
01056      the mtime will be negated to mark it as deleted. This insert/update
01057      procedure will perform an exclusive lock of the row (InnoDB feature).
01058 
01059      Note: All reads of the row must be done with LOCK IN SHARE MODE.
01060      */
01061     function _exclusiveLock( $filePath, $fname = false )
01062     {
01063         if ( $fname )
01064             $fname .= "::_exclusiveLock($filePath)";
01065         else
01066             $fname = "_exclusiveLock($filePath)";
01067         if ( $this->transactionCount == 0 )
01068             $this->_begin( $fname );
01069         $data = array( 'name' => $filePath,
01070                        'name_hash' => md5( $filePath ),
01071                        'expired' => 1,
01072                        'mtime' => -1 ); // -1 is used to reserve this entry.
01073         $tries = 0;
01074         $maxTries = $this->dbparams['max_execute_tries'];
01075         while ( $tries < $maxTries )
01076         {
01077             $this->_insertUpdate( TABLE_METADATA,
01078                                   $data,
01079                                   "mtime=-ABS(mtime), expired=1",
01080                                   $fname,
01081                                   false ); // turn off error reporting
01082             $errno = mysql_errno( $this->db );
01083             if ( $errno == 1205 || // Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT)
01084                  $errno == 1213 )  // Error: 1213 SQLSTATE: 40001 (ER_LOCK_DEADLOCK)
01085             {
01086                 $tries++;
01087                 continue;
01088             }
01089             else if ( $errno == 0 )
01090             {
01091                 return true;
01092             }
01093             break;
01094         }
01095         return $this->_fail( "Failed to perform exclusive lock on file $filePath" );
01096     }
01097 
01098     /**
01099     * Uses a secondary database connection to check outside the transaction scope
01100     * if a file has been generated during the current process execution
01101     * @param string $filePath
01102     * @param int $expiry
01103     * @param int $curtime
01104     * @param int $ttl
01105     * @param string $fname
01106     * @return bool false if the file exists and is not expired, true otherwise
01107     **/
01108     function _verifyExclusiveLock( $filePath, $expiry, $curtime, $ttl, $fname = false )
01109     {
01110         // we need to create a new backend connection in order to be outside the
01111         // current transaction scope
01112         if ( $this->backendVerify === null )
01113         {
01114             $backendclass = get_class( $this );
01115             $this->backendVerify = new $backendclass( $filePath );
01116             $this->backendVerify->_connect( true );
01117         }
01118 
01119         // we then check the file metadata in this scope to see if it was created
01120         // in between
01121         $metaData = $this->backendVerify->_fetchMetadata( $filePath );
01122         if ( $metaData !== false )
01123         {
01124             $mtime = $metaData['mtime'];
01125             $expiry = max( $curtime, $expiry );
01126             if ( $mtime > 0 && !eZDBFileHandler::isExpired( $filePath, $mtime, $expiry, $curtime, $ttl ) )
01127                 return false;
01128         }
01129         return true;
01130     }
01131 
01132     function _sharedLock( $filePath, $fname = false )
01133     {
01134         if ( $fname )
01135             $fname .= "::_sharedLock($filePath)";
01136         else
01137             $fname = "_sharedLock($filePath)";
01138         if ( $this->transactionCount == 0 )
01139             $this->_begin( $fname );
01140         $tries = 0;
01141         $maxTries = $this->dbparams['max_execute_tries'];
01142         while ( $tries < $maxTries )
01143         {
01144             $res = $this->_query( "SELECT * FROM " . TABLE_METADATA . " WHERE name_hash=" . $this->_md5( $filePath ) . " LOCK IN SHARE MODE", $fname, false ); // turn off error reporting
01145             $errno = mysql_errno( $this->db );
01146             if ( $errno == 1205 || // Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT)
01147                  $errno == 1213 )  // Error: 1213 SQLSTATE: 40001 (ER_LOCK_DEADLOCK)
01148             {
01149                 $tries++;
01150                 continue;
01151             }
01152             break;
01153         }
01154         if ( !$res )
01155             return $this->_fail( "Failed to perform shared lock on file $filePath" );
01156         return mysql_fetch_assoc( $res );
01157     }
01158 
01159     /*!
01160      Protects a custom function with SQL queries in a database transaction,
01161      if the function reports an error the transaciton is ROLLBACKed.
01162 
01163      The first argument to the _protect() is the callback and the second is the name of the function (for query reporting). The remainder of arguments are sent to the callback.
01164 
01165      A return value of false from the callback is considered a failure, any other value is returned from _protect(). For extended error handling call _fail() and return the value.
01166      */
01167     function _protect()
01168     {
01169         $args = func_get_args();
01170         $callback = array_shift( $args );
01171         $fname    = array_shift( $args );
01172 
01173         $maxTries = $this->dbparams['max_execute_tries'];
01174         $tries = 0;
01175         while ( $tries < $maxTries )
01176         {
01177             if ( $this->transactionCount == 0 )
01178                 $this->_query( "BEGIN", $fname );
01179             $this->transactionCount++;
01180 
01181             $result = call_user_func_array( $callback, $args );
01182 
01183             $errno = mysql_errno( $this->db );
01184             if ( $errno == 1205 || // Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT)
01185                  $errno == 1213 )  // Error: 1213 SQLSTATE: 40001 (ER_LOCK_DEADLOCK)
01186             {
01187                 $tries++;
01188                 if ( $this->transactionCount - 1 == 0 )
01189                     $this->_query( 'ROLLBACK', $fname );
01190                 continue;
01191             }
01192 
01193             if ( $result === false )
01194             {
01195                 $this->transactionCount--;
01196                 if ( $this->transactionCount == 0 )
01197                     $this->_query( 'ROLLBACK', $fname );
01198                 return false;
01199             }
01200             elseif ( $result instanceof eZMySQLBackendError )
01201             {
01202                 eZDebug::writeError( $result->errorValue, $result->errorText );
01203                 $this->transactionCount--;
01204                 if ( $this->transactionCount == 0 )
01205                     $this->_query( 'ROLLBACK', $fname );
01206                 return false;
01207             }
01208 
01209             break; // All is good, so break out of loop
01210         }
01211 
01212         $this->transactionCount--;
01213         if ( $this->transactionCount == 0 )
01214             $this->_query( "COMMIT", $fname );
01215         return $result;
01216     }
01217 
01218     function _handleErrorType( $res )
01219     {
01220         if ( $res === false )
01221         {
01222             eZDebug::writeError( "SQL failed" );
01223         }
01224         elseif ( $res instanceof eZMySQLBackendError )
01225         {
01226             eZDebug::writeError( $res->errorValue, $res->errorText );
01227         }
01228     }
01229 
01230     /*!
01231      Checks if $result is a failure type and returns true if so, false otherwise.
01232 
01233      A failure is either the value false or an error object of type eZMySQLBackendError.
01234      */
01235     function _isFailure( $result )
01236     {
01237         if ( $result === false || ($result instanceof eZMySQLBackendError ) )
01238         {
01239             return true;
01240         }
01241         return false;
01242     }
01243 
01244     /*!
01245      Helper method for removing leftover file data rows for the file path $filePath.
01246      Note: This should be run after insert/updating filedata entries.
01247 
01248      Entries which are after $contentLength or which have different chunk offset than
01249      the defined chunk_size in $dbparams will be removed.
01250 
01251      \param $filePath The file path which was inserted/updated
01252      \param $contentLength The length of the file data
01253      \parma $fname Name of the function caller
01254      */
01255     function _cleanupFiledata( $filePath, $contentLength, $fname )
01256     {
01257         $chunkSize = $this->dbparams['chunk_size'];
01258         $sql = "DELETE FROM " . TABLE_DATA . " WHERE name_hash = " . $this->_md5( $filePath ) . " AND (offset % $chunkSize != 0 OR offset > $contentLength)";
01259         if ( !$this->_query( $sql, $fname ) )
01260             return $this->_fail( "Failed to remove old file data." );
01261 
01262         return true;
01263     }
01264 
01265     /*!
01266      Creates an error object which can be read by some backend functions.
01267 
01268      \param $value The value which is sent to the debug system.
01269      \param $text The text/header for the value.
01270      */
01271     function _fail( $value, $text = false )
01272     {
01273         $value .= "\n" . mysql_errno( $this->db ) . ": " . mysql_error( $this->db );
01274         //include_once( 'kernel/classes/clusterfilehandlers/dbbackends/mysqlbackenderror.php' );
01275         return new eZMySQLBackendError( $value, $text );
01276     }
01277 
01278     /*!
01279      Performs mysql query and returns mysql result.
01280      Times the sql execution, adds accumulator timings and reports SQL to debug.
01281 
01282      \param $fname The function name that started the query, should contain relevant arguments in the text.
01283      */
01284     function _query( $query, $fname = false, $reportError = true )
01285     {
01286         eZDebug::accumulatorStart( 'mysql_cluster_query', 'mysql_cluster_total', 'Mysql_cluster_queries' );
01287         $time = microtime( true );
01288 
01289         $res = mysql_query( $query, $this->db );
01290         if ( !$res && $reportError )
01291         {
01292             $this->_error( $query, $fname );
01293         }
01294 
01295         $numRows = mysql_affected_rows( $this->db );
01296 
01297         $time = microtime( true ) - $time;
01298         eZDebug::accumulatorStop( 'mysql_cluster_query' );
01299 
01300         $this->_report( $query, $fname, $time, $numRows );
01301         return $res;
01302     }
01303 
01304     /*!
01305      Make sure that $value is escaped and qouted according to type and returned as a string.
01306      The returned value can directly be put into SQLs.
01307      */
01308     function _quote( $value )
01309     {
01310         if ( is_integer( $value ) )
01311             return (string)$value;
01312         elseif ( is_null( $value ) )
01313             return 'NULL';
01314         else
01315             return "'" . mysql_real_escape_string( $value ) . "'";
01316     }
01317 
01318     /*!
01319      Make sure that $value is escaped and qouted and turned into and MD5.
01320      The returned value can directly be put into SQLs.
01321      */
01322     function _md5( $value )
01323     {
01324         return "MD5('" . mysql_real_escape_string( $value ) . "')";
01325     }
01326 
01327     /*!
01328      Prints error message $error to debug system.
01329 
01330      \param $query The query that was attempted, will be printed if $error is \c false
01331      \param $fname The function name that started the query, should contain relevant arguments in the text.
01332      \param $error The error message, if this is an array the first element is the value to dump and the second the error header (for eZDebug::writeNotice). If this is \c false a generic message is shown.
01333      */
01334     function _error( $query, $fname, $error = "Failed to execute SQL for function:" )
01335     {
01336         if ( $error === false )
01337         {
01338             $error = "Failed to execute SQL for function:";
01339         }
01340         else if ( is_array( $error ) )
01341         {
01342             $fname = $error[1];
01343             $error = $error[0];
01344         }
01345 
01346         eZDebug::writeError( "$error\n" . mysql_errno( $this->db ) . ': ' . mysql_error( $this->db ), $fname );
01347     }
01348 
01349     /*!
01350      Report SQL $query to debug system.
01351 
01352      \param $fname The function name that started the query, should contain relevant arguments in the text.
01353      \param $timeTaken Number of seconds the query + related operations took (as float).
01354      \param $numRows Number of affected rows.
01355      */
01356     function _report( $query, $fname, $timeTaken, $numRows = false )
01357     {
01358         if ( !$this->dbparams['sql_output'] )
01359             return;
01360 
01361         $rowText = '';
01362         if ( $numRows !== false )
01363             $rowText = "$numRows rows, ";
01364         static $numQueries = 0;
01365         if ( strlen( $fname ) == 0 )
01366             $fname = "_query";
01367         $backgroundClass = ($this->transactionCount > 0  ? "debugtransaction transactionlevel-$this->transactionCount" : "");
01368         eZDebug::writeNotice( "$query", "cluster::mysql::{$fname}[{$rowText}" . number_format( $timeTaken, 3 ) . " ms] query number per page:" . $numQueries++, $backgroundClass );
01369     }
01370 
01371     /**
01372     * Returns the name_trunk for a file path
01373     * @param string $filePath
01374     * @param string $scope
01375     * @return string
01376     * @todo -ceZDBFileHandlerMysqlBackend Implement eZDBFileHandlerMysqlBackend.nameTrunk()
01377     **/
01378     static function nameTrunk( $filePath, $scope )
01379     {
01380         switch ( $scope )
01381         {
01382             case 'viewcache':
01383             {
01384                 $nameTrunk = substr( $filePath, 0, strrpos( $filePath, '-' ) + 1 );
01385             } break;
01386 
01387             case 'template-block':
01388             {
01389                 $templateBlockCacheDir = eZTemplateCacheBlock::templateBlockCacheDir();
01390                 $templateBlockPath = str_replace( $templateBlockCacheDir, '', $filePath );
01391                 if ( strstr( $templateBlockPath, 'subtree/' ) !== false )
01392                 {
01393                     // 6 = strlen( 'cache/' );
01394                     $len = strlen( $templateBlockCacheDir ) + strpos( $templateBlockPath, 'cache/' ) + 6;
01395                     $nameTrunk = substr( $filePath, 0, $len  );
01396                 }
01397                 else
01398                 {
01399                     $nameTrunk = $filePath;
01400                 }
01401             } break;
01402 
01403             default:
01404             {
01405                 $nameTrunk = $filePath;
01406             }
01407         }
01408         return $nameTrunk;
01409     }
01410 
01411     public $db   = null;
01412     public $numQueries = 0;
01413     public $transactionCount = 0;
01414     public $dbparams;
01415     private $backendVerify = null;
01416 }
01417 
01418 ?>