Passed
Pull Request — master (#2920)
by Luís
09:30
created

SQLServerSchemaManager   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 272
Duplicated Lines 13.6 %

Test Coverage

Coverage 24.78%

Importance

Changes 0
Metric Value
wmc 45
dl 37
loc 272
ccs 28
cts 113
cp 0.2478
rs 8.3673
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A _getPortableTableDefinition() 0 3 1
A closeActiveDatabaseConnections() 0 8 1
A _getPortableTableForeignKeysList() 0 23 3
F _getPortableTableColumnDefinition() 3 61 18
A _getPortableTableForeignKeyDefinition() 8 8 1
B dropDatabase() 22 22 4
A _getPortableSequenceDefinition() 0 3 1
A _getPortableTableIndexesList() 0 9 3
A getPortableNamespaceDefinition() 0 3 1
A alterTable() 0 12 4
A _getPortableDatabaseDefinition() 0 3 1
A _getPortableViewDefinition() 0 4 1
B listTableIndexes() 0 21 5
A getColumnConstraintSQL() 0 8 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SQLServerSchemaManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SQLServerSchemaManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Schema;
21
22
use Doctrine\DBAL\DBALException;
23
use Doctrine\DBAL\Driver\DriverException;
24
use Doctrine\DBAL\Types\Type;
25
26
/**
27
 * SQL Server Schema Manager.
28
 *
29
 * @license http://www.opensource.org/licenses/mit-license.php MIT
30
 * @author  Konsta Vesterinen <[email protected]>
31
 * @author  Lukas Smith <[email protected]> (PEAR MDB2 library)
32
 * @author  Juozas Kaziukenas <[email protected]>
33
 * @author  Steve Müller <[email protected]>
34
 * @since   2.0
35
 */
36
class SQLServerSchemaManager extends AbstractSchemaManager
37
{
38
    /**
39
     * {@inheritdoc}
40
     */
41 View Code Duplication
    public function dropDatabase($database)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
42
    {
43
        try {
44
            parent::dropDatabase($database);
45
        } catch (DBALException $exception) {
46
            $exception = $exception->getPrevious();
47
48
            if (! $exception instanceof DriverException) {
49
                throw $exception;
50
            }
51
52
            // If we have a error code 3702, the drop database operation failed
53
            // because of active connections on the database.
54
            // To force dropping the database, we first have to close all active connections
55
            // on that database and issue the drop database operation again.
56
            if ($exception->getErrorCode() !== 3702) {
57
                throw $exception;
58
            }
59
60
            $this->closeActiveDatabaseConnections($database);
61
62
            parent::dropDatabase($database);
63
        }
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69
    protected function _getPortableSequenceDefinition($sequence)
70
    {
71
        return new Sequence($sequence['name'], $sequence['increment'], $sequence['start_value']);
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77 1
    protected function _getPortableTableColumnDefinition($tableColumn)
78
    {
79 1
        $dbType = strtok($tableColumn['type'], '(), ');
80 1
        $fixed = null;
81 1
        $length = (int) $tableColumn['length'];
82 1
        $default = $tableColumn['default'];
83
84 1
        if (!isset($tableColumn['name'])) {
85 1
            $tableColumn['name'] = '';
86
        }
87
88 1
        while ($default != ($default2 = preg_replace("/^\((.*)\)$/", '$1', $default))) {
89
            $default = trim($default2, "'");
90
91
            if ($default == 'getdate()') {
92
                $default = $this->_platform->getCurrentTimestampSQL();
93
            }
94
        }
95
96
        switch ($dbType) {
97 1
            case 'nchar':
98 1
            case 'nvarchar':
99 1
            case 'ntext':
100
                // Unicode data requires 2 bytes per character
101
                $length = $length / 2;
102
                break;
103 1
            case 'varchar':
104
                // TEXT type is returned as VARCHAR(MAX) with a length of -1
105
                if ($length == -1) {
106
                    $dbType = 'text';
107
                }
108
                break;
109
        }
110
111 1
        if ('char' === $dbType || 'nchar' === $dbType || 'binary' === $dbType) {
112
            $fixed = true;
113
        }
114
115 1
        $type                   = $this->_platform->getDoctrineTypeMapping($dbType);
116 1
        $type                   = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
117 1
        $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
118
119
        $options = [
120 1
            'length'        => ($length == 0 || !in_array($type, ['text', 'string'])) ? null : $length,
121
            'unsigned'      => false,
122 1
            'fixed'         => (bool) $fixed,
123 1
            'default'       => $default !== 'NULL' ? $default : null,
124 1
            'notnull'       => (bool) $tableColumn['notnull'],
125 1
            'scale'         => $tableColumn['scale'],
126 1
            'precision'     => $tableColumn['precision'],
127 1
            'autoincrement' => (bool) $tableColumn['autoincrement'],
128 1
            'comment'       => $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null,
129
        ];
130
131 1
        $column = new Column($tableColumn['name'], Type::getType($type), $options);
132
133 1 View Code Duplication
        if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
134 1
            $column->setPlatformOption('collation', $tableColumn['collation']);
135
        }
136
137 1
        return $column;
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
144
    {
145
        $foreignKeys = [];
146
147
        foreach ($tableForeignKeys as $tableForeignKey) {
148
            if ( ! isset($foreignKeys[$tableForeignKey['ForeignKey']])) {
149
                $foreignKeys[$tableForeignKey['ForeignKey']] = [
150
                    'local_columns' => [$tableForeignKey['ColumnName']],
151
                    'foreign_table' => $tableForeignKey['ReferenceTableName'],
152
                    'foreign_columns' => [$tableForeignKey['ReferenceColumnName']],
153
                    'name' => $tableForeignKey['ForeignKey'],
154
                    'options' => [
155
                        'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']),
156
                        'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc'])
157
                    ]
158
                ];
159
            } else {
160
                $foreignKeys[$tableForeignKey['ForeignKey']]['local_columns'][] = $tableForeignKey['ColumnName'];
161
                $foreignKeys[$tableForeignKey['ForeignKey']]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName'];
162
            }
163
        }
164
165
        return parent::_getPortableTableForeignKeysList($foreignKeys);
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171
    protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null)
