Dramatically improved performance of db/build

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@40035 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2007-08-15 02:50:39 +00:00
parent 65c7a3f0f4
commit 9b1487c376
5 changed files with 266 additions and 67 deletions

View File

@ -227,5 +227,57 @@ class ManifestBuilder {
require_once($filename);
}
}
/**
* Updates the active table list in the class info in the manifest, but leaves everything else as-is.
* Much quicker to run than compileManifest :-)
*/
static function update_db_tables() {
global $_ALL_CLASSES;
$_ALL_CLASSES['hastable'] = array();
$tables = DB::getConn()->tableList();
// We need to iterate through the full class lists, because the table names come out in lowercase
foreach($_ALL_CLASSES['exists'] as $class) {
if(isset($tables[strtolower($class)])) $_ALL_CLASSES['hastable'][$class] = $class;
}
self::write_manifest();
}
/**
* Write the manifest file, containing the updated values in the applicable globals
*/
static function write_manifest() {
global $_CLASS_MANIFEST, $_TEMPLATE_MANIFEST, $_CSS_MANIFEST, $_ALL_CLASSES;
$manifest = "\$_CLASS_MANIFEST = " . var_export($_CLASS_MANIFEST, true) . ";\n";
// Config manifest
$baseDir = dirname($_SERVER['SCRIPT_FILENAME']) . "/..";
$baseDir = ereg_replace("/[^/]+/\\.\\.","",$baseDir);
$topLevel = scandir($baseDir);
foreach($topLevel as $filename) {
if(is_dir("$baseDir/$filename/") && file_exists("$baseDir/$filename/_config.php")) {
$manifest .= "require_once(\"$baseDir/$filename/_config.php\");\n";
}
}
$manifest .= "\$_TEMPLATE_MANIFEST = " . var_export($_TEMPLATE_MANIFEST, true) . ";\n";
$manifest .= "\$_CSS_MANIFEST = " . var_export($_CSS_MANIFEST, true) . ";\n";
$manifest .= "\$_ALL_CLASSES = " . var_export($_ALL_CLASSES, true) . ";\n";
$manifest = "<?php\n$manifest\n?>";
if($fh = fopen(MANIFEST_FILE,"w")) {
fwrite($fh, $manifest);
fclose($fh);
} else {
die("Cannot write manifest file! Check permissions of " . MANIFEST_FILE);
}
}
}
?>

View File

