From 9b1487c376bf00fee2eadfe56552cdc66af12add Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Wed, 15 Aug 2007 02:50:39 +0000 Subject: [PATCH] Dramatically improved performance of db/build git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@40035 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- core/ManifestBuilder.php | 52 +++++++++++++++++++ core/model/Database.php | 99 ++++++++++++++++++++++++++++-------- core/model/DatabaseAdmin.php | 19 +++++-- core/model/MySQLDatabase.php | 83 ++++++++++++++++++++++++++++-- core/model/SiteTree.php | 80 ++++++++++++++++------------- 5 files changed, 266 insertions(+), 67 deletions(-) diff --git a/core/ManifestBuilder.php b/core/ManifestBuilder.php index ed70d57e5..2d882f117 100644 --- a/core/ManifestBuilder.php +++ b/core/ManifestBuilder.php @@ -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 = ""; + + if($fh = fopen(MANIFEST_FILE,"w")) { + fwrite($fh, $manifest); + fclose($fh); + + } else { + die("Cannot write manifest file! Check permissions of " . MANIFEST_FILE); + } + } + } ?> \ No newline at end of file diff --git a/core/model/Database.php b/core/model/Database.php index a4a78e22f..a5799d779 100755 --- a/core/model/Database.php +++ b/core/model/Database.php @@ -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 "
  • Table $table: created
  • "; } @@ -137,20 +191,14 @@ abstract class Database extends Object { $this->checkAndRepairTable($table); } - // Create custom fields if($fieldSchema) { foreach($fieldSchema as $fieldName => $fieldSpec) { - // echo "
  • $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 "
  • Index $table.$index: created as $spec
  • "; } } else if($this->indexList[$table][$index] != $spec) { - $this->alterIndex($table, $index, $spec); + $this->transAlterIndex($table, $index, $spec); if(!Database::$supressOutput) { echo "
  • Index $table.$index: changed to $spec (from {$this->indexList[$table][$index]})
  • "; } @@ -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 "
  • Field $table.$field: created as $spec
  • "; } } 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 "
  • Field $table.$field: changed to $spec (from {$this->fieldList[$table][$field]})
  • "; } } + Profiler::unmark('requireField'); } /** diff --git a/core/model/DatabaseAdmin.php b/core/model/DatabaseAdmin.php index 941d44918..cdfc4bb11 100644 --- a/core/model/DatabaseAdmin.php +++ b/core/model/DatabaseAdmin.php @@ -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 '

    Creating database tables

    '; } - + + $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 "
  • $dataClass"; } - + Profiler::mark("requireTable $dataClass"); singleton($dataClass)->requireTable(); + Profiler::unmark("requireTable $dataClass"); } } + $conn->endSchemaUpdate(); - ManifestBuilder::compileManifest(); + ManifestBuilder::update_db_tables(); if(!$quiet) { echo '

    Creating database records

    '; @@ -173,7 +179,9 @@ class DatabaseAdmin extends Controller { echo "
  • $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'); } diff --git a/core/model/MySQLDatabase.php b/core/model/MySQLDatabase.php index ff4fa6149..f989518fd 100644 --- a/core/model/MySQLDatabase.php +++ b/core/model/MySQLDatabase.php @@ -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. diff --git a/core/model/SiteTree.php b/core/model/SiteTree.php index 59d7a771a..94341c9f7 100644 --- a/core/model/SiteTree.php +++ b/core/model/SiteTree.php @@ -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 = "

    Welcome to SilverStripe! This is the default homepage. You can edit this page by opening the CMS.

    "; - $homepage->URLSegment = "home"; - $homepage->Status = "Published"; - $homepage->write(); - $homepage->publish("Stage", "Live"); - - if(!Database::$supressOutput) { - echo "
  • Home page created
  • "; - } - } - - if(DB::query("SELECT COUNT(*) FROM SiteTree")->value() == 1) { - $aboutus = new Page(); - $aboutus->Title = "About Us"; - $aboutus->Content = "

    You can fill this page out with your own content, or delete it and create your own pages.

    "; - $aboutus->URLSegment = "about-us"; - $aboutus->Status = "Published"; - $aboutus->write(); - $aboutus->publish("Stage", "Live"); - - if(!Database::$supressOutput) { - echo "
  • About Us created
  • "; - } - - $contactus = new Page(); - $contactus->Title = "Contact Us"; - $contactus->Content = "

    You can fill this page out with your own content, or delete it and create your own pages.

    "; - $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 = "

    Welcome to SilverStripe! This is the default homepage. You can edit this page by opening the CMS.

    "; + $homepage->URLSegment = "home"; + $homepage->Status = "Published"; + $homepage->write(); + echo 'Created the homepage: ' . $homepage->ID; + $homepage->publish("Stage", "Live"); + $homepage->flushCache(); + + if(!Database::$supressOutput) { + echo "
  • Home page created
  • "; + } + } + + if(DB::query("SELECT COUNT(*) FROM SiteTree")->value() == 1) { + $aboutus = new Page(); + $aboutus->Title = "About Us"; + $aboutus->Content = "

    You can fill this page out with your own content, or delete it and create your own pages.

    "; + $aboutus->URLSegment = "about-us"; + $aboutus->Status = "Published"; + $aboutus->write(); + $aboutus->publish("Stage", "Live"); + + if(!Database::$supressOutput) { + echo "
  • About Us created
  • "; + } + + $contactus = new Page(); + $contactus->Title = "Contact Us"; + $contactus->Content = "

    You can fill this page out with your own content, or delete it and create your own pages.

    "; + $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; }