Failed Conditions
Pull Request — master (#3429)
by Gabriel
12:54
created

_getPortableDatabaseDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 2
     */
29 4
    public function dropDatabase($database)
30
    {
31 2
        try {
32 6
            parent::dropDatabase($database);
33 6
        } catch (DBALException $exception) {
34 4
            $exception = $exception->getPrevious();
35 6
            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 2
            // To force dropping the database, we first have to close all active connections
44 2
            // on that database and issue the drop database operation again.
45 4
            if ($exception->getErrorCode() !== 1940) {
46 4
                throw $exception;
47 1
            }
48
49 3
            $this->killUserSessions($database);
50
51 3
            parent::dropDatabase($database);
52
        }
53 2
    }
54
55
    /**
56 1
     * {@inheritdoc}
57
     */
58 3
    protected function _getPortableViewDefinition($view)
59
    {
60 3
        $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 63
     * {@inheritdoc}
79
     */
80 189
    protected function _getPortableTableDefinition($table)
81
    {
82 189
        $table = array_change_key_case($table, CASE_LOWER);
83
84 126
        return $this->getQuotedIdentifierName($table['table_name']);
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     *
90 28
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
91
     */
92 84
    protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
93 28
    {
94 71
        $indexBuffer = [];
95 56
        foreach ($tableIndexes as $tableIndex) {
96 45
            $tableIndex = array_change_key_case($tableIndex, CASE_LOWER);
97 15
98 30
            $keyName = strtolower($tableIndex['name']);
99 45
            $buffer  = [];
100 13
101 43
            if (strtolower($tableIndex['is_primary']) === 'p') {
102 39
                $keyName              = 'primary';
103 26
                $buffer['primary']    = true;
104 38
                $buffer['non_unique'] = false;
105 12
            } else {
106 24
                $buffer['primary']    = false;
107 39
                $buffer['non_unique'] = ! $tableIndex['is_unique'];
108 15
            }
109 45
            $buffer['key_name']    = $keyName;
110 30
            $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']);
111 30
            $indexBuffer[]         = $buffer;
112 28
        }
113
114 56
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
115
    }
116
117
    /**
118 35
     * {@inheritdoc}
119
     */
120 105
    protected function _getPortableTableColumnDefinition($tableColumn)
121
    {
122 105
        $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
123 35
124 77
        $dbType = strtolower($tableColumn['data_type']);
125 73
        if (strpos($dbType, 'timestamp(') === 0) {
126 14
            if (strpos($dbType, 'with time zone')) {
127 13
                $dbType = 'timestamptz';
128
            } else {
129 14
                $dbType = 'timestamp';
130
            }
131 35
        }
132
133 105
        $unsigned = $fixed = $precision = $scale = $length = null;
134
135 70
        if (! isset($tableColumn['column_name'])) {
136
            $tableColumn['column_name'] = '';
137
        }
138 35
139
        // Default values returned from database sometimes have trailing spaces.
140 105
        $tableColumn['data_default'] = trim($tableColumn['data_default']);
141 32
142 70
        if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') {
143 64
            $tableColumn['data_default'] = null;
144 35
        }
145
146 80
        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 35
        }
150 31
151 70
        if ($tableColumn['data_precision'] !== null) {
152 62
            $precision = (int) $tableColumn['data_precision'];
153 35
        }
154 32
155 70
        if ($tableColumn['data_scale'] !== null) {
156 64
            $scale = (int) $tableColumn['data_scale'];
157 35
        }
158 35
159 105
        $type                    = $this->_platform->getDoctrineTypeMapping($dbType);
160 70
        $type                    = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type);
161 105
        $tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type);
162 35
163 101
        switch ($dbType) {
164 70
            case 'number':
165 93
                if ($precision === 20 && $scale === 0) {
166
                    $type = 'bigint';
167 93
                } elseif ($precision === 5 && $scale === 0) {
168 4
                    $type = 'smallint';
169 92
                } elseif ($precision === 1 && $scale === 0) {
170 13
                    $type = 'boolean';
171 60
                } elseif ($scale > 0) {
172 10
                    $type = 'decimal';
173 31
                }
174 19
175 81
                break;
176 52
            case 'varchar':
177 49
            case 'varchar2':
178 39
            case 'nvarchar2':
179 33
                $length = $tableColumn['char_length'];
180 36
                $fixed  = false;
181 34
                break;
182 33
            case 'char':
183 29
            case 'nchar':
184 15
                $length = $tableColumn['char_length'];
185 10
                $fixed  = true;
186 10
                break;
187
        }
