MySqlSchemaManager.php 10.6 KB
Newer Older
romanb's avatar
romanb committed
1 2
<?php

3
namespace Doctrine\DBAL\Schema;
romanb's avatar
romanb committed
4

5
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
6
use Doctrine\DBAL\Platforms\MySqlPlatform;
7
use Doctrine\DBAL\Types\Type;
8 9 10 11 12
use const CASE_LOWER;
use function array_change_key_case;
use function array_shift;
use function array_values;
use function end;
13
use function explode;
14 15 16 17 18 19 20
use function preg_match;
use function preg_replace;
use function str_replace;
use function stripslashes;
use function strpos;
use function strtok;
use function strtolower;
21
use function trim;
22

romanb's avatar
romanb committed
23
/**
24
 * Schema manager for the MySql RDBMS.
romanb's avatar
romanb committed
25
 */
26
class MySqlSchemaManager extends AbstractSchemaManager
27
{
Benjamin Morel's avatar
Benjamin Morel committed
28 29 30
    /**
     * {@inheritdoc}
     */
31 32
    protected function _getPortableViewDefinition($view)
    {
33
        return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']);
34 35
    }

Benjamin Morel's avatar
Benjamin Morel committed
36 37 38
    /**
     * {@inheritdoc}
     */
39 40
    protected function _getPortableTableDefinition($table)
    {
41
        return array_shift($table);
42 43
    }

Benjamin Morel's avatar
Benjamin Morel committed
44 45 46
    /**
     * {@inheritdoc}
     */
47 48
    protected function _getPortableUserDefinition($user)
    {
49
        return [
50 51
            'user' => $user['User'],
            'password' => $user['Password'],
52
        ];
53 54
    }

Benjamin Morel's avatar
Benjamin Morel committed
55 56 57
    /**
     * {@inheritdoc}
     */
belgattitude's avatar
belgattitude committed
58
    protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
59
    {
Steve Müller's avatar
Steve Müller committed
60
        foreach ($tableIndexes as $k => $v) {
61
            $v = array_change_key_case($v, CASE_LOWER);
62
            if ($v['key_name'] === 'PRIMARY') {
63 64 65 66
                $v['primary'] = true;
            } else {
                $v['primary'] = false;
            }
67
            if (strpos($v['index_type'], 'FULLTEXT') !== false) {
68
                $v['flags'] = ['FULLTEXT'];
69
            } elseif (strpos($v['index_type'], 'SPATIAL') !== false) {
70
                $v['flags'] = ['SPATIAL'];
71
            }
72 73
            $v['length'] = $v['sub_part'] ?? null;

74
            $tableIndexes[$k] = $v;
75
        }
76

77
        return parent::_getPortableTableIndexesList($tableIndexes, $tableName);
78 79
    }

Benjamin Morel's avatar
Benjamin Morel committed
80 81 82
    /**
     * {@inheritdoc}
     */
83 84 85 86 87
    protected function _getPortableSequenceDefinition($sequence)
    {
        return end($sequence);
    }

Benjamin Morel's avatar
Benjamin Morel committed
88 89 90
    /**
     * {@inheritdoc}
     */
91 92
    protected function _getPortableDatabaseDefinition($database)
    {
93
        return $database['Database'];
94
    }
95

96
    /**
Benjamin Morel's avatar
Benjamin Morel committed
97
     * {@inheritdoc}
98
     */
99 100
    protected function _getPortableTableColumnDefinition($tableColumn)
    {
101 102 103
        $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);

        $dbType = strtolower($tableColumn['type']);
104
        $dbType = strtok($dbType, '(), ');
105
        $length = $tableColumn['length'] ?? strtok('(), ');
Benjamin Morel's avatar
Benjamin Morel committed
106

107
        $fixed = null;
108

109
        if (! isset($tableColumn['name'])) {
110 111
            $tableColumn['name'] = '';
        }
112

belgattitude's avatar
belgattitude committed
113
        $scale     = null;
114
        $precision = null;
115

116
        $type = $this->_platform->getDoctrineTypeMapping($dbType);
117 118 119

        // In cases where not connected to a database DESCRIBE $table does not return 'Comment'
        if (isset($tableColumn['comment'])) {
belgattitude's avatar
belgattitude committed
120
            $type                   = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
121 122
            $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
        }
123

124 125
        switch ($dbType) {
            case 'char':
Steve Müller's avatar
Steve Müller committed
126
            case 'binary':
127
                $fixed = true;
128
                break;
129 130 131 132
            case 'float':
            case 'double':
            case 'real':
            case 'numeric':
133
            case 'decimal':
Steve Müller's avatar
Steve Müller committed
134
                if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['type'], $match)) {
135
                    $precision = $match[1];
belgattitude's avatar
belgattitude committed
136 137
                    $scale     = $match[2];
                    $length    = null;
138
                }
