From 336bce8bf8d7bae6e6b176462bf7c60c9e96fc71 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 19 May 2009 03:55:14 +0000 Subject: [PATCH] ENHANCEMENT Added DataObject::$create_table_options to pass through special options to Database->requireTable(). Contains a keyed array by database driver. Example use is specifying different storage engines for MySQL: array('MySQLDatabase'=>'ENGINE=InnoDB') git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@77160 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- core/model/DB.php | 5 +-- core/model/DataObject.php | 20 +++++++++++- core/model/Database.php | 59 ++++++++++++++++++++++++++++++------ core/model/MySQLDatabase.php | 27 ++++++++++++++--- tests/model/DatabaseTest.php | 16 ++++++++++ 5 files changed, 109 insertions(+), 18 deletions(-) diff --git a/core/model/DB.php b/core/model/DB.php index 550329616..7f2adeef6 100755 --- a/core/model/DB.php +++ b/core/model/DB.php @@ -187,9 +187,10 @@ class DB { * - true: Create a single column index on the field named the same as the index. * - array('fields' => array('A','B','C'), 'type' => 'index/unique/fulltext'): This gives you full * control over the index. + * @param string $options SQL statement to append to the CREATE TABLE call. */ - static function requireTable($table, $fieldSchema = null, $indexSchema = null, $hasAutoIncPK=true) { - return DB::$globalConn->requireTable($table, $fieldSchema, $indexSchema, $hasAutoIncPK); + static function requireTable($table, $fieldSchema = null, $indexSchema = null, $hasAutoIncPK=true, $options = null) { + return DB::$globalConn->requireTable($table, $fieldSchema, $indexSchema, $hasAutoIncPK, $options); } /** diff --git a/core/model/DataObject.php b/core/model/DataObject.php index 55617d586..fe4d6b4f0 100644 --- a/core/model/DataObject.php +++ b/core/model/DataObject.php @@ -2666,7 +2666,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity if($fields) { $hasAutoIncPK = ($this->class == ClassInfo::baseDataClass($this->class)); - DB::requireTable($this->class, $fields, $indexes, $hasAutoIncPK); + DB::requireTable($this->class, $fields, $indexes, $hasAutoIncPK, $this->stat('create_table_options')); } else { DB::dontRequireTable($this->class); } @@ -3019,6 +3019,24 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity "Created" => "SSDatetime", "Title" => 'Text', ); + + /** + * Specify custom options for a CREATE TABLE call. + * Can be used to specify a custom storage engine for specific database table. + * All options have to be keyed for a specific database implementation, + * identified by their class name (extending from {@link Database}). + * + * + * array( + * 'MySQLDatabase' => 'ENGINE=MyISAM' + * ) + * + * + * @var array + */ + static $create_table_options = array( + 'MySQLDatabase' => 'ENGINE=MyISAM' + ); /** * If a field is in this array, then create a database index diff --git a/core/model/Database.php b/core/model/Database.php index 54724f82f..b8e8d2a9e 100755 --- a/core/model/Database.php +++ b/core/model/Database.php @@ -71,9 +71,13 @@ abstract class Database extends Object { /** * Create a new table. * The table will have a single field - the integer key ID. - * @param string $table Name of table to create. + * @param string $table + * @param array $fields + * @param array $indexes + * @param string $driver + * @param array $options */ - abstract function createTable($table, $fields = null, $indexes = null); + abstract function createTable($table, $fields = null, $indexes = null, $options = null); /** * Alter a table's schema. @@ -177,12 +181,12 @@ abstract class Database extends Object { foreach($this->schemaUpdateTransaction as $tableName => $changes) { switch($changes['command']) { case 'create': - $this->createTable($tableName, $changes['newFields'], $changes['newIndexes']); + $this->createTable($tableName, $changes['newFields'], $changes['newIndexes'], $changes['options']); break; case 'alter': $this->alterTable($tableName, $changes['newFields'], $changes['newIndexes'], - $changes['alteredFields'], $changes['alteredIndexes']); + $changes['alteredFields'], $changes['alteredIndexes'], $changes['alteredOptions']); break; } } @@ -191,9 +195,23 @@ abstract class Database extends Object { // Transactional schema altering functions - they don't do anyhting except for update schemaUpdateTransaction - function transCreateTable($table) { - $this->schemaUpdateTransaction[$table] = array('command' => 'create', 'newFields' => array(), 'newIndexes' => array()); + /** + * @param string $table + * @param string $options + */ + function transCreateTable($table, $options = null) { + $this->schemaUpdateTransaction[$table] = array('command' => 'create', 'newFields' => array(), 'newIndexes' => array(), 'options' => $options); } + + /** + * @param string $table + * @param array $options + */ + function transAlterTable($table, $options) { + $this->transInitTable($table); + $this->schemaUpdateTransaction[$table]['alteredOptions'] = $options; + } + function transCreateField($table, $field, $schema) { $this->transInitTable($table); $this->schemaUpdateTransaction[$table]['newFields'][$field] = $schema; @@ -223,6 +241,7 @@ abstract class Database extends Object { 'newIndexes' => array(), 'alteredFields' => array(), 'alteredIndexes' => array(), + 'alteredOptions' => '' ); } } @@ -231,16 +250,36 @@ abstract class Database extends Object { /** * Generate the following table in the database, modifying whatever already exists * as necessary. + * @todo Change detection for CREATE TABLE $options other than "Engine" + * * @param string $table The name of the table * @param string $fieldSchema A list of the fields to create, in the same form as DataObject::$db * @param string $indexSchema A list of indexes to create. See {@link requireIndex()} + * @param array $options */ - function requireTable($table, $fieldSchema = null, $indexSchema = null, $hasAutoIncPK=true) { + function requireTable($table, $fieldSchema = null, $indexSchema = null, $hasAutoIncPK=true, $options = null) { if(!isset($this->tableList[strtolower($table)])) { - $this->transCreateTable($table); + $this->transCreateTable($table, $options); Database::alteration_message("Table $table: created","created"); } else { - $this->checkAndRepairTable($table); + $this->checkAndRepairTable($table, $options); + + // Check if options changed + if($options && isset($options[$this->class])) { + $tableOptionsChanged = false; + if(preg_match('/ENGINE=([^\s]*)/', $options[$this->class], $alteredEngineMatches)) { + $alteredEngine = $alteredEngineMatches[1]; + $tableStatus = DB::query(sprintf( + 'SHOW TABLE STATUS WHERE "Name" = \'%s\'', + $table + ))->first(); + $tableOptionsChanged = ($tableStatus['Engine'] != $alteredEngine); + } + + if($tableOptionsChanged) { + $this->transAlterTable($table, $options); + } + } } //DB ABSTRACTION: we need to convert this to a db-specific version: @@ -253,7 +292,7 @@ abstract class Database extends Object { $fieldObj->setTable($table); $fieldObj->requireField(); } - } + } // Create custom indexes if($indexSchema) { diff --git a/core/model/MySQLDatabase.php b/core/model/MySQLDatabase.php index 8ec50825c..358b80700 100644 --- a/core/model/MySQLDatabase.php +++ b/core/model/MySQLDatabase.php @@ -190,18 +190,25 @@ class MySQLDatabase extends Database { return $this->query("SHOW DATABASES")->column(); } - public function createTable($tableName, $fields = null, $indexes = null) { + /** + * @param string $table + * @param array $fields + * @param array $indexes + * @param string $options + */ + public function createTable($table, $fields = null, $indexes = null, $options = null) { $fieldSchemas = $indexSchemas = ""; + $addOptions = (isset($options[$this->class])) ? $options[$this->class] : null; if(!isset($fields['ID'])) $fields['ID'] = "int(11) not null auto_increment"; if($fields) foreach($fields as $k => $v) $fieldSchemas .= "\"$k\" $v,\n"; if($indexes) foreach($indexes as $k => $v) $indexSchemas .= $this->getIndexSqlDefinition($k, $v) . ",\n"; - - $this->query("CREATE TABLE \"$tableName\" ( + + $this->query("CREATE TABLE \"$table\" ( $fieldSchemas $indexSchemas primary key (ID) - ) TYPE=MyISAM"); + ) {$addOptions}"); } /** @@ -211,9 +218,11 @@ class MySQLDatabase extends Database { * @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 + * @param $alteredOptions */ - public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null, $alteredIndexes = null) { + public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null, $alteredIndexes = null, $alteredOptions = null) { $fieldSchemas = $indexSchemas = ""; + $alterList = array(); if($newFields) foreach($newFields as $k => $v) $alterList[] .= "ADD \"$k\" $v"; if($newIndexes) foreach($newIndexes as $k => $v) $alterList[] .= "ADD " . $this->getIndexSqlDefinition($k, $v); @@ -225,6 +234,14 @@ class MySQLDatabase extends Database { $alterations = implode(",\n", $alterList); $this->query("ALTER TABLE \"$tableName\" $alterations"); + + if($alteredOptions && isset($alteredOptions[$this->class])) { + $this->query(sprintf("ALTER TABLE \"%s\" %s", $tableName, $alteredOptions[$this->class])); + Database::alteration_message( + sprintf("Table %s options changed: %s", $tableName, $alteredOptions[$this->class]), + "changed" + ); + } } public function renameTable($oldTableName, $newTableName) { diff --git a/tests/model/DatabaseTest.php b/tests/model/DatabaseTest.php index 9f8ae8799..4d58b5c17 100644 --- a/tests/model/DatabaseTest.php +++ b/tests/model/DatabaseTest.php @@ -45,9 +45,25 @@ class DatabaseTest extends SapphireTest { self::create_temp_db(); } + function testMySQLCreateTableOptions() { + if(DB::getConn() instanceof MySQLDatabase) { + $ret = DB::query(sprintf( + 'SHOW TABLE STATUS WHERE "Name" = \'%s\'', + 'DatabaseTest_MyObject' + ))->first(); + $this->assertEquals($ret['Engine'],'InnoDB', + "MySQLDatabase tables can be changed to InnoDB through DataObject::\$create_table_options" + ); + } + + } + } class DatabaseTest_MyObject extends DataObject implements TestOnly { + + static $create_table_options = array('MySQLDatabase' => 'ENGINE=InnoDB'); + static $db = array( 'MyField' => 'Varchar' );