| Current Path : /home/sunflowe/www2/j15/tmp/install_4f118f48ca3c3/backend/akeeba/engines/dump/ |
| Current File : /home/sunflowe/www2/j15/tmp/install_4f118f48ca3c3/backend/akeeba/engines/dump/native.php |
<?php
/**
* Akeeba Engine
* The modular PHP5 site backup engine
* @copyright Copyright (c)2009-2012 Nicholas K. Dionysopoulos
* @license GNU GPL version 3 or, at your option, any later version
* @package akeebaengine
* @version $Id$
*/
// Protection against direct access
defined('AKEEBAENGINE') or die('Restricted access');
/**
* A generic MySQL database dump class, using Joomla!'s JDatabase class for handling the connection.
* Now supports views; merge, in-memory, federated, blackhole, etc tables
* Configuration parameters:
* host <string> MySQL database server host name or IP address
* port <string> MySQL database server port (optional)
* username <string> MySQL user name, for authentication
* password <string> MySQL password, for authentication
* database <string> MySQL database
* dumpFile <string> Absolute path to dump file; must be writable (optional; if left blank it is automatically calculated)
*/
class AEDumpNative extends AEAbstractDump
{
// **********************************************************************
// Private fields
// **********************************************************************
/** @var array Contains the sorted (by dependencies) list of tables/views to backup */
private $tables = array();
/** @var array Contains the configuration data of the tables */
private $tables_data = array();
/** @var array Maps database table names to their abstracted format */
private $table_name_map = array();
/** @var array Contains the dependencies of tables and views (temporary) */
private $dependencies = array();
/** @var string The next table to backup */
private $nextTable;
/** @var integer The next row of the table to start backing up from */
private $nextRange;
/** @var integer Current table's row count */
private $maxRange;
/** @var bool Use extended INSERTs */
private $extendedInserts = false;
/** @var integer Maximum packet size for extended INSERTs, in bytes */
private $packetSize = 0;
/** @var string Extended INSERT query, while it's being constructed */
private $query = '';
/** @var int Dump part's maximum size */
private $partSize = 0;
/**
* Implements the constructor of the class
*
* @return AEDumpNative
*/
public function __construct()
{
parent::__construct();
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: New instance");
}
/**
* Implements the _prepare abstract method
*
*/
protected function _prepare()
{
// Process parameters, passed to us using the setup() public method
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Processing parameters");
if( is_array($this->_parametersArray) ) {
$this->driver = array_key_exists('driver', $this->_parametersArray) ? $this->_parametersArray['driver'] : $this->driver;
$this->host = array_key_exists('host', $this->_parametersArray) ? $this->_parametersArray['host'] : $this->host;
$this->port = array_key_exists('port', $this->_parametersArray) ? $this->_parametersArray['port'] : $this->port;
$this->username = array_key_exists('username', $this->_parametersArray) ? $this->_parametersArray['username'] : $this->username;
$this->username = array_key_exists('user', $this->_parametersArray) ? $this->_parametersArray['user'] : $this->username;
$this->password = array_key_exists('password', $this->_parametersArray) ? $this->_parametersArray['password'] : $this->password;
$this->database = array_key_exists('database', $this->_parametersArray) ? $this->_parametersArray['database'] : $this->database;
$this->prefix = array_key_exists('prefix', $this->_parametersArray) ? $this->_parametersArray['prefix'] : $this->prefix;
$this->dumpFile = array_key_exists('dumpFile', $this->_parametersArray) ? $this->_parametersArray['dumpFile'] : $this->dumpFile;
$this->processEmptyPrefix = array_key_exists('process_empty_prefix', $this->_parametersArray) ? $this->_parametersArray['process_empty_prefix'] : $this->processEmptyPrefix;
}
// Make sure we have self-assigned the first part
$this->partNumber = 0;
// Get DB backup only mode
$configuration =& AEFactory::getConfiguration();
// Find tables to be included and put them in the $_tables variable
$this->enforceSQLCompatibility(); // Joomla! inadvertently sets MySQL40 compatibility and we have to work around it :(
$this->getTablesToBackup();
if($this->getError()) return;
// Find where to store the database backup files
$this->getBackupFilePaths($this->partNumber);
// Remove any leftovers
$this->removeOldFiles();
// Initialize the extended INSERTs feature
$this->extendedInserts = ($configuration->get('engine.dump.common.extended_inserts', 0) != 0);
$this->packetSize = $configuration->get('engine.dump.common.packet_size', 0);
if( $this->packetSize == 0 ) $this->extendedInserts = false;
// Initialize the split dump feature
$this->partSize = $configuration->get('engine.dump.common.splitsize', 1048756);
if( AEUtilScripting::getScriptingParameter('db.saveasname','normal') == 'output' )
{
$this->partSize = 0;
}
if( ($this->partSize != 0) && ($this->packetSize != 0) && ($this->packetSize > $this->partSize) )
{
$this->packetSize = $this->partSize / 2;
}
// Initialize the algorithm
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Initializing algorithm for first run");
$this->nextTable = array_shift( $this->tables );
$this->nextRange = 0;
$this->query = '';
// FIX 2.2: First table of extra databases was not being written to disk.
// This deserved a place in the Bug Fix Hall Of Fame. In subsequent calls to _init, the $fp in
// _writeline() was not nullified. Therefore, the first dump chunk (that is, the first table's
// definition and first chunk of its data) were not written to disk. This call causes $fp to be
// nullified, causing it to be recreated, pointing to the correct file. Holly crap, it took me
// half an hour to get it!
$null = null;
$this->writeline($null);
// Finally, mark ourselves "prepared".
$this->setState('prepared');
}
/**
* Implements the _run() abstract method
*/
protected function _run()
{
// Check if we are already done
if ($this->getState() == 'postrun') {
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Already finished");
$this->setStep("");
$this->setSubstep("");
return;
}
// Mark ourselves as still running (we will test if we actually do towards the end ;) )
$this->setState('running');
// Check if we are still adding a database dump part to the archive, or if
// we have to post-process a part
if( AEUtilScripting::getScriptingParameter('db.saveasname','normal') != 'output' )
{
$archiver =& AEFactory::getArchiverEngine();
$configuration =& AEFactory::getConfiguration();
if($configuration->get('engine.postproc.common.after_part',0))
{
if(!empty($archiver->finishedPart))
{
$filename = array_shift($archiver->finishedPart);
AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Preparing to post process '.basename($filename));
$post_proc =& AEFactory::getPostprocEngine();
$result = $post_proc->processPart( $filename );
$this->propagateFromObject($post_proc);
if($result === false)
{
$this->setWarning('Failed to process file '.basename($filename));
}
else
{
AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Successfully processed file '.basename($filename));
}
// Should we delete the file afterwards?
if(
$configuration->get('engine.postproc.common.delete_after',false)
&& $post_proc->allow_deletes
&& ($result !== false)
)
{
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Deleting already processed file '.basename($filename));
AEPlatform::getInstance()->unlink($filename);
}
if($post_proc->break_after) {
$configuration->set('volatile.breakflag', true);
return;
}
}
}
if($configuration->get('volatile.engine.archiver.processingfile',false))
{
// We had already started archiving the db file, but it needs more time
$finished = true;
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Continuing adding the SQL dump part to the archive");
$archiver->addFile(null,null,null);
$this->propagateFromObject($archiver);
if($this->getError()) return;
$finished = !$configuration->get('volatile.engine.archiver.processingfile',false);
if($finished)
{
$this->getNextDumpPart();
}
else
{
return;
}
}
}
// Initialize local variables
$db =& $this->getDB();
if($this->getError()) return;
if( !is_object($db) || ($db === false) )
{
$this->setError(__CLASS__.'::_run() Could not connect to database?!');
return;
}
$outData = ''; // Used for outputting INSERT INTO commands
$this->enforceSQLCompatibility(); // Apply MySQL compatibility option
if($this->getError()) return;
// Touch SQL dump file
$nada = "";
$this->writeline($nada);
// Get this table's information
$tableName = $this->nextTable;
$tableAbstract = trim( $this->table_name_map[$tableName] );
$dump_records = $this->tables_data[$tableName]['dump_records'];
// If it is the first run, find number of rows and get the CREATE TABLE command
if( $this->nextRange == 0 )
{
if($this->getError()) return;
$outCreate = '';
if(is_array($this->tables_data[$tableName])) {
if(array_key_exists('create', $this->tables_data[$tableName])) {
$outCreate = $this->tables_data[$tableName]['create'];
}
}
if(empty($outCreate) && !empty($tableName)) {
// The CREATE command wasn't cached. Time to create it. The $type and $dependencies
// variables will be thrown away.
$type = 'table';
$dependencies = array();
$outCreate = $this->get_create($tableAbstract, $tableName, $type, $dependencies);
}
// Write the CREATE command
if(!$this->writeDump($outCreate)) return;
// Create drop statements if required (the key is defined by the scripting engine)
$configuration =& AEFactory::getConfiguration();
if( AEUtilScripting::getScriptingParameter('db.dropstatements',0) )
{
if(array_key_exists('create', $this->tables_data[$tableName])) {
// @todo This looks cheesy...
$dropStatement = $this->createDrop($this->tables_data[$tableName]['create']);
} else {
$type = 'table';
$createStatement = $this->get_create($tableAbstract, $tableName, $type, $dependencies);
$dropStatement = $this->createDrop($createStatement);
}
if(!empty($dropStatement))
{
if(!$this->writeDump($outCreate)) return;
}
}
if( $dump_records )
{
// We are dumping data from a table, get the row count
$this->getRowCount( $tableAbstract );
}
else
{
// We should not dump any data
AEUtilLogger::WriteLog(_AE_LOG_INFO, "Skipping dumping data of " . $tableAbstract);
$this->maxRange = 0;
$this->nextRange = 1;
$outData = '';
$numRows = 0;
}
}
// Check if we have more work to do on this table
$configuration =& AEFactory::getConfiguration();
$batchsize = intval($configuration->get('engine.dump.common.batchsize', 1000));
if($batchsize <= 0) $batchsize = 1000;
if( ($this->nextRange < $this->maxRange) )
{
$timer =& AEFactory::getTimer();
// Get the number of rows left to dump from the current table
$sql = "SELECT * FROM `$tableAbstract`";
if( $this->nextRange == 0 )
{
// First run, get a cursor to all records
$db->setQuery( $sql, 0, $batchsize );
AEUtilLogger::WriteLog(_AE_LOG_INFO, "Beginning dump of " . $tableAbstract);
}
else
{
// Subsequent runs, get a cursor to the rest of the records
$db->setQuery( $sql, $this->nextRange, $batchsize );
AEUtilLogger::WriteLog(_AE_LOG_INFO, "Continuing dump of " . $tableAbstract . " from record #{$this->nextRange}");
}
$this->query = '';
$numRows = 0;
$use_abstract = AEUtilScripting::getScriptingParameter('db.abstractnames', 1);
$filters =& AEFactory::getFilters();
$mustFilter = $filters->hasFilterType('dbobject', 'children');
while( is_array($myRow = $db->loadAssoc(false)) && ( $numRows < ($this->maxRange - $this->nextRange) ) ) {
$this->createNewPartIfRequired();
$numRows++;
$numOfFields = count( $myRow );
// If row-level filtering is enabled, please run the filtering
if($mustFilter) {
$isFiltered = $filters->isFiltered(
array(
'table' => $tableAbstract,
'row' => $myRow
),
$configuration->get('volatile.database.root', '[SITEDB]'),
'dbobject', 'children');
if($isFiltered) continue;
}
if(
(!$this->extendedInserts) || // Add header on simple INSERTs, or...
( $this->extendedInserts && empty($this->query) ) //...on extended INSERTs if there are no other data, yet
)
{
$newQuery = true;
if( $numOfFields > 0 ) $this->query = "INSERT INTO `" . (!$use_abstract ? $tableName : $tableAbstract) . "` VALUES ";
}
else
{
// On other cases, just mark that we should add a comma and start a new VALUES entry
$newQuery = false;
}
$outData = '(';
// Step through each of the row's values
$fieldID = 0;
// Used in running backup fix
$isCurrentBackupEntry = false;
// Fix 1.2a - NULL values were being skipped
if( $numOfFields > 0 ) foreach( $myRow as $value )
{
// The ID of the field, used to determine placement of commas
$fieldID++;
// Fix 2.0: Mark currently running backup as successful in the DB snapshot
if($tableAbstract == '#__ak_stats')
{
if($fieldID == 1)
{
// Compare the ID to the currently running
$statistics =& AEFactory::getStatistics();
$isCurrentBackupEntry = ($value == $statistics->getId());
}
elseif ($fieldID == 6)
{
// Treat the status field
$value = $isCurrentBackupEntry ? 'complete' : $value;
}
}
// Post-process the value
if( is_null($value) )
{
$outData .= "NULL"; // Cope with null values
} else {
// Accommodate for runtime magic quotes
$value = @get_magic_quotes_runtime() ? stripslashes( $value ) : $value;
$outData .= $db->Quote($value);
}
if( $fieldID < $numOfFields ) $outData .= ', ';
} // foreach
$outData .= ')';
if( $numOfFields )
{
// If it's an existing query and we have extended inserts
if($this->extendedInserts && !$newQuery)
{
// Check the existing query size
$query_length = strlen($this->query);
$data_length = strlen($outData);
if( ($query_length + $data_length) > $this->packetSize )
{
// We are about to exceed the packet size. Write the data so far.
$this->query .= ";\n";
if(!$this->writeDump($this->query)) return;
// Then, start a new query
$this->query = '';
$this->query = "INSERT INTO `" . (!$use_abstract ? $tableName : $tableAbstract) . "` VALUES ";
$this->query .= $outData;
}
else
{
// We have room for more data. Append $outData to the query.
$this->query .= ', ';
$this->query .= $outData;
}
}
elseif($this->extendedInserts && $newQuery)
// If it's a brand new insert statement in an extended INSERTs set
{
// Append the data to the INSERT statement
$this->query .= $outData;
// Let's see the size of the dumped data...
$query_length = strlen($this->query);
if($query_length >= $this->packetSize)
{
// This was a BIG query. Write the data to disk.
$this->query .= ";\n";
if(!$this->writeDump($this->query)) return;
// Then, start a new query
$this->query = '';
}
}
else
// It's a normal (not extended) INSERT statement
{
// Append the data to the INSERT statement
$this->query .= $outData;
// Write the data to disk.
$this->query .= ";\n";
if(!$this->writeDump($this->query)) return;
// Then, start a new query
$this->query = '';
}
}
$outData = '';
// Check for imminent timeout
if( $timer->getTimeLeft() <= 0 ) {
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Breaking dump of $tableAbstract after $numRows rows; will continue on next step");
break;
}
} // for (all rows left)
// Advance the _nextRange pointer
$this->nextRange += ($numRows != 0) ? $numRows : 1;
$this->setStep($tableName);
$this->setSubstep($this->nextRange . ' / ' . $this->maxRange);
} // if more work on the table
// Finalize any pending query
// WARNING! If we do not do that now, the query will be emptied in the next operation and all
// accumulated data will go away...
if(!empty($this->query))
{
$this->query .= ";\n";
if(!$this->writeDump($this->query)) return;
$this->query = '';
}
// Check for end of table dump (so that it happens inside the same operation)
if( !($this->nextRange < $this->maxRange) )
{
// Tell the user we are done with the table
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Done dumping " . $tableAbstract);
if(count($this->tables) == 0)
{
// We have finished dumping the database!
AEUtilLogger::WriteLog(_AE_LOG_INFO, "End of database detected; flushing the dump buffers...");
$null = null;
$this->writeDump($null);
AEUtilLogger::WriteLog(_AE_LOG_INFO, "Database has been successfully dumped to SQL file(s)");
$this->setState('postrun');
$this->setStep('');
$this->setSubstep('');
$this->nextTable = '';
$this->nextRange = 0;
} elseif(count($this->tables) != 0) {
// Switch tables
$this->nextTable = array_shift( $this->tables );
$this->nextRange = 0;
$this->setStep($this->nextTable);
$this->setSubstep('');
}
}
$null = null;
$this->writeline($null);
}
/**
* Implements the _finalize() abstract method
*
*/
protected function _finalize()
{
static $addedExtraSQL = false;
// This makes sure that we don't re-add the extra SQL if the archiver needed more time
// to include our file in the archive...
if(!$addedExtraSQL)
{
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Adding any extra SQL statements imposed by the filters");
$filters =& AEFactory::getFilters();
$this->writeline( $filters->getExtraSQL($this->databaseRoot) );
}
// Close the file pointer (otherwise the SQL file is left behind)
$this->closeFile();
// If we are not just doing a main db only backup, add the SQL file to the archive
$finished = true;
$configuration =& AEFactory::getConfiguration();
if( AEUtilScripting::getScriptingParameter('db.saveasname','normal') != 'output' )
{
$archiver =& AEFactory::getArchiverEngine();
$configuration =& AEFactory::getConfiguration();
if( $configuration->get('volatile.engine.archiver.processingfile',false) )
{
// We had already started archiving the db file, but it needs more time
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Continuing adding the SQL dump to the archive");
$archiver->addFile(null,null,null);
if($this->getError()) return;
$finished = !$configuration->get('volatile.engine.archiver.processingfile',false);
}
else
{
// We have to add the dump file to the archive
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Adding the SQL dump to the archive");
$archiver->addFileRenamed( $this->tempFile, $this->saveAsName );
if($this->getError()) return;
$finished = !$configuration->get('volatile.engine.archiver.processingfile',false);
}
}
else
{
// We just have to move the dump file to its final destination
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Moving the SQL dump to its final location");
$result = AEPlatform::getInstance()->move( $this->tempFile, $this->saveAsName );
if(!$result)
{
$this->setError('Could not move the SQL dump to its final location');
}
}
// Make sure that if the archiver needs more time to process the file we can supply it
if($finished)
{
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Removing temporary file");
AEUtilTempfiles::unregisterAndDeleteTempFile( $this->tempFile, true );
if($this->getError()) return;
$this->setState('finished');
}
}
/**
* Applies the SQL compatibility setting
*/
protected function enforceSQLCompatibility()
{
$configuration =& AEFactory::getConfiguration();
$db =& $this->getDB();
if($this->getError()) return;
$verParts = explode( '.', $db->getVersion() );
if ( $verParts[0] == 5 ) {
switch( $configuration->get('engine.dump.common.mysql_compatibility') )
{
case 1:
$sql = "SET sql_mode='HIGH_NOT_PRECEDENCE,NO_TABLE_OPTIONS'";
break;
case 0:
default:
$sql = "SET sql_mode='HIGH_NOT_PRECEDENCE'";
break;
}
$db->setQuery( $sql );
$db->query();
}
// Try to enforce SQL_BIG_SELECTS option
$db->setQuery('SET OPTION SQL_BIG_SELECTS=1');
$db->query();
}
/**
* Gets the row count for table $tableAbstract. Also updates the $this->maxRange variable.
*
* @param string $tableAbstract The abstract name of the table (works with canonical names too, though)
* @return integer Row count of the table
*/
private function getRowCount( $tableAbstract )
{
$db =& $this->getDB();
if($this->getError()) return;
$sql = "SELECT COUNT(*) FROM `$tableAbstract`";
$db->setQuery( $sql );
$this->maxRange = $db->loadResult();
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Rows on " . $tableAbstract . " : " . $this->maxRange);
return $this->maxRange;
}
/**
* Creates a new dump part
*/
private function getNextDumpPart()
{
// On database dump only mode we mustn't create part files!
if( AEUtilScripting::getScriptingParameter('db.saveasname','normal') == 'output' ) return false;
// If the archiver is still processing, quit
$finished = true;
$configuration =& AEFactory::getConfiguration();
$archiver =& AEFactory::getArchiverEngine();
if( $configuration->get('volatile.engine.archiver.processingfile',false) ) return false;
// We have to add the dump file to the archive
$this->closeFile();
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Adding the SQL dump part to the archive");
$archiver->addFileRenamed( $this->tempFile, $this->saveAsName );
if($this->getError()) return false;
$finished = !$configuration->get('volatile.engine.archiver.processingfile',false);
if(!$finished) return false; // Return if the file didn't finish getting added to the archive
// Remove the old file
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Removing dump part's temporary file");
AEUtilTempfiles::unregisterAndDeleteTempFile( $this->tempFile, true );
// Create the new dump part
$this->partNumber++;
$this->getBackupFilePaths($this->partNumber);
$null = null;
$this->writeline($null);
return true;
}
private function createNewPartIfRequired()
{
if( $this->partSize == 0 ) return true;
$filesize = 0;
if(@file_exists($this->tempFile)) $filesize = @filesize($this->tempFile);
if( $this->extendedInserts )
{
$projectedSize = $filesize + $this->packetSize;
}
else
{
$projectedSize = $filesize + strlen($this->query);
}
if( $projectedSize > $this->partSize )
{
return $this->getNextDumpPart();
}
return true;
}
// =============================================================================
// Dependency processing - the Twilight Zone starts here
// =============================================================================
/**
* Scans the database for tables to be backed up and sorts them according to
* their dependencies on one another.
*/
private function getTablesToBackup()
{
$configuration =& AEFactory::getConfiguration();
$notracking = $configuration->get('engine.dump.native.nodependencies', 0);
// First, get a map of table names <--> abstract names
$this->get_tables_mapping();
if($this->getError()) return;
if($notracking) {
// Do not process table & view dependencies
$this->get_tables_data_without_dependencies();
if($this->getError()) return;
} else {
// Process table & view dependencies (default)
// Find the type and CREATE command of each table/view in the database
$this->get_tables_data();
if($this->getError()) return;
// Process dependencies and rearrange tables respecting them
$this->process_dependencies();
if($this->getError()) return;
// Remove dependencies array
$this->dependencies = array();
}
}
/**
* Generates a mapping between table names as they're stored in the database
* and their abstract representation.
*/
private function get_tables_mapping()
{
// Get a database connection
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Finding tables to include in the backup set");
$db =& $this->getDB();
if($this->getError()) return;
// Reset internal tables
$this->table_name_map = array();
// Get the list of all database tables
$sql = "SHOW TABLES";
$db->setQuery( $sql );
$all_tables = $db->loadResultArray();
$registry =& AEFactory::getConfiguration();
$root = $registry->get('volatile.database.root', '[SITEDB]');
// If we have filters, make sure the tables pass the filtering
$filters =& AEFactory::getFilters();
foreach( $all_tables as $table_name )
{
if( substr($table_name,0,3) == '#__' )
{
AEUtilLogger::WriteLog(_AE_LOG_WARNING, __CLASS__." :: Table $table_name has a prefix of #__. This would cause restoration errors; table skipped.");
continue;
}
$table_abstract = $this->getAbstract($table_name);
if( substr($table_abstract,0,4) != 'bak_' ) // Skip backup tables
{
// Apply exclusion filters
if( !$filters->isFiltered($table_abstract, $root, 'dbobject', 'all') ) {
AEUtilLogger::WriteLog(_AE_LOG_INFO, __CLASS__." :: Adding $table_name (internal name $table_abstract)");
$this->table_name_map[$table_name] = $table_abstract;
} else {
AEUtilLogger::WriteLog(_AE_LOG_INFO, __CLASS__." :: Skipping $table_name (internal name $table_abstract)");
}
}
else
{
AEUtilLogger::WriteLog(_AE_LOG_INFO, __CLASS__." :: Backup table $table_name automatically skipped.");
}
}
// If we have MySQL > 5.0 add the list of stored procedures, stored functions
// and triggers, but only if user has allows that and the target compatibility is
// not MySQL 4! Also, if dependency tracking is disabled, we won't dump triggers,
// functions and procedures.
$enable_entities = $registry->get('engine.dump.native.advanced_entitites', true);
$compatibility = $registry->get('engine.dump.common.mysql_compatibility', 0);
$notracking = $registry->get('engine.dump.native.nodependencies', 0);
$verParts = explode( '.', $db->getVersion() );
if ( ($verParts[0] == 5) && $enable_entities && ($compatibility == 0) && ($notracking == 0) )
{
// Cache the database name if this is the main site's database
// 1. Stored procedures
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Listing stored PROCEDUREs");
$sql = "SHOW PROCEDURE STATUS WHERE `Db`=".$db->Quote($this->database);
$db->setQuery( $sql );
$all_entries = $db->loadResultArray(1);
// If we have filters, make sure the tables pass the filtering
if(is_array($all_entries))
if(count($all_entries))
foreach( $all_entries as $entity_name )
{
$entity_abstract = $this->getAbstract($entity_name);
if(!(substr($entity_abstract,0,4) == 'bak_')) // Skip backup entities
{
if( !$filters->isFiltered($entity_abstract, $root, 'dbobject', 'all') ) $this->table_name_map[$entity_name] = $entity_abstract;
}
}
// 2. Stored functions
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Listing stored FUNCTIONs");
$sql = "SHOW FUNCTION STATUS WHERE `Db`=".$db->Quote($this->database);
$db->setQuery( $sql );
$all_entries = $db->loadResultArray(1);
// If we have filters, make sure the tables pass the filtering
if(is_array($all_entries))
if(count($all_entries))
foreach( $all_entries as $entity_name )
{
$entity_abstract = $this->getAbstract($entity_name);
if(!(substr($entity_abstract,0,4) == 'bak_')) // Skip backup entities
{
// Apply exclusion filters if set
if( !$filters->isFiltered($entity_abstract, $root, 'dbobject', 'all') ) $this->table_name_map[$entity_name] = $entity_abstract;
}
}
// 3. Triggers
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Listing stored TRIGGERs");
$sql = "SHOW TRIGGERS";
$db->setQuery( $sql );
$all_entries = $db->loadResultArray();
// If we have filters, make sure the tables pass the filtering
if(is_array($all_entries))
if(count($all_entries))
foreach( $all_entries as $entity_name )
{
$entity_abstract = $this->getAbstract($entity_name);
if(!(substr($entity_abstract,0,4) == 'bak_')) // Skip backup entities
{
// Apply exclusion filters if set
if( !$filters->isFiltered($entity_abstract, $root, 'dbobject', 'all') ) $this->table_name_map[$entity_name] = $entity_abstract;
}
}
} // if MySQL 5
}
/**
* Populates the _tables array with the metadata of each table and generates
* dependency information for views and merge tables
*/
private function get_tables_data()
{
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Starting CREATE TABLE and dependency scanning");
// Get a database connection
$db =& $this->getDB();
if($this->getError()) return;
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Got database connection");
// Reset internal tables
$this->tables_data = array();
$this->dependencies = array();
// Get a list of tables where their engine type is shown
$sql = 'SHOW TABLES';
$db->setQuery( $sql );
$metadata_list = $db->loadRowList();
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Got SHOW TABLES");
// Get filters and filter root
$registry =& AEFactory::getConfiguration();
$root = $registry->get('volatile.database.root', '[SITEDB]');
$filters =& AEFactory::getFilters();
foreach($metadata_list as $table_metadata)
{
// Skip over tables not included in the backup set
if(!array_key_exists($table_metadata[0], $this->table_name_map)) continue;
// Basic information
$table_name = $table_metadata[0];
$table_abstract = $this->table_name_map[$table_metadata[0]];
$new_entry = array(
'type' => 'table',
'dump_records' => true
);
// Get the CREATE command
$dependencies = array();
$new_entry['create'] = $this->get_create($table_abstract, $table_name, $new_entry['type'], $dependencies);
$new_entry['dependencies'] = $dependencies;
if( $new_entry['type'] == 'view' )
{
$new_entry['dump_records'] = false;
} else {
$new_entry['dump_records'] = true;
}
// Scan for the table engine.
$engine = null; // So that we detect VIEWs correctly
if( $new_entry['type'] == 'table' )
{
$engine = 'MyISAM'; // So that even with MySQL 4 hosts we don't screw this up
$engine_keys = array('ENGINE=', 'TYPE=');
foreach($engine_keys as $engine_key)
{
$start_pos = strrpos($new_entry['create'], $engine_key);
if( $start_pos !== false )
{
// Advance the start position just after the position of the ENGINE keyword
$start_pos += strlen($engine_key);
// Try to locate the space after the engine type
$end_pos = stripos($new_entry['create'], ' ', $start_pos);
if( $end_pos === false)
{
// Uh... maybe it ends with ENGINE=EngineType;
$end_pos = stripos($new_entry['create'], ';', $start_pos);
}
if( $end_pos !== false)
{
// Grab the string
$engine = substr( $new_entry['create'], $start_pos, $end_pos - $start_pos );
if(empty($engine))
{
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "*** DEBUG *** $table_name - engine $engine");
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, $new_entry['create']);
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "start $start_pos - end $end_pos");
}
}
}
}
$engine = strtoupper($engine);
}
switch($engine)
{
/*
// Views -- They are detected based on their CREATE statement
case null:
$new_entry['type'] = 'view';
$new_entry['dump_records'] = false;
break;
*/
// Merge tables
case 'MRG_MYISAM':
$new_entry['type'] = 'merge';
$new_entry['dump_records'] = false;
break;
// Tables whose data we do not back up (memory, federated and can-have-no-data tables)
case 'MEMORY':
case 'EXAMPLE':
case 'BLACKHOLE':
case 'FEDERATED':
$new_entry['dump_records'] = false;
break;
// Normal tables and VIEWs
default:
break;
} // switch
// Table Data Filter - skip dumping table contents of filtered out tables
if( $filters->isFiltered($table_abstract, $root, 'dbobject', 'content') )
{
$new_entry['dump_records'] = false;
}
$this->tables_data[$table_name] = $new_entry;
} // foreach
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Got table list");
// If we have MySQL > 5.0 add stored procedures, stored functions and triggers
$enable_entities = $registry->get('engine.dump.native.advanced_entitites', true);
$compatibility = $registry->get('engine.dump.common.mysql_compatibility', 0);
$verParts = explode( '.', $db->getVersion() );
if ( ($verParts[0] == 5) && $enable_entities && ($compatibility == 0) )
{
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Listing MySQL entities");
// Get a list of procedures
$sql = 'SHOW PROCEDURE STATUS WHERE `Db`='.$db->Quote($this->database);
$db->setQuery( $sql );
$metadata_list = $db->loadRowList();
if(is_array($metadata_list))
if(count($metadata_list))
foreach($metadata_list as $entity_metadata)
{
// Skip over entities not included in the backup set
if(!array_key_exists($entity_metadata[1], $this->table_name_map)) continue;
// Basic information
$entity_name = $entity_metadata[1];
$entity_abstract = $this->table_name_map[$entity_metadata[1]];
$new_entry = array(
'type' => 'procedure',
'dump_records' => false
);
// There's no point trying to add a non-procedure entity
if($entity_metadata[2] != 'PROCEDURE') continue;
$dependencies = array();
$new_entry['create'] = $this->get_create($entity_abstract, $entity_name, $new_entry['type'], $dependencies);
$new_entry['dependencies'] = $dependencies;
$this->tables_data[$entity_name] = $new_entry;
} // foreach
// Get a list of functions
$sql = 'SHOW FUNCTION STATUS WHERE `Db`='.$db->Quote($this->database);
$db->setQuery( $sql );
$metadata_list = $db->loadRowList();
if(is_array($metadata_list))
if(count($metadata_list))
foreach($metadata_list as $entity_metadata)
{
// Skip over entities not included in the backup set
if(!array_key_exists($entity_metadata[1], $this->table_name_map)) continue;
// Basic information
$entity_name = $entity_metadata[1];
$entity_abstract = $this->table_name_map[$entity_metadata[1]];
$new_entry = array(
'type' => 'function',
'dump_records' => false
);
// There's no point trying to add a non-function entity
if($entity_metadata[2] != 'FUNCTION') continue;
$dependencies = array();
$new_entry['create'] = $this->get_create($entity_abstract, $entity_name, $new_entry['type'], $dependencies);
$new_entry['dependencies'] = $dependencies;
$this->tables_data[$entity_name] = $new_entry;
} // foreach
// Get a list of triggers
$sql = 'SHOW TRIGGERS';
$db->setQuery( $sql );
$metadata_list = $db->loadRowList();
if(is_array($metadata_list))
if(count($metadata_list))
foreach($metadata_list as $entity_metadata)
{
// Skip over entities not included in the backup set
if(!array_key_exists($entity_metadata[0], $this->table_name_map)) continue;
// Basic information
$entity_name = $entity_metadata[0];
$entity_abstract = $this->table_name_map[$entity_metadata[0]];
$new_entry = array(
'type' => 'trigger',
'dump_records' => false
);
$dependencies = array();
$new_entry['create'] = $this->get_create($entity_abstract, $entity_name, $new_entry['type'], $dependencies);
$new_entry['dependencies'] = $dependencies;
$this->tables_data[$entity_name] = $new_entry;
} // foreach
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Got MySQL entities list");
}
// Only store unique values
if(count($dependencies) > 0)
$dependencies = array_unique($dependencies);
}
/**
* Populates the _tables array with the metadata of each table
*/
private function get_tables_data_without_dependencies()
{
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Pushing table data (without dependency tracking)");
// Reset internal tables
$this->tables_data = array();
$this->dependencies = array();
// Get filters and filter root
$registry =& AEFactory::getConfiguration();
$root = $registry->get('volatile.database.root', '[SITEDB]');
$filters =& AEFactory::getFilters();
foreach($this->table_name_map as $table_name => $table_abstract)
{
$new_entry = array(
'type' => 'table',
'dump_records' => true
);
// Table Data Filter - skip dumping table contents of filtered out tables
if( $filters->isFiltered($table_abstract, $root, 'dbobject', 'content') )
{
$new_entry['dump_records'] = false;
}
$this->tables_data[$table_name] = $new_entry;
$this->tables[] = $table_name;
} // foreach
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Got table list");
}
/**
* Gets the CREATE TABLE command for a given table/view/procedure/function/trigger
* @param string $table_abstract The abstracted name of the entity
* @param string $table_name The name of the table
* @param string $type The type of the entity to scan. If it's found to differ, the correct type is returned.
* @param array $dependencies The dependencies of this table
* @return string The CREATE command, w/out newlines
*/
private function get_create( $table_abstract, $table_name, &$type, &$dependencies )
{
$configuration =& AEFactory::getConfiguration();
$notracking = $configuration->get('engine.dump.native.nodependencies', 0);
$db =& $this->getDB();
if($this->getError()) return;
switch($type)
{
case 'table':
case 'merge':
case 'view':
$sql = "SHOW CREATE TABLE `$table_abstract`";
break;
case 'procedure':
$sql = "SHOW CREATE PROCEDURE `$table_abstract`";
break;
case 'function':
$sql = "SHOW CREATE FUNCTION `$table_abstract`";
break;
case 'trigger':
$sql = "SHOW CREATE TRIGGER `$table_abstract`";
break;
}
$db->setQuery( $sql );
$temp = $db->loadRowList();
if( in_array($type, array('procedure','function','trigger')) )
{
$table_sql = $temp[0][2];
}
else
{
$table_sql = $temp[0][1];
}
unset( $temp );
// Smart table type detection
if( in_array($type, array('table','merge','view')) )
{
// Check for CREATE VIEW
$pattern = '/^CREATE(.*) VIEW (.*)/i';
$result = preg_match($pattern, $table_sql);
if($result === 1)
{
// This is a view.
$type = 'view';
}
else
{
// This is a table.
$type = 'table';
// # Fix 3.2.1: USING BTREE / USING HASH in indices causes issues migrating Joomla! 1.6 from MySQL 5.1 to MySQL 5.0
if($configuration->get('engine.dump.native.nobtree',1)) {
$table_sql = str_replace(' USING BTREE',' ',$table_sql);
$table_sql = str_replace(' USING HASH',' ',$table_sql);
}
// # Fix 3.2.3 (reverts changes in 3.2.1): Translate TYPE= to ENGINE=
if($configuration->get('engine.dump.common.mysql_compatibility',1)) {
// MySQL 4.0 compatibility
$table_sql = str_replace('ENGINE=','TYPE=',$table_sql);
} else {
// Default compatibility
$table_sql = str_replace('TYPE=','ENGINE=',$table_sql);
}
}
// Is it a VIEW but we don't have SHOW VIEW privileges?
if(empty($table_sql)) $type = 'view';
}
// Replace table name and names of referenced tables with their abstracted
// forms and populate dependency tables at the same time
// On DB only backup we don't want any replacing to take place, do we?
if( !AEUtilScripting::getScriptingParameter('db.abstractnames',1) ) $old_table_sql = $table_sql;
// Return dependency information only if dependency tracking is enabled
if(!$notracking) {
// Even on simple tables, we may have foreign key references.
// As a result, we need to replace those referenced table names
// as well. On views and merge arrays, we have referenced tables
// by definition.
$dependencies = array();
// First, the table/view/merge table name itself:
$table_sql = str_replace( $table_name , $table_abstract, $table_sql );
// Now, loop for all table entries
foreach($this->table_name_map as $ref_normal => $ref_abstract)
{
if( $pos = strpos($table_sql, "`$ref_normal`") )
{
// Add a reference hit
$this->dependencies[$ref_normal][] = $table_name;
// Add the dependency to this table's metadata
$dependencies[] = $ref_normal;
// Do the replacement
$table_sql = str_replace("`$ref_normal`", "`$ref_abstract`", $table_sql);
}
}
}
// On DB only backup we don't want any replacing to take place, do we?
if( !AEUtilScripting::getScriptingParameter('db.abstractnames',1) ) $table_sql = $old_table_sql;
// Replace newlines with spaces
$table_sql = str_replace( "\n", " ", $table_sql ) . ";\n";
$table_sql = str_replace( "\r", " ", $table_sql );
$table_sql = str_replace( "\t", " ", $table_sql );
// Post-process CREATE VIEW
if($type == 'view')
{
$pos_view = strpos($table_sql, ' VIEW ');
if($pos_view > 7 )
{
// Only post process if there are view properties between the CREATE and VIEW keywords
$propstring = substr($table_sql, 7, $pos_view - 7); // Properties string
// Fetch the ALGORITHM={UNDEFINED | MERGE | TEMPTABLE} keyword
$algostring = '';
$algo_start = strpos($propstring, 'ALGORITHM=');
if($algo_start !== false)
{
$algo_end = strpos($propstring, ' ', $algo_start);
$algostring = substr($propstring, $algo_start, $algo_end - $algo_start + 1);
}
// Create our modified create statement
$table_sql = 'CREATE OR REPLACE '.$algostring.substr($table_sql, $pos_view);
}
}
elseif($type == 'procedure')
{
$pos_entity = strpos($table_sql, ' PROCEDURE ');
$table_sql = 'CREATE'.substr($table_sql, $pos_entity);
}
elseif($type == 'function')
{
$pos_entity = strpos($table_sql, ' FUNCTION ');
$table_sql = 'CREATE'.substr($table_sql, $pos_entity);
}
elseif($type == 'trigger')
{
$pos_entity = strpos($table_sql, ' TRIGGER ');
$table_sql = 'CREATE'.substr($table_sql, $pos_entity);
}
// Add DROP statements for DB only backup
if( AEUtilScripting::getScriptingParameter('db.dropstatements',0) )
{
if( ($type == 'table') || ($type == 'merge') )
{
// Table or merge tables, get a DROP TABLE statement
$drop = "DROP TABLE IF EXISTS `$table_name`;\n";
}
elseif($type == 'view')
{
// Views get a DROP VIEW statement
$drop = "DROP VIEW IF EXISTS `$table_name`;\n";
}
elseif($type == 'procedure')
{
// Procedures get a DROP PROCEDURE statement and proper delimiter strings
$drop = "DROP PROCEDURE IF EXISTS `$table_name`;\n";
$drop .= "DELIMITER // ";
$table_sql = str_replace( "\r", " ", $table_sql );
$table_sql = str_replace( "\t", " ", $table_sql );
$table_sql = rtrim($table_sql,";\n")." // DELIMITER ;\n";
}
elseif($type == 'function')
{
// Procedures get a DROP FUNCTION statement and proper delimiter strings
$drop = "DROP FUNCTION IF EXISTS `$table_name`;\n";
$drop .= "DELIMITER // ";
$table_sql = str_replace( "\r", " ", $table_sql );
$table_sql = rtrim($table_sql,";\n")."// DELIMITER ;\n";
}
elseif($type == 'trigger')
{
// Procedures get a DROP TRIGGER statement and proper delimiter strings
$drop = "DROP TRIGGER IF EXISTS `$table_name`;\n";
$drop .= "DELIMITER // ";
$table_sql = str_replace( "\r", " ", $table_sql );
$table_sql = str_replace( "\t", " ", $table_sql );
$table_sql = rtrim($table_sql,";\n")."// DELIMITER ;\n";
}
$table_sql = $drop . $table_sql;
}
return $table_sql;
}
private function process_dependencies()
{
if(count($this->table_name_map) > 0)
foreach($this->table_name_map as $table_name => $table_abstract)
{
$this->push_table($table_name);
}
AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Processed dependencies");
}
/**
* Pushes a table in the _tables stack, making sure it will appear after
* its dependencies and other tables/views depending on it will eventually
* appear after it. It's a complicated chicken-and-egg problem. Just make
* sure you don't have any bloody circular references!!
* @param string $table_name Canonical name of the table to push
* @param array $stack When called recursive, other views/tables previously processed in order to detect *ahem* dependency loops...
*/
private function push_table($table_name, $stack = array(), $currentRecursionDepth = 0)
{
// Load information
$table_data = $this->tables_data[$table_name];
$table_abstract = $this->table_name_map[$table_name];
if(array_key_exists('dependencies', $table_data))
{
$referenced = $table_data['dependencies'];
} else {
$referenced = array();
}
unset($table_data);
// Try to find the minimum insert position, so as to appear after the last referenced table
$insertpos = false;
if(count($referenced))
{
foreach($referenced as $referenced_table)
{
if(count($this->tables))
{
$newpos = array_search($referenced_table, $this->tables);
if($newpos !== false) {
if($insertpos === false)
{
$insertpos = $newpos;
}
else
{
$insertpos = max($insertpos, $newpos);
}
}
}
}
}
// Add to the _tables array
if(count($this->tables) && !($insertpos === false)) {
array_splice($this->tables, $insertpos+1, 0, $table_name);
}
else
{
$this->tables[] = $table_name;
}
// Here's what... Some other table/view might depend on us, so we must appear
// before it (actually, it must appear after us). So, we scan for such
// tables/views and relocate them
if(count($this->dependencies))
{
if(array_key_exists($table_name, $this->dependencies))
{
foreach($this->dependencies[$table_name] as $depended_table)
{
// First, make sure that either there is no stack, or the
// depended table doesn't belong it. In any other case, we
// were fooled to follow an endless dependency loop and we
// will simply bail out and let the user sort things out.
if(count($stack) > 0)
if(in_array($depended_table, $stack)) continue;
$my_position = array_search($table_name, $this->tables);
$remove_position = array_search($depended_table, $this->tables);
if( ($remove_position !== false) && ($remove_position < $my_position) )
{
$stack[] = $table_name;
array_splice($this->tables, $remove_position, 1);
// Where should I put the other table/view now? Don't tell me.
// I have to recurse...
if($currentRecursionDepth < 19)
{
$this->push_table($depended_table, $stack, ++$currentRecursionDepth);
}
} // if remove_position
} // foreach
} // if in dependencies
} // if there are dependencies
}
/**
* Creates a drop query from a CREATE query
* @param $query string The CREATE query to process
* @return string The DROP statement
*/
private function createDrop($query)
{
// Initialize
$dropQuery = '';
// Parse CREATE TABLE commands
if( substr($query, 0, 12) == 'CREATE TABLE')
{
// Try to get the table name
$restOfQuery = trim(substr($query, 12, strlen($query)-12 )); // Rest of query, after CREATE TABLE
// Is there a backtick?
if(substr($restOfQuery,0,1) == '`')
{
// There is... Good, we'll just find the matching backtick
$pos = strpos($restOfQuery, '`', 1);
$tableName = substr($restOfQuery,1,$pos - 1);
}
else
{
// Nope, let's assume the table name ends in the next blank character
$pos = strpos($restOfQuery, ' ', 1);
$tableName = substr($restOfQuery,1,$pos - 1);
}
unset($restOfQuery);
// Try to drop the table anyway
$dropQuery = 'DROP TABLE IF EXISTS `'.$tableName.'`;';
}
// Parse CREATE VIEW commands
elseif( (substr($query, 0, 7) == 'CREATE ') && (strpos($query, ' VIEW ') !== false) )
{
// Try to get the view name
$view_pos = strpos($query, ' VIEW ');
$restOfQuery = trim( substr($query, $view_pos + 6) ); // Rest of query, after VIEW string
// Is there a backtick?
if(substr($restOfQuery,0,1) == '`')
{
// There is... Good, we'll just find the matching backtick
$pos = strpos($restOfQuery, '`', 1);
$tableName = substr($restOfQuery,1,$pos - 1);
}
else
{
// Nope, let's assume the table name ends in the next blank character
$pos = strpos($restOfQuery, ' ', 1);
$tableName = substr($restOfQuery,1,$pos - 1);
}
unset($restOfQuery);
$dropQuery = 'DROP VIEW IF EXISTS `'.$tableName.'`;';
}
// CREATE PROCEDURE pre-processing
elseif( (substr($query, 0, 7) == 'CREATE ') && (strpos($query, 'PROCEDURE ') !== false) )
{
// Try to get the procedure name
$entity_keyword = ' PROCEDURE ';
$entity_pos = strpos($query, $entity_keyword);
$restOfQuery = trim( substr($query, $entity_pos + strlen($entity_keyword)) ); // Rest of query, after entity key string
// Is there a backtick?
if(substr($restOfQuery,0,1) == '`')
{
// There is... Good, we'll just find the matching backtick
$pos = strpos($restOfQuery, '`', 1);
$entity_name = substr($restOfQuery,1,$pos - 1);
}
else
{
// Nope, let's assume the entity name ends in the next blank character
$pos = strpos($restOfQuery, ' ', 1);
$entity_name = substr($restOfQuery,1,$pos - 1);
}
unset($restOfQuery);
$dropQuery = 'DROP'.$entity_keyword.'IF EXISTS `'.$entity_name.'`;';
}
// CREATE FUNCTION pre-processing
elseif( (substr($query, 0, 7) == 'CREATE ') && (strpos($query, 'FUNCTION ') !== false) )
{
// Try to get the procedure name
$entity_keyword = ' FUNCTION ';
$entity_pos = strpos($query, $entity_keyword);
$restOfQuery = trim( substr($query, $entity_pos + strlen($entity_keyword)) ); // Rest of query, after entity key string
// Is there a backtick?
if(substr($restOfQuery,0,1) == '`')
{
// There is... Good, we'll just find the matching backtick
$pos = strpos($restOfQuery, '`', 1);
$entity_name = substr($restOfQuery,1,$pos - 1);
}
else
{
// Nope, let's assume the entity name ends in the next blank character
$pos = strpos($restOfQuery, ' ', 1);
$entity_name = substr($restOfQuery,1,$pos - 1);
}
unset($restOfQuery);
// Try to drop the entity anyway
$dropQuery = 'DROP'.$entity_keyword.'IF EXISTS `'.$entity_name.'`;';
}
// CREATE TRIGGER pre-processing
elseif( (substr($query, 0, 7) == 'CREATE ') && (strpos($query, 'TRIGGER ') !== false) )
{
// Try to get the procedure name
$entity_keyword = ' TRIGGER ';
$entity_pos = strpos($query, $entity_keyword);
$restOfQuery = trim( substr($query, $entity_pos + strlen($entity_keyword)) ); // Rest of query, after entity key string
// Is there a backtick?
if(substr($restOfQuery,0,1) == '`')
{
// There is... Good, we'll just find the matching backtick
$pos = strpos($restOfQuery, '`', 1);
$entity_name = substr($restOfQuery,1,$pos - 1);
}
else
{
// Nope, let's assume the entity name ends in the next blank character
$pos = strpos($restOfQuery, ' ', 1);
$entity_name = substr($restOfQuery,1,$pos - 1);
}
unset($restOfQuery);
// Try to drop the entity anyway
$dropQuery = 'DROP'.$entity_keyword.'IF EXISTS `'.$entity_name.'`;';
}
return $dropQuery;
}
}