188 35
189 35
        $options = [
190 105
            'notnull'    => (bool) ($tableColumn['nullable'] === 'N'),
191 105
            'fixed'      => (bool) $fixed,
192 105
            'unsigned'   => (bool) $unsigned,
193 105
            'default'    => $tableColumn['data_default'],
194 105
            'length'     => $length,
195 105
            'precision'  => $precision,
196 87
            'scale'      => $scale,
197 70
            'comment'    => isset($tableColumn['comments']) && $tableColumn['comments'] !== ''
198 34
                ? $tableColumn['comments']
199
                : null,
200 35
        ];
201
202 70
        return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options);
203
    }
204
205
    /**
206 25
     * {@inheritdoc}
207
     */
208 75
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
209 25
    {
210 59
        $list = [];
211 59
        foreach ($tableForeignKeys as $value) {
212 27
            $value = array_change_key_case($value, CASE_LOWER);
213 26
            if (! isset($list[$value['constraint_name']])) {
214 18
                if ($value['delete_rule'] === 'NO ACTION') {
215 16
                    $value['delete_rule'] = null;
216 9
                }
217 9
218 18
                $list[$value['constraint_name']] = [
219 18
                    'name' => $this->getQuotedIdentifierName($value['constraint_name']),
220 9
                    'local' => [],
221 9
                    'foreign' => [],
222 18
                    'foreignTable' => $value['references_table'],
223 18
                    'onDelete' => $value['delete_rule'],
224
                ];
225 9
            }
226 9
227 18
            $localColumn   = $this->getQuotedIdentifierName($value['local_column']);
228 27
            $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']);
229 9
230 18
            $list[$value['constraint_name']]['local'][$value['position']]   = $localColumn;
231 18
            $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn;
232 25
        }
233 25
234 59
        $result = [];
235 59
        foreach ($list as $constraint) {
236 27
            $result[] = new ForeignKeyConstraint(
237 27
                array_values($constraint['local']),
238 27
                $this->getQuotedIdentifierName($constraint['foreignTable']),
239 27
                array_values($constraint['foreign']),
240 18
                $this->getQuotedIdentifierName($constraint['name']),
241 18
                ['onDelete' => $constraint['onDelete']]
242
            );
243 25
        }
244
245 50
        return $result;
246
    }
247
248
    /**
249 5
     * {@inheritdoc}
250
     */
251 14
    protected function _getPortableSequenceDefinition($sequence)
252
    {
253 14
        $sequence = array_change_key_case($sequence, CASE_LOWER);
254 5
255 14
        return new Sequence(
256 14
            $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 2
     * {@inheritdoc}
274
     */
275 6
    protected function _getPortableDatabaseDefinition($database)
276
    {
277 6
        $database = array_change_key_case($database, CASE_LOWER);
278
279 4
        return $database['username'];
280
    }
281
282
    /**
283 2
     * {@inheritdoc}
284
     */
285 6
    public function createDatabase($database = null)
286
    {
287 4
        if ($database === null) {
288
            $database = $this->_conn->getDatabase();
289 2
        }
290 2
291 6
        $params   = $this->_conn->getParams();
292 4
        $username = $database;
293 6
        $password = $params['password'];
294 2
295 4
        $query = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password;
296 6
        $this->_conn->executeUpdate($query);
297 2
298 6
        $query = 'GRANT DBA TO ' . $username;
299 4
        $this->_conn->executeUpdate($query);
300 4
    }
301
302
    /**
303
     * @param string $table
304
     *
305 99
     * @return bool
306
     */
307 317
    public function dropAutoincrement($table)
308
    {
309 317
        assert($this->_platform instanceof OraclePlatform);
310 99
311 317
        $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 99
     * {@inheritdoc}
321
     */
322 317
    public function dropTable($name)
323
    {
324 317
        $this->tryMethod('dropAutoincrement', $name);
325 52
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 66
     * @return string The quoted identifier.
338
     */
339 197
    private function getQuotedIdentifierName($identifier)
340 52
    {
341 131
        if (preg_match('/[a-z]/', $identifier)) {
342 103
            return $this->_platform->quoteIdentifier($identifier);
343 66
        }
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 1
     * @return void
356
     */
357 2
    private function killUserSessions($user)
358 1
    {
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 1
SQL;
370
371 3
        $activeUserSessions = $this->_conn->fetchAll($sql, [strtoupper($user)]);
372 1
373 2
        foreach ($activeUserSessions as $activeUserSession) {
374 3
            $activeUserSession = array_change_key_case($activeUserSession, CASE_LOWER);
375 1
376 3
            $this->_execSql(
377 3
                sprintf(
378 3
                    "ALTER SYSTEM KILL SESSION '%s, %s' IMMEDIATE",
379 2
                    $activeUserSession['sid'],
380 2
                    $activeUserSession['serial#']
381
                )
382 1
            );
383
        }
384 2
    }
385
}
386