From a445a120207dcfbc6a1ee384f330b2cd18007c3b Mon Sep 17 00:00:00 2001 From: SkieDr Date: Tue, 29 Sep 2009 22:06:31 +0400 Subject: [PATCH 1/3] Implement tests for new dbo source methods for additional table and field parameters support --- .../model/datasources/dbo/dbo_mysql.test.php | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php b/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php index f8ba9669a..013af639c 100644 --- a/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php +++ b/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php @@ -599,5 +599,66 @@ class DboMysqlTest extends CakeTestCase { $this->db->query($this->db->dropSchema($schema1)); } + +/** + * testReadTableParameters method + * + * @access public + * @return void + */ + function testReadTableParameters() { + $this->db->cacheSources = $this->db->testing = false; + $this->db->query('CREATE TABLE ' . $this->db->fullTableName('tinyint') . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'); + $result = $this->db->readTableParameters('tinyint'); + $expected = array( + 'charset' => 'utf8', + 'collate' => 'utf8_unicode_ci', + 'engine' => 'InnoDB'); + $this->assertEqual($result, $expected); + $this->db->query('DROP TABLE ' . $this->db->fullTableName('tinyint')); + $this->db->query('CREATE TABLE ' . $this->db->fullTableName('tinyint') . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id)) ENGINE=MyISAM DEFAULT CHARSET=cp1250 COLLATE=cp1250_general_ci;'); + $result = $this->db->readTableParameters('tinyint'); + $expected = array( + 'charset' => 'cp1250', + 'collate' => 'cp1250_general_ci', + 'engine' => 'MyISAM'); + $this->assertEqual($result, $expected); + $this->db->query('DROP TABLE ' . $this->db->fullTableName('tinyint')); + } + +/** + * testBuildTableParameters method + * + * @access public + * @return void + */ + function testBuildTableParameters() { + $this->db->cacheSources = $this->db->testing = false; + $data = array( + 'charset' => 'utf8', + 'collate' => 'utf8_unicode_ci', + 'engine' => 'InnoDB'); + $result = $this->db->buildTableParameters($data); + $expected = array( + 'DEFAULT CHARSET=utf8', + 'COLLATE=utf8_unicode_ci', + 'ENGINE=InnoDB'); + $this->assertEqual($result, $expected); + } + +/** + * testBuildTableParameters method + * + * @access public + * @return void + */ + function testGetCharsetName() { + $this->db->cacheSources = $this->db->testing = false; + $result = $this->db->getCharsetName('utf8_unicode_ci'); + $this->assertEqual($result, 'utf8'); + $result = $this->db->getCharsetName('cp1250_general_ci'); + $this->assertEqual($result, 'cp1250'); + } + } ?> \ No newline at end of file From 86d0a04c8dd69569d7f93882b4e7ba4f4e5bf7b9 Mon Sep 17 00:00:00 2001 From: SkieDr Date: Tue, 29 Sep 2009 19:30:02 +0400 Subject: [PATCH 2/3] fixture generator for new table and field level parameters --- cake/console/libs/tasks/fixture.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cake/console/libs/tasks/fixture.php b/cake/console/libs/tasks/fixture.php index 8b4ef2da9e..3d58ddc65 100644 --- a/cake/console/libs/tasks/fixture.php +++ b/cake/console/libs/tasks/fixture.php @@ -260,16 +260,24 @@ class FixtureTask extends Shell { $out = "array(\n"; foreach ($tableInfo as $field => $fieldInfo) { if (is_array($fieldInfo)) { - if ($field != 'indexes') { + if (!in_array($field, array('indexes', 'tableParameters'))) { $col = "\t\t'{$field}' => array('type'=>'" . $fieldInfo['type'] . "', "; $col .= join(', ', $this->_Schema->__values($fieldInfo)); - } else { + } elseif ($field == 'indexes') { $col = "\t\t'indexes' => array("; $props = array(); foreach ((array)$fieldInfo as $key => $index) { $props[] = "'{$key}' => array(".join(', ', $this->_Schema->__values($index)).")"; } $col .= join(', ', $props); + } elseif ($field == 'tableParameters') { + //@todo add charset, collate and engine here + $col = "\t\t'tableParameters' => array("; + $props = array(); + foreach ((array)$fieldInfo as $key => $param) { + $props[] = "'{$key}' => '$param'"; + } + $col .= join(', ', $props); } $col .= ")"; $cols[] = $col; From 627eff5f24cff52dce87f438bad93769c4910752 Mon Sep 17 00:00:00 2001 From: SkieDr Date: Tue, 29 Sep 2009 19:25:42 +0400 Subject: [PATCH 3/3] Implementation of table level and field level parameters support --- cake/libs/model/cake_schema.php | 5 + cake/libs/model/datasources/dbo/dbo_mysql.php | 83 ++++++++++++++++- cake/libs/model/datasources/dbo_source.php | 93 +++++++++++++++++-- .../model/datasources/dbo/dbo_mysql.test.php | 34 +++++++ 4 files changed, 208 insertions(+), 7 deletions(-) diff --git a/cake/libs/model/cake_schema.php b/cake/libs/model/cake_schema.php index 33641c4b8..ba37b329d 100644 --- a/cake/libs/model/cake_schema.php +++ b/cake/libs/model/cake_schema.php @@ -237,6 +237,7 @@ class CakeSchema extends Object { if (empty($tables[$Object->table])) { $tables[$Object->table] = $this->__columns($Object); $tables[$Object->table]['indexes'] = $db->index($Object); + $tables[$Object->table]['tableParameters'] = $db->readTableParameters($table); unset($currentTables[$key]); } if (!empty($Object->hasAndBelongsToMany)) { @@ -250,6 +251,7 @@ class CakeSchema extends Object { $key = array_search($table, $currentTables); $tables[$Object->$class->table] = $this->__columns($Object->$class); $tables[$Object->$class->table]['indexes'] = $db->index($Object->$class); + $tables[$Object->$class->table]['tableParameters'] = $db->readTableParameters($table); unset($currentTables[$key]); } } @@ -279,12 +281,15 @@ class CakeSchema extends Object { if (in_array($table, $systemTables)) { $tables[$Object->table] = $this->__columns($Object); $tables[$Object->table]['indexes'] = $db->index($Object); + $tables[$Object->table]['tableParameters'] = $db->readTableParameters($table); } elseif ($models === false) { $tables[$table] = $this->__columns($Object); $tables[$table]['indexes'] = $db->index($Object); + $tables[$table]['tableParameters'] = $db->readTableParameters($table); } else { $tables['missing'][$table] = $this->__columns($Object); $tables['missing'][$table]['indexes'] = $db->index($Object); + $tables['missing'][$table]['tableParameters'] = $db->readTableParameters($table); } } } diff --git a/cake/libs/model/datasources/dbo/dbo_mysql.php b/cake/libs/model/datasources/dbo/dbo_mysql.php index 1cdd83b8f..83a7c952e 100644 --- a/cake/libs/model/datasources/dbo/dbo_mysql.php +++ b/cake/libs/model/datasources/dbo/dbo_mysql.php @@ -75,6 +75,30 @@ class DboMysqlBase extends DboSource { 'rollback' => 'ROLLBACK' ); +/** + * List of engine specific additional field parameters used on table creating + * + * @var array + * @access public + */ + var $fieldParameters = array( + 'charset' => array('value' => 'CHARACTER SET', 'quote' => false, 'join' => ' ', 'column' => false, 'position' => 'beforeDefault'), + 'collate' => array('value' => 'COLLATE', 'quote' => false, 'join' => ' ', 'column' => 'Collation', 'position' => 'beforeDefault'), + 'comment' => array('value' => 'COMMENT', 'quote' => true, 'join' => ' ', 'column' => 'Comment', 'position' => 'afterDefault') + ); + +/** + * List of table engine specific parameters used on table creating + * + * @var array + * @access public + */ + var $tableParameters = array( + 'charset' => array('value' => 'DEFAULT CHARSET', 'quote' => false, 'join' => '=', 'column' => 'charset'), + 'collate' => array('value' => 'COLLATE', 'quote' => false, 'join' => '=', 'column' => 'Collation'), + 'engine' => array('value' => 'ENGINE', 'quote' => false, 'join' => '=', 'column' => 'Engine') + ); + /** * MySQL column definition * @@ -457,6 +481,38 @@ class DboMysql extends DboMysqlBase { } } +/** + * Returns an detailed array of sources (tables) in the database. + * + * @param string $name Table name to get parameters + * @return array Array of tablenames in the database + */ + function listDetailedSources($name = null) { + $condition = ''; + if (is_string($name)) { + $condition = ' WHERE Name=' . $this->value($name); + } + $result = $this->query('SHOW TABLE STATUS FROM ' . $this->name($this->config['database']) . $condition . ';'); + if (!$result) { + return array(); + } else { + $tables = array(); + foreach ($result as $row) { + $tables[$row['TABLES']['Name']] = $row['TABLES']; + if (!empty($row['TABLES']['Collation'])) { + $charset = $this->getCharsetName($row['TABLES']['Collation']); + if ($charset) { + $tables[$row['TABLES']['Name']]['charset'] = $charset; + } + } + } + if (is_string($name)) { + return $tables[$name]; + } + return $tables; + } + } + /** * Returns an array of the fields in given table name. * @@ -469,7 +525,7 @@ class DboMysql extends DboMysqlBase { return $cache; } $fields = false; - $cols = $this->query('DESCRIBE ' . $this->fullTableName($model)); + $cols = $this->query('SHOW FULL COLUMNS FROM ' . $this->fullTableName($model)); foreach ($cols as $column) { $colKey = array_keys($column); @@ -486,12 +542,37 @@ class DboMysql extends DboMysqlBase { if (!empty($column[0]['Key']) && isset($this->index[$column[0]['Key']])) { $fields[$column[0]['Field']]['key'] = $this->index[$column[0]['Key']]; } + foreach ($this->fieldParameters as $name => $value) { + if (!empty($column[0][$value['column']])) { + $fields[$column[0]['Field']][$name] = $column[0][$value['column']]; + } + } + if (isset($fields[$column[0]['Field']]['collate'])) { + $charset = $this->getCharsetName($fields[$column[0]['Field']]['collate']); + if ($charset) { + $fields[$column[0]['Field']]['charset'] = $charset; + } + } } } $this->__cacheDescription($this->fullTableName($model, false), $fields); return $fields; } +/** + * Query charset by collation + * + * @param string $name Collation name + * @return string Character set name + */ + function getCharsetName($name) { + $cols = $this->query('SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= ' . $this->value($name) . ';'); + if (isset($cols[0]['COLLATIONS']['CHARACTER_SET_NAME'])) { + return $cols[0]['COLLATIONS']['CHARACTER_SET_NAME']; + } + return false; + } + /** * Returns a quoted and escaped string of $data for use in an SQL statement. * diff --git a/cake/libs/model/datasources/dbo_source.php b/cake/libs/model/datasources/dbo_source.php index c2ccd0212..53541f8e6 100644 --- a/cake/libs/model/datasources/dbo_source.php +++ b/cake/libs/model/datasources/dbo_source.php @@ -91,6 +91,22 @@ class DboSource extends DataSource { 'rollback' => 'ROLLBACK' ); +/** + * List of table engine specific parameters used on table creating + * + * @var array + * @access protected + */ + var $tableParameters = array(); + +/** + * List of engine specific additional field parameters used on table creating + * + * @var array + * @access protected + */ + var $fieldParameters = array(); + /** * Constructor */ @@ -1370,15 +1386,17 @@ class DboSource extends DataSource { return "DELETE {$alias} FROM {$table} {$aliases}{$conditions}"; break; case 'schema': - foreach (array('columns', 'indexes') as $var) { + foreach (array('columns', 'indexes', 'tableParameters') as $var) { if (is_array(${$var})) { ${$var} = "\t" . join(",\n\t", array_filter(${$var})); + } else { + ${$var} = ''; } } if (trim($indexes) != '') { $columns .= ','; } - return "CREATE TABLE {$table} (\n{$columns}{$indexes});"; + return "CREATE TABLE {$table} (\n{$columns}{$indexes}){$tableParameters};"; break; case 'alter': break; @@ -2334,7 +2352,7 @@ class DboSource extends DataSource { foreach ($schema->tables as $curTable => $columns) { if (!$tableName || $tableName == $curTable) { - $cols = $colList = $indexes = array(); + $cols = $colList = $indexes = $tableParameters = array(); $primary = null; $table = $this->fullTableName($curTable); @@ -2345,14 +2363,16 @@ class DboSource extends DataSource { if (isset($col['key']) && $col['key'] == 'primary') { $primary = $name; } - if ($name !== 'indexes') { + if ($name !== 'indexes' && $name !== 'tableParameters') { $col['name'] = $name; if (!isset($col['type'])) { $col['type'] = 'string'; } $cols[] = $this->buildColumn($col); - } else { + } elseif ($name == 'indexes') { $indexes = array_merge($indexes, $this->buildIndex($col, $table)); + } elseif ($name == 'tableParameters') { + $tableParameters = array_merge($tableParameters, $this->buildTableParameters($col, $table)); } } if (empty($indexes) && !empty($primary)) { @@ -2360,7 +2380,7 @@ class DboSource extends DataSource { $indexes = array_merge($indexes, $this->buildIndex($col, $table)); } $columns = $cols; - $out .= $this->renderStatement('schema', compact('table', 'columns', 'indexes')) . "\n\n"; + $out .= $this->renderStatement('schema', compact('table', 'columns', 'indexes', 'tableParameters')) . "\n\n"; } } return $out; @@ -2440,6 +2460,16 @@ class DboSource extends DataSource { $column['default'] = null; } + foreach ($this->fieldParameters as $paramName => $value) { + if (isset($column[$paramName]) && $value['position'] == 'beforeDefault') { + $val = $column[$paramName]; + if ($value['quote']) { + $val = $this->value($val); + } + $out .= ' ' . $value['value'] . $value['join'] . $val; + } + } + if (isset($column['key']) && $column['key'] == 'primary' && $type == 'integer') { $out .= ' ' . $this->columns['primary_key']['name']; } elseif (isset($column['key']) && $column['key'] == 'primary') { @@ -2453,6 +2483,17 @@ class DboSource extends DataSource { } elseif (isset($column['null']) && $column['null'] == false) { $out .= ' NOT NULL'; } + + foreach ($this->fieldParameters as $paramName => $value) { + if (isset($column[$paramName]) && $value['position'] == 'afterDefault') { + $val = $column[$paramName]; + if ($value['quote']) { + $val = $this->value($val); + } + $out .= ' ' . $value['value'] . $value['join'] . $val; + } + } + return $out; } @@ -2486,6 +2527,46 @@ class DboSource extends DataSource { return $join; } +/** + * Read additional table parameters + * + * @param array $parameters + * @param string $table + * @return array + */ + function readTableParameters($name) { + $parameters = array(); + if ($this->isInterfaceSupported('listDetailedSources')) { + $currentTableDetails = $this->listDetailedSources($name); + foreach ($this->tableParameters as $paramName => $parameter) { + if (!empty($parameter['column']) && !empty($currentTableDetails[$parameter['column']])) { + $parameters[$paramName] = $currentTableDetails[$parameter['column']]; + } + } + } + return $parameters; + } + +/** + * Format parameters for create table + * + * @param array $parameters + * @param string $table + * @return array + */ + function buildTableParameters($parameters, $table = null) { + $result = array(); + foreach ($parameters as $name => $value) { + if (isset($this->tableParameters[$name])) { + if ($this->tableParameters[$name]['quote']) { + $value = $this->value($value); + } + $result[] = $this->tableParameters[$name]['value'] . $this->tableParameters[$name]['join'] . $value; + } + } + return $result; + } + /** * Guesses the data type of an array * diff --git a/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php b/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php index 013af639c..d2ad2be67 100644 --- a/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php +++ b/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php @@ -385,6 +385,40 @@ class DboMysqlTest extends CakeTestCase { $this->db->query('DROP TABLE ' . $name); } +/** + * testBuildColumn method + * + * @access public + * @return void + */ + function testBuildColumn() { + $this->db->columns = array('varchar(255)' => 1); + $data = array( + 'name' => 'testName', + 'type' => 'varchar(255)', + 'default', + 'null' => true, + 'key', + 'comment' => 'test' + ); + $result = $this->db->buildColumn($data); + $expected = '`testName` DEFAULT NULL COMMENT \'test\''; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'testName', + 'type' => 'varchar(255)', + 'default', + 'null' => true, + 'key', + 'charset' => 'utf8', + 'collate' => 'utf8_unicode_ci' + ); + $result = $this->db->buildColumn($data); + $expected = '`testName` CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL'; + $this->assertEqual($result, $expected); + } + /** * MySQL 4.x returns index data in a different format, * Using a mock ensure that MySQL 4.x output is properly parsed.