@ -58,7 +58,12 @@ abstract class Database extends Object {
* The table will have a single field - the integer key ID.
* @param string $table Name of table to create.
*/
abstract function createTable($table);
abstract function createTable($table, $fields = null, $indexes = null);
/**
* Alter a table's schema.
*/
abstract function alterTable($table, $newFields, $newIndexes, $alteredFields, $alteredIndexes);
/**
* Rename a table.
@ -112,6 +117,59 @@ abstract class Database extends Object {
*/
protected $indexList;
/**
* Large array structure that represents a schema update transaction
*/
protected $schemaUpdateTransaction;
/**
* Start a schema-updating transaction.
* All calls to requireTable/Field/Index will keep track of the changes requested, but not actually do anything.
* Once
*/
function beginSchemaUpdate() {
$this->tableList = $this->tableList();
$this->indexList = null;
$this->fieldList = null;
$this->schemaUpdateTransaction = array();
}
function endSchemaUpdate() {
foreach($this->schemaUpdateTransaction as $tableName => $changes) {
switch($changes['command']) {
case 'create':
$this->createTable($tableName, $changes['newFields'], $changes['newIndexes']);
break;
case 'alter':
$this->alterTable($tableName, $changes['newFields'], $changes['newIndexes'],
$changes['alteredFields'], $changes['alteredIndexes']);
break;
}
}
$this->schemaUpdateTransaction = null;
}
// Transactional schema altering functions - they don't do anyhting except for update schemaUpdateTransaction
function transCreateTable($table) {
$this->schemaUpdateTransaction[$table] = array('command' => 'create');
}
function transCreateField($table, $field, $schema) {
$this->schemaUpdateTransaction[$table]['newFields'][$field] = $schema;
}
function transCreateIndex($table, $index, $schema) {
$this->schemaUpdateTransaction[$table]['newIndexes'][$index] = $schema;
}
function transAlterField($table, $field, $schema) {
$this->schemaUpdateTransaction[$table]['alteredFields'][$field] = $schema;
}
function transAlterIndex($table, $index, $schema) {
$this->schemaUpdateTransaction[$table]['alteredIndexes'][$index] = $schema;
}
/**
* Generate the following table in the database, modifying whatever already exists
* as necessary.
@ -124,12 +182,8 @@ abstract class Database extends Object {
* control over the index.
*/
function requireTable($table, $fieldSchema = null, $indexSchema = null) {
if(!isset($this->tableList)) {
$this->tableList = $this->tableList();
}
if(!isset($this->tableList[strtolower($table)])) {
$this->createTable($table);
$this->transCreateTable($table);
if(!Database::$supressOutput) {
echo "<li style=\"color: orange\">Table $table: created</li>";
}
@ -137,20 +191,14 @@ abstract class Database extends Object {
$this->checkAndRepairTable($table);
}
// Create custom fields
if($fieldSchema) {
foreach($fieldSchema as $fieldName => $fieldSpec) {
// echo "<li>$fieldName - " .ViewableData::castingObjectCreator($fieldSpec);
// Debug::show(ViewableData::castingObjectCreator($fieldSpec));
$fieldObj = eval(ViewableData::castingObjectCreator($fieldSpec));
$fieldObj->setTable($table);
$fieldObj->requireField();
}
}
}
// Create custom indexes
if($indexSchema) {
@ -189,16 +237,18 @@ abstract class Database extends Object {
}
$spec = ereg_replace(" *, *",",",$spec);
if(!isset($this->indexList[$table])) {
if(!isset($this->tableList[strtolower($table)])) $newTable = true;
if(!$newTable && !isset($this->indexList[$table])) {
$this->indexList[$table] = $this->indexList($table);
}
if(!isset($this->indexList[$table][$index])) {
$this->createIndex($table, $index, $spec);
if($newTable || !isset($this->indexList[$table][$index])) {
$this->transCreateIndex($table, $index, $spec);
if(!Database::$supressOutput) {
echo "<li style=\"color: red\">Index $table.$index: created as $spec</li>";
}
} else if($this->indexList[$table][$index] != $spec) {
$this->alterIndex($table, $index, $spec);
$this->transAlterIndex($table, $index, $spec);
if(!Database::$supressOutput) {
echo "<li style=\"color: orange\">Index $table.$index: changed to $spec <i style=\"color: #AAA\">(from {$this->indexList[$table][$index]})</i></li>";
}
@ -212,27 +262,34 @@ abstract class Database extends Object {
* @param string $spec The field specification.
*/
function requireField($table, $field, $spec) {
Profiler::mark('requireField');
// Collations didn't come in until MySQL 4.1. Anything earlier will throw a syntax error if you try and use
// collations.
if(!$this->supportsCollations()) {
$spec = eregi_replace(' *character set [^ ]+( collate [^ ]+)?( |$)','\\2',$spec);
}
if(!isset($this->tableList[strtolower($table)])) $newTable = true;
if(!isset($this->fieldList[$table])) {
if(!$newTable && !isset($this->fieldList[$table])) {
$this->fieldList[$table] = $this->fieldList($table);
}
if(!isset($this->fieldList[$table][$field])) {
$this->createField($table, $field, $spec);
if($newTable || !isset($this->fieldList[$table][$field])) {
Profiler::mark('createField');
$this->transCreateField($table, $field, $spec);
Profiler::unmark('createField');
if(!Database::$supressOutput) {
echo "<li style=\"color: red\">Field $table.$field: created as $spec</li>";
}
} else if($this->fieldList[$table][$field] != $spec) {
$this->alterField($table, $field, $spec);
Profiler::mark('alterField');
$this->transAlterField($table, $field, $spec);
Profiler::unmark('alterField');
if(!Database::$supressOutput) {
echo "<li style=\"color: orange\">Field $table.$field: changed to $spec <i style=\"color: #AAA\">(from {$this->fieldList[$table][$field]})</i></li>";
}
}
Profiler::unmark('requireField');
}
/**

View File

@ -121,6 +121,9 @@ class DatabaseAdmin extends Controller {
* @param boolean $quiet Don't show messages
*/
function doBuild($quiet = false) {
$conn = DB::getConn();
Profiler::mark('doBuild');
if($quiet) {
DB::quiet();
} else {
@ -136,8 +139,8 @@ class DatabaseAdmin extends Controller {
}
// Get all our classes
ManifestBuilder::compileManifest();
ManifestBuilder::includeEverything();
// ManifestBuilder::compileManifest();
// ManifestBuilder::includeEverything();
// Build the database. Most of the hard work is handled by DataObject
$dataClasses = ClassInfo::subclassesFor('DataObject');
@ -146,7 +149,8 @@ class DatabaseAdmin extends Controller {
if(!$quiet) {
echo '<p><b>Creating database tables</b></p>';
}
$conn->beginSchemaUpdate();
foreach($dataClasses as $dataClass) {
// Test_ indicates that it's the data class is part of testing system
@ -154,12 +158,14 @@ class DatabaseAdmin extends Controller {
if(!$quiet) {
echo "<li>$dataClass";
}
Profiler::mark("requireTable $dataClass");
singleton($dataClass)->requireTable();
Profiler::unmark("requireTable $dataClass");
}
}
$conn->endSchemaUpdate();
ManifestBuilder::compileManifest();
ManifestBuilder::update_db_tables();
if(!$quiet) {
echo '<p><b>Creating database records</b></p>';
@ -173,7 +179,9 @@ class DatabaseAdmin extends Controller {
echo "<li>$dataClass";
}
Profiler::mark("requireDefaultRecords $dataClass");
singleton($dataClass)->requireDefaultRecords();
Profiler::unmark("requireDefaultRecords $dataClass");
}
}
@ -182,6 +190,7 @@ class DatabaseAdmin extends Controller {
if(isset($_REQUEST['from_installer'])) {
echo "OK";
}
Profiler::unmark('doBuild');
}

View File

@ -114,20 +114,91 @@ class MySQLDatabase extends Database {
public function createDatabase() {
$this->query("CREATE DATABASE $this->database");
$this->query("USE $this->database");
$this->tableList = $this->fieldList = $this->indexList = null;
if(mysql_select_db($this->database, $this->dbConn)) {
$this->active = true;
return true;
}
}
/**
* Drop the database that this object is currently connected to.
* Use with caution.
*/
public function dropDatabase() {
$this->query("DROP DATABASE $this->database");
}
public function createTable($tableName) {
$this->query("CREATE TABLE `$tableName` (ID int(11) not null auto_increment, primary key (ID)) TYPE=MyISAM");
/**
* Returns the name of the currently selected database
*/
public function currentDatabase() {
return $this->database;
}
/**
* Switches to the given database.
* If the database doesn't exist, you should call createDatabase() after calling selectDatabase()
*/
public function selectDatabase($dbname) {
$this->database = $dbname;
if($this->databaseExists($this->databse)) mysql_select_db($this->database, $this->dbConn);
$this->tableList = $this->fieldList = $this->indexList = null;
}
/**
* Returns true if the named database exists.
*/
public function databaseExists($name) {
$SQL_name = Convert::raw2sql($name);
return $this->query("SHOW DATABASES LIKE '$SQL_name'")->value() ? true : false;
}
public function createTable($tableName, $fields = null, $indexes = null) {
$fieldSchemas = $indexSchemas = "";
if($fields) foreach($fields as $k => $v) $fieldSchemas .= "`$k` $v,\n";
if($indexes) foreach($indexes as $k => $v) $fieldSchemas .= $this->getIndexSqlDefinition($k, $v) . ",\n";
$this->query("CREATE TABLE `$tableName` (
ID int(11) not null auto_increment,
$fieldSchemas
$indexSchemas
primary key (ID)
) TYPE=MyISAM");
}
/**
* Alter a table's schema.
* @param $table The name of the table to alter
* @param $newFields New fields, a map of field name => field schema
* @param $newIndexes New indexes, a map of index name => index type
* @param $alteredFields Updated fields, a map of field name => field schema
* @param $alteredIndexes Updated indexes, a map of index name => index type
*/
public function alterTable($table, $newFields, $newIndexes, $alteredFields, $alteredIndexes) {
$fieldSchemas = $indexSchemas = "";
if($newFields) foreach($newFields as $k => $v) $alterList[] .= "ADD `$k` $v";
if($newIndexes) foreach($newIndexes as $k => $v) $alterList[] .= "ADD " . $this->getIndexSqlDefinition($k, $v) . ",\n";
if($alteredFields) foreach($alteredFields as $k => $v) $alterList[] .= "CHANGE `$k` `$k` $v";
if($alteredIndexes) foreach($alteredIndexes as $k => $v) {
$alterList[] .= "DROP INDEX `$k`";
$alterList[] .= "ADD ". $this->getIndexSqlDefinition($k, $v);
}
$alterations = implode(",\n", $alterList);
$this->query("ALTER TABLE `$tableName` " . $alterations);
}
public function renameTable($oldTableName, $newTableName) {
$this->query("ALTER TABLE `$oldTableName` RENAME `$newTableName`");
}
/**
* Checks a table's integrity and repairs it if necessary.
* @var string $tableName The name of the table.
@ -220,14 +291,17 @@ class MySQLDatabase extends Database {
* @param string $indexSpec The specification of the index, see Database::requireIndex() for more details.
*/
public function createIndex($tableName, $indexName, $indexSpec) {
$this->query("ALTER TABLE `$tableName` ADD " . $this->getIndexSqlDefinition($indexName, $indexSpec));
}
protected function getIndexSqlDefinition($indexName, $indexSpec) {
$indexSpec = trim($indexSpec);
if($indexSpec[0] != '(') list($indexType, $indexFields) = explode(' ',$indexSpec,2);
else $indexFields = $indexSpec;
if(!isset($indexType)) {
$indexType = "index";
}
$this->query("ALTER TABLE `$tableName` ADD $indexType `$indexName` $indexFields");
return "$indexType `$indexName` $indexFields";
}
/**
@ -280,7 +354,6 @@ class MySQLDatabase extends Database {
return $indexList;
}
/**
* Returns a list of all the tables in the database.
* Table names will all be in lowercase.

View File

@ -395,43 +395,49 @@ class SiteTree extends DataObject {
function requireDefaultRecords() {
parent::requireDefaultRecords();
if(!DataObject::get_one("SiteTree", "URLSegment = 'home'")) {
$homepage = new Page();
$homepage->Title = "Home";
$homepage->Content = "<p>Welcome to SilverStripe! This is the default homepage. You can edit this page by opening <a href=\"admin/\">the CMS</a>.</p>";
$homepage->URLSegment = "home";
$homepage->Status = "Published";
$homepage->write();
$homepage->publish("Stage", "Live");
if(!Database::$supressOutput) {
echo "<li style=\"color: orange\">Home page created</li>";
}
}
if(DB::query("SELECT COUNT(*) FROM SiteTree")->value() == 1) {
$aboutus = new Page();
$aboutus->Title = "About Us";
$aboutus->Content = "<p>You can fill this page out with your own content, or delete it and create your own pages.<br /></p>";
$aboutus->URLSegment = "about-us";
$aboutus->Status = "Published";
$aboutus->write();
$aboutus->publish("Stage", "Live");
if(!Database::$supressOutput) {
echo "<li style=\"color: orange\">About Us created</li>";
}
$contactus = new Page();
$contactus->Title = "Contact Us";
$contactus->Content = "<p>You can fill this page out with your own content, or delete it and create your own pages.<br /></p>";
$contactus->URLSegment = "contact-us";
$contactus->Status = "Published";
$contactus->write();
$contactus->publish("Stage", "Live");
}
if($this->class == 'SiteTree') {
if(!DataObject::get_one("SiteTree", "URLSegment = 'home'")) {
$homepage = new Page();
echo 'Running with the homepage: ' . $homepage->ID;
$homepage->Title = "Home";
$homepage->Content = "<p>Welcome to SilverStripe! This is the default homepage. You can edit this page by opening <a href=\"admin/\">the CMS</a>.</p>";
$homepage->URLSegment = "home";
$homepage->Status = "Published";
$homepage->write();
echo 'Created the homepage: ' . $homepage->ID;
$homepage->publish("Stage", "Live");
$homepage->flushCache();
if(!Database::$supressOutput) {
echo "<li style=\"color: orange\">Home page created</li>";
}
}
if(DB::query("SELECT COUNT(*) FROM SiteTree")->value() == 1) {
$aboutus = new Page();
$aboutus->Title = "About Us";
$aboutus->Content = "<p>You can fill this page out with your own content, or delete it and create your own pages.<br /></p>";
$aboutus->URLSegment = "about-us";
$aboutus->Status = "Published";
$aboutus->write();
$aboutus->publish("Stage", "Live");
if(!Database::$supressOutput) {
echo "<li style=\"color: orange\">About Us created</li>";
}
$contactus = new Page();
$contactus->Title = "Contact Us";
$contactus->Content = "<p>You can fill this page out with your own content, or delete it and create your own pages.<br /></p>";
$contactus->URLSegment = "contact-us";
$contactus->Status = "Published";
$contactus->write();
$contactus->publish("Stage", "Live");
$contactus->flushCache();
}
}
}
//------------------------------------------------------------------------------------//
@ -697,6 +703,8 @@ class SiteTree extends DataObject {
{
$fields = call_user_func($extension,$fields);
}
$this->extend('updateCMSFields', $fields);
return $fields;
}