Passed
Pull Request — master (#3025)
by Michael
14:33
created

OracleSchemaManager   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 387
Duplicated Lines 0 %

Test Coverage

Coverage 87.11%

Importance

Changes 0
Metric Value
wmc 65
dl 0
loc 387
rs 3.2
c 0
b 0
f 0
ccs 169
cts 194
cp 0.8711

15 Methods

Rating   Name   Duplication   Size   Complexity  
A dropTable() 0 5 1
A createDatabase() 0 15 2
A dropDatabase() 0 22 4
A getQuotedIdentifierName() 0 7 2
A dropAutoincrement() 0 10 2
A _getPortableTableForeignKeysList() 0 36 5
A _getPortableTableIndexesList() 0 23 4
F _getPortableTableColumnDefinition() 0 115 37
A _getPortableSequenceDefinition() 0 8 1
A _getPortableViewDefinition() 0 5 1
A killUserSessions() 0 24 2
A _getPortableFunctionDefinition() 0 5 1
A _getPortableDatabaseDefinition() 0 5 1
A _getPortableUserDefinition() 0 6 1
A _getPortableTableDefinition() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like OracleSchemaManager 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 OracleSchemaManager, 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\Platforms\OraclePlatform;
25
use Doctrine\DBAL\Types\Type;
26
use const CASE_LOWER;
27
use function array_change_key_case;
28
use function array_values;
29
use function is_null;
30
use function preg_match;
31
use function sprintf;
32
use function strpos;
33
use function strtolower;
34
use function strtoupper;
35
use function trim;
36
37
/**
38
 * Oracle Schema Manager.
39
 *
40
 * @author Konsta Vesterinen <[email protected]>
41
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
42
 * @author Benjamin Eberlei <[email protected]>
43
 * @since  2.0
44
 */
45
class OracleSchemaManager extends AbstractSchemaManager
46
{
47
    /**
48
     * {@inheritdoc}
49
     */
50 2
    public function dropDatabase($database)
51
    {
52
        try {
53 2
            parent::dropDatabase($database);
54 2
        } catch (DBALException $exception) {
55 2
            $exception = $exception->getPrevious();
56
57 2
            if (! $exception instanceof DriverException) {
58
                throw $exception;
59
            }
60
61
            // If we have a error code 1940 (ORA-01940), the drop database operation failed
62
            // because of active connections on the database.
63
            // To force dropping the database, we first have to close all active connections
64
            // on that database and issue the drop database operation again.
65 2
            if ($exception->getErrorCode() !== 1940) {
66 2
                throw $exception;
67
            }
68
69 1
            $this->killUserSessions($database);
70
71 1
            parent::dropDatabase($database);
72
        }
73 1
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 1
    protected function _getPortableViewDefinition($view)
79
    {
80 1
        $view = \array_change_key_case($view, CASE_LOWER);
81
82 1
        return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']);
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88
    protected function _getPortableUserDefinition($user)
89
    {
90
        $user = \array_change_key_case($user, CASE_LOWER);
91
92
        return [
93
            'user' => $user['username'],
94
        ];
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 63
    protected function _getPortableTableDefinition($table)
101
    {
102 63
        $table = \array_change_key_case($table, CASE_LOWER);
103
104 63
        return $this->getQuotedIdentifierName($table['table_name']);
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     *
110
     * @license New BSD License
111
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
112
     */
113 26
    protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
114
    {
115 26
        $indexBuffer = [];
116 26
        foreach ($tableIndexes as $tableIndex) {
117 15
            $tableIndex = \array_change_key_case($tableIndex, CASE_LOWER);
118
119 15
            $keyName = strtolower($tableIndex['name']);
120 15
            $buffer  = [];
121
122 15
            if (strtolower($tableIndex['is_primary']) == "p") {
123 13
                $keyName = 'primary';
124 13
                $buffer['primary'] = true;
125 13
                $buffer['non_unique'] = false;
126
            } else {
127 12
                $buffer['primary'] = false;
128 12
                $buffer['non_unique'] = ($tableIndex['is_unique'] == 0) ? true : false;
129
            }
130 15
            $buffer['key_name'] = $keyName;
131 15
            $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']);
132 15
            $indexBuffer[] = $buffer;
133
        }
134
135 26
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141 33
    protected function _getPortableTableColumnDefinition($tableColumn)
142
    {
143 33
        $tableColumn = \array_change_key_case($tableColumn, CASE_LOWER);
144
145 33
        $dbType = strtolower($tableColumn['data_type']);
146 33
        if (strpos($dbType, "timestamp(") === 0) {
147 7
            if (strpos($dbType, "with time zone")) {
148 3
                $dbType = "timestamptz";
149
            } else {
150 7
                $dbType = "timestamp";
151
            }
152
        }
153
154 33
        $unsigned = $fixed = null;
155
156 33
        if ( ! isset($tableColumn['column_name'])) {
157
            $tableColumn['column_name'] = '';
158
        }
159
160
        // Default values returned from database sometimes have trailing spaces.
161 33
        $tableColumn['data_default'] = trim($tableColumn['data_default']);
162
163 33
        if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') {
164 32
            $tableColumn['data_default'] = null;
165
        }
166
167 33
        if (null !== $tableColumn['data_default']) {
168
            // Default values returned from database are enclosed in single quotes.
169 7
            $tableColumn['data_default'] = trim($tableColumn['data_default'], "'");
170
        }
171
172 33
        $precision = null;
173 33
        $scale = null;
174
175 33
        $type = $this->_platform->getDoctrineTypeMapping($dbType);
176 33
        $type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type);
177 33
        $tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type);
178
179 33
        switch ($dbType) {
180 33
            case 'number':
181 29
                if ($tableColumn['data_precision'] == 20 && $tableColumn['data_scale'] == 0) {
182
                    $precision = 20;
183
                    $scale = 0;
184
                    $type = 'bigint';
185 29
                } elseif ($tableColumn['data_precision'] == 5 && $tableColumn['data_scale'] == 0) {
186
                    $type = 'smallint';
187
                    $precision = 5;
188
                    $scale = 0;
189 29
                } elseif ($tableColumn['data_precision'] == 1 && $tableColumn['data_scale'] == 0) {
190
                    $precision = 1;
191
                    $scale = 0;
192
                    $type = 'boolean';
193 29
                } elseif ($tableColumn['data_scale'] > 0) {
194 5
                    $precision = $tableColumn['data_precision'];
195 5
                    $scale = $tableColumn['data_scale'];
196 5
                    $type = 'decimal';
197
                }
198 29
                $length = null;
199 29
                break;
200 19
            case 'pls_integer':
201 19
            case 'binary_integer':
202
                $length = null;
203
                break;
204 19
            case 'varchar':
205 19
            case 'varchar2':
206 14
            case 'nvarchar2':
207 11
                $length = $tableColumn['char_length'];
208 11
                $fixed = false;
209 11
                break;
210 14
            case 'char':
211 12
            case 'nchar':
212 5
                $length = $tableColumn['char_length'];
213 5
                $fixed = true;
214 5
                break;
215 12
            case 'date':
216 12
            case 'timestamp':
217 7
                $length = null;
218 7
                break;
219 12
            case 'float':
220 12
            case 'binary_float':
221 12
            case 'binary_double':
222
                $precision = $tableColumn['data_precision'];
223
                $scale = $tableColumn['data_scale'];
224
                $length = null;
225
                break;
226 12
            case 'clob':
227 6
            case 'nclob':
228 9
                $length = null;
229 9
                break;
230 6
            case 'blob':
231 4
            case 'raw':
232 3
            case 'long raw':
233 3
            case 'bfile':
234 5
                $length = null;
235 5
                break;
236 3
            case 'rowid':
237 3
            case 'urowid':
238
            default:
239 3
                $length = null;
240
        }
241
242
        $options = [
243 33
            'notnull'    => (bool) ($tableColumn['nullable'] === 'N'),
244 33
            'fixed'      => (bool) $fixed,
245 33
            'unsigned'   => (bool) $unsigned,
246 33
            'default'    => $tableColumn['data_default'],
247 33
            'length'     => $length,
248 33
            'precision'  => $precision,
249 33
            'scale'      => $scale,
250 33
            'comment'    => isset($tableColumn['comments']) && '' !== $tableColumn['comments']
251 17
                ? $tableColumn['comments']
252
                : null,
253
        ];
254
255 33
        return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options);
256
    }
257
258
    /**
259
     * {@inheritdoc}
260
     */
261 23
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
262
    {
263 23
        $list = [];
264 23
        foreach ($tableForeignKeys as $value) {
265 9
            $value = \array_change_key_case($value, CASE_LOWER);
266 9
            if (!isset($list[$value['constraint_name']])) {
267 9
                if ($value['delete_rule'] == "NO ACTION") {
268 8
                    $value['delete_rule'] = null;
269
                }
270
271 9
                $list[$value['constraint_name']] = [
272 9
                    'name' => $this->getQuotedIdentifierName($value['constraint_name']),
273
                    'local' => [],
274
                    'foreign' => [],
275 9
                    'foreignTable' => $value['references_table'],
276 9
                    'onDelete' => $value['delete_rule'],
277
                ];
278
            }
279
280 9
            $localColumn = $this->getQuotedIdentifierName($value['local_column']);
281 9
            $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']);
282
283 9
            $list[$value['constraint_name']]['local'][$value['position']] = $localColumn;
284 9
            $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn;
285
        }
286
287 23
        $result = [];
288 23
        foreach ($list as $constraint) {
289 9
            $result[] = new ForeignKeyConstraint(
290 9
                array_values($constraint['local']), $this->getQuotedIdentifierName($constraint['foreignTable']),
291 9
                array_values($constraint['foreign']), $this->getQuotedIdentifierName($constraint['name']),
292 9
                ['onDelete' => $constraint['onDelete']]
293
            );
294
        }
295
296 23
        return $result;
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302 5
    protected function _getPortableSequenceDefinition($sequence)
303
    {
304 5
        $sequence = \array_change_key_case($sequence, CASE_LOWER);
305
306 5
        return new Sequence(
307 5
            $this->getQuotedIdentifierName($sequence['sequence_name']),
308 5
            (int) $sequence['increment_by'],
309 5
            (int) $sequence['min_value']
310
        );
311
    }
312
313
    /**
314
     * {@inheritdoc}
315
     */
316
    protected function _getPortableFunctionDefinition($function)
317
    {
318
        $function = \array_change_key_case($function, CASE_LOWER);
319
320
        return $function['name'];
321
    }
322
323
    /**
324
     * {@inheritdoc}
325
     */
326 2
    protected function _getPortableDatabaseDefinition($database)
327
    {
328 2
        $database = \array_change_key_case($database, CASE_LOWER);
329
330 2
        return $database['username'];
331
    }
332
333
    /**
334
     * {@inheritdoc}
335
     */
336 2
    public function createDatabase($database = null)
337
    {
338 2
        if (is_null($database)) {
339
            $database = $this->_conn->getDatabase();
340
        }
341
342 2
        $params = $this->_conn->getParams();
343 2
        $username   = $database;
344 2
        $password   = $params['password'];
345
346 2
        $query  = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password;
347 2
        $this->_conn->executeUpdate($query);
348
349 2
        $query = 'GRANT DBA TO ' . $username;
350 2
        $this->_conn->executeUpdate($query);
351 2
    }
352
353
    /**
354
     * @param string $table
355
     *
356
     * @return bool
357
     */
358 96
    public function dropAutoincrement($table)
359
    {
360 96
        assert($this->_platform instanceof OraclePlatform);
361
362 96
        $sql = $this->_platform->getDropAutoincrementSql($table);
363 96
        foreach ($sql as $query) {
364 96
            $this->_conn->executeUpdate($query);
365
        }
366
367
        return true;
368
    }
369
370
    /**
371
     * {@inheritdoc}
372
     */
373 96
    public function dropTable($name)
374
    {
375 96
        $this->tryMethod('dropAutoincrement', $name);
376
377 96
        parent::dropTable($name);
378 50
    }
379
380
    /**
381
     * Returns the quoted representation of the given identifier name.
382
     *
383
     * Quotes non-uppercase identifiers explicitly to preserve case
384
     * and thus make references to the particular identifier work.
385
     *
386
     * @param string $identifier The identifier to quote.
387
     *
388
     * @return string The quoted identifier.
389
     */
390 64
    private function getQuotedIdentifierName($identifier)
391
    {
392 64
        if (preg_match('/[a-z]/', $identifier)) {
393 52
            return $this->_platform->quoteIdentifier($identifier);
394
        }
395
396 64
        return $identifier;
397
    }
398
399
    /**
400
     * Kills sessions connected with the given user.
401
     *
402
     * This is useful to force DROP USER operations which could fail because of active user sessions.
403
     *
404
     * @param string $user The name of the user to kill sessions for.
405
     *
406
     * @return void
407
     */
408 1
    private function killUserSessions($user)
409
    {
410
        $sql = <<<SQL
411 1
SELECT
412
    s.sid,
413
    s.serial#
414
FROM
415
    gv\$session s,
416
    gv\$process p
417
WHERE
418
    s.username = ?
419
    AND p.addr(+) = s.paddr
420
SQL;
421
422 1
        $activeUserSessions = $this->_conn->fetchAll($sql, [strtoupper($user)]);
423
424 1
        foreach ($activeUserSessions as $activeUserSession) {
425 1
            $activeUserSession = array_change_key_case($activeUserSession, \CASE_LOWER);
426
427 1
            $this->_execSql(
428 1
                sprintf(
429 1
                    "ALTER SYSTEM KILL SESSION '%s, %s' IMMEDIATE",
430 1
                    $activeUserSession['sid'],
431 1
                    $activeUserSession['serial#']
432
                )
433
            );
434
        }
435 1
    }
436
}
437