172
    {
173
        foreach ($tableIndexRows as &$tableIndex) {
174
            $tableIndex['non_unique'] = (boolean) $tableIndex['non_unique'];
175
            $tableIndex['primary'] = (boolean) $tableIndex['primary'];
176
            $tableIndex['flags'] = $tableIndex['flags'] ? [$tableIndex['flags']] : null;
177
        }
178
179
        return parent::_getPortableTableIndexesList($tableIndexRows, $tableName);
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185 View Code Duplication
    protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
186
    {
187
        return new ForeignKeyConstraint(
188
            $tableForeignKey['local_columns'],
189
            $tableForeignKey['foreign_table'],
190
            $tableForeignKey['foreign_columns'],
191
            $tableForeignKey['name'],
192
            $tableForeignKey['options']
193
        );
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    protected function _getPortableTableDefinition($table)
200
    {
201
        return $table['name'];
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    protected function _getPortableDatabaseDefinition($database)
208
    {
209
        return $database['name'];
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    protected function getPortableNamespaceDefinition(array $namespace)
216
    {
217
        return $namespace['name'];
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223
    protected function _getPortableViewDefinition($view)
224
    {
225
        // @todo
226
        return new View($view['name'], null);
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232
    public function listTableIndexes($table)
233
    {
234
        $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase());
235
236
        try {
237
            $tableIndexes = $this->_conn->fetchAll($sql);
238
        } catch (\PDOException $e) {
239
            if ($e->getCode() == "IMSSP") {
240
                return [];
241
            } else {
242
                throw $e;
243
            }
244
        } catch (DBALException $e) {
245
            if (strpos($e->getMessage(), 'SQLSTATE [01000, 15472]') === 0) {
246
                return [];
247
            } else {
248
                throw $e;
249
            }
250
        }
251
252
        return $this->_getPortableTableIndexesList($tableIndexes, $table);
253
    }
254
255
    /**
256
     * {@inheritdoc}
257
     */
258
    public function alterTable(TableDiff $tableDiff)
259
    {
260
        if (count($tableDiff->removedColumns) > 0) {
261
            foreach ($tableDiff->removedColumns as $col) {
262
                $columnConstraintSql = $this->getColumnConstraintSQL($tableDiff->name, $col->getName());
263
                foreach ($this->_conn->fetchAll($columnConstraintSql) as $constraint) {
264
                    $this->_conn->exec("ALTER TABLE $tableDiff->name DROP CONSTRAINT " . $constraint['Name']);
265
                }
266
            }
267
        }
268
269
        parent::alterTable($tableDiff);
270
    }
271
272
    /**
273
     * Returns the SQL to retrieve the constraints for a given column.
274
     *
275
     * @param string $table
276
     * @param string $column
277
     *
278
     * @return string
279
     */
280
    private function getColumnConstraintSQL($table, $column)
281
    {
282
        return "SELECT SysObjects.[Name]
283
            FROM SysObjects INNER JOIN (SELECT [Name],[ID] FROM SysObjects WHERE XType = 'U') AS Tab
284
            ON Tab.[ID] = Sysobjects.[Parent_Obj]
285
            INNER JOIN sys.default_constraints DefCons ON DefCons.[object_id] = Sysobjects.[ID]
286
            INNER JOIN SysColumns Col ON Col.[ColID] = DefCons.[parent_column_id] AND Col.[ID] = Tab.[ID]
287
            WHERE Col.[Name] = " . $this->_conn->quote($column) ." AND Tab.[Name] = " . $this->_conn->quote($table) . "
288
            ORDER BY Col.[Name]";
289
    }
290
291
    /**
292
     * Closes currently active connections on the given database.
293
     *
294
     * This is useful to force DROP DATABASE operations which could fail because of active connections.
295
     *
296
     * @param string $database The name of the database to close currently active connections for.
297
     *
298
     * @return void
299
     */
300
    private function closeActiveDatabaseConnections($database)
301
    {
302
        $database = new Identifier($database);
303
304
        $this->_execSql(
305
            sprintf(
306
                'ALTER DATABASE %s SET SINGLE_USER WITH ROLLBACK IMMEDIATE',
307
                $database->getQuotedName($this->_platform)
308
            )
309
        );
310
    }
311
}
312