OracleSchemaManager::killUserSessions()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 28
rs 9.472
c 0
b 0
f 0
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 assert;
30
use function is_null;
31
use function preg_match;
32
use function sprintf;
33
use function strpos;
34
use function strtolower;
35
use function strtoupper;
36
use function trim;
37
38
/**
39
 * Oracle Schema Manager.
40
 *
41
 * @author Konsta Vesterinen <[email protected]>
42
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
43
 * @author Benjamin Eberlei <[email protected]>
44
 * @since  2.0
45
 */
46
class OracleSchemaManager extends AbstractSchemaManager
47
{
48
    /**
49
     * {@inheritdoc}
50
     */
51 View Code Duplication
    public function dropDatabase($database)
52
    {
53
        try {
54
            parent::dropDatabase($database);
55
        } catch (DBALException $exception) {
56
            $exception = $exception->getPrevious();
57
58
            if (! $exception instanceof DriverException) {
59
                throw $exception;
60
            }
61
62
            // If we have a error code 1940 (ORA-01940), the drop database operation failed
63
            // because of active connections on the database.
64
            // To force dropping the database, we first have to close all active connections
65
            // on that database and issue the drop database operation again.
66
            if ($exception->getErrorCode() !== 1940) {
67
                throw $exception;
68
            }
69
70
            $this->killUserSessions($database);
71
72
            parent::dropDatabase($database);
73
        }
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    protected function _getPortableViewDefinition($view)
80
    {
81
        $view = \array_change_key_case($view, CASE_LOWER);
82
83
        return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']);
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    protected function _getPortableUserDefinition($user)
90
    {
91
        $user = \array_change_key_case($user, CASE_LOWER);
92
93
        return [
94
            'user' => $user['username'],
95
        ];
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    protected function _getPortableTableDefinition($table)
102
    {
103
        $table = \array_change_key_case($table, CASE_LOWER);
104
105
        return $this->getQuotedIdentifierName($table['table_name']);
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     *
111
     * @license New BSD License
112
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
113
     */
114
    protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
115
    {
116
        $indexBuffer = [];
117
        foreach ($tableIndexes as $tableIndex) {
118
            $tableIndex = \array_change_key_case($tableIndex, CASE_LOWER);
119
120
            $keyName = strtolower($tableIndex['name']);
121
            $buffer  = [];
122
123
            if (strtolower($tableIndex['is_primary']) == "p") {
124
                $keyName = 'primary';
125
                $buffer['primary'] = true;
126
                $buffer['non_unique'] = false;
127
            } else {
128
                $buffer['primary'] = false;
129
                $buffer['non_unique'] = ! $tableIndex['is_unique'];
130
            }
131
            $buffer['key_name'] = $keyName;
132
            $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']);
133
            $indexBuffer[] = $buffer;
134
        }
135
136
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142
    protected function _getPortableTableColumnDefinition($tableColumn)
143
    {
144
        $tableColumn = \array_change_key_case($tableColumn, CASE_LOWER);
145
146
        $dbType = strtolower($tableColumn['data_type']);
147
        if (strpos($dbType, "timestamp(") === 0) {
148
            if (strpos($dbType, "with time zone")) {
149
                $dbType = "timestamptz";
150
            } else {
151
                $dbType = "timestamp";
152
            }
153
        }
154
155
        $unsigned = $fixed = null;
156
157
        if ( ! isset($tableColumn['column_name'])) {
158
            $tableColumn['column_name'] = '';
159
        }
160
161
        // Default values returned from database sometimes have trailing spaces.
162
        $tableColumn['data_default'] = trim($tableColumn['data_default']);
163
164 View Code Duplication
        if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') {
165
            $tableColumn['data_default'] = null;
166
        }
167
168 View Code Duplication
        if (null !== $tableColumn['data_default']) {
169
            // Default values returned from database are enclosed in single quotes.
170
            $tableColumn['data_default'] = trim($tableColumn['data_default'], "'");
171
        }
172
173
        $precision = null;
174
        $scale = null;
175
176
        $type = $this->_platform->getDoctrineTypeMapping($dbType);
177
        $type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type);
178
        $tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type);
179
180
        switch ($dbType) {
181
            case 'number':
182
                if ($tableColumn['data_precision'] == 20 && $tableColumn['data_scale'] == 0) {
183
                    $precision = 20;
184
                    $scale = 0;
185
                    $type = 'bigint';
186
                } elseif ($tableColumn['data_precision'] == 5 && $tableColumn['data_scale'] == 0) {
187
                    $type = 'smallint';
188
                    $precision = 5;
189
                    $scale = 0;
190
                } elseif ($tableColumn['data_precision'] == 1 && $tableColumn['data_scale'] == 0) {
191
                    $precision = 1;
192
                    $scale = 0;
193
                    $type = 'boolean';
194
                } elseif ($tableColumn['data_scale'] > 0) {
195
                    $precision = $tableColumn['data_precision'];
196
                    $scale = $tableColumn['data_scale'];
197
                    $type = 'decimal';
198
                }
199
                $length = null;
200
                break;
201
            case 'pls_integer':
202
            case 'binary_integer':
203
                $length = null;
204
                break;
205
            case 'varchar':
206
            case 'varchar2':
207
            case 'nvarchar2':
208
                $length = $tableColumn['char_length'];
209
                $fixed = false;
210
                break;
211
            case 'char':
212
            case 'nchar':
213
                $length = $tableColumn['char_length'];
214
                $fixed = true;
215
                break;
216
            case 'date':
217
            case 'timestamp':
218
                $length = null;
219
                break;
220
            case 'float':
221
            case 'binary_float':
222
            case 'binary_double':
223
                $precision = $tableColumn['data_precision'];
224
                $scale = $tableColumn['data_scale'];
225
                $length = null;
226
                break;
227
            case 'clob':
228
            case 'nclob':
229
                $length = null;
230
                break;
231
            case 'blob':
232
            case 'raw':
233
            case 'long raw':
234
            case 'bfile':
235
                $length = null;
236
                break;
237
            case 'rowid':
238
            case 'urowid':
239
            default:
240
                $length = null;
241
        }
242
243
        $options = [
244
            'notnull'    => (bool) ($tableColumn['nullable'] === 'N'),
245
            'fixed'      => (bool) $fixed,
246
            'unsigned'   => (bool) $unsigned,
247
            'default'    => $tableColumn['data_default'],
248
            'length'     => $length,
249
            'precision'  => $precision,
250
            'scale'      => $scale,
251
            'comment'    => isset($tableColumn['comments']) && '' !== $tableColumn['comments']
252
                ? $tableColumn['comments']
253
                : null,
254
        ];
255
256
        return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options);
257
    }
258
259
    /**
260
     * {@inheritdoc}
261
     */
262
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
263
    {
264
        $list = [];
265
        foreach ($tableForeignKeys as $value) {
266
            $value = \array_change_key_case($value, CASE_LOWER);
267
            if (!isset($list[$value['constraint_name']])) {
268
                if ($value['delete_rule'] == "NO ACTION") {
269
                    $value['delete_rule'] = null;
270
                }
271
272
                $list[$value['constraint_name']] = [
273
                    'name' => $this->getQuotedIdentifierName($value['constraint_name']),
274
                    'local' => [],
275
                    'foreign' => [],
276
                    'foreignTable' => $value['references_table'],
277
                    'onDelete' => $value['delete_rule'],
278
                ];
279
            }
280
281
            $localColumn = $this->getQuotedIdentifierName($value['local_column']);
282
            $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']);
283
284
            $list[$value['constraint_name']]['local'][$value['position']] = $localColumn;
285
            $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn;
286
        }
287
288
        $result = [];
289
        foreach ($list as $constraint) {
290
            $result[] = new ForeignKeyConstraint(
291
                array_values($constraint['local']), $this->getQuotedIdentifierName($constraint['foreignTable']),
292
                array_values($constraint['foreign']), $this->getQuotedIdentifierName($constraint['name']),
293
                ['onDelete' => $constraint['onDelete']]
294
            );
295
        }
296
297
        return $result;
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303
    protected function _getPortableSequenceDefinition($sequence)
304
    {
305
        $sequence = \array_change_key_case($sequence, CASE_LOWER);
306
307
        return new Sequence(
308
            $this->getQuotedIdentifierName($sequence['sequence_name']),
309
            (int) $sequence['increment_by'],
310
            (int) $sequence['min_value']
311
        );
312
    }
313
314
    /**
315
     * {@inheritdoc}
316
     */
317
    protected function _getPortableFunctionDefinition($function)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
318
    {
319
        $function = \array_change_key_case($function, CASE_LOWER);
320
321
        return $function['name'];
322
    }
323
324
    /**
325
     * {@inheritdoc}
326
     */
327
    protected function _getPortableDatabaseDefinition($database)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
328
    {
329
        $database = \array_change_key_case($database, CASE_LOWER);
330
331
        return $database['username'];
332
    }
333
334
    /**
335
     * {@inheritdoc}
336
     */
337
    public function createDatabase($database = null)
338
    {
339
        if ($database === null) {
340
            $database = $this->_conn->getDatabase();
341
        }
342
343
        $params = $this->_conn->getParams();
344
        $username   = $database;
345
        $password   = $params['password'];
346
347
        $query  = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password;
0 ignored issues
show
Security introduced by
'CREATE USER ' . $userna...TIFIED BY ' . $password is used as a query on line 348. If $password can contain user-input, it is usually preferable to use a parameter placeholder like :paramName and pass the dynamic input as second argument array('param' => $password).

Instead of embedding dynamic parameters in SQL, Doctrine also allows you to pass them separately and insert a placeholder instead:

function findUser(Doctrine\DBAL\Connection $con, $email) {
    // Unsafe
    $con->executeQuery("SELECT * FROM users WHERE email = '".$email."'");

    // Safe
    $con->executeQuery(
        "SELECT * FROM users WHERE email = :email",
        array('email' => $email)
    );
}
Loading history...
348
        $this->_conn->executeUpdate($query);
349
350
        $query = 'GRANT DBA TO ' . $username;
0 ignored issues
show
Security introduced by
'GRANT DBA TO ' . $username is used as a query on line 351. If $username can contain user-input, it is usually preferable to use a parameter placeholder like :paramName and pass the dynamic input as second argument array('param' => $username).

Instead of embedding dynamic parameters in SQL, Doctrine also allows you to pass them separately and insert a placeholder instead:

function findUser(Doctrine\DBAL\Connection $con, $email) {
    // Unsafe
    $con->executeQuery("SELECT * FROM users WHERE email = '".$email."'");

    // Safe
    $con->executeQuery(
        "SELECT * FROM users WHERE email = :email",
        array('email' => $email)
    );
}
Loading history...
351
        $this->_conn->executeUpdate($query);
352
    }
353
354
    /**
355
     * @param string $table
356
     *
357
     * @return bool
358
     */
359
    public function dropAutoincrement($table)
360
    {
361
        assert($this->_platform instanceof OraclePlatform);
362
363
        $sql = $this->_platform->getDropAutoincrementSql($table);
364
        foreach ($sql as $query) {
365
            $this->_conn->executeUpdate($query);
366
        }
367
368
        return true;
369
    }
370
371
    /**
372
     * {@inheritdoc}
373
     */
374
    public function dropTable($name)
375
    {
376
        $this->tryMethod('dropAutoincrement', $name);
377
378
        parent::dropTable($name);
379
    }
380
381
    /**
382
     * Returns the quoted representation of the given identifier name.
383
     *
384
     * Quotes non-uppercase identifiers explicitly to preserve case
385
     * and thus make references to the particular identifier work.
386
     *
387
     * @param string $identifier The identifier to quote.
388
     *
389
     * @return string The quoted identifier.
390
     */
391
    private function getQuotedIdentifierName($identifier)
392
    {
393
        if (preg_match('/[a-z]/', $identifier)) {
394
            return $this->_platform->quoteIdentifier($identifier);
395
        }
396
397
        return $identifier;
398
    }
399
400
    /**
401
     * Kills sessions connected with the given user.
402
     *
403
     * This is useful to force DROP USER operations which could fail because of active user sessions.
404
     *
405
     * @param string $user The name of the user to kill sessions for.
406
     *
407
     * @return void
408
     */
409
    private function killUserSessions($user)
410
    {
411
        $sql = <<<SQL
412
SELECT
413
    s.sid,
414
    s.serial#
415
FROM
416
    gv\$session s,
417
    gv\$process p
418
WHERE
419
    s.username = ?
420
    AND p.addr(+) = s.paddr
421
SQL;
422
423
        $activeUserSessions = $this->_conn->fetchAll($sql, [strtoupper($user)]);
424
425
        foreach ($activeUserSessions as $activeUserSession) {
426
            $activeUserSession = array_change_key_case($activeUserSession, \CASE_LOWER);
427
428
            $this->_execSql(
429
                sprintf(
430
                    "ALTER SYSTEM KILL SESSION '%s, %s' IMMEDIATE",
431
                    $activeUserSession['sid'],
432
                    $activeUserSession['serial#']
433
                )
434
            );
435
        }
436
    }
437
}
438