139
                break;
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
            case 'tinytext':
                $length = MySqlPlatform::LENGTH_LIMIT_TINYTEXT;
                break;
            case 'text':
                $length = MySqlPlatform::LENGTH_LIMIT_TEXT;
                break;
            case 'mediumtext':
                $length = MySqlPlatform::LENGTH_LIMIT_MEDIUMTEXT;
                break;
            case 'tinyblob':
                $length = MySqlPlatform::LENGTH_LIMIT_TINYBLOB;
                break;
            case 'blob':
                $length = MySqlPlatform::LENGTH_LIMIT_BLOB;
                break;
            case 'mediumblob':
                $length = MySqlPlatform::LENGTH_LIMIT_MEDIUMBLOB;
                break;
158 159 160 161 162 163
            case 'tinyint':
            case 'smallint':
            case 'mediumint':
            case 'int':
            case 'integer':
            case 'bigint':
164 165
            case 'year':
                $length = null;
166
                break;
167 168
        }

169
        if ($this->_platform instanceof MariaDb1027Platform) {
170
            $columnDefault = $this->getMariaDb1027ColumnDefault($this->_platform, $tableColumn['default']);
171
        } else {
172
            $columnDefault = $tableColumn['default'];
173
        }
174

175
        $options = [
belgattitude's avatar
belgattitude committed
176
            'length'        => $length !== null ? (int) $length : null,
177
            'unsigned'      => strpos($tableColumn['type'], 'unsigned') !== false,
178
            'fixed'         => (bool) $fixed,
179
            'default'       => $columnDefault,
belgattitude's avatar
belgattitude committed
180
            'notnull'       => $tableColumn['null'] !== 'YES',
181 182
            'scale'         => null,
            'precision'     => null,
183
            'autoincrement' => strpos($tableColumn['extra'], 'auto_increment') !== false,
184 185 186
            'comment'       => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
                ? $tableColumn['comment']
                : null,
187
        ];
188

189
        if ($scale !== null && $precision !== null) {
belgattitude's avatar
belgattitude committed
190
            $options['scale']     = (int) $scale;
191
            $options['precision'] = (int) $precision;
192 193
        }

194 195 196 197 198 199 200
        $column = new Column($tableColumn['field'], Type::getType($type), $options);

        if (isset($tableColumn['collation'])) {
            $column->setPlatformOption('collation', $tableColumn['collation']);
        }

        return $column;
201 202
    }

203
    /**
204
     * Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers.
205
     *
206
     * - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted
207
     *   to distinguish them from expressions (see MDEV-10134).
208 209
     * - CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE are stored in information_schema
     *   as current_timestamp(), currdate(), currtime()
210
     * - Quoted 'NULL' is not enforced by Maria, it is technically possible to have
211
     *   null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053)
212
     * - \' is always stored as '' in information_schema (normalized)
213 214 215 216
     *
     * @link https://mariadb.com/kb/en/library/information-schema-columns-table/
     * @link https://jira.mariadb.org/browse/MDEV-13132
     *
217
     * @param string|null $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7
218
     */
