Skip to content

Commit

Permalink
fixing mysql set and enum types for import/export (#54)
Browse files Browse the repository at this point in the history
* fixing mysql set and enum types for import/export

* fixing length for SET types when non-native handling is used

* fixing phpstan complaint

* fixing length validation of set type, adding array string as type of set columns in generated models
  • Loading branch information
jaydiablo authored Jun 28, 2019
1 parent 409d5b2 commit b6aa061
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 24 deletions.
50 changes: 28 additions & 22 deletions lib/Doctrine/DataDict/Mysql.php
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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';
Expand Down
3 changes: 3 additions & 0 deletions lib/Doctrine/Import/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down
2 changes: 1 addition & 1 deletion lib/Doctrine/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
103 changes: 102 additions & 1 deletion tests/DataDict/MysqlTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions tests/DoctrineTest/Doctrine_UnitTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading

0 comments on commit b6aa061

Please sign in to comment.