Completed
Push — master ( c7757e...39cb21 )
by Luís
16s
created

Doctrine/DBAL/Schema/SQLServerSchemaManager.php (1 issue)

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)
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
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)
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