belgattitude's avatar
belgattitude committed
219 220
    private function getMariaDb1027ColumnDefault(MariaDb1027Platform $platform, ?string $columnDefault) : ?string
    {
221
        if ($columnDefault === 'NULL' || $columnDefault === null) {
222 223
            return null;
        }
224
        if ($columnDefault[0] === "'") {
225
            return stripslashes(
226 227 228
                str_replace(
                    "''",
                    "'",
229 230 231
                    preg_replace('/^\'(.*)\'$/', '$1', $columnDefault)
                )
            );
232
        }
belgattitude's avatar
belgattitude committed
233
        switch ($columnDefault) {
234 235 236 237 238 239 240 241
            case 'current_timestamp()':
                return $platform->getCurrentTimestampSQL();
            case 'curdate()':
                return $platform->getCurrentDateSQL();
            case 'curtime()':
                return $platform->getCurrentTimeSQL();
        }
        return $columnDefault;
242 243
    }

Benjamin Morel's avatar
Benjamin Morel committed
244 245 246
    /**
     * {@inheritdoc}
     */
247
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
romanb's avatar
romanb committed
248
    {
249
        $list = [];
Benjamin Morel's avatar
Benjamin Morel committed
250
        foreach ($tableForeignKeys as $value) {
251
            $value = array_change_key_case($value, CASE_LOWER);
252 253
            if (! isset($list[$value['constraint_name']])) {
                if (! isset($value['delete_rule']) || $value['delete_rule'] === 'RESTRICT') {
254 255
                    $value['delete_rule'] = null;
                }
256
                if (! isset($value['update_rule']) || $value['update_rule'] === 'RESTRICT') {
257 258
                    $value['update_rule'] = null;
                }
259

260
                $list[$value['constraint_name']] = [
261
                    'name' => $value['constraint_name'],
262 263
                    'local' => [],
                    'foreign' => [],
264 265 266
                    'foreignTable' => $value['referenced_table_name'],
                    'onDelete' => $value['delete_rule'],
                    'onUpdate' => $value['update_rule'],
267
                ];
268
            }
belgattitude's avatar
belgattitude committed
269
            $list[$value['constraint_name']]['local'][]   = $value['column_name'];
270
            $list[$value['constraint_name']]['foreign'][] = $value['referenced_column_name'];
271
        }
272

273
        $result = [];
Steve Müller's avatar
Steve Müller committed
274
        foreach ($list as $constraint) {
275
            $result[] = new ForeignKeyConstraint(
belgattitude's avatar
belgattitude committed
276 277 278 279
                array_values($constraint['local']),
                $constraint['foreignTable'],
                array_values($constraint['foreign']),
                $constraint['name'],
280
                [
281 282
                    'onDelete' => $constraint['onDelete'],
                    'onUpdate' => $constraint['onUpdate'],
283
                ]
284
            );
285
        }
286

287
        return $result;
romanb's avatar
romanb committed
288
    }
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328

    public function listTableDetails($tableName)
    {
        $table = parent::listTableDetails($tableName);

        /** @var MySqlPlatform $platform */
        $platform = $this->_platform;
        $sql      = $platform->getListTableMetadataSQL($tableName);

        $tableOptions = $this->_conn->fetchAssoc($sql);

        $table->addOption('engine', $tableOptions['ENGINE']);
        if ($tableOptions['TABLE_COLLATION'] !== null) {
            $table->addOption('collation', $tableOptions['TABLE_COLLATION']);
        }
        if ($tableOptions['AUTO_INCREMENT'] !== null) {
            $table->addOption('autoincrement', $tableOptions['AUTO_INCREMENT']);
        }
        $table->addOption('comment', $tableOptions['TABLE_COMMENT']);

        if ($tableOptions['CREATE_OPTIONS'] === null) {
            return $table;
        }

        $createOptionsString = trim($tableOptions['CREATE_OPTIONS']);

        $createOptions = [];

        if ($createOptionsString !== '') {
            foreach (explode(' ', $createOptionsString) as $option) {
                [$createOption, $value] = explode('=', $option);

                $createOptions[$createOption] = $value;
            }
        }

        $table->addOption('create_options', $createOptions);

        return $table;
    }
329
}