eZ Publish  [4.0]
ezdbgarbagecollector.php
Go to the documentation of this file.
00001 <?php
00002 //
00003 // Definition of eZDBGarbageCollector class
00004 //
00005 // Created on: <09-Jun-2005 08:42:13 amos>
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 ezdbgarbagecollector.php
00032 */
00033 
00034 /*!
00035   \class eZDBGrbageCollector ezdbgarbagecollector.php
00036   \brief Handles garbage collection in the database.
00037 
00038   Cleans up garbage (leaks) in the database which can be left behind by
00039   bugs or crashes in the system.
00040 
00041   Garbage collection is divided into several static functions which performs
00042   cleanup of one or more tables. What is common for all functions are the
00043   \a $maxTime and \a $sleepTime parameters which controls how long a GC
00044   operation can go on and how long it should sleep between batch operations.
00045 
00046   \code
00047   eZDBGarbageCollector::collectBaskets();
00048   // or
00049   // Perform GC for 5 minutes with 2 second sleep intervals
00050   eZDBGarbageCollector::collectBaskets( 5*60, 2 );
00051   \endcode
00052 
00053 */
00054 
00055 //include_once( 'kernel/classes/ezpersistentobject.php' );
00056 
00057 class eZDBGarbageCollector
00058 {
00059     /*!
00060      Controls the default value for how many items are cleaned in one batch operation.
00061     */
00062     const ITEM_LIMIT = 3000;
00063 
00064     /*!
00065      \static
00066      Removes all baskets which are missing a session entry.
00067      This usually means the basket was not cleaned up when the session was removed.
00068 
00069      \param $maxTime The maximum number of seconds the collector should run.
00070                      If set to \c false or \c true it will continue until all garbage is collected,
00071                      \c false means to try the fastest way possible (may use lots of resources).
00072      \param $sleepTime Controls how many seconds it will sleep between each batch of cleanup,
00073                        setting it to \c false means that it should never sleep.
00074      \param $limit Controls how many items are cleaned in one batch operation,
00075                    a value of \c false means to use default value.
00076 
00077      \note The function will most likely use a little more time than \a $maxTime so
00078            don't depend on it to be accurate.
00079 
00080      \note This will also remove the product collection the basket is using.
00081 
00082      \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
00083      the calls within a db transaction; thus within db->begin and db->commit.
00084     */
00085     function collectBaskets( $maxTime = false, $sleepTime = false, $limit = false )
00086     {
00087         $db = eZDB::instance();
00088 
00089         if ( $maxTime === false and $db->hasRequiredServerVersion( '4.0', 'mysql' ) )
00090         {
00091             $sql = "DELETE FROM ezbasket, ezproductcollection, ezproductcollection_item, ezproductcollection_item_opt
00092 USING ezsession
00093       RIGHT JOIN ezbasket
00094         ON ezsession.session_key = ezbasket.session_id
00095       LEFT JOIN ezproductcollection
00096         ON ezbasket.productcollection_id = ezproductcollection.id
00097       LEFT JOIN ezproductcollection_item
00098         ON ezproductcollection.id = ezproductcollection_item.productcollection_id
00099       LEFT JOIN ezproductcollection_item_opt
00100         ON ezproductcollection_item.id = ezproductcollection_item_opt.item_id
00101 WHERE ezsession.session_key IS NULL";
00102             $db->query( $sql );
00103             return;
00104         }
00105 
00106         // Find all baskets which are lacking a session (db leaks)
00107         if ( $db->hasRequiredServerVersion( '8', 'oracle' ) )
00108         {
00109             $sql = "SELECT id, productcollection_id
00110 FROM ezsession, ezbasket
00111 WHERE ezbasket.session_id = ezsession.session_key (+) AND
00112       ezsession.session_key IS NULL";
00113         }
00114         else
00115         {
00116             $sql = "SELECT id, productcollection_id
00117 FROM ezsession
00118      RIGHT JOIN ezbasket
00119        ON ezbasket.session_id = ezsession.session_key
00120 WHERE ezsession.session_key IS NULL";
00121         }
00122         if ( $limit === false )
00123             $limit = eZDBGarbageCollector::ITEM_LIMIT;
00124         $end = false;
00125         if ( is_numeric( $maxTime ) )
00126             $end = time() + $maxTime;
00127 
00128         //include_once( 'kernel/classes/ezproductcollection.php' );
00129 
00130         do
00131         {
00132             $rows = $db->arrayQuery( $sql, array( 'offset' => 0, 'limit' => $limit ) );
00133             if ( count( $rows ) == 0 )
00134                 break;
00135 
00136             $productCollectionIDList = array();
00137             $idList = array();
00138             foreach ( $rows as $row )
00139             {
00140                 $idList[] = (int)$row['id'];
00141                 $productCollectionIDList[] = (int)$row['productcollection_id'];
00142             }
00143             eZProductCollection::cleanupList( $productCollectionIDList );
00144 
00145             $ids = implode( ', ', $idList );
00146             $db->query( "DELETE FROM ezbasket WHERE id IN ( $ids )" );
00147 
00148             // Stop when we used up our time
00149             if ( $end !== false and time() > $end )
00150                 break;
00151 
00152             // Sleep a little if required, reduces load on server
00153             if ( $sleepTime !== false )
00154                 sleep( $sleepTime );
00155 
00156         } while ( true );
00157     }
00158 
00159     /*!
00160      Removes entries from ezproductcollection which is missing relations to other tables.
00161 
00162      \param $maxTime The maximum number of seconds the collector should run.
00163                      If set to \c false or \c true it will continue until all garbage is collected,
00164                      \c false means to try the fastest way possible (may use lots of resources).
00165      \param $sleepTime Controls how many seconds it will sleep between each batch of cleanup,
00166                        setting it to \c false means that it should never sleep.
00167      \param $limit Controls how many items are cleaned in one batch operation,
00168                    a value of \c false means to use default value.
00169 
00170      \note The function will most likely use a little more time than \a $maxTime so
00171            don't depend on it to be accurate.
00172 
00173      \note The current code hardcodes the related tables.
00174 
00175      \warning This code may be very slow on large sites.
00176 
00177      \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
00178      the calls within a db transaction; thus within db->begin and db->commit.
00179     */
00180     function collectProductCollections( $maxTime = false, $sleepTime = false, $limit = false )
00181     {
00182         $db = eZDB::instance();
00183 
00184         // Create a temporary table for filling in collection ids
00185         // that are in use
00186         if ( $db->databaseName() == 'mysql' )
00187             $db->query( "DROP TABLE IF EXISTS ezproductcollection_used" );
00188         $db->query( "CREATE TABLE ezproductcollection_used ( id int )" );
00189 
00190         // Fill in collections used by orders
00191         $db->query( "INSERT INTO ezproductcollection_used (id) SELECT productcollection_id FROM ezorder" );
00192 
00193         // Fill in collections used by wish lists
00194         $db->query( "INSERT INTO ezproductcollection_used (id) SELECT productcollection_id FROM ezwishlist" );
00195 
00196         // Fill in collections used by baskets
00197         $db->query( "INSERT INTO ezproductcollection_used (id) SELECT productcollection_id FROM ezbasket" );
00198 
00199         // Create the index for faster selects
00200         $db->query( "CREATE INDEX ezproductcollection_used_id on ezproductcollection_used( id )" );
00201 
00202         if ( $maxTime === false and $db->hasRequiredServerVersion( '4.0', 'mysql' ) )
00203         {
00204             // Delete entries which are not in ezproductcollection_used
00205             $db->query( "DELETE FROM ezproductcollection, ezproductcollection_item, ezproductcollection_item_opt
00206 USING ezproductcollection_used
00207       RIGHT JOIN ezproductcollection
00208         ON ezproductcollection_used.id = ezproductcollection.id
00209       LEFT JOIN ezproductcollection_item
00210         ON ezproductcollection.id = ezproductcollection_item.productcollection_id
00211       LEFT JOIN ezproductcollection_item_opt
00212         ON ezproductcollection_item.id = ezproductcollection_item_opt.item_id
00213 WHERE ezproductcollection_used.id IS NULL" );
00214             $db->query( $sql );
00215 
00216             // Remove the temporary table
00217             $db->query( "DROP TABLE ezproductcollection_used" );
00218 
00219             return;
00220         }
00221 
00222         // Find all product collections which are not in use
00223         if ( $db->hasRequiredServerVersion( '8', 'oracle' ) )
00224         {
00225             $sql = "SELECT ezproductcollection.id
00226 FROM ezproductcollection_used, ezproductcollection
00227 WHERE ezproductcollection_used.id = ezproductcollection.id (+) AND
00228       ezproductcollection_used.id IS NULL";
00229         }
00230         else
00231         {
00232             $sql = "SELECT ezproductcollection.id
00233 FROM ezproductcollection_used
00234      RIGHT JOIN ezproductcollection
00235        ON ezproductcollection_used.id = ezproductcollection.id
00236 WHERE ezproductcollection_used.id IS NULL";
00237         }
00238         if ( $limit === false )
00239             $limit = eZDBGarbageCollector::ITEM_LIMIT;
00240         $end = false;
00241         if ( is_numeric( $maxTime ) )
00242             $end = time() + $maxTime;
00243 
00244         //include_once( 'kernel/classes/ezproductcollectionitem.php' );
00245 
00246         do
00247         {
00248             $rows = $db->arrayQuery( $sql, array( 'offset' => 0, 'limit' => $limit ) );
00249             if ( count( $rows ) == 0 )
00250                 break;
00251 
00252             $idList = array();
00253             foreach ( $rows as $row )
00254             {
00255                 $idList[] = (int)$row['id'];
00256             }
00257             eZProductCollectionItem::cleanupList( $idList );
00258 
00259             $ids = implode( ', ', $idList );
00260             $db->query( "DELETE FROM ezproductcollection WHERE id IN ( $ids )" );
00261             $db->query( "DELETE FROM ezproductcollection_used WHERE id IN ( $ids )" );
00262 
00263             // Stop when we used up our time
00264             if ( $end !== false and time() > $end )
00265                 break;
00266 
00267             // Sleep a little if required, reduces load on server
00268             if ( $sleepTime !== false )
00269                 sleep( $sleepTime );
00270 
00271         } while ( true );
00272 
00273         // Remove the temporary table
00274         $db->query( "DROP TABLE ezproductcollection_used" );
00275     }
00276 
00277     /*!
00278      \static
00279      Removes all product collection items which are missing a product collection.
00280 
00281      \param $maxTime The maximum number of seconds the collector should run.
00282                      If set to \c false or \c true it will continue until all garbage is collected,
00283                      \c false means to try the fastest way possible (may use lots of resources).
00284      \param $sleepTime Controls how many seconds it will sleep between each batch of cleanup,
00285                        setting it to \c false means that it should never sleep.
00286      \param $limit Controls how many items are cleaned in one batch operation,
00287                    a value of \c false means to use default value.
00288 
00289      \note The function will most likely use a little more time than \a $maxTime so
00290            don't depend on it to be accurate.
00291 
00292      \note This will also remove the product collection item option the item is using.
00293 
00294      \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
00295      the calls within a db transaction; thus within db->begin and db->commit.
00296     */
00297     function collectProductCollectionItems( $maxTime = false, $sleepTime = false, $limit = false )
00298     {
00299         $db = eZDB::instance();
00300 
00301         if ( $maxTime === false and $db->hasRequiredServerVersion( '4.0', 'mysql' ) )
00302         {
00303             $sql = "DELETE FROM ezproductcollection_item, ezproductcollection_item_opt
00304 USING ezproductcollection
00305       LEFT JOIN ezproductcollection_item
00306         ON ezproductcollection.id = ezproductcollection_item.productcollection_id
00307       LEFT JOIN ezproductcollection_item_opt
00308         ON ezproductcollection_item.id = ezproductcollection_item_opt.item_id
00309 WHERE ezproductcollection.id IS NULL";
00310             $db->query( $sql );
00311 
00312             return;
00313         }
00314 
00315         // Find all items which are lacking a collection (db leaks)
00316         if ( $db->hasRequiredServerVersion( '8', 'oracle' ) )
00317         {
00318             $sql = "SELECT ezproductcollection_item.productcollection_id
00319 FROM ezproductcollection, ezproductcollection_item
00320 WHERE ezproductcollection.id = ezproductcollection_item.productcollection_id (+) AND
00321       ezproductcollection.id IS NULL";
00322         }
00323         else
00324         {
00325             $sql = "SELECT ezproductcollection_item.productcollection_id
00326 FROM ezproductcollection
00327      RIGHT JOIN ezproductcollection_item
00328        ON ezproductcollection.id = ezproductcollection_item.productcollection_id
00329 WHERE ezproductcollection.id IS NULL";
00330         }
00331         if ( $limit === false )
00332             $limit = eZDBGarbageCollector::ITEM_LIMIT;
00333         $end = false;
00334         if ( is_numeric( $maxTime ) )
00335             $end = time() + $maxTime;
00336 
00337         //include_once( 'kernel/classes/ezproductcollectionitemoption.php' );
00338 
00339         do
00340         {
00341             $rows = $db->arrayQuery( $sql, array( 'offset' => 0, 'limit' => $limit ) );
00342             if ( count( $rows ) == 0 )
00343                 break;
00344 
00345             $idList = array();
00346             foreach ( $rows as $row )
00347             {
00348                 $idList[] = (int)$row['productcollection_id'];
00349             }
00350             eZProductCollectionItemOption::cleanupList( $idList );
00351 
00352             $ids = implode( ', ', $idList );
00353             $db->query( "DELETE FROM ezproductcollection_item WHERE productcollection_id IN ( $ids )" );
00354 
00355             // Stop when we used up our time
00356             if ( $end !== false and time() > $end )
00357                 break;
00358 
00359             // Sleep a little if required, reduces load on server
00360             if ( $sleepTime !== false )
00361                 sleep( $sleepTime );
00362 
00363         } while ( true );
00364     }
00365 
00366     /*!
00367      \static
00368      Removes all product collection item options which are missing a product collection item.
00369 
00370      \param $maxTime The maximum number of seconds the collector should run.
00371                      If set to \c false or \c true it will continue until all garbage is collected,
00372                      \c false means to try the fastest way possible (may use lots of resources).
00373      \param $sleepTime Controls how many seconds it will sleep between each batch of cleanup,
00374                        setting it to \c false means that it should never sleep.
00375      \param $limit Controls how many items are cleaned in one batch operation,
00376                    a value of \c false means to use default value.
00377 
00378      \note The function will most likely use a little more time than \a $maxTime so
00379            don't depend on it to be accurate.
00380 
00381      \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
00382      the calls within a db transaction; thus within db->begin and db->commit.
00383     */
00384     function collectProductCollectionItemOptions( $maxTime = false, $sleepTime = false, $limit = false )
00385     {
00386         $db = eZDB::instance();
00387 
00388         if ( $maxTime === false and $db->hasRequiredServerVersion( '4.0', 'mysql' ) )
00389         {
00390             $sql = "DELETE FROM ezproductcollection_item_opt
00391 USING ezproductcollection_item
00392       LEFT JOIN ezproductcollection_item_opt
00393         ON ezproductcollection_item.id = ezproductcollection_item_opt.item_id
00394 WHERE ezproductcollection_item.id IS NULL";
00395             $db->query( $sql );
00396 
00397             return;
00398         }
00399 
00400         // Find all items which are lacking a collection (db leaks)
00401         if ( $db->hasRequiredServerVersion( '8', 'oracle' ) )
00402         {
00403             $sql = "SELECT ezproductcollection_item_opt.item_id
00404 FROM ezproductcollection_item, ezproductcollection_item_opt
00405 WHERE ezproductcollection_item.id = ezproductcollection_item_opt.item_id (+) AND
00406       ezproductcollection_item.id IS NULL";
00407         }
00408         else
00409         {
00410             $sql = "SELECT ezproductcollection_item_opt.item_id
00411 FROM ezproductcollection_item
00412      RIGHT JOIN ezproductcollection_item_opt
00413        ON ezproductcollection_item.id = ezproductcollection_item_opt.item_id
00414 WHERE ezproductcollection_item.id IS NULL";
00415         }
00416         if ( $limit === false )
00417             $limit = eZDBGarbageCollector::ITEM_LIMIT;
00418         $end = false;
00419         if ( is_numeric( $maxTime ) )
00420             $end = time() + $maxTime;
00421 
00422         do
00423         {
00424             $rows = $db->arrayQuery( $sql, array( 'offset' => 0, 'limit' => $limit ) );
00425             if ( count( $rows ) == 0 )
00426                 break;
00427 
00428             $idList = array();
00429             foreach ( $rows as $row )
00430             {
00431                 $idList[] = (int)$row['item_id'];
00432             }
00433 
00434             $ids = implode( ', ', $idList );
00435             $db->query( "DELETE FROM ezproductcollection_item_opt WHERE item_id IN ( $ids )" );
00436 
00437             // Stop when we used up our time
00438             if ( $end !== false and time() > $end )
00439                 break;
00440 
00441             // Sleep a little if required, reduces load on server
00442             if ( $sleepTime !== false )
00443                 sleep( $sleepTime );
00444 
00445         } while ( true );
00446     }
00447 }
00448 
00449 ?>