Failed Conditions
Push — master ( 30b923...92920e )
by Marco
19s queued 13s
created

OracleSchemaManager::dropDatabase()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119

Importance

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