|
eZ Publish
[4.0]
|
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 ?>