diff --git a/lib/Doctrine/DataDict/Mysql.php b/lib/Doctrine/DataDict/Mysql.php index 63aa852b2..f6254a9d1 100644 --- a/lib/Doctrine/DataDict/Mysql.php +++ b/lib/Doctrine/DataDict/Mysql.php @@ -146,25 +146,25 @@ public function getNativeDeclaration($field) return $length ? 'CHAR(' . $length . ')' : 'CHAR(255)'; case 'enum': - if ($this->conn->getAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM)) { - $values = array(); - foreach ($field['values'] as $value) { - $values[] = $this->conn->quote($value, 'varchar'); - } - return 'ENUM(' . implode(', ', $values) . ')'; - } else { - $field['length'] = isset($field['length']) && $field['length'] ? $field['length']:255; - } - // no break case 'set': - if ($this->conn->getAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET)) { + if (($this->conn->getAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET) && $field['type'] === 'set') || + ($this->conn->getAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM) && $field['type'] === 'enum') + ) { $values = array(); foreach ($field['values'] as $value) { $values[] = $this->conn->quote($value, 'varchar'); } - return 'SET(' . implode(', ', $values) . ')'; + return strtoupper($field['type']) . '(' . implode(', ', $values) . ')'; } else { - $field['length'] = isset($field['length']) && $field['length'] ? $field['length']:255; + if ($field['type'] === 'enum' && !empty($field['values'])) { + $length = max(array_map('strlen', $field['values'])); + } elseif ($field['type'] === 'set' && !empty($field['values'])) { + $length = strlen(implode(',', $field['values'])); + } else { + $length = isset($field['length']) && $field['length'] ? $field['length']:255; + } + + $field['length'] = $length; } // no break case 'varchar': @@ -253,7 +253,7 @@ public function getNativeDeclaration($field) } /** - * Maps a native array description of a field to a MDB2 datatype and length + * Maps a native array description of a field to a Mysql datatype and length * * @param array $field native field description * @return array containing the various possible types, length, sign, fixed @@ -279,7 +279,13 @@ public function getPortableDeclaration(array $field) $unsigned = $fixed = null; if (! isset($field['name'])) { - $field['name'] = ''; + // Mysql's DESCRIBE returns a "Field" column, not a "Name" column + // this method is called with output from that query in Doctrine_Import_Mysql::listTableColumns + if (isset($field['field'])) { + $field['name'] = $field['field']; + } else { + $field['name'] = ''; + } } $values = null; @@ -343,7 +349,8 @@ public function getPortableDeclaration(array $field) } break; case 'enum': - $type[] = 'enum'; + case 'set': + $type[] = $dbType; preg_match_all('/\'((?:\'\'|[^\'])*)\'/', $field['type'], $matches); $length = 0; $fixed = false; @@ -352,21 +359,20 @@ public function getPortableDeclaration(array $field) $value = str_replace('\'\'', '\'', $value); $length = max($length, strlen($value)); } - if ($length == '1' && count($matches[1]) == 2) { + if ($dbType === 'enum' && $length == '1' && count($matches[1]) == 2) { $type[] = 'boolean'; if (preg_match('/^(is|has)/', $field['name'])) { $type = array_reverse($type); } } + if ($dbType === 'set') { + $length = strlen(implode(',', $matches[1])); + } + $values = $matches[1]; } $type[] = 'integer'; - break; - case 'set': - $fixed = false; - $type[] = 'text'; - $type[] = 'integer'; break; case 'date': $type[] = 'date'; diff --git a/lib/Doctrine/Import/Builder.php b/lib/Doctrine/Import/Builder.php index cdb54ccd5..ffc71167d 100644 --- a/lib/Doctrine/Import/Builder.php +++ b/lib/Doctrine/Import/Builder.php @@ -695,6 +695,9 @@ public function buildPhpDocs(array $definition) case 'decimal': $type = 'float'; break; + case 'set': + $type = 'string[]'; + break; case 'blob': case 'clob': case 'timestamp': diff --git a/lib/Doctrine/Validator.php b/lib/Doctrine/Validator.php index ccd0dce13..f166201ee 100644 --- a/lib/Doctrine/Validator.php +++ b/lib/Doctrine/Validator.php @@ -97,7 +97,7 @@ public static function validateLength($value, $type, $maximumLength) if ($maximumLength === null) { return true; } - if ($type == 'timestamp' || $type == 'integer' || $type == 'enum') { + if ($type === 'timestamp' || $type === 'integer' || $type === 'enum' || $type === 'set') { return true; } elseif ($type == 'array' || $type == 'object') { $length = strlen(serialize($value)); diff --git a/tests/DataDict/MysqlTestCase.php b/tests/DataDict/MysqlTestCase.php index 6b2c18018..65df15992 100644 --- a/tests/DataDict/MysqlTestCase.php +++ b/tests/DataDict/MysqlTestCase.php @@ -46,11 +46,19 @@ public function testGetPortableDeclarationSupportsNativeIntegerTypes() { $type = $this->dataDict->getPortableDeclaration(array('type' => 'tinyint')); - $this->assertEqual($type, array('type' => array('integer', 'boolean'), 'length' => 1, 'unsigned' => null, 'fixed' => null)); + + // If column name starts with "is" or "has" treat as a boolean + $type = $this->dataDict->getPortableDeclaration(array('type' => 'tinyint', 'field' => 'isenabled')); + + $this->assertEqual($type, array('type' => array('boolean', 'integer'), + 'length' => 1, + 'unsigned' => null, + 'fixed' => null)); + $type = $this->dataDict->getPortableDeclaration(array('type' => 'smallint unsigned')); $this->assertEqual($type, array('type' => array('integer'), @@ -124,6 +132,13 @@ public function testGetPortableDeclarationSupportsNativeStringTypes() 'unsigned' => null, 'fixed' => true)); + $type = $this->dataDict->getPortableDeclaration(array('type' => 'char(1)', 'field' => 'hascontent')); + + $this->assertEqual($type, array('type' => array('boolean', 'string'), + 'length' => 1, + 'unsigned' => null, + 'fixed' => true)); + $type = $this->dataDict->getPortableDeclaration(array('type' => 'varchar(1)')); $this->assertEqual($type, array('type' => array('string', 'boolean'), @@ -249,6 +264,92 @@ public function testGetPortableDeclarationSupportsNativeBlobTypes() 'fixed' => null)); } + public function testGetPortableDeclarationSupportsNativeEnumTypes() + { + $field = array( + 'field' => 'letter', + 'type' => "enum('a','b','c')", + 'null' => 'NO', + 'key' => '', + 'default' => 'a', + 'extra' => '' + ); + + $type = $this->dataDict->getPortableDeclaration($field); + + $this->assertEqual($type, array('type' => array('enum', 'integer'), + 'length' => 1, + 'unsigned' => null, + 'fixed' => false, + 'values' => array('a', 'b', 'c'))); + + $field['type'] = "set('a','b','c')"; + + $type = $this->dataDict->getPortableDeclaration($field); + + $this->assertEqual($type, array('type' => array('set', 'integer'), + 'length' => 5, + 'unsigned' => null, + 'fixed' => false, + 'values' => array('a', 'b', 'c'))); + + // Custom "boolean" type when ENUM only has two values + $field['type'] = "enum('y','n')"; + + $type = $this->dataDict->getPortableDeclaration($field); + + $this->assertEqual($type, array('type' => array('enum', 'boolean', 'integer'), + 'length' => 1, + 'unsigned' => null, + 'fixed' => false, + 'values' => array('y', 'n'))); + + // Another special case where types are flipped when field name is "is" or "has" + $field['field'] = 'isenabled'; + + $type = $this->dataDict->getPortableDeclaration($field); + + $this->assertEqual($type, array('type' => array('boolean', 'enum', 'integer'), + 'length' => 1, + 'unsigned' => null, + 'fixed' => false, + 'values' => array('y', 'n'))); + } + + public function testGetNativeDefinitionSupportsEnumTypes() + { + $a = array('type' => 'enum', 'fixed' => false, 'values' => array('a', 'b', 'c')); + + // Native ENUM type disabled, should be VARCHAR + $this->assertEqual($this->dataDict->getNativeDeclaration($a), 'VARCHAR(1)'); + + // Native ENUM type still disabled, should still be VARCHAR + // this test is here because there was an issue where SET type was used if the ATTR_USE_NATIVE_SET setting + // was enabled but the ENUM one was not (due to an intentional case fall-through) + $this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET, true); + $this->assertEqual($this->dataDict->getNativeDeclaration($a), 'VARCHAR(1)'); + + // Native type enabled + $this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, true); + $this->assertEqual($this->dataDict->getNativeDeclaration($a), "ENUM('a', 'b', 'c')"); + } + + public function testGetNativeDefinitionSupportsSetTypes() + { + $a = array('type' => 'set', 'fixed' => false, 'values' => array('a', 'b', 'c')); + + // Native SET type disabled, should be VARCHAR + $this->assertEqual($this->dataDict->getNativeDeclaration($a), 'VARCHAR(5)'); + + // Enabling ENUM native type should have no effect on SET + $this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, true); + $this->assertEqual($this->dataDict->getNativeDeclaration($a), 'VARCHAR(5)'); + + // Native type enabled + $this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET, true); + $this->assertEqual($this->dataDict->getNativeDeclaration($a), "SET('a', 'b', 'c')"); + } + public function testGetNativeDefinitionSupportsIntegerType() { $a = array('type' => 'integer', 'length' => 20, 'fixed' => false); diff --git a/tests/DoctrineTest/Doctrine_UnitTestCase.php b/tests/DoctrineTest/Doctrine_UnitTestCase.php index a961b5940..a25d3ff78 100644 --- a/tests/DoctrineTest/Doctrine_UnitTestCase.php +++ b/tests/DoctrineTest/Doctrine_UnitTestCase.php @@ -283,6 +283,9 @@ public function setUp() } $this->init = true; + + $this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET, false); + $this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, false); } public function tearDown() diff --git a/tests/Export/MysqlTestCase.php b/tests/Export/MysqlTestCase.php index 1f65d47ae..1f76279c5 100644 --- a/tests/Export/MysqlTestCase.php +++ b/tests/Export/MysqlTestCase.php @@ -35,6 +35,7 @@ class Doctrine_Export_Mysql_TestCase extends Doctrine_UnitTestCase public function prepareTables() { } + public function prepareData() { } @@ -49,6 +50,7 @@ public function testAlterTableThrowsExceptionWithoutValidTableName() $this->pass(); } } + public function testCreateTableExecutesSql() { $name = 'mytable'; @@ -60,6 +62,7 @@ public function testCreateTableExecutesSql() $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (id INT UNSIGNED) ENGINE = MYISAM'); } + public function testCreateTableSupportsDefaultTableType() { $name = 'mytable'; @@ -71,6 +74,7 @@ public function testCreateTableSupportsDefaultTableType() // INNODB is the default type $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (id INT UNSIGNED) ENGINE = INNODB'); } + public function testCreateTableSupportsMultiplePks() { $name = 'mytable'; @@ -82,6 +86,7 @@ public function testCreateTableSupportsMultiplePks() $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (name CHAR(10), type MEDIUMINT, PRIMARY KEY(name, type)) ENGINE = INNODB'); } + public function testCreateTableSupportsAutoincPks() { $name = 'mytable'; @@ -94,6 +99,7 @@ public function testCreateTableSupportsAutoincPks() $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (id INT UNSIGNED AUTO_INCREMENT, PRIMARY KEY(id)) ENGINE = INNODB'); } + public function testCreateTableSupportsCharType() { $name = 'mytable'; @@ -105,6 +111,7 @@ public function testCreateTableSupportsCharType() $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (id CHAR(3)) ENGINE = MYISAM'); } + public function testCreateTableSupportsCharType2() { $name = 'mytable'; @@ -116,6 +123,7 @@ public function testCreateTableSupportsCharType2() $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (id CHAR(255)) ENGINE = MYISAM'); } + public function testCreateTableSupportsVarcharType() { $name = 'mytable'; @@ -127,6 +135,7 @@ public function testCreateTableSupportsVarcharType() $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (id VARCHAR(100)) ENGINE = MYISAM'); } + public function testCreateTableSupportsIntegerType() { $name = 'mytable'; @@ -138,6 +147,7 @@ public function testCreateTableSupportsIntegerType() $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (id BIGINT) ENGINE = MYISAM'); } + public function testCreateTableSupportsBlobType() { $name = 'mytable'; @@ -149,6 +159,7 @@ public function testCreateTableSupportsBlobType() $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (content LONGBLOB) ENGINE = MYISAM'); } + public function testCreateTableSupportsBlobType2() { $name = 'mytable'; @@ -172,6 +183,72 @@ public function testCreateTableSupportsBooleanType() $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (id TINYINT(1)) ENGINE = MYISAM'); } + + public function testCreateTableSupportsEnumType() + { + $name = 'mytable'; + + $fields = array( + 'letter' => array( + 'type' => 'enum', + 'values' => array('a', 'b', 'c'), + 'default' => 'a', + 'notnull' => true, + 'length' => '1', + ) + ); + + $options = array('type' => 'MYISAM'); + + $this->export->createTable($name, $fields, $options); + + // Native enum support not enabled, should be VARCHAR + $this->assertEqual( + $this->adapter->pop(), + "CREATE TABLE mytable (letter VARCHAR(1) DEFAULT 'a' NOT NULL) ENGINE = MYISAM" + ); + + $this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, true); + $this->export->createTable($name, $fields, $options); + + $this->assertEqual( + $this->adapter->pop(), + "CREATE TABLE mytable (letter ENUM('a', 'b', 'c') DEFAULT 'a' NOT NULL) ENGINE = MYISAM" + ); + } + + public function testCreateTableSupportsSetType() + { + $name = 'mytable'; + + $fields = array( + 'letter' => array( + 'type' => 'set', + 'values' => array('a', 'b', 'c'), + 'default' => 'a', + 'notnull' => true, + ) + ); + + $options = array('type' => 'MYISAM'); + + $this->export->createTable($name, $fields, $options); + + // Native Set not enabled, should be VARCHAR + $this->assertEqual( + $this->adapter->pop(), + "CREATE TABLE mytable (letter VARCHAR(5) DEFAULT 'a' NOT NULL) ENGINE = MYISAM" + ); + + $this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET, true); + $this->export->createTable($name, $fields, $options); + + $this->assertEqual( + $this->adapter->pop(), + "CREATE TABLE mytable (letter SET('a', 'b', 'c') DEFAULT 'a' NOT NULL) ENGINE = MYISAM" + ); + } + public function testCreateTableSupportsForeignKeys() { $name = 'mytable'; @@ -191,6 +268,7 @@ public function testCreateTableSupportsForeignKeys() $this->assertEqual($sql[0], 'CREATE TABLE mytable (id TINYINT(1), foreignKey INT, INDEX foreignKey_idx (foreignKey)) ENGINE = INNODB'); $this->assertEqual($sql[1], 'ALTER TABLE mytable ADD FOREIGN KEY (foreignKey) REFERENCES sometable(id)'); } + public function testForeignKeyIdentifierQuoting() { $this->conn->setAttribute(Doctrine_Core::ATTR_QUOTE_IDENTIFIER, true); @@ -214,6 +292,7 @@ public function testForeignKeyIdentifierQuoting() $this->conn->setAttribute(Doctrine_Core::ATTR_QUOTE_IDENTIFIER, false); } + public function testIndexIdentifierQuoting() { $this->conn->setAttribute(Doctrine_Core::ATTR_QUOTE_IDENTIFIER, true); @@ -236,6 +315,7 @@ public function testIndexIdentifierQuoting() $this->conn->setAttribute(Doctrine_Core::ATTR_QUOTE_IDENTIFIER, false); } + public function testCreateTableDoesNotAutoAddIndexesWhenIndexForFkFieldAlreadyExists() { $name = 'mytable'; @@ -255,12 +335,14 @@ public function testCreateTableDoesNotAutoAddIndexesWhenIndexForFkFieldAlreadyEx $this->assertEqual($sql[0], 'CREATE TABLE mytable (id TINYINT(1), foreignKey INT, INDEX myindex_idx (foreignKey)) ENGINE = INNODB'); $this->assertEqual($sql[1], 'ALTER TABLE mytable ADD FOREIGN KEY (foreignKey) REFERENCES sometable(id)'); } + public function testCreateDatabaseExecutesSql() { $this->export->createDatabase('db'); $this->assertEqual($this->adapter->pop(), 'CREATE DATABASE db'); } + public function testDropDatabaseExecutesSql() { $this->export->dropDatabase('db'); @@ -276,6 +358,7 @@ public function testDropIndexExecutesSql() $this->assertEqual($this->adapter->pop(), 'DROP INDEX relevancy_idx ON sometable'); } + public function testUnknownIndexSortingAttributeThrowsException() { $fields = array('id' => array('sorting' => 'ASC'), @@ -288,6 +371,7 @@ public function testUnknownIndexSortingAttributeThrowsException() $this->pass(); } } + public function testIndexDeclarationsSupportSortingAndLengthAttributes() { $fields = array('id' => array('sorting' => 'ASC', 'length' => 10), @@ -295,6 +379,7 @@ public function testIndexDeclarationsSupportSortingAndLengthAttributes() $this->assertEqual($this->export->getIndexFieldDeclarationList($fields), 'id(10) ASC, name(1) DESC'); } + public function testCreateTableSupportsIndexesUsingSingleFieldString() { $fields = array('id' => array('type' => 'integer', 'unsigned' => 1, 'autoincrement' => true), @@ -309,6 +394,7 @@ public function testCreateTableSupportsIndexesUsingSingleFieldString() $this->export->createTable('sometable', $fields, $options); $this->assertEqual($this->adapter->pop(), 'CREATE TABLE sometable (id INT UNSIGNED AUTO_INCREMENT, name VARCHAR(4), INDEX myindex_idx (name), PRIMARY KEY(id)) ENGINE = INNODB'); } + public function testCreateTableSupportsIndexesWithCustomSorting() { $fields = array('id' => array('type' => 'integer', 'unsigned' => 1, 'autoincrement' => true), @@ -348,6 +434,7 @@ public function testCreateTableSupportsFulltextIndexes() $this->assertEqual($this->adapter->pop(), 'CREATE TABLE sometable (id INT UNSIGNED AUTO_INCREMENT, content VARCHAR(4), FULLTEXT INDEX myindex_idx (content DESC), PRIMARY KEY(id)) ENGINE = MYISAM'); } + public function testCreateTableSupportsCompoundForeignKeys() { $name = 'mytable'; @@ -366,6 +453,7 @@ public function testCreateTableSupportsCompoundForeignKeys() $this->assertEqual($sql[0], 'CREATE TABLE mytable (id TINYINT(1), lang INT, INDEX id_idx (id), INDEX lang_idx (lang)) ENGINE = INNODB'); $this->assertEqual($sql[1], 'ALTER TABLE mytable ADD FOREIGN KEY (id, lang) REFERENCES sometable(id, lang)'); } + public function testCreateTableSupportsFieldCharset() { $sql = $this->export->createTableSql('mytable', array( @@ -374,6 +462,7 @@ public function testCreateTableSupportsFieldCharset() $this->assertEqual($sql[0], 'CREATE TABLE mytable (name VARCHAR(255) CHARACTER SET utf8) ENGINE = INNODB'); } + public function testCreateTableSupportsFieldCollation() { $sql = $this->export->createTableSql('mytable', array(