Exponential Basic 2.4.0.1 Exponential Basic CMS — PHP Class Reference

mysqli.php
Go to the documentation of this file.
1 <?php
12 /*
13 This is the structure / SQL CREATE for the DFS database table.
14 It can be created anywhere, in the same database on the same server, or on a
15 distinct database / server.
16
17 CREATE TABLE ezdfsfile (
18 `name` text NOT NULL,
19 name_trunk text NOT NULL,
20 name_hash varchar(34) NOT NULL DEFAULT '',
21 datatype varchar(255) NOT NULL DEFAULT 'application/octet-stream',
22 scope varchar(25) NOT NULL DEFAULT '',
23 size bigint(20) unsigned NOT NULL DEFAULT '0',
24 mtime int(11) NOT NULL DEFAULT '0',
25 expired tinyint(1) NOT NULL DEFAULT '0',
26 PRIMARY KEY (name_hash),
27 KEY ezdfsfile_name (`name`(250)),
28 KEY ezdfsfile_name_trunk (name_trunk(250)),
29 KEY ezdfsfile_mtime (mtime),
30 KEY ezdfsfile_expired_name (expired,`name`(250))
31 ) ENGINE=InnoDB;
32
33 CREATE TABLE ezdfsfile_cache (
34 `name` text NOT NULL,
35 name_trunk text NOT NULL,
36 name_hash varchar(34) NOT NULL DEFAULT '',
37 datatype varchar(255) NOT NULL DEFAULT 'application/octet-stream',
38 scope varchar(25) NOT NULL DEFAULT '',
39 size bigint(20) unsigned NOT NULL DEFAULT '0',
40 mtime int(11) NOT NULL DEFAULT '0',
41 expired tinyint(1) NOT NULL DEFAULT '0',
42 PRIMARY KEY (name_hash),
43 KEY ezdfsfile_name (`name`(250)),
44 KEY ezdfsfile_name_trunk (name_trunk(250)),
45 KEY ezdfsfile_mtime (mtime),
46 KEY ezdfsfile_expired_name (expired,`name`(250))
47 ) ENGINE=InnoDB;
48 */
49
50 class eZDFSFileHandlerMySQLiBackend implements eZClusterEventNotifier
51 {
55 const TIME_UNTIL_RETRY = 100;
56
62 protected $maxCopyTries;
63
67 public function __construct()
68 {
69 $this->eventHandler = ezpEvent::getInstance();
70 $fileINI = eZINI::instance( 'file.ini' );
71 $this->maxCopyTries = (int)$fileINI->variable( 'eZDFSClusteringSettings', 'MaxCopyRetries' );
72
73 if ( defined( 'CLUSTER_METADATA_TABLE_CACHE' ) )
74 {
75 $this->metaDataTableCache = CLUSTER_METADATA_TABLE_CACHE;
76 }
77 else if ( $fileINI->hasVariable( 'eZDFSClusteringSettings', 'MetaDataTableNameCache' ) )
78 {
79 $this->metaDataTableCache = $fileINI->variable( 'eZDFSClusteringSettings', 'MetaDataTableNameCache' );
80 }
81
82 $this->cacheDir = eZINI::instance( 'site.ini' )->variable( 'FileSettings', 'CacheDir' );
83 $this->storageDir = eZINI::instance( 'site.ini' )->variable( 'FileSettings', 'StorageDir' );
84 }
85
95 protected function dbTable( $filePath )
96 {
97 if ( $this->metaDataTableCache == $this->metaDataTable )
98 return $this->metaDataTable;
99
100 if ( strpos( $filePath, $this->cacheDir ) !== false && strpos( $filePath, $this->storageDir ) === false )
101 {
102 return $this->metaDataTableCache;
103 }
104
105 return $this->metaDataTable;
106 }
107
115 public function _connect()
116 {
117 $siteINI = eZINI::instance( 'site.ini' );
118
119 // DB Connection setup
120 // This part is not actually required since _connect will only be called
121 // once, but it is useful to run the unit tests. So be it.
122 // @todo refactor this using eZINI::setVariable in unit tests
123 if ( self::$dbparams === null )
124 {
125 $fileINI = eZINI::instance( 'file.ini' );
126
127 self::$dbparams = array();
128 self::$dbparams['host'] = $fileINI->variable( 'eZDFSClusteringSettings', 'DBHost' );
129 $dbPort = $fileINI->variable( 'eZDFSClusteringSettings', 'DBPort' );
130 self::$dbparams['port'] = $dbPort !== '' ? $dbPort : null;
131 self::$dbparams['socket'] = $fileINI->variable( 'eZDFSClusteringSettings', 'DBSocket' );
132 self::$dbparams['dbname'] = $fileINI->variable( 'eZDFSClusteringSettings', 'DBName' );
133 self::$dbparams['user'] = $fileINI->variable( 'eZDFSClusteringSettings', 'DBUser' );
134 self::$dbparams['pass'] = $fileINI->variable( 'eZDFSClusteringSettings', 'DBPassword' );
135
136 self::$dbparams['max_connect_tries'] = $fileINI->variable( 'eZDFSClusteringSettings', 'DBConnectRetries' );
137 self::$dbparams['max_execute_tries'] = $fileINI->variable( 'eZDFSClusteringSettings', 'DBExecuteRetries' );
138
139 self::$dbparams['sql_output'] = $siteINI->variable( "DatabaseSettings", "SQLOutput" ) == "enabled";
140
141 self::$dbparams['cache_generation_timeout'] = $siteINI->variable( "ContentSettings", "CacheGenerationTimeout" );
142 }
143
144 $serverString = self::$dbparams['host'];
145 if ( self::$dbparams['socket'] )
146 $serverString .= ':' . self::$dbparams['socket'];
147 elseif ( self::$dbparams['port'] )
148 $serverString .= ':' . self::$dbparams['port'];
149
150 $maxTries = self::$dbparams['max_connect_tries'];
151 $tries = 0;
152 eZDebug::accumulatorStart( 'mysql_cluster_connect', 'MySQL Cluster', 'Cluster database connection' );
153 while ( $tries < $maxTries )
154 {
155 if ( $this->db = mysqli_connect( self::$dbparams['host'], self::$dbparams['user'], self::$dbparams['pass'], self::$dbparams['dbname'], self::$dbparams['port'] ) )
156 break;
157 ++$tries;
158 }
159 eZDebug::accumulatorStop( 'mysql_cluster_connect' );
160 if ( !$this->db )
161 {
162 throw new eZClusterHandlerDBNoConnectionException(
163 $serverString,
164 self::$dbparams['user'],
165 self::$dbparams['pass'],
166 'Error ' . mysqli_connect_errno() . ': ' . mysqli_connect_error()
167 );
168 }
169
170 /*if ( !mysql_select_db( self::$dbparams['dbname'], $this->db ) )
171 throw new eZClusterHandlerDBNoDatabaseException( self::$dbparams['dbname'] );*/
172
173 // DFS setup
174 $this->dfsbackend = eZDFSFileHandlerBackendFactory::build();
175
176 $charset = trim( $siteINI->variable( 'DatabaseSettings', 'Charset' ) );
177 if ( $charset === '' )
178 {
179 $charset = eZTextCodec::internalCharset();
180 }
181
182 if ( $charset )
183 {
184 if ( !mysqli_set_charset( $this->db, eZMySQLCharset::mapTo( $charset ) ) )
185 {
186 $this->_fail( "Failed to set Database charset to $charset." );
187 }
188 }
189 }
190
194 public function _disconnect()
195 {
196 if ( $this->db !== null )
197 {
198 mysqli_close( $this->db );
199 $this->db = null;
200 }
201 }
202
212 public function _copy( $srcFilePath, $dstFilePath, $fname = false )
213 {
214 if ( $fname )
215 $fname .= "::_copy($srcFilePath, $dstFilePath)";
216 else
217 $fname = "_copy($srcFilePath, $dstFilePath)";
218
219 // fetch source file metadata
220 $metaData = $this->_fetchMetadata( $srcFilePath, $fname );
221 // if source file does not exist then do nothing.
222 // @todo Throw an exception here.
223 // Info: $srcFilePath
224 if ( !$metaData )
225 {
226 return false;
227 }
228 $result = $this->_protect( array( $this, "_copyInner" ), $fname,
229 $srcFilePath, $dstFilePath, $fname, $metaData );
230
231 $this->eventHandler->notify( 'cluster/deleteFile', array( $dstFilePath ) );
232
233 return $result;
234 }
235
247 private function _copyInner( $srcFilePath, $dstFilePath, $fname, $metaData )
248 {
249 $this->_delete( $dstFilePath, true, $fname );
250
251 $datatype = $metaData['datatype'];
252 $filePathHash = md5( $dstFilePath );
253 $scope = $metaData['scope'];
254 $contentLength = $metaData['size'];
255 $fileMTime = $metaData['mtime'];
256 $nameTrunk = self::nameTrunk( $dstFilePath, $scope );
257
258 // Copy file metadata.
259 if ( $this->_insertUpdate( $this->dbTable( $dstFilePath ),
260 array( 'datatype'=> $datatype,
261 'name' => $dstFilePath,
262 'name_trunk' => $nameTrunk,
263 'name_hash' => $filePathHash,
264 'scope' => $scope,
265 'size' => $contentLength,
266 'mtime' => $fileMTime,
267 'expired' => ($fileMTime < 0) ? 1 : 0 ),
268 "datatype=VALUES(datatype), scope=VALUES(scope), size=VALUES(size), mtime=VALUES(mtime), expired=VALUES(expired)",
269 $fname ) === false )
270 {
271 return $this->_fail( $srcFilePath, "Failed to insert file metadata on copying." );
272 }
273
274 // Copy file data.
275 if ( !$this->dfsbackend->copyFromDFSToDFS( $srcFilePath, $dstFilePath ) )
276 {
277 return $this->_fail( $srcFilePath, "Failed to copy DFS://$srcFilePath to DFS://$dstFilePath" );
278 }
279 return true;
280 }
281
296 public function _purge( $filePath, $onlyExpired = false, $expiry = false, $fname = false )
297 {
298 if ( $fname )
299 $fname .= "::_purge($filePath)";
300 else
301 $fname = "_purge($filePath)";
302 $sql = "DELETE FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=" . $this->_md5( $filePath );
303 if ( $expiry !== false )
304 {
305 $sql .= " AND mtime<" . (int)$expiry;
306 }
307 elseif ( $onlyExpired )
308 {
309 $sql .= " AND expired=1";
310 }
311 if ( !$this->_query( $sql, $fname ) )
312 {
313 return $this->_fail( "Purging file metadata for $filePath failed" );
314 }
315 if ( mysqli_affected_rows( $this->db ) == 1 )
316 {
317 $this->dfsbackend->delete( $filePath );
318 }
319
320 $this->eventHandler->notify( 'cluster/deleteFile', array( $filePath ) );
321
322 return true;
323 }
324
339 public function _purgeByLike( $like, $onlyExpired = false, $limit = 50, $expiry = false, $fname = false )
340 {
341 if ( $fname )
342 $fname .= "::_purgeByLike($like, $onlyExpired)";
343 else
344 $fname = "_purgeByLike($like, $onlyExpired)";
345
346 // common query part used for both DELETE and SELECT
347 $where = " WHERE name LIKE " . $this->_quote( $like, true );
348
349 if ( $expiry !== false )
350 $where .= " AND mtime < " . (int)$expiry;
351 elseif ( $onlyExpired )
352 $where .= " AND expired = 1";
353
354 if ( $limit )
355 $sqlLimit = " LIMIT $limit";
356 else
357 $sqlLimit = "";
358
359 $this->_begin( $fname );
360
361 // select query, in FOR UPDATE mode
362 $selectSQL = "SELECT name FROM " . $this->dbTable( $like ) .
363 "{$where} {$sqlLimit} FOR UPDATE";
364 if ( !$res = $this->_query( $selectSQL, $fname ) )
365 {
366 $this->_rollback( $fname );
367 return $this->_fail( "Selecting file metadata by like statement $like failed" );
368 }
369 $resultCount = mysqli_num_rows( $res );
370
371 // if there are no results, we can just return 0 and stop right here
372 if ( $resultCount == 0 )
373 {
374 $this->_rollback( $fname );
375 return 0;
376 }
377 // the candidate for purge are indexed in an array
378 else
379 {
380 for ( $i = 0; $i < $resultCount; $i++ )
381 {
382 $row = mysqli_fetch_assoc( $res );
383 $files[] = $row['name'];
384 }
385 }
386
387 // delete query
388 $deleteSQL = "DELETE FROM " . $this->dbTable( $like ) . " {$where} {$sqlLimit}";
389 if ( !$res = $this->_query( $deleteSQL, $fname ) )
390 {
391 $this->_rollback( $fname );
392 return $this->_fail( "Purging file metadata by like statement $like failed" );
393 }
394 $deletedDBFiles = mysqli_affected_rows( $this->db );
395
396 $commitResult = $this->_commit( $fname );
397 if ( $commitResult === true || $commitResult === null )
398 {
399 $this->dfsbackend->delete( $files );
400
401 return $deletedDBFiles;
402 }
403
404 return false;
405 }
406
422 public function _delete( $filePath, $insideOfTransaction = false, $fname = false )
423 {
424 if ( $fname )
425 $fname .= "::_delete($filePath)";
426 else
427 $fname = "_delete($filePath)";
428 // @todo Check if this is requried: _protec will already take care of
429 // checking if a transaction is running. But leave it like this
430 // for now.
431 if ( $insideOfTransaction )
432 {
433 $res = $this->_deleteInner( $filePath, $fname );
434 if ( !$res || $res instanceof eZMySQLBackendError )
435 {
436 $this->_handleErrorType( $res );
437 }
438 }
439 else
440 {
441 $res = $this->_protect( array( $this, '_deleteInner' ), $fname,
442 $filePath, $insideOfTransaction, $fname );
443 }
444
445 $this->eventHandler->notify( 'cluster/deleteFile', array( $filePath ) );
446
447 return $res;
448 }
449
457 protected function _deleteInner( $filePath, $fname )
458 {
459 if ( !$this->_query( "UPDATE " . $this->dbTable( $filePath ) . " SET mtime=-ABS(mtime), expired=1 WHERE name_hash=" . $this->_md5( $filePath ), $fname ) )
460 return $this->_fail( "Deleting file $filePath failed" );
461 return true;
462 }
463
478 public function _deleteByLike( $like, $fname = false )
479 {
480 if ( $fname )
481 $fname .= "::_deleteByLike($like)";
482 else
483 $fname = "_deleteByLike($like)";
484 $return = $this->_protect( array( $this, '_deleteByLikeInner' ), $fname,
485 $like, $fname );
486
487 if ( $return )
488 $this->eventHandler->notify( 'cluster/deleteByLike', array( $like ) );
489
490 return $return;
491 }
492
500 private function _deleteByLikeInner( $like, $fname )
501 {
502 $sql = "UPDATE " . $this->dbTable( $like ) . " SET mtime=-ABS(mtime), expired=1\nWHERE name like ". $this->_quote( $like, true );
503 if ( !$res = $this->_query( $sql, $fname ) )
504 {
505 return $this->_fail( "Failed to delete files by like: '$like'" );
506 }
507 return true;
508 }
509
518 public function _deleteByWildcard( $wildcard, $fname = false )
519 {
520 if ( $fname )
521 $fname .= "::_deleteByWildcard($wildcard)";
522 else
523 $fname = "_deleteByWildcard($wildcard)";
524 return $this->_protect( array( $this, '_deleteByWildcardInner' ), $fname,
525 $wildcard, $fname );
526 }
527
536 protected function _deleteByWildcardInner( $wildcard, $fname )
537 {
538 // Convert wildcard to regexp.
539 $regex = '^' . mysqli_real_escape_string( $this->db, $wildcard ) . '$';
540
541 $regex = str_replace( array( '.' ),
542 array( '\.' ),
543 $regex );
544
545 $regex = str_replace( array( '?', '*', '{', '}', ',' ),
546 array( '.', '.*', '(', ')', '|' ),
547 $regex );
548
549 $sql = "UPDATE " . $this->dbTable( $wildcard ) . " SET mtime=-ABS(mtime), expired=1\nWHERE name REGEXP '$regex'";
550 if ( !$res = $this->_query( $sql, $fname ) )
551 {
552 return $this->_fail( "Failed to delete files by wildcard: '$wildcard'" );
553 }
554 return true;
555 }
556
564 public function _deleteByDirList( $dirList, $commonPath, $commonSuffix, $fname = false )
565 {
566 if ( $fname )
567 $fname .= "::_deleteByDirList(" . join( ", ", $dirList ) . ", $commonPath, $commonSuffix)";
568 else
569 $fname = "_deleteByDirList(" . join( ", ", $dirList ) . ", $commonPath, $commonSuffix)";
570 return $this->_protect( array( $this, '_deleteByDirListInner' ), $fname,
571 $dirList, $commonPath, $commonSuffix, $fname );
572 }
573
581 protected function _deleteByDirListInner( $dirList, $commonPath, $commonSuffix, $fname )
582 {
583 foreach ( $dirList as $dirItem )
584 {
585 if ( strstr( $commonPath, '/cache/content' ) !== false or strstr( $commonPath, '/cache/template-block' ) !== false )
586 {
587 $event = 'cluster/deleteByNametrunk';
588 $nametrunk = "$commonPath/$dirItem/$commonSuffix";
589 $eventParameters = array( $nametrunk );
590 $where = "WHERE name_trunk = " . $this->_quote( $nametrunk );
591 unset( $nametrunk );
592 }
593 else
594 {
595 $event = 'cluster/deleteByDirList';
596 $eventParameters = array( $commonPath, $dirItem, $commonSuffix );
597 $where = "WHERE name LIKE ".$this->_quote( "$commonPath/$dirItem/$commonSuffix%", true );
598 }
599 $sql = "UPDATE " . $this->dbTable( $commonPath ) . " SET mtime=-ABS(mtime), expired=1\n$where";
600 if ( !$res = $this->_query( $sql, $fname ) )
601 {
602 eZDebug::writeError( "Failed to delete files in dir: '$commonPath/$dirItem/$commonSuffix%'", __METHOD__ );
603 $event = false;
604 }
605
606 if ( $event )
607 {
608 $this->eventHandler->notify( $event, $eventParameters );
609 unset( $event );
610 }
611 }
612 return true;
613 }
614
624 public function _exists( $filePath, $fname = false, $ignoreExpiredFiles = true, $checkOnDFS = false )
625 {
626 if ( $fname )
627 $fname .= "::_exists($filePath)";
628 else
629 $fname = "_exists($filePath)";
630
631 $row = $this->eventHandler->filter( 'cluster/fileExists', $filePath );
632
633 if ( !is_array( $row ) )
634 {
635 $row = $this->_selectOneRow( "SELECT name, mtime FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=" . $this->_md5( $filePath ),
636 $fname, "Failed to check file '$filePath' existance: ", true );
637 }
638 if ( $row === false )
639 return false;
640
641 if ( $ignoreExpiredFiles )
642 $rc = $row[1] >= 0;
643 else
644 $rc = true;
645
646 if ( $checkOnDFS && $rc )
647 {
648 $rc = $this->dfsbackend->existsOnDFS( $filePath );
649 }
650 return $rc;
651 }
652
659 private function mkdir_p( $dir )
660 {
661 // create parent directories
662 $dirElements = explode( '/', $dir );
663 if ( count( $dirElements ) == 0 )
664 return true;
665
666 $result = true;
667 $currentDir = $dirElements[0];
668
669 if ( $currentDir != '' && !file_exists( $currentDir ) && !eZDir::mkdir( $currentDir, false ) )
670 return false;
671
672 for ( $i = 1; $i < count( $dirElements ); ++$i )
673 {
674 $dirElement = $dirElements[$i];
675 if ( strlen( $dirElement ) == 0 )
676 continue;
677
678 $currentDir .= '/' . $dirElement;
679
680 if ( !file_exists( $currentDir ) && !eZDir::mkdir( $currentDir, false ) )
681 return false;
682
683 $result = true;
684 }
685
686 return $result;
687 }
688
694 protected function __mkdir_p( $dir )
695 {
696 return $this->mkdir_p( $dir );
697 }
698
708 public function _fetch( $filePath, $uniqueName = false )
709 {
710 $metaData = $this->_fetchMetadata( $filePath );
711 if ( !$metaData )
712 {
713 // @todo Throw an exception
714 eZDebug::writeError( "File '$filePath' does not exist while trying to fetch.", __METHOD__ );
715 return false;
716 }
717
718 $dfsFileSize = $this->dfsbackend->getDfsFileSize( $filePath );
719 $loopCount = 0;
720 $localFileSize = 0;
721
722 do
723 {
724 // create temporary file
725 $tmpid = getmypid() . '-' . mt_rand() .'tmp';
726 if ( strrpos( $filePath, '.' ) > 0 )
727 $tmpFilePath = substr_replace( $filePath, $tmpid, strrpos( $filePath, '.' ), 0 );
728 else
729 $tmpFilePath = $filePath . '.' . $tmpid;
730 $this->mkdir_p( dirname( $tmpFilePath ) );
731 eZDebugSetting::writeDebug( 'kernel-clustering', "copying DFS://$filePath to FS://$tmpFilePath on try: $loopCount " );
732
733 // copy DFS file to temporary FS path
734 // @todo Throw an exception
735 if ( !$this->dfsbackend->copyFromDFS( $filePath, $tmpFilePath ) )
736 {
737 eZDebug::writeError("Failed copying DFS://$filePath to FS://$tmpFilePath ");
738 usleep( self::TIME_UNTIL_RETRY );
739 ++$loopCount;
740 continue;
741 }
742
743 if ( $uniqueName !== true )
744 {
745 if( !eZFile::rename( $tmpFilePath, $filePath, false, eZFile::CLEAN_ON_FAILURE | eZFile::APPEND_DEBUG_ON_FAILURE ) )
746 {
747 usleep( self::TIME_UNTIL_RETRY );
748 ++$loopCount;
749 continue;
750 }
751 }
752 $filePath = ($uniqueName) ? $tmpFilePath : $filePath ;
753
754 // If all data has been written correctly, return the filepath.
755 // Otherwise let the loop continue
756 clearstatcache( true, $filePath );
757 $localFileSize = filesize( $filePath );
758 if ( $dfsFileSize == $localFileSize )
759 {
760 return $filePath;
761 }
762 // Sizes might have been corrupted by FS problems. Enforcing temp file removal.
763 else if ( file_exists( $tmpFilePath ) )
764 {
765 unlink( $tmpFilePath );
766 }
767
768 usleep( self::TIME_UNTIL_RETRY );
769 ++$loopCount;
770 }
771 while ( $dfsFileSize > $localFileSize && $loopCount < $this->maxCopyTries );
772
773 // Copy from DFS has failed :-(
774 eZDebug::writeError( "Size ({$localFileSize}) of written data for file '{$filePath}' does not match expected size {$metaData['size']}", __METHOD__ );
775 return false;
776 }
777
783 public function _fetchContents( $filePath, $fname = false )
784 {
785 if ( $fname )
786 $fname .= "::_fetchContents($filePath)";
787 else
788 $fname = "_fetchContents($filePath)";
789 $metaData = $this->_fetchMetadata( $filePath, $fname );
790 // @todo Throw an exception
791 if ( !$metaData )
792 {
793 eZDebug::writeError( "File '$filePath' does not exist while trying to fetch its contents.", __METHOD__ );
794 return false;
795 }
796
797 // @todo Catch an exception
798 if ( !$contents = $this->dfsbackend->getContents( $filePath ) )
799 {
800 eZDebug::writeError("An error occured while reading contents of DFS://$filePath", __METHOD__ );
801 return false;
802 }
803 return $contents;
804 }
805
814 function _fetchMetadata( $filePath, $fname = false )
815 {
816 $metadata = $this->eventHandler->filter( 'cluster/loadMetadata', $filePath );
817 if ( is_array( $metadata ) )
818 return $metadata;
819
820 if ( $fname )
821 $fname .= "::_fetchMetadata($filePath)";
822 else
823 $fname = "_fetchMetadata($filePath)";
824 $sql = "SELECT * FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=" . $this->_md5( $filePath );
825 $metadata = $this->_selectOneAssoc( $sql, $fname,
826 "Failed to retrieve file metadata: $filePath",
827 true );
828 if ( is_array( $metadata ) )
829 $this->eventHandler->notify( 'cluster/storeMetadata', array( $metadata ) );
830
831 return $metadata;
832 }
833
840 public function _linkCopy( $srcPath, $dstPath, $fname = false )
841 {
842 if ( $fname )
843 $fname .= "::_linkCopy($srcPath,$dstPath)";
844 else
845 $fname = "_linkCopy($srcPath,$dstPath)";
846 return $this->_copy( $srcPath, $dstPath, $fname );
847 }
848
859 public function _passThrough( $filePath, $startOffset = 0, $length = false, $fname = false )
860 {
861 if ( $fname )
862 $fname .= "::_passThrough($filePath)";
863 else
864 $fname = "_passThrough($filePath)";
865
866 $metaData = $this->_fetchMetadata( $filePath, $fname );
867 // @todo Throw an exception
868 if ( !$metaData )
869 return false;
870
871 // @todo Catch an exception
872 $this->dfsbackend->passthrough( $filePath, $startOffset, $length );
873
874 return true;
875 }
876
884 public function _rename( $srcFilePath, $dstFilePath )
885 {
886 if ( strcmp( $srcFilePath, $dstFilePath ) == 0 )
887 return;
888
889 // fetch source file metadata
890 $metaData = $this->_fetchMetadata( $srcFilePath );
891 // if source file does not exist then do nothing.
892 // @todo Throw an exception
893 if ( !$metaData )
894 return false;
895
896 $this->_begin( __METHOD__ );
897
898 $dstFilePathStr = mysqli_real_escape_string( $this->db, $dstFilePath );
899 $dstNameTrunkStr = mysqli_real_escape_string( $this->db, self::nameTrunk( $dstFilePath, $metaData['scope'] ) );
900
901 // Mark entry for update to lock it
902 $sql = "SELECT * FROM " . $this->dbTable( $srcFilePath ) . " WHERE name_hash = " . $this->_md5( $srcFilePath ) . " FOR UPDATE";
903 if ( !$this->_query( $sql, "_rename($srcFilePath, $dstFilePath)" ) )
904 {
905 // @todo Throw an exception
906 eZDebug::writeError( "Failed locking file '$srcFilePath'", __METHOD__ );
907 $this->_rollback( __METHOD__ );
908 return false;
909 }
910
911 if ( $this->_exists( $dstFilePath, false, false ) )
912 $this->_purge( $dstFilePath, false );
913
914 // Create a new meta-data entry for the new file to make foreign keys happy.
915 $sql = "INSERT INTO " . $this->dbTable( $srcFilePath ) . " (name, name_trunk, name_hash, datatype, scope, size, mtime, expired) SELECT '$dstFilePathStr' AS name, '$dstNameTrunkStr' as name_trunk, " . $this->_md5( $dstFilePath ) . " AS name_hash, datatype, scope, size, mtime, expired FROM " . $this->dbTable( $srcFilePath ) . " WHERE name_hash = " . $this->_md5( $srcFilePath );
916 if ( !$this->_query( $sql, "_rename($srcFilePath, $dstFilePath)" ) )
917 {
918 eZDebug::writeError( "Failed making new file entry '$dstFilePath'", __METHOD__ );
919 $this->_rollback( __METHOD__ );
920 // @todo Throw an exception
921 return false;
922 }
923
924 if ( !$this->dfsbackend->copyFromDFSToDFS( $srcFilePath, $dstFilePath ) )
925 {
926 return $this->_fail( "Failed to copy DFS://$srcFilePath to DFS://$dstFilePath" );
927 }
928
929 // Remove old entry
930 $sql = "DELETE FROM " . $this->dbTable( $srcFilePath ) . " WHERE name_hash = " . $this->_md5( $srcFilePath );
931 if ( !$this->_query( $sql, "_rename($srcFilePath, $dstFilePath)" ) )
932 {
933 eZDebug::writeError( "Failed removing old file '$srcFilePath'", __METHOD__ );
934 $this->_rollback( __METHOD__ );
935 // @todo Throw an exception
936 return false;
937 }
938
939 // delete original DFS file
940 // @todo Catch an exception
941 $this->dfsbackend->delete( $srcFilePath );
942
943 $this->_commit( __METHOD__ );
944
945 $this->eventHandler->notify( 'cluster/deleteFile', array( $srcFilePath ) );
946 // no need to notify about the destination filePath, as it is already cleared by purge called above
947
948 return true;
949 }
950
960 function _store( $filePath, $datatype, $scope, $fname = false )
961 {
962 if ( !is_readable( $filePath ) )
963 {
964 eZDebug::writeError( "Unable to store file '$filePath' since it is not readable.", __METHOD__ );
965 return;
966 }
967 if ( $fname )
968 $fname .= "::_store($filePath, $datatype, $scope)";
969 else
970 $fname = "_store($filePath, $datatype, $scope)";
971
972 $return = $this->_protect( array( $this, '_storeInner' ), $fname,
973 $filePath, $datatype, $scope, $fname );
974
975 $this->eventHandler->notify( 'cluster/deleteFile', array( $filePath ) );
976
977 return $return;
978 }
979
989 function _storeInner( $filePath, $datatype, $scope, $fname )
990 {
991 // Insert file metadata.
992 clearstatcache( true, $filePath );
993 $fileMTime = filemtime( $filePath );
994 // This check intentionally match 'false' value as well
995 if ( $fileMTime == 0 )
996 $fileMTime = -1;
997 $contentLength = filesize( $filePath );
998 $filePathHash = md5( $filePath );
999 $nameTrunk = self::nameTrunk( $filePath, $scope );
1000
1001 if ( $this->_insertUpdate( $this->dbTable( $filePath ),
1002 array( 'datatype' => $datatype,
1003 'name' => $filePath,
1004 'name_trunk' => $nameTrunk,
1005 'name_hash' => $filePathHash,
1006 'scope' => $scope,
1007 'size' => $contentLength,
1008 'mtime' => $fileMTime,
1009 'expired' => ( $fileMTime < 0 ) ? 1 : 0 ),
1010 "datatype=VALUES(datatype), scope=VALUES(scope), size=VALUES(size), mtime=VALUES(mtime), expired=VALUES(expired)",
1011 $fname ) === false )
1012 {
1013 return false;
1014 }
1015
1016 // copy given $filePath to DFS
1017 if ( !$this->dfsbackend->copyToDFS( $filePath ) )
1018 {
1019 return $this->_fail( "Failed to copy FS://$filePath to DFS://$filePath" );
1020 }
1021
1022 return true;
1023 }
1024
1036 function _storeContents( $filePath, $contents, $scope, $datatype, $mtime = false, $fname = false )
1037 {
1038 if ( $fname )
1039 $fname .= "::_storeContents($filePath, ..., $scope, $datatype)";
1040 else
1041 $fname = "_storeContents($filePath, ..., $scope, $datatype)";
1042
1043 return $this->_protect(
1044 array( $this, '_storeContentsInner' ),
1045 $fname,
1046 $filePath,
1047 $contents,
1048 $scope,
1049 $datatype,
1050 $mtime,
1051 $fname
1052 );
1053 }
1054
1064 function _storeContentsInner( $filePath, $contents, $scope, $datatype, $curTime, $fname )
1065 {
1066 // Insert file metadata.
1067 $contentLength = strlen( $contents );
1068 $filePathHash = md5( $filePath );
1069 $nameTrunk = self::nameTrunk( $filePath, $scope );
1070 if ( $curTime === false )
1071 $curTime = time();
1072
1073 if ( $this->_insertUpdate( $this->dbTable( $filePath ),
1074 array( 'datatype' => $datatype,
1075 'name' => $filePath,
1076 'name_trunk' => $nameTrunk,
1077 'name_hash' => $filePathHash,
1078 'scope' => $scope,
1079 'size' => $contentLength,
1080 'mtime' => $curTime,
1081 'expired' => ( $curTime < 0 ) ? 1 : 0 ),
1082 "datatype=VALUES(datatype), name_trunk='$nameTrunk', scope=VALUES(scope), size=VALUES(size), mtime=VALUES(mtime), expired=VALUES(expired)",
1083 $fname ) === false )
1084 {
1085 return false;
1086 }
1087
1088 if ( !$this->dfsbackend->createFileOnDFS( $filePath, $contents ) )
1089 {
1090 return $this->_fail( "Failed to open DFS://$filePath for writing" );
1091 }
1092
1093 $this->eventHandler->notify( 'cluster/deleteFile', array( $filePath ) );
1094
1095 return true;
1096 }
1097
1098
1109 public function _getFileList( $scopes = false, $excludeScopes = false, $limit = false, $path = false )
1110 {
1111 $filePathList = array();
1112 $tables = array_unique( array( $this->metaDataTable, $this->metaDataTableCache ) );
1113
1114 foreach ( $tables as $table )
1115 {
1116 $query = 'SELECT name FROM ' . $table;
1117
1118 if ( is_array( $scopes ) && count( $scopes ) > 0 )
1119 {
1120 $query .= ' WHERE scope ';
1121 if ( $excludeScopes )
1122 $query .= 'NOT ';
1123 $query .= "IN ('" . implode( "', '", $scopes ) . "')";
1124 }
1125 if ( $path != false && $scopes == false)
1126 {
1127 $query .= " WHERE name LIKE '" . $path . "%'";
1128 }
1129 else if ( $path != false)
1130 {
1131 $query .= " AND name LIKE '" . $path . "%'";
1132 }
1133 if ( $limit && array_sum($limit) )
1134 {
1135 $query .= " LIMIT {$limit[0]}, {$limit[1]}";
1136 }
1137 $rslt = $this->_query( $query, "_getFileList( array( " . implode( ', ', is_array( $scopes ) ? $scopes : array() ) . " ), $excludeScopes )" );
1138 if ( !$rslt )
1139 {
1140 eZDebug::writeError( 'Unable to get file list', __METHOD__ );
1141 throw new Exception(
1142 "dfs/mysqli DB error: " . mysqli_error( $this->db ) . "\nSQL Query: $query",
1143 mysqli_errno( $this->db )
1144 );
1145 }
1146
1147 while ( $row = mysqli_fetch_row( $rslt ) )
1148 {
1149 $filePathList[] = $row[0];
1150 }
1151
1152 mysqli_free_result( $rslt );
1153 }
1154
1155 return $filePathList;
1156 }
1157
1165 protected function _die( $msg, $sql = null )
1166 {
1167 if ( $this->db )
1168 {
1169 eZDebug::writeError( $sql, "$msg" . mysqli_error( $this->db ) );
1170 }
1171 else
1172 {
1174 eZDebug::writeError( $sql, "$msg: " . mysqli_error() );
1175 }
1176 }
1177
1189 function _insert( $table, $array, $fname )
1190 {
1191 $keys = array_keys( $array );
1192 $query = "INSERT INTO $table (" . join( ", ", $keys ) . ") VALUES (" . $this->_sqlList( $array ) . ")";
1193 $res = $this->_query( $query, $fname );
1194 if ( !$res )
1195 {
1196 // @todo Throw an exception
1197 return false;
1198 }
1199 return mysqli_insert_id( $this->db );
1200 }
1201
1218 protected function _insertUpdate( $table, $array, $update, $fname, $reportError = true )
1219 {
1220 $keys = array_keys( $array );
1221 $query = "INSERT INTO $table (" . join( ", ", $keys ) . ") VALUES (" . $this->_sqlList( $array ) . ")\nON DUPLICATE KEY UPDATE $update";
1222 $res = $this->_query( $query, $fname, $reportError );
1223 if ( !$res )
1224 {
1225 // @todo Throw an exception
1226 return $this->_fail( $query, __METHOD__ );
1227 }
1228 return mysqli_insert_id( $this->db );
1229 }
1230
1238 protected function _sqlList( $array )
1239 {
1240 $text = "";
1241 $sep = "";
1242 foreach ( $array as $e )
1243 {
1244 $text .= $sep;
1245 $text .= $this->_quote( $e );
1246 $sep = ", ";
1247 }
1248 return $text;
1249 }
1250
1263 protected function _selectOneRow( $query, $fname, $error = false, $debug = false )
1264 {
1265 return $this->_selectOne( $query, $fname, $error, $debug, "mysqli_fetch_row" );
1266 }
1267
1280 protected function _selectOneAssoc( $query, $fname, $error = false, $debug = false )
1281 {
1282 return $this->_selectOne( $query, $fname, $error, $debug, "mysqli_fetch_assoc" );
1283 }
1284
1300 protected function _selectOne( $query, $fname, $error = false, $debug = false, $fetchCall )
1301 {
1302 eZDebug::accumulatorStart( 'mysql_cluster_query', 'MySQL Cluster', 'DB queries' );
1303 $time = microtime( true );
1304
1305 $res = @mysqli_query( $this->db, $query );
1306 if ( !$res )
1307 {
1308 if ( mysqli_errno( $this->db ) == 1146 )
1309 {
1310 throw new eZDFSFileHandlerTableNotFoundException(
1311 $query, mysqli_error( $this->db ) );
1312 }
1313 else
1314 {
1315 $this->_error( $query, $fname, $error );
1316 eZDebug::accumulatorStop( 'mysql_cluster_query' );
1317 throw new Exception(
1318 "dfs/mysqli DB error: " . mysqli_error( $this->db ) . "\nSQL Query: $query",
1319 mysqli_errno( $this->db )
1320 );
1321 }
1322 }
1323
1324 // we test the return value of mysqli_num_rows and not mysql_fetch, unlike in the mysql handler,
1325 // since fetch will return null and not false if there are no results
1326 $nRows = mysqli_num_rows( $res );
1327 if ( $nRows > 1 )
1328 {
1329 eZDebug::writeError( 'Duplicate entries found', $fname );
1330 eZDebug::accumulatorStop( 'mysql_cluster_query' );
1331 // @todo throw an exception instead. Should NOT happen.
1332 }
1333 elseif ( $nRows === 0 )
1334 {
1335 eZDebug::accumulatorStop( 'mysql_cluster_query' );
1336 return false;
1337 }
1338
1339 $row = $fetchCall( $res );
1340 mysqli_free_result( $res );
1341 if ( $debug )
1342 $query = "SQL for _selectOneAssoc:\n" . $query . "\n\nRESULT:\n" . var_export( $row, true );
1343
1344 $time = microtime( true ) - $time;
1345 eZDebug::accumulatorStop( 'mysql_cluster_query' );
1346
1347 $this->_report( $query, $fname, $time );
1348 return $row;
1349 }
1350
1355 protected function _begin( $fname = false )
1356 {
1357 if ( $fname )
1358 $fname .= "::_begin";
1359 else
1360 $fname = "_begin";
1361 $this->transactionCount++;
1362 if ( $this->transactionCount == 1 )
1363 $this->_query( "BEGIN", $fname );
1364 }
1365
1374 protected function _commit( $fname = false )
1375 {
1376 if ( $fname )
1377 $fname .= "::_commit";
1378 else
1379 $fname = "_commit";
1380 $this->transactionCount--;
1381 if ( $this->transactionCount == 0 )
1382 {
1383 return $this->_query("COMMIT", $fname) !== false;
1384 }
1385
1386 return null;
1387 }
1388
1394 protected function _rollback( $fname = false )
1395 {
1396 if ( $fname )
1397 $fname .= "::_rollback";
1398 else
1399 $fname = "_rollback";
1400 $this->transactionCount--;
1401 if ( $this->transactionCount == 0 )
1402 $this->_query( "ROLLBACK", $fname );
1403 }
1404
1419 protected function _protect()
1420 {
1421 $args = func_get_args();
1422 $callback = array_shift( $args );
1423 $fname = array_shift( $args );
1424
1425 $maxTries = self::$dbparams['max_execute_tries'];
1426 $tries = 0;
1427 while ( $tries < $maxTries )
1428 {
1429 $this->_begin( $fname );
1430
1431 $result = call_user_func_array( $callback, $args );
1432
1433 $errno = mysqli_errno( $this->db );
1434 if ( $errno == 1205 || // Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT)
1435 $errno == 1213 ) // Error: 1213 SQLSTATE: 40001 (ER_LOCK_DEADLOCK)
1436 {
1437 $tries++;
1438 $this->_rollback( $fname );
1439 continue;
1440 }
1441
1442 // @todo replace with an exception
1443 if ( $result === false )
1444 {
1445 $this->_rollback( $fname );
1446 return false;
1447 }
1448 elseif ( $result instanceof eZMySQLBackendError )
1449 {
1450 eZDebug::writeError( $result->errorValue, $result->errorText );
1451 $this->_rollback( $fname );
1452 return false;
1453 }
1454
1455 break; // All is good, so break out of loop
1456 }
1457
1458 $this->_commit( $fname );
1459 return $result;
1460 }
1461
1465 protected function _handleErrorType( $res )
1466 {
1467 if ( $res === false )
1468 {
1469 eZDebug::writeError( "SQL failed" );
1470 }
1471 elseif ( $res instanceof eZMySQLBackendError )
1472 {
1473 eZDebug::writeError( $res->errorValue, $res->errorText );
1474 }
1475 }
1476
1484 protected function _isFailure( $result )
1485 {
1486 if ( $result === false || ($result instanceof eZMySQLBackendError ) )
1487 {
1488 return true;
1489 }
1490 return false;
1491 }
1492
1500 protected function _fail( $value, $text = false )
1501 {
1502 $value .= "\n" . mysqli_errno( $this->db ) . ": " . mysqli_error( $this->db );
1503 return new eZMySQLBackendError( $value, $text );
1504 }
1505
1518 protected function _query( $query, $fname = false, $reportError = true )
1519 {
1520 eZDebug::accumulatorStart( 'mysql_cluster_query', 'MySQL Cluster', 'DB queries' );
1521 $time = microtime( true );
1522
1523 $res = mysqli_query( $this->db, $query );
1524 if ( !$res && $reportError )
1525 {
1526 $this->_error( $query, $fname );
1527 }
1528
1529 $numRows = mysqli_affected_rows( $this->db );
1530
1531 $time = microtime( true ) - $time;
1532 eZDebug::accumulatorStop( 'mysql_cluster_query' );
1533
1534 $this->_report( $query, $fname, $time, $numRows );
1535 return $res;
1536 }
1537
1547 protected function _quote( $value, $escapeUnderscoreWildcards = false )
1548 {
1549 if ( $value === null )
1550 {
1551 return 'NULL';
1552 }
1553 elseif ( is_integer( $value ) )
1554 {
1555 return (string)$value;
1556 }
1557 else
1558 {
1559 if ( $escapeUnderscoreWildcards )
1560 return "'" . addcslashes( mysqli_real_escape_string( $this->db, $value ), "_" ) . "'";
1561 else
1562 return "'" . mysqli_real_escape_string( $this->db, $value )."'";
1563 }
1564 }
1565
1573 protected function _md5( $value )
1574 {
1575 return "MD5('" . mysqli_real_escape_string( $this->db, $value ) . "')";
1576 }
1577
1589 protected function _error( $query, $fname, $error = "Failed to execute SQL for function:" )
1590 {
1591 if ( $error === false )
1592 {
1593 $error = "Failed to execute SQL for function:";
1594 }
1595 else if ( is_array( $error ) )
1596 {
1597 $fname = $error[1];
1598 $error = $error[0];
1599 }
1600
1601 eZDebug::writeError( "$error\n" . mysqli_errno( $this->db ) . ': ' . mysqli_error( $this->db ), $fname );
1602 }
1603
1613 function _report( $query, $fname, $timeTaken, $numRows = false )
1614 {
1615 if ( !self::$dbparams['sql_output'] )
1616 return;
1617
1618 $rowText = '';
1619 if ( $numRows !== false )
1620 $rowText = "$numRows rows, ";
1621 static $numQueries = 0;
1622 if ( strlen( $fname ) == 0 )
1623 $fname = "_query";
1624 $backgroundClass = ($this->transactionCount > 0 ? "debugtransaction transactionlevel-$this->transactionCount" : "");
1625 eZDebug::writeNotice( "$query", "cluster::mysql::{$fname}[{$rowText}" . number_format( $timeTaken, 3 ) . " ms] query number per page:" . $numQueries++, $backgroundClass );
1626 }
1627
1644 public function _startCacheGeneration( $filePath, $generatingFilePath )
1645 {
1646 $fname = "_startCacheGeneration( {$filePath} )";
1647
1648 $nameHash = $this->_md5( $generatingFilePath );
1649 $mtime = time();
1650
1651 $insertData = array( 'name' => "'" . mysqli_real_escape_string( $this->db, $generatingFilePath ) . "'",
1652 'name_trunk' => "'" . mysqli_real_escape_string( $this->db, $generatingFilePath ) . "'",
1653 'name_hash' => $nameHash,
1654 'scope' => "''",
1655 'datatype' => "''",
1656 'mtime' => $mtime,
1657 'expired' => 0 );
1658 $query = 'INSERT INTO ' . $this->dbTable( $filePath ) . ' ( '. implode(', ', array_keys( $insertData ) ) . ' ) ' .
1659 "VALUES(" . implode( ', ', $insertData ) . ")";
1660
1661 if ( !$this->_query( $query, "_startCacheGeneration( $filePath )", false ) )
1662 {
1663 $errno = mysqli_errno( $this->db );
1664
1665 // once deadlock found / lock wait timeout found, we return ko status which will force read from stale cache
1666 // 'remaining' item is irrelevant in this case, but it needs to be set
1667 // 1205 is 'Lock wait timeout exceeded; try restarting transaction'
1668 // 1213 is 'Deadlock found when trying to get lock; try restarting transaction'
1669 if ( in_array( $errno, array( 1205, 1213 ) ) )
1670 {
1671 return array( 'result' => 'ko', 'remaining' => 0 );
1672 }
1673
1674 if ( $errno != 1062 )
1675 {
1676 eZDebug::writeError( "Unexpected error #$errno when trying to start cache generation on $filePath (".mysqli_error( $this->db ).")", __METHOD__ );
1677 eZDebug::writeDebug( $query, '$query' );
1678
1679 // @todo throw an exception
1680 return array( 'result' => 'error' );
1681 }
1682 // error 1062 is expected, since it means duplicate key (file is being generated)
1683 else
1684 {
1685 // generation timout check
1686 $query = "SELECT mtime FROM " . $this->dbTable( $filePath ) . " WHERE name_hash = {$nameHash}";
1687 $row = $this->_selectOneRow( $query, $fname, false, false );
1688
1689 // file has been renamed, i.e it is no longer a .generating file
1690 if ( $row and !isset( $row[0] ) )
1691 return array( 'result' => 'ok', 'mtime' => $mtime );
1692
1693 $remainingGenerationTime = $this->remainingCacheGenerationTime( $row );
1694 if ( $remainingGenerationTime < 0 )
1695 {
1696 $previousMTime = $row[0];
1697
1698 eZDebugSetting::writeDebug( 'kernel-clustering', "$filePath generation has timedout, taking over", __METHOD__ );
1699 $updateQuery = "UPDATE " . $this->dbTable( $filePath ) . " SET mtime = {$mtime} WHERE name_hash = {$nameHash} AND mtime = {$previousMTime}";
1700
1701 // we run the query manually since the default _query won't
1702 // report affected rows
1703 $res = mysqli_query( $this->db, $updateQuery );
1704 if ( ( $res !== false ) and mysqli_affected_rows( $this->db ) == 1 )
1705 {
1706 return array( 'result' => 'ok', 'mtime' => $mtime );
1707 }
1708 else
1709 {
1710 // @todo This would require an actual error handling
1711 eZDebug::writeError( "An error occured taking over timedout generating cache file $generatingFilePath (".mysqli_error( $this->db ).")", __METHOD__ );
1712 return array( 'result' => 'error' );
1713 }
1714 }
1715 else
1716 {
1717 return array( 'result' => 'ko', 'remaining' => $remainingGenerationTime );
1718 }
1719 }
1720 }
1721 return array( 'result' => 'ok', 'mtime' => $mtime );
1722 }
1723
1733 public function _endCacheGeneration( $filePath, $generatingFilePath, $rename )
1734 {
1735 $fname = "_endCacheGeneration( $filePath )";
1736
1737 // no rename: the .generating entry is just deleted
1738 if ( $rename === false )
1739 {
1740 $this->_query( "DELETE FROM " . $this->dbTable( $filePath ) . " WHERE name_hash = " . $this->_md5( $generatingFilePath ), $fname, true );
1741 $this->dfsbackend->delete( $generatingFilePath );
1742 return true;
1743 }
1744 // rename mode: the generating file and its contents are renamed to the
1745 // final name
1746 else
1747 {
1748 $this->_begin( $fname );
1749
1750 // both files are locked for update
1751 if ( !$res = $this->_query( "SELECT * FROM " . $this->dbTable( $filePath ) . " WHERE name_hash = " . $this->_md5( $generatingFilePath ) . " FOR UPDATE", $fname, true ) )
1752 {
1753 $this->_rollback( $fname );
1754 // @todo Throw an exception
1755 return false;
1756 }
1757 $generatingMetaData = mysqli_fetch_assoc( $res );
1758
1759 if ( empty( $generatingMetaData ) )
1760 {
1761 eZDebug::writeError("An error occured while ending cache generation, $generatingFilePath", $fname );
1762 $this->_rollback( $fname );
1763 return false;
1764 }
1765
1766 // the original file does not exist: we move the generating file
1767 $res = $this->_query( "SELECT * FROM " . $this->dbTable( $filePath ) . " WHERE name_hash = " . $this->_md5( $filePath ) . " FOR UPDATE", $fname, false );
1768 if ( mysqli_num_rows( $res ) == 0 )
1769 {
1770 $metaData = $generatingMetaData;
1771 $metaData['name'] = $filePath;
1772 $metaData['name_hash'] = md5( $filePath );
1773 $metaData['name_trunk'] = $this->nameTrunk( $filePath, $metaData['scope'] );
1774 $insertSQL = "INSERT INTO " . $this->dbTable( $filePath ) . " ( " . implode( ', ', array_keys( $metaData ) ) . " ) " .
1775 "VALUES( " . $this->_sqlList( $metaData ) . ")";
1776 if ( !$this->_query( $insertSQL, $fname, true ) )
1777 {
1778 eZDebug::writeError("An error occured creating the metadata entry for $filePath", $fname );
1779 $this->_rollback( $fname );
1780 // @todo Throw an exception
1781 return false;
1782 }
1783 // here we rename the actual FILE. The .generating file has been
1784 // created on DFS, and should be renamed
1785 if ( !$this->dfsbackend->renameOnDFS( $generatingFilePath, $filePath ) )
1786 {
1787 eZDebug::writeError("An error occured renaming DFS://$generatingFilePath to DFS://$filePath", $fname );
1788 $this->_rollback( $fname );
1789 // @todo Throw an exception
1790 return false;
1791 }
1792 $this->_query( "DELETE FROM " . $this->dbTable( $filePath ) . " WHERE name_hash = " . $this->_md5( $generatingFilePath ), $fname, true );
1793 }
1794 // the original file exists: we move the generating data to this file and update it
1795 else
1796 {
1797 if ( !$this->dfsbackend->renameOnDFS( $generatingFilePath, $filePath ) )
1798 {
1799 eZDebug::writeError("An error occured renaming DFS://$generatingFilePath to DFS://$filePath", $fname );
1800 $this->_rollback( $fname );
1801 // @todo Throw an exception
1802 return false;
1803 }
1804
1805 $mtime = $generatingMetaData['mtime'];
1806 $filesize = $generatingMetaData['size'];
1807 if ( !$this->_query( "UPDATE " . $this->dbTable( $filePath ) . " SET mtime = '{$mtime}', expired = 0, size = '{$filesize}' WHERE name_hash = " . $this->_md5( $filePath ), $fname, true ) )
1808 {
1809 $this->_rollback( $fname );
1810 // @todo Throw an exception
1811 return false;
1812 }
1813 $this->_query( "DELETE FROM " . $this->dbTable( $filePath ) . " WHERE name_hash = " . $this->_md5( $generatingFilePath ), $fname, true );
1814 }
1815
1816 $this->_commit( $fname );
1817 }
1818
1819 return true;
1820 }
1821
1831 public function _checkCacheGenerationTimeout( $generatingFilePath, $generatingFileMtime )
1832 {
1833 $fname = "_checkCacheGenerationTimeout( $generatingFilePath, $generatingFileMtime )";
1834
1835 // reporting
1836 eZDebug::accumulatorStart( 'mysql_cluster_query', 'MySQL Cluster', 'DB queries' );
1837 $time = microtime( true );
1838
1839 $nameHash = $this->_md5( $generatingFilePath );
1840 $newMtime = time();
1841
1842 // The update query will only succeed if the mtime wasn't changed in between
1843 $query = "UPDATE " . $this->dbTable( $generatingFilePath ) . " SET mtime = $newMtime WHERE name_hash = {$nameHash} AND mtime = $generatingFileMtime";
1844 $res = mysqli_query( $this->db, $query );
1845 if ( !$res )
1846 {
1847 // @todo Throw an exception
1848 $this->_error( $query, $fname );
1849 return false;
1850 }
1851 $numRows = mysqli_affected_rows( $this->db );
1852
1853 // reporting. Manual here since we don't use _query
1854 $time = microtime( true ) - $time;
1855 $this->_report( $query, $fname, $time, $numRows );
1856
1857 // no rows affected or row updated with the same value
1858 // f.e a cache-block which takes less than 1 sec to get generated
1859 // if a line has been updated by the same values, mysqli_affected_rows
1860 // returns 0, and updates nothing, we need to extra check this,
1861 if ( $numRows == 0 )
1862 {
1863 $query = "SELECT mtime FROM " . $this->dbTable( $generatingFilePath ) . " WHERE name_hash = {$nameHash}";
1864 $res = mysqli_query( $this->db, $query );
1865 if ( !$res )
1866 return false;
1867 $row = mysqli_fetch_row( $res );
1868 if ( isset( $row[0] ) && $row[0] == $generatingFileMtime )
1869 {
1870 return true;
1871 }
1872
1873 // @todo Check if an exception makes sense here
1874 return false;
1875 }
1876 // rows affected: mtime has changed, or row has been removed
1877 if ( $numRows == 1 )
1878 {
1879 return true;
1880 }
1881 else
1882 {
1883 eZDebugSetting::writeDebug( 'kernel-clustering', "No rows affected by query '$query', record has been modified", __METHOD__ );
1884 return false;
1885 }
1886 }
1887
1894 public function _abortCacheGeneration( $generatingFilePath )
1895 {
1896 $fname = "_abortCacheGeneration( $generatingFilePath )";
1897
1898 $this->_begin( $fname );
1899
1900 $sql = "DELETE FROM " . $this->dbTable( $generatingFilePath ) . " WHERE name_hash = " . $this->_md5( $generatingFilePath );
1901 $this->_query( $sql, "_abortCacheGeneration( '$generatingFilePath' )" );
1902 $this->dfsbackend->delete( $generatingFilePath );
1903
1904 $this->_commit( $fname );
1905 }
1906
1913 static protected function nameTrunk( $filePath, $scope )
1914 {
1915 switch ( $scope )
1916 {
1917 case 'viewcache':
1918 {
1919 $fileName = basename( $filePath );
1920 $nameTrunk = dirname( $filePath ) . '/' . substr( $fileName, 0, strpos( $fileName, '-' ) + 1 );
1921 } break;
1922
1923 case 'template-block':
1924 {
1925 $templateBlockCacheDir = eZTemplateCacheBlock::templateBlockCacheDir();
1926 $templateBlockPath = str_replace( $templateBlockCacheDir, '', $filePath );
1927 if ( strstr( $templateBlockPath, 'subtree/' ) !== false )
1928 {
1929 // 6 = strlen( 'cache/' );
1930 $len = strlen( $templateBlockCacheDir ) + strpos( $templateBlockPath, 'cache/' ) + 6;
1931 $nameTrunk = substr( $filePath, 0, $len );
1932 }
1933 else
1934 {
1935 $nameTrunk = $filePath;
1936 }
1937 } break;
1938
1939 default:
1940 {
1941 $nameTrunk = $filePath;
1942 }
1943 }
1944 return $nameTrunk;
1945 }
1946
1954 protected function remainingCacheGenerationTime( $row )
1955 {
1956 if ( !isset( $row[0] ) )
1957 return -1;
1958
1959 return ( $row[0] + self::$dbparams['cache_generation_timeout'] ) - time();
1960 }
1961
1975 public function expiredFilesList( $scopes, $limit = array( 0, 100 ), $expiry = false )
1976 {
1977 $tables = array( $this->metaDataTable, $this->metaDataTableCache );
1978
1979 if ( count( $scopes ) == 0 )
1980 throw new ezcBaseValueException( 'scopes', $scopes, "array of scopes", "parameter" );
1981
1982 $scopeString = $this->_sqlList( $scopes );
1983
1984 $filePathList = array();
1985
1986 foreach ( $tables as $table)
1987 {
1988 $query = "SELECT name FROM " . $table . " WHERE expired = 1 AND scope IN( $scopeString )";
1989 if ( $expiry !== false )
1990 {
1991 $query .= ' AND mtime < ' . (time() - $expiry);
1992 }
1993 if ( $limit !== false )
1994 {
1995 $query .= " LIMIT {$limit[0]}, {$limit[1]}";
1996 }
1997 $res = $this->_query( $query, __METHOD__ );
1998
1999 while ( $row = mysqli_fetch_row( $res ) )
2000 $filePathList[] = $row[0];
2001
2002 mysqli_free_result( $res );
2003 }
2004
2005 return $filePathList;
2006 }
2007
2014 public function registerListener( eZClusterEventListener $listener )
2015 {
2016 $suppliedEvents = array(
2017 'cluster/storeMetadata',
2018 'cluster/loadMetadata',
2019 'cluster/fileExists',
2020 'cluster/deleteFile',
2021 'cluster/deleteByLike',
2022 'cluster/deleteByDirList',
2023 'cluster/deleteByNametrunk'
2024 );
2025
2026 foreach ( $suppliedEvents as $eventName )
2027 {
2028 list( $domain, $method ) = explode( '/', $eventName );
2029 $this->eventHandler->attach( $eventName, array( $listener, $method ) );
2030 }
2031 }
2032
2043 public function deleteCacheFiles( $limit )
2044 {
2045 if ( $this->metaDataTable === $this->metaDataTableCache )
2046 {
2047 throw new InvalidArgumentException( "The split table features is disabled: cache and storage table are identical" );
2048 }
2049
2050 $like = addcslashes( eZSys::cacheDirectory(), '_' ) . DIRECTORY_SEPARATOR . '%';
2051
2052 $query = "DELETE FROM {$this->metaDataTable} WHERE name LIKE '$like' LIMIT $limit";
2053 if ( !mysqli_query( $this->db, $query ) )
2054 {
2055 throw new RuntimeException( "MySQLi error in $query\n" . mysqli_error( $this->db ), mysqli_errno( $this->db ) );
2056 }
2057
2058 return mysqli_affected_rows( $this->db );
2059 }
2060
2066 public function applyServerUri( $filePath )
2067 {
2068 return $this->dfsbackend->applyServerUri( $filePath );
2069 }
2070
2075 public $db = null;
2076
2081 protected static $dbparams = null;
2082
2087 protected $numQueries = 0;
2088
2095 protected $transactionCount = 0;
2096
2101 protected $dfsbackend = null;
2102
2107 protected $eventHandler;
2108
2113 protected $metaDataTable = 'ezdfsfile';
2114
2120 protected $metaDataTableCache = 'ezdfsfile_cache';
2121
2127 protected $cacheDir;
2128
2134 protected $storageDir;
2135 }
$i
$i
Definition: admin/adlist.php:91
$files
$files
Definition: articlepreview.php:91
eZClusterHandlerDBNoConnectionException
Definition: cluster/noconnection.php:20
eZDFSFileHandlerBackendFactory\build
static build()
Definition: ezdfsfilehandlerbackendfactory.php:22
eZDFSFileHandlerMySQLiBackend
Definition: mysqli.php:51
eZDFSFileHandlerMySQLiBackend\$metaDataTable
$metaDataTable
Definition: mysqli.php:2113
eZDFSFileHandlerMySQLiBackend\registerListener
registerListener(eZClusterEventListener $listener)
Definition: mysqli.php:2014
eZDFSFileHandlerMySQLiBackend\_storeInner
_storeInner( $filePath, $datatype, $scope, $fname)
Definition: mysqli.php:989
eZDFSFileHandlerMySQLiBackend\_rename
_rename( $srcFilePath, $dstFilePath)
Definition: mysqli.php:884
eZDFSFileHandlerMySQLiBackend\_connect
_connect()
Definition: mysqli.php:115
eZDFSFileHandlerMySQLiBackend\dbTable
dbTable( $filePath)
Definition: mysqli.php:95
eZDFSFileHandlerMySQLiBackend\remainingCacheGenerationTime
remainingCacheGenerationTime( $row)
Definition: mysqli.php:1954
eZDFSFileHandlerMySQLiBackend\_insertUpdate
_insertUpdate( $table, $array, $update, $fname, $reportError=true)
Definition: mysqli.php:1218
eZDFSFileHandlerMySQLiBackend\applyServerUri
applyServerUri( $filePath)
Definition: mysqli.php:2066
eZDFSFileHandlerMySQLiBackend\_purgeByLike
_purgeByLike( $like, $onlyExpired=false, $limit=50, $expiry=false, $fname=false)
Definition: mysqli.php:339
eZDFSFileHandlerMySQLiBackend\_begin
_begin( $fname=false)
Definition: mysqli.php:1355
eZDFSFileHandlerMySQLiBackend\$transactionCount
$transactionCount
Definition: mysqli.php:2095
eZDFSFileHandlerMySQLiBackend\_fetch
_fetch( $filePath, $uniqueName=false)
Definition: mysqli.php:708
eZDFSFileHandlerMySQLiBackend\_endCacheGeneration
_endCacheGeneration( $filePath, $generatingFilePath, $rename)
Definition: mysqli.php:1733
eZDFSFileHandlerMySQLiBackend\_md5
_md5( $value)
Definition: mysqli.php:1573
eZDFSFileHandlerMySQLiBackend\$dbparams
static $dbparams
Definition: mysqli.php:2081
eZDFSFileHandlerMySQLiBackend\_copyInner
_copyInner( $srcFilePath, $dstFilePath, $fname, $metaData)
Definition: mysqli.php:247
eZDFSFileHandlerMySQLiBackend\$dfsbackend
$dfsbackend
Definition: mysqli.php:2101
eZDFSFileHandlerMySQLiBackend\_checkCacheGenerationTimeout
_checkCacheGenerationTimeout( $generatingFilePath, $generatingFileMtime)
Definition: mysqli.php:1831
eZDFSFileHandlerMySQLiBackend\_selectOneAssoc
_selectOneAssoc( $query, $fname, $error=false, $debug=false)
Definition: mysqli.php:1280
eZDFSFileHandlerMySQLiBackend\$storageDir
$storageDir
Definition: mysqli.php:2134
eZDFSFileHandlerMySQLiBackend\nameTrunk
static nameTrunk( $filePath, $scope)
Definition: mysqli.php:1913
eZDFSFileHandlerMySQLiBackend\_store
_store( $filePath, $datatype, $scope, $fname=false)
Definition: mysqli.php:960
eZDFSFileHandlerMySQLiBackend\_sqlList
_sqlList( $array)
Definition: mysqli.php:1238
eZDFSFileHandlerMySQLiBackend\_insert
_insert( $table, $array, $fname)
Definition: mysqli.php:1189
eZDFSFileHandlerMySQLiBackend\_error
_error( $query, $fname, $error="Failed to execute SQL for function:")
Definition: mysqli.php:1589
eZDFSFileHandlerMySQLiBackend\_fetchMetadata
_fetchMetadata( $filePath, $fname=false)
Definition: mysqli.php:814
eZDFSFileHandlerMySQLiBackend\_disconnect
_disconnect()
Definition: mysqli.php:194
eZDFSFileHandlerMySQLiBackend\deleteCacheFiles
deleteCacheFiles( $limit)
Definition: mysqli.php:2043
eZDFSFileHandlerMySQLiBackend\_die
_die( $msg, $sql=null)
Definition: mysqli.php:1165
eZDFSFileHandlerMySQLiBackend\_protect
_protect()
Definition: mysqli.php:1419
eZDFSFileHandlerMySQLiBackend\_delete
_delete( $filePath, $insideOfTransaction=false, $fname=false)
Definition: mysqli.php:422
eZDFSFileHandlerMySQLiBackend\_deleteByLike
_deleteByLike( $like, $fname=false)
Definition: mysqli.php:478
eZDFSFileHandlerMySQLiBackend\$maxCopyTries
$maxCopyTries
Definition: mysqli.php:62
eZDFSFileHandlerMySQLiBackend\_deleteByDirList
_deleteByDirList( $dirList, $commonPath, $commonSuffix, $fname=false)
Definition: mysqli.php:564
eZDFSFileHandlerMySQLiBackend\TIME_UNTIL_RETRY
const TIME_UNTIL_RETRY
Definition: mysqli.php:55
eZDFSFileHandlerMySQLiBackend\_startCacheGeneration
_startCacheGeneration( $filePath, $generatingFilePath)
Definition: mysqli.php:1644
eZDFSFileHandlerMySQLiBackend\_report
_report( $query, $fname, $timeTaken, $numRows=false)
Definition: mysqli.php:1613
eZDFSFileHandlerMySQLiBackend\_query
_query( $query, $fname=false, $reportError=true)
Definition: mysqli.php:1518
eZDFSFileHandlerMySQLiBackend\_storeContentsInner
_storeContentsInner( $filePath, $contents, $scope, $datatype, $curTime, $fname)
Definition: mysqli.php:1064
eZDFSFileHandlerMySQLiBackend\_deleteByWildcard
_deleteByWildcard( $wildcard, $fname=false)
Definition: mysqli.php:518
eZDFSFileHandlerMySQLiBackend\$metaDataTableCache
$metaDataTableCache
Definition: mysqli.php:2120
eZDFSFileHandlerMySQLiBackend\_quote
_quote( $value, $escapeUnderscoreWildcards=false)
Definition: mysqli.php:1547
eZDFSFileHandlerMySQLiBackend\_linkCopy
_linkCopy( $srcPath, $dstPath, $fname=false)
Definition: mysqli.php:840
eZDFSFileHandlerMySQLiBackend\$numQueries
$numQueries
Definition: mysqli.php:2087
eZDFSFileHandlerMySQLiBackend\_getFileList
_getFileList( $scopes=false, $excludeScopes=false, $limit=false, $path=false)
Definition: mysqli.php:1109
eZDFSFileHandlerMySQLiBackend\_passThrough
_passThrough( $filePath, $startOffset=0, $length=false, $fname=false)
Definition: mysqli.php:859
eZDFSFileHandlerMySQLiBackend\_deleteByDirListInner
_deleteByDirListInner( $dirList, $commonPath, $commonSuffix, $fname)
Definition: mysqli.php:581
eZDFSFileHandlerMySQLiBackend\_abortCacheGeneration
_abortCacheGeneration( $generatingFilePath)
Definition: mysqli.php:1894
eZDFSFileHandlerMySQLiBackend\_storeContents
_storeContents( $filePath, $contents, $scope, $datatype, $mtime=false, $fname=false)
Definition: mysqli.php:1036
eZDFSFileHandlerMySQLiBackend\_fetchContents
_fetchContents( $filePath, $fname=false)
Definition: mysqli.php:783
eZDFSFileHandlerMySQLiBackend\expiredFilesList
expiredFilesList( $scopes, $limit=array(0, 100), $expiry=false)
Definition: mysqli.php:1975
eZDFSFileHandlerMySQLiBackend\_copy
_copy( $srcFilePath, $dstFilePath, $fname=false)
Definition: mysqli.php:212
eZDFSFileHandlerMySQLiBackend\_commit
_commit( $fname=false)
Definition: mysqli.php:1374
eZDFSFileHandlerMySQLiBackend\__mkdir_p
__mkdir_p( $dir)
Definition: mysqli.php:694
eZDFSFileHandlerMySQLiBackend\_isFailure
_isFailure( $result)
Definition: mysqli.php:1484
eZDFSFileHandlerMySQLiBackend\$db
$db
Definition: mysqli.php:2075
eZDFSFileHandlerMySQLiBackend\_deleteInner
_deleteInner( $filePath, $fname)
Definition: mysqli.php:457
eZDFSFileHandlerMySQLiBackend\_selectOne
_selectOne( $query, $fname, $error=false, $debug=false, $fetchCall)
Definition: mysqli.php:1300
eZDFSFileHandlerMySQLiBackend\_rollback
_rollback( $fname=false)
Definition: mysqli.php:1394
eZDFSFileHandlerMySQLiBackend\$cacheDir
$cacheDir
Definition: mysqli.php:2127
eZDFSFileHandlerMySQLiBackend\_handleErrorType
_handleErrorType( $res)
Definition: mysqli.php:1465
eZDFSFileHandlerMySQLiBackend\mkdir_p
mkdir_p( $dir)
Definition: mysqli.php:659
eZDFSFileHandlerMySQLiBackend\__construct
__construct()
Definition: mysqli.php:67
eZDFSFileHandlerMySQLiBackend\_purge
_purge( $filePath, $onlyExpired=false, $expiry=false, $fname=false)
Definition: mysqli.php:296
eZDFSFileHandlerMySQLiBackend\$eventHandler
$eventHandler
Definition: mysqli.php:2107
eZDFSFileHandlerMySQLiBackend\_fail
_fail( $value, $text=false)
Definition: mysqli.php:1500
eZDFSFileHandlerMySQLiBackend\_exists
_exists( $filePath, $fname=false, $ignoreExpiredFiles=true, $checkOnDFS=false)
Definition: mysqli.php:624
eZDFSFileHandlerMySQLiBackend\_selectOneRow
_selectOneRow( $query, $fname, $error=false, $debug=false)
Definition: mysqli.php:1263
eZDFSFileHandlerMySQLiBackend\_deleteByLikeInner
_deleteByLikeInner( $like, $fname)
Definition: mysqli.php:500
eZDFSFileHandlerMySQLiBackend\_deleteByWildcardInner
_deleteByWildcardInner( $wildcard, $fname)
Definition: mysqli.php:536
eZDFSFileHandlerTableNotFoundException
Definition: table_not_found.php:20
eZDebugSetting\writeDebug
static writeDebug( $conditionName, $string, $label="")
Definition: ezdebugsetting.php:128
eZDebug\writeError
static writeError( $string, $label="", $backgroundClass="")
Definition: ezdebug.php:546
eZDebug\writeDebug
static writeDebug( $string, $label="", $backgroundClass="")
Definition: ezdebug.php:589
eZDebug\writeNotice
static writeNotice( $string, $label="", $backgroundClass="")
Definition: ezdebug.php:460
eZDebug\accumulatorStart
static accumulatorStart( $key, $inGroup=false, $name=false, $recursive=false)
Definition: ezdebug.php:1362
eZDebug\accumulatorStop
static accumulatorStop( $key, $recursive=false)
Definition: ezdebug.php:1390
eZDir\mkdir
static mkdir( $dir, $perm=false, $recursive=false)
Definition: ezdir.php:74
eZFile\CLEAN_ON_FAILURE
const CLEAN_ON_FAILURE
Definition: ezfile.php:32
eZFile\rename
static rename( $srcFile, $destFile, $mkdir=false, $flags=0)
Definition: ezfile.php:153
eZFile\APPEND_DEBUG_ON_FAILURE
const APPEND_DEBUG_ON_FAILURE
Definition: ezfile.php:33
eZMySQLBackendError
Error class for the MySQL backend (cluster code).
Definition: classes/clusterfilehandlers/dbbackends/mysqlbackenderror.php:18
eZSys\cacheDirectory
static cacheDirectory()
Definition: ezsys.php:528
eZTextCodec\internalCharset
static internalCharset()
Definition: ezi18n/classes/eztextcodec.php:547
ezpEvent\getInstance
static getInstance()
Definition: ezpevent.php:176
$result
$result
Definition: companysearch.php:94
$args
$args
Definition: companytypelist.php:125
file_exists
static file_exists( $inifilename)
Definition: eZPBINIFile.php:195
$msg
$msg
Definition: editmessage.php:47
$query
if(isset( $Year) &&!is_numeric( $Year)||isset( $Month) &&!is_numeric( $Month)) $query
Definition: entryexitpages.php:65
$event
if(isset( $Action) && $Action=="New") else $event
Definition: eventedit.php:189
$error
$error
Definition: ezarticle/admin/categoryedit.php:51
$contents
$contents
Definition: ezarticle/user/extendedsearch.php:52
$numRows
foreach( $appointments as $appointment) for( $i=0; $i< count( $appointments); $i++) $numRows
Definition: ezcalendar/user/dayview.php:251
$row
$row
Definition: ezcalendar/user/dayview.php:403
$fileName
if(eZObjectPermission::hasPermission( $file->id(), "filemanager_file", "r", $user)==false) $fileName
Definition: ezfilemanager/user/filedownload.php:43
$filePath
$filePath
Definition: ezfilemanager/user/filedownload.php:50
$limit
$limit
Definition: ezimagecatalogue/admin/imagelist.php:312
$dir
$dir
Definition: ezsitemanager/admin/filelist.php:87
$res
$res
Definition: ezstats/admin/cron.php:45
$debug
$debug
Definition: ezstats/admin/cron.php:36
$time
$time
Definition: homepage.php:327
eZClusterEventListener
Definition: ezclustereventlistener.php:15
eZClusterEventNotifier
Definition: ezclustereventnotifier.php:16
Copyright © 1998 – 2026 7x. All rights reserved. Licensed under the GNU GPLv2+.