Failed Conditions
Push — develop ( 776429...7c38e8 )
by Sergei
64:07 queued 10s
created

OracleSchemaManager::dropAutoincrement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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