Skip to content

Commit b6aa061

Browse files
authored
fixing mysql set and enum types for import/export (#54)
* 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
1 parent 409d5b2 commit b6aa061

File tree

6 files changed

+226
-24
lines changed

6 files changed

+226
-24
lines changed

lib/Doctrine/DataDict/Mysql.php

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -146,25 +146,25 @@ public function getNativeDeclaration($field)
146146

147147
return $length ? 'CHAR(' . $length . ')' : 'CHAR(255)';
148148
case 'enum':
149-
if ($this->conn->getAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM)) {
150-
$values = array();
151-
foreach ($field['values'] as $value) {
152-
$values[] = $this->conn->quote($value, 'varchar');
153-
}
154-
return 'ENUM(' . implode(', ', $values) . ')';
155-
} else {
156-
$field['length'] = isset($field['length']) && $field['length'] ? $field['length']:255;
157-
}
158-
// no break
159149
case 'set':
160-
if ($this->conn->getAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET)) {
150+
if (($this->conn->getAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET) && $field['type'] === 'set') ||
151+
($this->conn->getAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM) && $field['type'] === 'enum')
152+
) {
161153
$values = array();
162154
foreach ($field['values'] as $value) {
163155
$values[] = $this->conn->quote($value, 'varchar');
164156
}
165-
return 'SET(' . implode(', ', $values) . ')';
157+
return strtoupper($field['type']) . '(' . implode(', ', $values) . ')';
166158
} else {
167-
$field['length'] = isset($field['length']) && $field['length'] ? $field['length']:255;
159+
if ($field['type'] === 'enum' && !empty($field['values'])) {
160+
$length = max(array_map('strlen', $field['values']));
161+
} elseif ($field['type'] === 'set' && !empty($field['values'])) {
162+
$length = strlen(implode(',', $field['values']));
163+
} else {
164+
$length = isset($field['length']) && $field['length'] ? $field['length']:255;
165+
}
166+
167+
$field['length'] = $length;
168168
}
169169
// no break
170170
case 'varchar':
@@ -253,7 +253,7 @@ public function getNativeDeclaration($field)
253253
}
254254

