Failed Conditions
Pull Request — master (#3359)
by Sergei
25:01 queued 22:04
created

OracleSchemaManager::_getPortableViewDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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