%PDF- %PDF-
| Direktori : /home1/lightco1/www/administrator/components/com_akeeba/BackupEngine/Dump/Reverse/ |
| Current File : //home1/lightco1/www/administrator/components/com_akeeba/BackupEngine/Dump/Reverse/Mysql.php |
<?php
/**
* Akeeba Engine
* The modular PHP5 site backup engine
*
* @copyright Copyright (c)2006-2017 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 3 or, at your option, any later version
* @package akeebaengine
*
*/
namespace Akeeba\Engine\Dump\Reverse;
// Protection against direct access
defined('AKEEBAENGINE') or die();
use Akeeba\Engine\Driver\Base as DriverBase;
use Akeeba\Engine\Dump\Native\Mysql as NativeMysql;
use Akeeba\Engine\Factory;
use Psr\Log\LogLevel;
/**
* A MySQL database dump class, using reverse engineering of the
* INFORMATION_SCHEMA views to deduce the DDL of the database entities.
*
* 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 Mysql extends NativeMysql
{
/**
* Implements the constructor of the class
*
* @return Mysql
*/
public function __construct()
{
Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: New instance");
}
protected function getTablesToBackup()
{
$configuration = Factory::getConfiguration();
$notracking = $configuration->get('engine.dump.native.nodependencies', 0);
// First, get a map of table names <--> abstract names
$this->reverse_engineer_db();
if ($this->getError())
{
return;
}
if (!$notracking)
{
// Process dependencies and rearrange tables respecting them
$this->process_dependencies();
if ($this->getError())
{
return;
}
// Remove dependencies array
$this->dependencies = array();
}
}
protected function reverse_engineer_db()
{
// Get a database connection
Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Reverse engineering database");
if ($this->getError())
{
return;
}
// Reset internal tables
$this->table_name_map = array();
$this->tables_data = array();
$this->dependencies = array();
// Clone the db object and point it to information_schema
$dbi = clone $this->getDB();
if (!$dbi->select('information_schema'))
{
Factory::getLog()->log(LogLevel::ERROR, __CLASS__ . " :: Could not connect to the INFORMATION_SCHEMA database");
}
// Get the list of all database tables and views
Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Reverse engineering Tables");
$this->reverse_engineer_tables($dbi);
$this->reverse_engineer_views($dbi);
// 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.
$registry = Factory::getConfiguration();
$enable_entities = $registry->get('engine.dump.native.advanced_entitites', true);
$notracking = $registry->get('engine.dump.native.nodependencies', 0);
if ($enable_entities && ($notracking == 0))
{
// @todo Triggers
// Factory::getLog()->log(LogLevel::DEBUG, __CLASS__." :: Reverse engineering Triggers");
// @todo Functions
// Factory::getLog()->log(LogLevel::DEBUG, __CLASS__." :: Reverse engineering Functions");
// @todo Procedures
// Factory::getLog()->log(LogLevel::DEBUG, __CLASS__." :: Reverse engineering Procedures");
}
$dbi->select($this->database);
}
/**
* Reverse engineers the Table definitions of this database
*
* @param DriverBase $dbi Database connection to INFORMATION_SCHEMA
*/
protected function reverse_engineer_tables(&$dbi)
{
$schema_name = $this->database;
$sql = 'SELECT * FROM `tables` WHERE `table_schema` = ' . $dbi->quote($schema_name);
$dbi->setQuery($sql);
$all_tables = $dbi->loadObjectList();
$registry = Factory::getConfiguration();
$root = $registry->get('volatile.database.root', '[SITEDB]');
// If we have filters, make sure the tables pass the filtering
$filters = Factory::getFilters();
if (!empty($all_tables))
{
foreach ($all_tables as $table_object)
{
// Extract the table name
$table_name = $table_object->TABLE_NAME;
// Skip system objects
if ($table_object->TABLE_TYPE != 'BASE TABLE')
{
continue;
}
// Filter and convert
if (substr($table_name, 0, 3) == '#__')
{
$this->setWarning(__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'))
{
Factory::getLog()->log(LogLevel::INFO, __CLASS__ . " :: Adding $table_name (internal name $table_abstract)");
$this->table_name_map[$table_name] = $table_abstract;
}
else
{
Factory::getLog()->log(LogLevel::INFO, __CLASS__ . " :: Skipping $table_name (internal name $table_abstract)");
continue;
}
}
else
{
Factory::getLog()->log(LogLevel::INFO, __CLASS__ . " :: Backup table $table_name automatically skipped.");
continue;
}
// Still here? The table is added. We now have to store its
// create command, dependency info and so on
$new_entry = array(
'type' => 'table',
'dump_records' => true
);
switch ($table_object->ENGINE)
{
// 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
default:
break;
}
// Table Data Filter - skip dumping table contents of filtered out tables
if ($filters->isFiltered($table_abstract, $root, 'dbobject', 'content'))
{
$new_entry['dump_records'] = false;
}
$new_entry['create'] = $this->get_create_table($dbi, $table_name, $table_abstract, $table_object, $dependencies);
$new_entry['dependencies'] = $dependencies;
$this->tables_data[$table_name] = $new_entry;
}
}
}
/**
* Gets the CREATE TABLE command of a given table
*
* @param DriverBase $dbi The db connection to the INFORMATION_SCHEMA db
* @param string $table_name The name of the table
* @param string $table_abstract The abstract name of the table
* @param \stdClass $table_object The TABLES object for this table
* @param array $dependencies Dependency tracking information
*
* @return string The CREATE TABLE definition
*/
protected function get_create_table(&$dbi, $table_name, $table_abstract, $table_object, &$dependencies)
{
$configuration = Factory::getConfiguration();
$notracking = $configuration->get('engine.dump.native.nodependencies', 0);
$useabstract = Factory::getEngineParamsProvider()->getScriptingParameter('db.abstractnames', 1);
$columns_sql = array();
$keys_sql = array();
$constraints_sql = array();
// =====================================================================
// ========== GENERATE SQL FOR COLUMNS
// =====================================================================
// Get columns
$query = 'SELECT * FROM `columns` WHERE `table_schema` = ' . $dbi->quote($this->database)
. ' AND `table_name` = ' . $dbi->quote($table_name) . ' ORDER BY `ordinal_position` ASC';
$dbi->setQuery($query);
$allColumns = $dbi->loadObjectList();
foreach ($allColumns as $oColumn)
{
$line = $dbi->quoteName($oColumn->COLUMN_NAME) . ' ' . $oColumn->COLUMN_TYPE . ' ';
$line .= ($oColumn->IS_NULLABLE == 'YES') ? 'NULL ' : 'NOT NULL ';
if (!in_array($oColumn->EXTRA, array('CURRENT_TIMESTAMP')))
{
$line .= ($oColumn->EXTRA == '') ? '' : ($oColumn->EXTRA . ' ');
}
if (in_array($oColumn->COLUMN_DEFAULT, array('CURRENT_TIMESTAMP')))
{
$line .= ' DEFAULT ' . $oColumn->COLUMN_DEFAULT;
}
else
{
$line .= ($oColumn->COLUMN_DEFAULT == '') ? '' : (' DEFAULT ' . $dbi->quote($oColumn->COLUMN_DEFAULT) . ' ');
}
$line .= ($oColumn->COLUMN_COMMENT == '') ? '' : (' COMMENT ' . $dbi->quote($oColumn->COLUMN_COMMENT));
$columns_sql[] = $line;
}
// =====================================================================
// ========== GENERATE SQL FOR KEYS AND INDICES
// =====================================================================
// Get the primary and unique key names
$query = 'SELECT `constraint_name`, `constraint_type` FROM `table_constraints` WHERE `table_schema` = ' . $dbi->quote($this->database)
. ' AND `table_name` = ' . $dbi->quote($table_name) . ' AND `constraint_type` IN(' .
$dbi->quote('PRIMARY KEY') . ', ' . $dbi->quote('UNIQUE') . ')';
$dbi->setQuery($query);
$specialKeys = $dbi->loadObjectList('constraint_name');
// Get the columns per key and key information
$query = 'SELECT * FROM `statistics` WHERE `table_schema` = ' . $dbi->quote($this->database)
. ' AND `table_name` = ' . $dbi->quote($table_name) . ' ORDER BY `INDEX_NAME` ASC, SEQ_IN_INDEX ASC';
$dbi->setQuery($query);
$allColumns = $dbi->loadObjectList();
$rawKeys = array();
if (!empty($allColumns))
{
foreach ($allColumns as $oColumn)
{
if (!array_key_exists($oColumn->INDEX_NAME, $rawKeys))
{
$entry = array(
'name' => $oColumn->INDEX_NAME,
'def' => 'KEY',
'columns' => array(),
'type' => 'BTREE',
);
if (array_key_exists($oColumn->INDEX_NAME, $specialKeys))
{
$entry['def'] = $specialKeys[$oColumn->INDEX_NAME]->constraint_type;
if ($entry['def'] == 'UNIQUE')
{
$entry['def'] = 'UNIQUE KEY';
}
elseif ($entry['def'] == 'PRIMARY')
{
$entry['def'] = 'PRIMARY KEY';
}
}
$rawKeys[$oColumn->INDEX_NAME] = $entry;
}
// This is the optional key length for each column
$subpart = '';
if ($oColumn->SUB_PART)
{
$subpart = '(' . $oColumn->SUB_PART . ')';
}
// Add the column to the index
$rawKeys[$oColumn->INDEX_NAME]['columns'][] = '`' . $oColumn->COLUMN_NAME . '`' . $subpart;
// Setup the index type
$rawKeys[$oColumn->INDEX_NAME]['type'] = $oColumn->INDEX_TYPE;
}
}
// Piece together the keys' SQL statements
if (!empty($rawKeys))
{
foreach ($rawKeys as $keydef)
{
$line = ' ' . $keydef['def'] . ' ';
if ($keydef['type'] == 'FULLTEXT')
{
$line = ' ' . $keydef['type'] . $line;
$keydef['type'] = '';
}
if ($keydef['def'] != 'PRIMARY KEY')
{
$line .= "`{$keydef['name']}` ";
}
$line .= '(';
$line .= implode(',', $keydef['columns']);
$line .= ')';
if (!empty($keydef['type']) && ($keydef['def'] != 'PRIMARY KEY'))
{
$line .= ' USING ' . $keydef['type'];
}
$keys_sql[] = $line;
}
}
// =====================================================================
// ========== GENERATE SQL FOR CONSTRAINTS
// =====================================================================
// Get the foreign key names
$query = 'SELECT * FROM `referential_constraints` WHERE `constraint_schema` = ' . $dbi->quote($this->database)
. ' AND `table_name` = ' . $dbi->quote($table_name);
$dbi->setQuery($query);
$foreignKeyInfo = $dbi->loadObjectList('CONSTRAINT_NAME');
// Get the columns per key and key information
$query = 'SELECT * FROM `key_column_usage` WHERE `constraint_schema` = ' . $dbi->quote($this->database)
. ' AND `table_name` = ' . $dbi->quote($table_name) . ' AND `referenced_table_name` IS NOT NULL';
$dbi->setQuery($query);
$allFKColumns = $dbi->loadObjectList();
$rawConstraints = array();
if (!empty($allFKColumns))
{
foreach ($allFKColumns as $oColumn)
{
if (!array_key_exists($oColumn->CONSTRAINT_NAME, $rawConstraints))
{
$entry = array(
'name' => $oColumn->CONSTRAINT_NAME,
'cols' => array(),
'refcols' => array(),
'reftable' => '',
'update' => '',
'delete' => '',
);
if ($useabstract)
{
$entry['name'] = $this->getAbstract($entry['name']);
}
if (array_key_exists($oColumn->CONSTRAINT_NAME, $foreignKeyInfo))
{
$entry['update'] = $foreignKeyInfo[$oColumn->CONSTRAINT_NAME]->UPDATE_RULE;
$entry['delete'] = $foreignKeyInfo[$oColumn->CONSTRAINT_NAME]->DELETE_RULE;
$reftable = $foreignKeyInfo[$oColumn->CONSTRAINT_NAME]->REFERENCED_TABLE_NAME;
// Add a reference hit
$this->dependencies[$reftable][] = $table_name;
// Add the dependency to this table's metadata
$dependencies[] = $reftable;
if ($useabstract)
{
$reftable = $this->getAbstract($reftable);
}
$entry['reftable'] = $reftable;
}
$rawConstraints[$oColumn->CONSTRAINT_NAME] = $entry;
}
$rawConstraints[$oColumn->CONSTRAINT_NAME]['cols'][] = '`' . $oColumn->COLUMN_NAME . '`';
$rawConstraints[$oColumn->CONSTRAINT_NAME]['refcols'][] = '`' . $oColumn->REFERENCED_COLUMN_NAME . '`';
}
}
// Piece together the constraints' SQL statements
if (!empty($rawConstraints))
{
foreach ($rawConstraints as $keydef)
{
$line = ' CONSTRAINT ';
if ($keydef['name'])
{
$line .= "`{$keydef['name']}` ";
}
$line .= 'FOREIGN KEY (';
$line .= implode(',', $keydef['cols']);
$line .= ') REFERENCES `' . $keydef['reftable'] . '` (';
$line .= implode(',', $keydef['refcols']);
$line .= ')';
if ($keydef['delete'])
{
$line .= ' ON DELETE ' . $keydef['delete'];
}
if ($keydef['update'])
{
$line .= ' ON UPDATE ' . $keydef['update'];
}
$constraints_sql[] = $line;
}
}
// =====================================================================
// ========== CONSTRUCT THE TABLE CREATE STATEMENT
// =====================================================================
// Create the SQL output
if ($useabstract)
{
$table_sql = "CREATE TABLE `$table_abstract` (";
}
else
{
$table_sql = "CREATE TABLE `$table_name` (";
}
$table_sql .= implode(',', $columns_sql);
if (count($keys_sql))
{
$table_sql .= ',' . implode(',', $keys_sql);
}
if (count($constraints_sql))
{
$table_sql .= ',' . implode(',', $constraints_sql);
}
$table_sql .= ")";
// Engine and stuff must also be exported here
if ($table_object->ENGINE)
{
$table_sql .= ' ENGINE=' . $table_object->ENGINE;
}
if ($table_object->TABLE_COLLATION)
{
$table_sql .= ' DEFAULT COLLATE ' . $table_object->TABLE_COLLATION;
}
$table_sql .= ";\n";
return $table_sql;
}
/**
* Reverse engineers the View definitions of this database
*
* @param DriverBase $dbi Database connection to INFORMATION_SCHEMA
*/
protected function reverse_engineer_views(&$dbi)
{
$schema_name = $this->database;
$sql = 'SELECT * FROM `views` WHERE `table_schema` = ' . $dbi->quote($schema_name);
$dbi->setQuery($sql);
$all_views = $dbi->loadObjectList();
$registry = Factory::getConfiguration();
$root = $registry->get('volatile.database.root', '[SITEDB]');
// If we have filters, make sure the tables pass the filtering
$filters = Factory::getFilters();
// First pass: populate the table_name_map
if (!empty($all_views))
{
foreach ($all_views as $table_object)
{
// Extract the table name
$table_name = $table_object->TABLE_NAME;
// Filter and convert
if (substr($table_name, 0, 3) == '#__')
{
$this->setWarning(__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'))
{
Factory::getLog()->log(LogLevel::INFO, __CLASS__ . " :: Adding $table_name (internal name $table_abstract)");
$this->table_name_map[$table_name] = $table_abstract;
}
else
{
Factory::getLog()->log(LogLevel::INFO, __CLASS__ . " :: Skipping $table_name (internal name $table_abstract)");
continue;
}
}
else
{
Factory::getLog()->log(LogLevel::INFO, __CLASS__ . " :: Backup table $table_name automatically skipped.");
continue;
}
}
}
// Second pass: get the create commands
if (!empty($all_views))
{
foreach ($all_views as $table_object)
{
// Extract the table name
$table_name = $table_object->TABLE_NAME;
if (!in_array($table_name, $this->table_name_map))
{
// Skip any views which have been filtered out
continue;
}
$table_abstract = $this->getAbstract($table_name);
// Still here? The view is added. We now have to store its
// create command, dependency info and so on
$new_entry = array(
'type' => 'view',
'dump_records' => false
);
$dependencies = array();
$table_sql = 'CREATE OR REPLACE VIEW `' . $table_name . '` AS ' . $table_object->VIEW_DEFINITION;
$old_table_sql = $table_sql;
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 (!Factory::getEngineParamsProvider()->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);
$new_entry['create'] = $table_sql;
$new_entry['dependencies'] = $dependencies;
$this->tables_data[$table_name] = $new_entry;
}
}
}
}