255255
/**
256-
* Maps a native array description of a field to a MDB2 datatype and length
256+
* Maps a native array description of a field to a Mysql datatype and length
257257
*
258258
* @param array $field native field description
259259
* @return array containing the various possible types, length, sign, fixed
@@ -279,7 +279,13 @@ public function getPortableDeclaration(array $field)
279279
$unsigned = $fixed = null;
280280

281281
if (! isset($field['name'])) {
282-
$field['name'] = '';
282+
// Mysql's DESCRIBE returns a "Field" column, not a "Name" column
283+
// this method is called with output from that query in Doctrine_Import_Mysql::listTableColumns
284+
if (isset($field['field'])) {
285+
$field['name'] = $field['field'];
286+
} else {
287+
$field['name'] = '';
288+
}
283289
}
284290

285291
$values = null;
@@ -343,7 +349,8 @@ public function getPortableDeclaration(array $field)
343349
}
344350
break;
345351
case 'enum':
346-
$type[] = 'enum';
352+
case 'set':
353+
$type[] = $dbType;
347354
preg_match_all('/\'((?:\'\'|[^\'])*)\'/', $field['type'], $matches);
348355
$length = 0;
349356
$fixed = false;
@@ -352,21 +359,20 @@ public function getPortableDeclaration(array $field)
352359
$value = str_replace('\'\'', '\'', $value);
353360
$length = max($length, strlen($value));
354361
}
355-
if ($length == '1' && count($matches[1]) == 2) {
362+
if ($dbType === 'enum' && $length == '1' && count($matches[1]) == 2) {
356363
$type[] = 'boolean';
357364
if (preg_match('/^(is|has)/', $field['name'])) {
358365
$type = array_reverse($type);
359366
}
360367
}
361368

369+
if ($dbType === 'set') {
370+
$length = strlen(implode(',', $matches[1]));
371+
}
372+
362373
$values = $matches[1];
363374
}
364375
$type[] = 'integer';
365-
break;
366-
case 'set':
367-
$fixed = false;
368-
$type[] = 'text';
369-
$type[] = 'integer';
370376
break;
371377
case 'date':
372378
$type[] = 'date';

lib/Doctrine/Import/Builder.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,9 @@ public function buildPhpDocs(array $definition)
695695
case 'decimal':
696696
$type = 'float';
697697
break;
698+
case 'set':
699+
$type = 'string[]';
700+
break;
698701
case 'blob':
699702
case 'clob':
700703
case 'timestamp':

lib/Doctrine/Validator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public static function validateLength($value, $type, $maximumLength)
9797
if ($maximumLength === null) {
9898
return true;
9999
}
100-
if ($type == 'timestamp' || $type == 'integer' || $type == 'enum') {
100+
if ($type === 'timestamp' || $type === 'integer' || $type === 'enum' || $type === 'set') {
101101
return true;
102102
} elseif ($type == 'array' || $type == 'object') {
103103
$length = strlen(serialize($value));

tests/DataDict/MysqlTestCase.php

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,19 @@ public function testGetPortableDeclarationSupportsNativeIntegerTypes()
4646
{
4747
$type = $this->dataDict->getPortableDeclaration(array('type' => 'tinyint'));
4848

49-
5049
$this->assertEqual($type, array('type' => array('integer', 'boolean'),
5150
'length' => 1,
5251
'unsigned' => null,
5352
'fixed' => null));
53+
54+
// If column name starts with "is" or "has" treat as a boolean
55+
$type = $this->dataDict->getPortableDeclaration(array('type' => 'tinyint', 'field' => 'isenabled'));
56+
57+
$this->assertEqual($type, array('type' => array('boolean', 'integer'),
58+
'length' => 1,
59+
'unsigned' => null,
60+
'fixed' => null));
61+
5462
$type = $this->dataDict->getPortableDeclaration(array('type' => 'smallint unsigned'));
5563

5664
$this->assertEqual($type, array('type' => array('integer'),
@@ -124,6 +132,13 @@ public function testGetPortableDeclarationSupportsNativeStringTypes()
124132
'unsigned' => null,
125133
'fixed' => true));
126134

135+
$type = $this->dataDict->getPortableDeclaration(array('type' => 'char(1)', 'field' => 'hascontent'));
136+
137+
$this->assertEqual($type, array('type' => array('boolean', 'string'),
138+
'length' => 1,
139+
'unsigned' => null,
140+
'fixed' => true));
141+
127142
$type = $this->dataDict->getPortableDeclaration(array('type' => 'varchar(1)'));
128143

129144
$this->assertEqual($type, array('type' => array('string', 'boolean'),
@@ -249,6 +264,92 @@ public function testGetPortableDeclarationSupportsNativeBlobTypes()
249264
'fixed' => null));
250265
}
251266

267+
public function testGetPortableDeclarationSupportsNativeEnumTypes()
268+
{
269+
$field = array(
270+
'field' => 'letter',
271+
'type' => "enum('a','b','c')",
272+
'null' => 'NO',
273+
'key' => '',
274+
'default' => 'a',
275+
'extra' => ''
276+
);
277+
278+
$type = $this->dataDict->getPortableDeclaration($field);
279+
280+
$this->assertEqual($type, array('type' => array('enum', 'integer'),
281+
'length' => 1,
282+
'unsigned' => null,
283+
'fixed' => false,
284+
'values' => array('a', 'b', 'c')));
285+
286+
$field['type'] = "set('a','b','c')";
287+
288+
$type = $this->dataDict->getPortableDeclaration($field);
289+
290+
$this->assertEqual($type, array('type' => array('set', 'integer'),
291+
'length' => 5,
292+
'unsigned' => null,
293+
'fixed' => false,
294+
'values' => array('a', 'b', 'c')));
295+
296+
// Custom "boolean" type when ENUM only has two values
297+
$field['type'] = "enum('y','n')";
298+
299+
$type = $this->dataDict->getPortableDeclaration($field);
300+
301+
$this->assertEqual($type, array('type' => array('enum', 'boolean', 'integer'),
302+
'length' => 1,
303+
'unsigned' => null,
304+
'fixed' => false,
305+
'values' => array('y', 'n')));
306+
307+
// Another special case where types are flipped when field name is "is" or "has"
308+
$field['field'] = 'isenabled';
309+
310+
$type = $this->dataDict->getPortableDeclaration($field);
311+
312+
$this->assertEqual($type, array('type' => array('boolean', 'enum', 'integer'),
313+
'length' => 1,
314+
'unsigned' => null,
315+
'fixed' => false,
316+
'values' => array('y', 'n')));
317+
}
318+
319+
public function testGetNativeDefinitionSupportsEnumTypes()
320+
{
321+
$a = array('type' => 'enum', 'fixed' => false, 'values' => array('a', 'b', 'c'));
322+
323+
// Native ENUM type disabled, should be VARCHAR
324+
$this->assertEqual($this->dataDict->getNativeDeclaration($a), 'VARCHAR(1)');
325+
326+
// Native ENUM type still disabled, should still be VARCHAR
327+
// this test is here because there was an issue where SET type was used if the ATTR_USE_NATIVE_SET setting
328+
// was enabled but the ENUM one was not (due to an intentional case fall-through)
329+
$this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET, true);
330+
$this->assertEqual($this->dataDict->getNativeDeclaration($a), 'VARCHAR(1)');
331+
332+
// Native type enabled
333+
$this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, true);
334+
$this->assertEqual($this->dataDict->getNativeDeclaration($a), "ENUM('a', 'b', 'c')");
335+
}
336+
337+
public function testGetNativeDefinitionSupportsSetTypes()
338+
{
339+
$a = array('type' => 'set', 'fixed' => false, 'values' => array('a', 'b', 'c'));
340+
341+
// Native SET type disabled, should be VARCHAR
342+
$this->assertEqual($this->dataDict->getNativeDeclaration($a), 'VARCHAR(5)');
343+
344+
// Enabling ENUM native type should have no effect on SET
345+
$this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, true);
346+
$this->assertEqual($this->dataDict->getNativeDeclaration($a), 'VARCHAR(5)');
347+
348+
// Native type enabled
349+
$this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET, true);
350+
$this->assertEqual($this->dataDict->getNativeDeclaration($a), "SET('a', 'b', 'c')");
351+
}
352+
252353
public function testGetNativeDefinitionSupportsIntegerType()
253354
{
254355
$a = array('type' => 'integer', 'length' => 20, 'fixed' => false);

tests/DoctrineTest/Doctrine_UnitTestCase.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,9 @@ public function setUp()
283283
}
284284

285285
$this->init = true;
286+
287+
$this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_SET, false);
288+
$this->conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, false);
286289
}
287290

288291
public function tearDown()

0 commit comments

Comments
 (0)