Completed
Branch master (8d5465)
by
unknown
31:25
created

DatabaseOracle   F

Complexity

Total Complexity 262

Size/Duplication

Total Lines 1394
Duplicated Lines 10.26 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 143
loc 1394
rs 3.9999
c 0
b 0
f 0
wmc 262
lcom 1
cbo 11

79 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A __destruct() 0 7 2
A getType() 0 3 1
A cascadingDeletes() 0 3 1
A cleanupTriggers() 0 3 1
A strictIPs() 0 3 1
A realTimestamps() 0 3 1
A implicitGroupby() 0 3 1
A implicitOrderby() 0 3 1
A searchableIPs() 0 3 1
D open() 0 85 11
A closeConnection() 0 3 1
A execFlags() 0 3 2
C doQuery() 6 59 9
A queryIgnore() 0 3 1
A freeResult() 0 7 2
A fetchObject() 0 7 2
A fetchRow() 0 7 2
A numRows() 0 7 2
A numFields() 0 7 2
A fieldName() 0 3 1
A insertId() 0 3 1
A dataSeek() 0 7 2
A lastError() 9 9 2
A lastErrno() 9 9 2
A affectedRows() 0 3 1
A indexInfo() 0 3 1
A indexUnique() 0 3 1
C insert() 0 28 7
C fieldBindStatement() 0 34 13
F insertOneRow() 38 106 24
C nativeInsertSelect() 10 48 9
B upsert() 10 25 6
A tableName() 0 17 3
A tableNameInternal() 0 5 1
A nextSequenceValue() 0 7 1
B getSequenceData() 0 27 4
A textFieldSize() 0 5 1
A limitResult() 0 7 2
A encodeBlob() 0 3 1
A decodeBlob() 0 7 2
A unionQueries() 0 6 2
A wasDeadlock() 0 3 1
A duplicateTableStructure() 0 15 2
B listTables() 0 24 4
A dropTable() 0 8 2
A timestamp() 0 3 1
A aggregateValue() 0 3 1
A getSoftwareLink() 0 3 1
A getServerVersion() 0 13 2
A indexExists() 0 16 2
A tableExists() 0 16 3
C fieldInfoMulti() 6 51 9
A fieldInfo() 0 7 2
A doBegin() 0 4 1
A doCommit() 0 10 3
A doRollback() 0 7 2
D sourceStream() 0 77 20
B selectDB() 0 21 5
A strencode() 0 3 1
A addQuotes() 0 8 3
A addIdentifierQuotes() 0 7 2
A removeIdentifierQuotes() 0 3 2
A isQuotedIdentifier() 0 3 1
A wrapFieldForWhere() 0 12 4
B wrapConditionsForWhere() 0 17 5
A selectRow() 0 9 2
D makeSelectOptions() 10 37 10
B delete() 0 33 4
F update() 38 104 25
A bitNot() 0 4 1
A bitAnd() 0 3 1
A bitOr() 0 3 1
A getDBname() 0 3 1
A getServer() 0 3 1
A buildGroupConcatField() 7 7 1
A buildStringCast() 0 3 1
A getSearchEngine() 0 3 1
A getInfinity() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DatabaseOracle often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DatabaseOracle, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This is the Oracle database abstraction layer.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @ingroup Database
22
 */
23
24
/**
25
 * The oci8 extension is fairly weak and doesn't support oci_num_rows, among
26
 * other things. We use a wrapper class to handle that and other
27
 * Oracle-specific bits, like converting column names back to lowercase.
28
 * @ingroup Database
29
 */
30
class ORAResult {
31
	private $rows;
32
	private $cursor;
33
	private $nrows;
34
35
	private $columns = [];
36
37
	private function array_unique_md( $array_in ) {
38
		$array_out = [];
39
		$array_hashes = [];
40
41
		foreach ( $array_in as $item ) {
42
			$hash = md5( serialize( $item ) );
43
			if ( !isset( $array_hashes[$hash] ) ) {
44
				$array_hashes[$hash] = $hash;
45
				$array_out[] = $item;
46
			}
47
		}
48
49
		return $array_out;
50
	}
51
52
	/**
53
	 * @param DatabaseBase $db
54
	 * @param resource $stmt A valid OCI statement identifier
55
	 * @param bool $unique
56
	 */
57
	function __construct( &$db, $stmt, $unique = false ) {
58
		$this->db =& $db;
0 ignored issues
show
Bug introduced by
The property db does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
59
60
		$this->nrows = oci_fetch_all( $stmt, $this->rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM );
61
		if ( $this->nrows === false ) {
62
			$e = oci_error( $stmt );
63
			$db->reportQueryError( $e['message'], $e['code'], '', __METHOD__ );
64
			$this->free();
65
66
			return;
67
		}
68
69
		if ( $unique ) {
70
			$this->rows = $this->array_unique_md( $this->rows );
71
			$this->nrows = count( $this->rows );
72
		}
73
74
		if ( $this->nrows > 0 ) {
75
			foreach ( $this->rows[0] as $k => $v ) {
76
				$this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) );
77
			}
78
		}
79
80
		$this->cursor = 0;
81
		oci_free_statement( $stmt );
82
	}
83
84
	public function free() {
85
		unset( $this->db );
86
	}
87
88
	public function seek( $row ) {
89
		$this->cursor = min( $row, $this->nrows );
90
	}
91
92
	public function numRows() {
93
		return $this->nrows;
94
	}
95
96
	public function numFields() {
97
		return count( $this->columns );
98
	}
99
100
	public function fetchObject() {
101
		if ( $this->cursor >= $this->nrows ) {
102
			return false;
103
		}
104
		$row = $this->rows[$this->cursor++];
105
		$ret = new stdClass();
106
		foreach ( $row as $k => $v ) {
107
			$lc = $this->columns[$k];
108
			$ret->$lc = $v;
109
		}
110
111
		return $ret;
112
	}
113
114
	public function fetchRow() {
115
		if ( $this->cursor >= $this->nrows ) {
116
			return false;
117
		}
118
119
		$row = $this->rows[$this->cursor++];
120
		$ret = [];
121
		foreach ( $row as $k => $v ) {
122
			$lc = $this->columns[$k];
123
			$ret[$lc] = $v;
124
			$ret[$k] = $v;
125
		}
126
127
		return $ret;
128
	}
129
}
130
131
/**
132
 * Utility class.
133
 * @ingroup Database
134
 */
135
class ORAField implements Field {
136
	private $name, $tablename, $default, $max_length, $nullable,
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
137
		$is_pk, $is_unique, $is_multiple, $is_key, $type;
138
139
	function __construct( $info ) {
140
		$this->name = $info['column_name'];
141
		$this->tablename = $info['table_name'];
142
		$this->default = $info['data_default'];
143
		$this->max_length = $info['data_length'];
144
		$this->nullable = $info['not_null'];
145
		$this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
146
		$this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
147
		$this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
148
		$this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
149
		$this->type = $info['data_type'];
150
	}
151
152
	function name() {
153
		return $this->name;
154
	}
155
156
	function tableName() {
157
		return $this->tablename;
158
	}
159
160
	function defaultValue() {
161
		return $this->default;
162
	}
163
164
	function maxLength() {
165
		return $this->max_length;
166
	}
167
168
	function isNullable() {
169
		return $this->nullable;
170
	}
171
172
	function isKey() {
173
		return $this->is_key;
174
	}
175
176
	function isMultipleKey() {
177
		return $this->is_multiple;
178
	}
179
180
	function type() {
181
		return $this->type;
182
	}
183
}
184
185
/**
186
 * @ingroup Database
187
 */
188
class DatabaseOracle extends Database {
189
	/** @var resource */
190
	protected $mLastResult = null;
191
192
	/** @var int The number of rows affected as an integer */
193
	protected $mAffectedRows;
194
195
	/** @var int */
196
	private $mInsertId = null;
197
198
	/** @var bool */
199
	private $ignoreDupValOnIndex = false;
200
201
	/** @var bool|array */
202
	private $sequenceData = null;
203
204
	/** @var string Character set for Oracle database */
205
	private $defaultCharset = 'AL32UTF8';
206
207
	/** @var array */
208
	private $mFieldInfoCache = [];
209
210
	function __construct( array $p ) {
211
		global $wgDBprefix;
212
213
		if ( $p['tablePrefix'] == 'get from global' ) {
214
			$p['tablePrefix'] = $wgDBprefix;
215
		}
216
		$p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
217
		parent::__construct( $p );
218
		Hooks::run( 'DatabaseOraclePostInit', [ $this ] );
219
	}
220
221
	function __destruct() {
222
		if ( $this->mOpened ) {
223
			MediaWiki\suppressWarnings();
224
			$this->close();
225
			MediaWiki\restoreWarnings();
226
		}
227
	}
228
229
	function getType() {
230
		return 'oracle';
231
	}
232
233
	function cascadingDeletes() {
234
		return true;
235
	}
236
237
	function cleanupTriggers() {
238
		return true;
239
	}
240
241
	function strictIPs() {
242
		return true;
243
	}
244
245
	function realTimestamps() {
246
		return true;
247
	}
248
249
	function implicitGroupby() {
250
		return false;
251
	}
252
253
	function implicitOrderby() {
254
		return false;
255
	}
256
257
	function searchableIPs() {
258
		return true;
259
	}
260
261
	/**
262
	 * Usually aborts on failure
263
	 * @param string $server
264
	 * @param string $user
265
	 * @param string $password
266
	 * @param string $dbName
267
	 * @throws DBConnectionError
268
	 * @return DatabaseBase|null
269
	 */
270
	function open( $server, $user, $password, $dbName ) {
271
		global $wgDBOracleDRCP;
272
		if ( !function_exists( 'oci_connect' ) ) {
273
			throw new DBConnectionError(
274
				$this,
275
				"Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n " .
276
					"(Note: if you recently installed PHP, you may need to restart your webserver\n " .
277
					"and database)\n" );
278
		}
279
280
		$this->close();
281
		$this->mUser = $user;
282
		$this->mPassword = $password;
283
		// changed internal variables functions
284
		// mServer now holds the TNS endpoint
285
		// mDBname is schema name if different from username
286
		if ( !$server ) {
287
			// backward compatibillity (server used to be null and TNS was supplied in dbname)
288
			$this->mServer = $dbName;
289
			$this->mDBname = $user;
290
		} else {
291
			$this->mServer = $server;
292
			if ( !$dbName ) {
293
				$this->mDBname = $user;
294
			} else {
295
				$this->mDBname = $dbName;
296
			}
297
		}
298
299
		if ( !strlen( $user ) ) { # e.g. the class is being loaded
300
			return null;
301
		}
302
303
		if ( $wgDBOracleDRCP ) {
304
			$this->setFlag( DBO_PERSISTENT );
305
		}
306
307
		$session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
308
309
		MediaWiki\suppressWarnings();
310
		if ( $this->mFlags & DBO_PERSISTENT ) {
311
			$this->mConn = oci_pconnect(
312
				$this->mUser,
313
				$this->mPassword,
314
				$this->mServer,
315
				$this->defaultCharset,
316
				$session_mode
317
			);
318
		} elseif ( $this->mFlags & DBO_DEFAULT ) {
319
			$this->mConn = oci_new_connect(
320
				$this->mUser,
321
				$this->mPassword,
322
				$this->mServer,
323
				$this->defaultCharset,
324
				$session_mode
325
			);
326
		} else {
327
			$this->mConn = oci_connect(
328
				$this->mUser,
329
				$this->mPassword,
330
				$this->mServer,
331
				$this->defaultCharset,
332
				$session_mode
333
			);
334
		}
335
		MediaWiki\restoreWarnings();
336
337
		if ( $this->mUser != $this->mDBname ) {
338
			// change current schema in session
339
			$this->selectDB( $this->mDBname );
340
		}
341
342
		if ( !$this->mConn ) {
343
			throw new DBConnectionError( $this, $this->lastError() );
344
		}
345
346
		$this->mOpened = true;
347
348
		# removed putenv calls because they interfere with the system globaly
349
		$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
350
		$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
351
		$this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
352
353
		return $this->mConn;
354
	}
355
356
	/**
357
	 * Closes a database connection, if it is open
358
	 * Returns success, true if already closed
359
	 * @return bool
360
	 */
361
	protected function closeConnection() {
362
		return oci_close( $this->mConn );
363
	}
364
365
	function execFlags() {
366
		return $this->mTrxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
367
	}
368
369
	protected function doQuery( $sql ) {
370
		wfDebug( "SQL: [$sql]\n" );
371
		if ( !StringUtils::isUtf8( $sql ) ) {
372
			throw new MWException( "SQL encoding is invalid\n$sql" );
373
		}
374
375
		// handle some oracle specifics
376
		// remove AS column/table/subquery namings
377
		if ( !$this->getFlag( DBO_DDLMODE ) ) {
378
			$sql = preg_replace( '/ as /i', ' ', $sql );
379
		}
380
381
		// Oracle has issues with UNION clause if the statement includes LOB fields
382
		// So we do a UNION ALL and then filter the results array with array_unique
383
		$union_unique = ( preg_match( '/\/\* UNION_UNIQUE \*\/ /', $sql ) != 0 );
384
		// EXPLAIN syntax in Oracle is EXPLAIN PLAN FOR and it return nothing
385
		// you have to select data from plan table after explain
386
		$explain_id = MWTimestamp::getLocalInstance()->format( 'dmYHis' );
387
388
		$sql = preg_replace(
389
			'/^EXPLAIN /',
390
			'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR',
391
			$sql,
392
			1,
393
			$explain_count
394
		);
395
396
		MediaWiki\suppressWarnings();
397
398
		$this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
399 View Code Duplication
		if ( $stmt === false ) {
400
			$e = oci_error( $this->mConn );
401
			$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
402
403
			return false;
404
		}
405
406
		if ( !oci_execute( $stmt, $this->execFlags() ) ) {
407
			$e = oci_error( $stmt );
408
			if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
409
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
410
411
				return false;
412
			}
413
		}
414
415
		MediaWiki\restoreWarnings();
416
417
		if ( $explain_count > 0 ) {
418
			return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table ' .
419
				'WHERE statement_id = \'' . $explain_id . '\'' );
420
		} elseif ( oci_statement_type( $stmt ) == 'SELECT' ) {
421
			return new ORAResult( $this, $stmt, $union_unique );
422
		} else {
423
			$this->mAffectedRows = oci_num_rows( $stmt );
424
425
			return true;
426
		}
427
	}
428
429
	function queryIgnore( $sql, $fname = '' ) {
430
		return $this->query( $sql, $fname, true );
431
	}
432
433
	/**
434
	 * Frees resources associated with the LOB descriptor
435
	 * @param ResultWrapper|ORAResult $res
436
	 */
437
	function freeResult( $res ) {
438
		if ( $res instanceof ResultWrapper ) {
439
			$res = $res->result;
440
		}
441
442
		$res->free();
0 ignored issues
show
Bug introduced by
It seems like $res is not always an object, but can also be of type resource. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
443
	}
444
445
	/**
446
	 * @param ResultWrapper|ORAResult $res
447
	 * @return mixed
448
	 */
449
	function fetchObject( $res ) {
450
		if ( $res instanceof ResultWrapper ) {
451
			$res = $res->result;
452
		}
453
454
		return $res->fetchObject();
0 ignored issues
show
Bug introduced by
It seems like $res is not always an object, but can also be of type resource. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
455
	}
456
457
	/**
458
	 * @param ResultWrapper|ORAResult $res
459
	 * @return mixed
460
	 */
461
	function fetchRow( $res ) {
462
		if ( $res instanceof ResultWrapper ) {
463
			$res = $res->result;
464
		}
465
466
		return $res->fetchRow();
0 ignored issues
show
Bug introduced by
It seems like $res is not always an object, but can also be of type resource. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
467
	}
468
469
	/**
470
	 * @param ResultWrapper|ORAResult $res
471
	 * @return int
472
	 */
473
	function numRows( $res ) {
474
		if ( $res instanceof ResultWrapper ) {
475
			$res = $res->result;
476
		}
477
478
		return $res->numRows();
0 ignored issues
show
Bug introduced by
It seems like $res is not always an object, but can also be of type resource. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
479
	}
480
481
	/**
482
	 * @param ResultWrapper|ORAResult $res
483
	 * @return int
484
	 */
485
	function numFields( $res ) {
486
		if ( $res instanceof ResultWrapper ) {
487
			$res = $res->result;
488
		}
489
490
		return $res->numFields();
0 ignored issues
show
Bug introduced by
It seems like $res is not always an object, but can also be of type resource. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
491
	}
492
493
	function fieldName( $stmt, $n ) {
494
		return oci_field_name( $stmt, $n );
495
	}
496
497
	/**
498
	 * This must be called after nextSequenceVal
499
	 * @return null|int
500
	 */
501
	function insertId() {
502
		return $this->mInsertId;
503
	}
504
505
	/**
506
	 * @param mixed $res
507
	 * @param int $row
508
	 */
509
	function dataSeek( $res, $row ) {
510
		if ( $res instanceof ORAResult ) {
511
			$res->seek( $row );
512
		} else {
513
			$res->result->seek( $row );
514
		}
515
	}
516
517 View Code Duplication
	function lastError() {
518
		if ( $this->mConn === false ) {
519
			$e = oci_error();
520
		} else {
521
			$e = oci_error( $this->mConn );
522
		}
523
524
		return $e['message'];
525
	}
526
527 View Code Duplication
	function lastErrno() {
528
		if ( $this->mConn === false ) {
529
			$e = oci_error();
530
		} else {
531
			$e = oci_error( $this->mConn );
532
		}
533
534
		return $e['code'];
535
	}
536
537
	function affectedRows() {
538
		return $this->mAffectedRows;
539
	}
540
541
	/**
542
	 * Returns information about an index
543
	 * If errors are explicitly ignored, returns NULL on failure
544
	 * @param string $table
545
	 * @param string $index
546
	 * @param string $fname
547
	 * @return bool
548
	 */
549
	function indexInfo( $table, $index, $fname = __METHOD__ ) {
550
		return false;
551
	}
552
553
	function indexUnique( $table, $index, $fname = __METHOD__ ) {
554
		return false;
555
	}
556
557
	function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
558
		if ( !count( $a ) ) {
559
			return true;
560
		}
561
562
		if ( !is_array( $options ) ) {
563
			$options = [ $options ];
564
		}
565
566
		if ( in_array( 'IGNORE', $options ) ) {
567
			$this->ignoreDupValOnIndex = true;
568
		}
569
570
		if ( !is_array( reset( $a ) ) ) {
571
			$a = [ $a ];
572
		}
573
574
		foreach ( $a as &$row ) {
575
			$this->insertOneRow( $table, $row, $fname );
576
		}
577
		$retVal = true;
578
579
		if ( in_array( 'IGNORE', $options ) ) {
580
			$this->ignoreDupValOnIndex = false;
581
		}
582
583
		return $retVal;
584
	}
585
586
	private function fieldBindStatement( $table, $col, &$val, $includeCol = false ) {
587
		$col_info = $this->fieldInfoMulti( $table, $col );
588
		$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
0 ignored issues
show
Bug introduced by
The method type does only exist in ORAField, but not in ORAResult.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
589
590
		$bind = '';
591
		if ( is_numeric( $col ) ) {
592
			$bind = $val;
593
			$val = null;
594
595
			return $bind;
596
		} elseif ( $includeCol ) {
597
			$bind = "$col = ";
598
		}
599
600
		if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) {
601
			$val = null;
602
		}
603
604
		if ( $val === 'NULL' ) {
605
			$val = null;
606
		}
607
608
		if ( $val === null ) {
609
			if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
0 ignored issues
show
Bug introduced by
The method isNullable does only exist in ORAField, but not in ORAResult.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
Bug introduced by
The method defaultValue does only exist in ORAField, but not in ORAResult.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
610
				$bind .= 'DEFAULT';
611
			} else {
612
				$bind .= 'NULL';
613
			}
614
		} else {
615
			$bind .= ':' . $col;
616
		}
617
618
		return $bind;
619
	}
620
621
	/**
622
	 * @param string $table
623
	 * @param array $row
624
	 * @param string $fname
625
	 * @return bool
626
	 * @throws DBUnexpectedError
627
	 */
628
	private function insertOneRow( $table, $row, $fname ) {
629
		global $wgContLang;
630
631
		$table = $this->tableName( $table );
632
		// "INSERT INTO tables (a, b, c)"
633
		$sql = "INSERT INTO " . $table . " (" . implode( ',', array_keys( $row ) ) . ')';
634
		$sql .= " VALUES (";
635
636
		// for each value, append ":key"
637
		$first = true;
638
		foreach ( $row as $col => &$val ) {
639
			if ( !$first ) {
640
				$sql .= ', ';
641
			} else {
642
				$first = false;
643
			}
644
			if ( $this->isQuotedIdentifier( $val ) ) {
645
				$sql .= $this->removeIdentifierQuotes( $val );
646
				unset( $row[$col] );
647
			} else {
648
				$sql .= $this->fieldBindStatement( $table, $col, $val );
649
			}
650
		}
651
		$sql .= ')';
652
653
		$this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
654 View Code Duplication
		if ( $stmt === false ) {
655
			$e = oci_error( $this->mConn );
656
			$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
657
658
			return false;
659
		}
660
		foreach ( $row as $col => &$val ) {
661
			$col_info = $this->fieldInfoMulti( $table, $col );
662
			$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
0 ignored issues
show
Bug introduced by
The method type does only exist in ORAField, but not in ORAResult.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
663
664
			if ( $val === null ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
665
				// do nothing ... null was inserted in statement creation
666
			} elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
667
				if ( is_object( $val ) ) {
668
					$val = $val->fetch();
669
				}
670
671
				// backward compatibility
672
				if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
673
					$val = $this->getInfinity();
674
				}
675
676
				$val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
677
				if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
678
					$e = oci_error( $stmt );
679
					$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
680
681
					return false;
682
				}
683 View Code Duplication
			} else {
684
				/** @var OCI_Lob[] $lob */
685
				$lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$lob was never initialized. Although not strictly required by PHP, it is generally a good practice to add $lob = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
686
				if ( $lob[$col] === false ) {
687
					$e = oci_error( $stmt );
688
					throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
689
				}
690
691
				if ( is_object( $val ) ) {
692
					$val = $val->fetch();
693
				}
694
695
				if ( $col_type == 'BLOB' ) {
696
					$lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
697
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_BLOB );
698
				} else {
699
					$lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
700
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
701
				}
702
			}
703
		}
704
705
		MediaWiki\suppressWarnings();
706
707 View Code Duplication
		if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
708
			$e = oci_error( $stmt );
709
			if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
710
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
711
712
				return false;
713
			} else {
714
				$this->mAffectedRows = oci_num_rows( $stmt );
715
			}
716
		} else {
717
			$this->mAffectedRows = oci_num_rows( $stmt );
718
		}
719
720
		MediaWiki\restoreWarnings();
721
722
		if ( isset( $lob ) ) {
723
			foreach ( $lob as $lob_v ) {
724
				$lob_v->free();
725
			}
726
		}
727
728
		if ( !$this->mTrxLevel ) {
729
			oci_commit( $this->mConn );
730
		}
731
732
		return oci_free_statement( $stmt );
733
	}
734
735
	function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
736
		$insertOptions = [], $selectOptions = []
737
	) {
738
		$destTable = $this->tableName( $destTable );
739
		if ( !is_array( $selectOptions ) ) {
740
			$selectOptions = [ $selectOptions ];
741
		}
742
		list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) =
743
			$this->makeSelectOptions( $selectOptions );
744 View Code Duplication
		if ( is_array( $srcTable ) ) {
745
			$srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
746
		} else {
747
			$srcTable = $this->tableName( $srcTable );
748
		}
749
750
		$sequenceData = $this->getSequenceData( $destTable );
751 View Code Duplication
		if ( $sequenceData !== false &&
752
			!isset( $varMap[$sequenceData['column']] )
753
		) {
754
			$varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
755
		}
756
757
		// count-alias subselect fields to avoid abigious definition errors
758
		$i = 0;
759
		foreach ( $varMap as &$val ) {
760
			$val = $val . ' field' . ( $i++ );
761
		}
762
763
		$sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
764
			" SELECT $startOpts " . implode( ',', $varMap ) .
765
			" FROM $srcTable $useIndex $ignoreIndex ";
766
		if ( $conds != '*' ) {
767
			$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
768
		}
769
		$sql .= " $tailOpts";
770
771
		if ( in_array( 'IGNORE', $insertOptions ) ) {
772
			$this->ignoreDupValOnIndex = true;
773
		}
774
775
		$retval = $this->query( $sql, $fname );
776
777
		if ( in_array( 'IGNORE', $insertOptions ) ) {
778
			$this->ignoreDupValOnIndex = false;
779
		}
780
781
		return $retval;
782
	}
783
784
	public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
785
		$fname = __METHOD__
786
	) {
787
		if ( !count( $rows ) ) {
788
			return true; // nothing to do
789
		}
790
791
		if ( !is_array( reset( $rows ) ) ) {
792
			$rows = [ $rows ];
793
		}
794
795
		$sequenceData = $this->getSequenceData( $table );
796 View Code Duplication
		if ( $sequenceData !== false ) {
797
			// add sequence column to each list of columns, when not set
798
			foreach ( $rows as &$row ) {
799
				if ( !isset( $row[$sequenceData['column']] ) ) {
800
					$row[$sequenceData['column']] =
801
						$this->addIdentifierQuotes( 'GET_SEQUENCE_VALUE(\'' .
802
							$sequenceData['sequence'] . '\')' );
803
				}
804
			}
805
		}
806
807
		return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname );
808
	}
809
810
	function tableName( $name, $format = 'quoted' ) {
811
		/*
812
		Replace reserved words with better ones
813
		Using uppercase because that's the only way Oracle can handle
814
		quoted tablenames
815
		*/
816
		switch ( $name ) {
817
			case 'user':
818
				$name = 'MWUSER';
819
				break;
820
			case 'text':
821
				$name = 'PAGECONTENT';
822
				break;
823
		}
824
825
		return strtoupper( parent::tableName( $name, $format ) );
826
	}
827
828
	function tableNameInternal( $name ) {
829
		$name = $this->tableName( $name );
830
831
		return preg_replace( '/.*\.(.*)/', '$1', $name );
832
	}
833
834
	/**
835
	 * Return the next in a sequence, save the value for retrieval via insertId()
836
	 *
837
	 * @param string $seqName
838
	 * @return null|int
839
	 */
840
	function nextSequenceValue( $seqName ) {
841
		$res = $this->query( "SELECT $seqName.nextval FROM dual" );
842
		$row = $this->fetchRow( $res );
0 ignored issues
show
Bug introduced by
It seems like $res defined by $this->query("SELECT {$s...me}.nextval FROM dual") on line 841 can also be of type boolean; however, DatabaseOracle::fetchRow() does only seem to accept object<ResultWrapper>|object<ORAResult>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
843
		$this->mInsertId = $row[0];
844
845
		return $this->mInsertId;
846
	}
847
848
	/**
849
	 * Return sequence_name if table has a sequence
850
	 *
851
	 * @param string $table
852
	 * @return bool
853
	 */
854
	private function getSequenceData( $table ) {
855
		if ( $this->sequenceData == null ) {
856
			$result = $this->doQuery( "SELECT lower(asq.sequence_name),
857
				lower(atc.table_name),
858
				lower(atc.column_name)
859
			FROM all_sequences asq, all_tab_columns atc
860
			WHERE decode(
861
					atc.table_name,
862
					'{$this->mTablePrefix}MWUSER',
863
					'{$this->mTablePrefix}USER',
864
					atc.table_name
865
				) || '_' ||
866
				atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
867
				AND asq.sequence_owner = upper('{$this->mDBname}')
868
				AND atc.owner = upper('{$this->mDBname}')" );
869
870
			while ( ( $row = $result->fetchRow() ) !== false ) {
871
				$this->sequenceData[$row[1]] = [
872
					'sequence' => $row[0],
873
					'column' => $row[2]
874
				];
875
			}
876
		}
877
		$table = strtolower( $this->removeIdentifierQuotes( $this->tableName( $table ) ) );
878
879
		return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
880
	}
881
882
	/**
883
	 * Returns the size of a text field, or -1 for "unlimited"
884
	 *
885
	 * @param string $table
886
	 * @param string $field
887
	 * @return mixed
888
	 */
889
	function textFieldSize( $table, $field ) {
890
		$fieldInfoData = $this->fieldInfo( $table, $field );
891
892
		return $fieldInfoData->maxLength();
893
	}
894
895
	function limitResult( $sql, $limit, $offset = false ) {
896
		if ( $offset === false ) {
897
			$offset = 0;
898
		}
899
900
		return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
901
	}
902
903
	function encodeBlob( $b ) {
904
		return new Blob( $b );
905
	}
906
907
	function decodeBlob( $b ) {
908
		if ( $b instanceof Blob ) {
909
			$b = $b->fetch();
910
		}
911
912
		return $b;
913
	}
914
915
	function unionQueries( $sqls, $all ) {
916
		$glue = ' UNION ALL ';
917
918
		return 'SELECT * ' . ( $all ? '' : '/* UNION_UNIQUE */ ' ) .
919
			'FROM (' . implode( $glue, $sqls ) . ')';
920
	}
921
922
	function wasDeadlock() {
923
		return $this->lastErrno() == 'OCI-00060';
924
	}
925
926
	function duplicateTableStructure( $oldName, $newName, $temporary = false,
927
		$fname = __METHOD__
928
	) {
929
		$temporary = $temporary ? 'TRUE' : 'FALSE';
930
931
		$newName = strtoupper( $newName );
932
		$oldName = strtoupper( $oldName );
933
934
		$tabName = substr( $newName, strlen( $this->mTablePrefix ) );
935
		$oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
936
		$newPrefix = strtoupper( $this->mTablePrefix );
937
938
		return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
939
			"'$oldPrefix', '$newPrefix', $temporary ); END;" );
940
	}
941
942
	function listTables( $prefix = null, $fname = __METHOD__ ) {
943
		$listWhere = '';
944
		if ( !empty( $prefix ) ) {
945
			$listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
946
		}
947
948
		$owner = strtoupper( $this->mDBname );
949
		$result = $this->doQuery( "SELECT table_name FROM all_tables " .
950
			"WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
951
952
		// dirty code ... i know
953
		$endArray = [];
954
		$endArray[] = strtoupper( $prefix . 'MWUSER' );
955
		$endArray[] = strtoupper( $prefix . 'PAGE' );
956
		$endArray[] = strtoupper( $prefix . 'IMAGE' );
957
		$fixedOrderTabs = $endArray;
958
		while ( ( $row = $result->fetchRow() ) !== false ) {
959
			if ( !in_array( $row['table_name'], $fixedOrderTabs ) ) {
960
				$endArray[] = $row['table_name'];
961
			}
962
		}
963
964
		return $endArray;
965
	}
966
967
	public function dropTable( $tableName, $fName = __METHOD__ ) {
968
		$tableName = $this->tableName( $tableName );
969
		if ( !$this->tableExists( $tableName ) ) {
970
			return false;
971
		}
972
973
		return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
974
	}
975
976
	function timestamp( $ts = 0 ) {
977
		return wfTimestamp( TS_ORACLE, $ts );
978
	}
979
980
	/**
981
	 * Return aggregated value function call
982
	 *
983
	 * @param array $valuedata
984
	 * @param string $valuename
985
	 * @return mixed
986
	 */
987
	public function aggregateValue( $valuedata, $valuename = 'value' ) {
988
		return $valuedata;
989
	}
990
991
	/**
992
	 * @return string Wikitext of a link to the server software's web site
993
	 */
994
	public function getSoftwareLink() {
995
		return '[{{int:version-db-oracle-url}} Oracle]';
996
	}
997
998
	/**
999
	 * @return string Version information from the database
1000
	 */
1001
	function getServerVersion() {
1002
		// better version number, fallback on driver
1003
		$rset = $this->doQuery(
1004
			'SELECT version FROM product_component_version ' .
1005
				'WHERE UPPER(product) LIKE \'ORACLE DATABASE%\''
1006
		);
1007
		$row = $rset->fetchRow();
1008
		if ( !$row ) {
1009
			return oci_server_version( $this->mConn );
1010
		}
1011
1012
		return $row['version'];
1013
	}
1014
1015
	/**
1016
	 * Query whether a given index exists
1017
	 * @param string $table
1018
	 * @param string $index
1019
	 * @param string $fname
1020
	 * @return bool
1021
	 */
1022
	function indexExists( $table, $index, $fname = __METHOD__ ) {
1023
		$table = $this->tableName( $table );
1024
		$table = strtoupper( $this->removeIdentifierQuotes( $table ) );
1025
		$index = strtoupper( $index );
1026
		$owner = strtoupper( $this->mDBname );
1027
		$sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
1028
		$res = $this->doQuery( $sql );
1029
		if ( $res ) {
1030
			$count = $res->numRows();
1031
			$res->free();
1032
		} else {
1033
			$count = 0;
1034
		}
1035
1036
		return $count != 0;
1037
	}
1038
1039
	/**
1040
	 * Query whether a given table exists (in the given schema, or the default mw one if not given)
1041
	 * @param string $table
1042
	 * @param string $fname
1043
	 * @return bool
1044
	 */
1045
	function tableExists( $table, $fname = __METHOD__ ) {
1046
		$table = $this->tableName( $table );
1047
		$table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
1048
		$owner = $this->addQuotes( strtoupper( $this->mDBname ) );
1049
		$sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
1050
		$res = $this->doQuery( $sql );
1051
		if ( $res && $res->numRows() > 0 ) {
1052
			$exists = true;
1053
		} else {
1054
			$exists = false;
1055
		}
1056
1057
		$res->free();
1058
1059
		return $exists;
1060
	}
1061
1062
	/**
1063
	 * Function translates mysql_fetch_field() functionality on ORACLE.
1064
	 * Caching is present for reducing query time.
1065
	 * For internal calls. Use fieldInfo for normal usage.
1066
	 * Returns false if the field doesn't exist
1067
	 *
1068
	 * @param array|string $table
1069
	 * @param string $field
1070
	 * @return ORAField|ORAResult
1071
	 */
1072
	private function fieldInfoMulti( $table, $field ) {
1073
		$field = strtoupper( $field );
1074
		if ( is_array( $table ) ) {
1075
			$table = array_map( [ &$this, 'tableNameInternal' ], $table );
1076
			$tableWhere = 'IN (';
1077
			foreach ( $table as &$singleTable ) {
1078
				$singleTable = $this->removeIdentifierQuotes( $singleTable );
1079
				if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
1080
					return $this->mFieldInfoCache["$singleTable.$field"];
1081
				}
1082
				$tableWhere .= '\'' . $singleTable . '\',';
1083
			}
1084
			$tableWhere = rtrim( $tableWhere, ',' ) . ')';
1085
		} else {
1086
			$table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
1087
			if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
1088
				return $this->mFieldInfoCache["$table.$field"];
1089
			}
1090
			$tableWhere = '= \'' . $table . '\'';
1091
		}
1092
1093
		$fieldInfoStmt = oci_parse(
1094
			$this->mConn,
1095
			'SELECT * FROM wiki_field_info_full WHERE table_name ' .
1096
				$tableWhere . ' and column_name = \'' . $field . '\''
1097
		);
1098 View Code Duplication
		if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
1099
			$e = oci_error( $fieldInfoStmt );
1100
			$this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
1101
1102
			return false;
1103
		}
1104
		$res = new ORAResult( $this, $fieldInfoStmt );
1105
		if ( $res->numRows() == 0 ) {
1106
			if ( is_array( $table ) ) {
1107
				foreach ( $table as &$singleTable ) {
1108
					$this->mFieldInfoCache["$singleTable.$field"] = false;
1109
				}
1110
			} else {
1111
				$this->mFieldInfoCache["$table.$field"] = false;
1112
			}
1113
			$fieldInfoTemp = null;
1114
		} else {
1115
			$fieldInfoTemp = new ORAField( $res->fetchRow() );
1116
			$table = $fieldInfoTemp->tableName();
1117
			$this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
1118
		}
1119
		$res->free();
1120
1121
		return $fieldInfoTemp;
1122
	}
1123
1124
	/**
1125
	 * @throws DBUnexpectedError
1126
	 * @param string $table
1127
	 * @param string $field
1128
	 * @return ORAField
1129
	 */
1130
	function fieldInfo( $table, $field ) {
1131
		if ( is_array( $table ) ) {
1132
			throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
1133
		}
1134
1135
		return $this->fieldInfoMulti( $table, $field );
1136
	}
1137
1138
	protected function doBegin( $fname = __METHOD__ ) {
1139
		$this->mTrxLevel = 1;
1140
		$this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
1141
	}
1142
1143
	protected function doCommit( $fname = __METHOD__ ) {
1144
		if ( $this->mTrxLevel ) {
1145
			$ret = oci_commit( $this->mConn );
1146
			if ( !$ret ) {
1147
				throw new DBUnexpectedError( $this, $this->lastError() );
1148
			}
1149
			$this->mTrxLevel = 0;
1150
			$this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
1151
		}
1152
	}
1153
1154
	protected function doRollback( $fname = __METHOD__ ) {
1155
		if ( $this->mTrxLevel ) {
1156
			oci_rollback( $this->mConn );
1157
			$this->mTrxLevel = 0;
1158
			$this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
1159
		}
1160
	}
1161
1162
	/**
1163
	 * defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
1164
	 *
1165
	 * @param resource $fp
1166
	 * @param bool|string $lineCallback
1167
	 * @param bool|callable $resultCallback
1168
	 * @param string $fname
1169
	 * @param bool|callable $inputCallback
1170
	 * @return bool|string
1171
	 */
1172
	function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
1173
		$fname = __METHOD__, $inputCallback = false ) {
1174
		$cmd = '';
1175
		$done = false;
1176
		$dollarquote = false;
1177
1178
		$replacements = [];
1179
1180
		while ( !feof( $fp ) ) {
1181
			if ( $lineCallback ) {
1182
				call_user_func( $lineCallback );
1183
			}
1184
			$line = trim( fgets( $fp, 1024 ) );
1185
			$sl = strlen( $line ) - 1;
1186
1187
			if ( $sl < 0 ) {
1188
				continue;
1189
			}
1190
			if ( '-' == $line[0] && '-' == $line[1] ) {
1191
				continue;
1192
			}
1193
1194
			// Allow dollar quoting for function declarations
1195
			if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) {
1196
				if ( $dollarquote ) {
1197
					$dollarquote = false;
1198
					$line = str_replace( '/*$mw$*/', '', $line ); // remove dollarquotes
1199
					$done = true;
1200
				} else {
1201
					$dollarquote = true;
1202
				}
1203
			} elseif ( !$dollarquote ) {
1204
				if ( ';' == $line[$sl] && ( $sl < 2 || ';' != $line[$sl - 1] ) ) {
1205
					$done = true;
1206
					$line = substr( $line, 0, $sl );
1207
				}
1208
			}
1209
1210
			if ( $cmd != '' ) {
1211
				$cmd .= ' ';
1212
			}
1213
			$cmd .= "$line\n";
1214
1215
			if ( $done ) {
1216
				$cmd = str_replace( ';;', ";", $cmd );
1217
				if ( strtolower( substr( $cmd, 0, 6 ) ) == 'define' ) {
1218
					if ( preg_match( '/^define\s*([^\s=]*)\s*=\s*\'\{\$([^\}]*)\}\'/', $cmd, $defines ) ) {
1219
						$replacements[$defines[2]] = $defines[1];
1220
					}
1221
				} else {
1222
					foreach ( $replacements as $mwVar => $scVar ) {
1223
						$cmd = str_replace( '&' . $scVar . '.', '`{$' . $mwVar . '}`', $cmd );
1224
					}
1225
1226
					$cmd = $this->replaceVars( $cmd );
1227
					if ( $inputCallback ) {
1228
						call_user_func( $inputCallback, $cmd );
1229
					}
1230
					$res = $this->doQuery( $cmd );
1231
					if ( $resultCallback ) {
1232
						call_user_func( $resultCallback, $res, $this );
1233
					}
1234
1235
					if ( false === $res ) {
1236
						$err = $this->lastError();
1237
1238
						return "Query \"{$cmd}\" failed with error code \"$err\".\n";
1239
					}
1240
				}
1241
1242
				$cmd = '';
1243
				$done = false;
1244
			}
1245
		}
1246
1247
		return true;
1248
	}
1249
1250
	function selectDB( $db ) {
1251
		$this->mDBname = $db;
1252
		if ( $db == null || $db == $this->mUser ) {
1253
			return true;
1254
		}
1255
		$sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
1256
		$stmt = oci_parse( $this->mConn, $sql );
1257
		MediaWiki\suppressWarnings();
1258
		$success = oci_execute( $stmt );
1259
		MediaWiki\restoreWarnings();
1260
		if ( !$success ) {
1261
			$e = oci_error( $stmt );
1262
			if ( $e['code'] != '1435' ) {
1263
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1264
			}
1265
1266
			return false;
1267
		}
1268
1269
		return true;
1270
	}
1271
1272
	function strencode( $s ) {
1273
		return str_replace( "'", "''", $s );
1274
	}
1275
1276
	function addQuotes( $s ) {
1277
		global $wgContLang;
1278
		if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
1279
			$s = $wgContLang->checkTitleEncoding( $s );
1280
		}
1281
1282
		return "'" . $this->strencode( $s ) . "'";
1283
	}
1284
1285
	public function addIdentifierQuotes( $s ) {
1286
		if ( !$this->getFlag( DBO_DDLMODE ) ) {
1287
			$s = '/*Q*/' . $s;
1288
		}
1289
1290
		return $s;
1291
	}
1292
1293
	public function removeIdentifierQuotes( $s ) {
1294
		return strpos( $s, '/*Q*/' ) === false ? $s : substr( $s, 5 );
1295
	}
1296
1297
	public function isQuotedIdentifier( $s ) {
1298
		return strpos( $s, '/*Q*/' ) !== false;
1299
	}
1300
1301
	private function wrapFieldForWhere( $table, &$col, &$val ) {
1302
		global $wgContLang;
1303
1304
		$col_info = $this->fieldInfoMulti( $table, $col );
1305
		$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
0 ignored issues
show
Bug introduced by
The method type does only exist in ORAField, but not in ORAResult.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1306
		if ( $col_type == 'CLOB' ) {
1307
			$col = 'TO_CHAR(' . $col . ')';
1308
			$val = $wgContLang->checkTitleEncoding( $val );
1309
		} elseif ( $col_type == 'VARCHAR2' ) {
1310
			$val = $wgContLang->checkTitleEncoding( $val );
1311
		}
1312
	}
1313
1314
	private function wrapConditionsForWhere( $table, $conds, $parentCol = null ) {
1315
		$conds2 = [];
1316
		foreach ( $conds as $col => $val ) {
1317
			if ( is_array( $val ) ) {
1318
				$conds2[$col] = $this->wrapConditionsForWhere( $table, $val, $col );
1319
			} else {
1320
				if ( is_numeric( $col ) && $parentCol != null ) {
1321
					$this->wrapFieldForWhere( $table, $parentCol, $val );
1322
				} else {
1323
					$this->wrapFieldForWhere( $table, $col, $val );
1324
				}
1325
				$conds2[$col] = $val;
1326
			}
1327
		}
1328
1329
		return $conds2;
1330
	}
1331
1332
	function selectRow( $table, $vars, $conds, $fname = __METHOD__,
1333
		$options = [], $join_conds = []
1334
	) {
1335
		if ( is_array( $conds ) ) {
1336
			$conds = $this->wrapConditionsForWhere( $table, $conds );
1337
		}
1338
1339
		return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
1340
	}
1341
1342
	/**
1343
	 * Returns an optional USE INDEX clause to go after the table, and a
1344
	 * string to go at the end of the query
1345
	 *
1346
	 * @param array $options An associative array of options to be turned into
1347
	 *   an SQL query, valid keys are listed in the function.
1348
	 * @return array
1349
	 */
1350
	function makeSelectOptions( $options ) {
1351
		$preLimitTail = $postLimitTail = '';
1352
		$startOpts = '';
1353
1354
		$noKeyOptions = [];
1355
		foreach ( $options as $key => $option ) {
1356
			if ( is_numeric( $key ) ) {
1357
				$noKeyOptions[$option] = true;
1358
			}
1359
		}
1360
1361
		$preLimitTail .= $this->makeGroupByWithHaving( $options );
1362
1363
		$preLimitTail .= $this->makeOrderBy( $options );
1364
1365
		if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1366
			$postLimitTail .= ' FOR UPDATE';
1367
		}
1368
1369
		if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1370
			$startOpts .= 'DISTINCT';
1371
		}
1372
1373 View Code Duplication
		if ( isset( $options['USE INDEX'] ) && !is_array( $options['USE INDEX'] ) ) {
1374
			$useIndex = $this->useIndexClause( $options['USE INDEX'] );
1375
		} else {
1376
			$useIndex = '';
1377
		}
1378
1379 View Code Duplication
		if ( isset( $options['IGNORE INDEX'] ) && !is_array( $options['IGNORE INDEX'] ) ) {
1380
			$ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
1381
		} else {
1382
			$ignoreIndex = '';
1383
		}
1384
1385
		return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1386
	}
1387
1388
	public function delete( $table, $conds, $fname = __METHOD__ ) {
1389
		if ( is_array( $conds ) ) {
1390
			$conds = $this->wrapConditionsForWhere( $table, $conds );
1391
		}
1392
		// a hack for deleting pages, users and images (which have non-nullable FKs)
1393
		// all deletions on these tables have transactions so final failure rollbacks these updates
1394
		$table = $this->tableName( $table );
1395
		if ( $table == $this->tableName( 'user' ) ) {
1396
			$this->update( 'archive', [ 'ar_user' => 0 ],
1397
				[ 'ar_user' => $conds['user_id'] ], $fname );
1398
			$this->update( 'ipblocks', [ 'ipb_user' => 0 ],
1399
				[ 'ipb_user' => $conds['user_id'] ], $fname );
1400
			$this->update( 'image', [ 'img_user' => 0 ],
1401
				[ 'img_user' => $conds['user_id'] ], $fname );
1402
			$this->update( 'oldimage', [ 'oi_user' => 0 ],
1403
				[ 'oi_user' => $conds['user_id'] ], $fname );
1404
			$this->update( 'filearchive', [ 'fa_deleted_user' => 0 ],
1405
				[ 'fa_deleted_user' => $conds['user_id'] ], $fname );
1406
			$this->update( 'filearchive', [ 'fa_user' => 0 ],
1407
				[ 'fa_user' => $conds['user_id'] ], $fname );
1408
			$this->update( 'uploadstash', [ 'us_user' => 0 ],
1409
				[ 'us_user' => $conds['user_id'] ], $fname );
1410
			$this->update( 'recentchanges', [ 'rc_user' => 0 ],
1411
				[ 'rc_user' => $conds['user_id'] ], $fname );
1412
			$this->update( 'logging', [ 'log_user' => 0 ],
1413
				[ 'log_user' => $conds['user_id'] ], $fname );
1414
		} elseif ( $table == $this->tableName( 'image' ) ) {
1415
			$this->update( 'oldimage', [ 'oi_name' => 0 ],
1416
				[ 'oi_name' => $conds['img_name'] ], $fname );
1417
		}
1418
1419
		return parent::delete( $table, $conds, $fname );
1420
	}
1421
1422
	/**
1423
	 * @param string $table
1424
	 * @param array $values
1425
	 * @param array $conds
1426
	 * @param string $fname
1427
	 * @param array $options
1428
	 * @return bool
1429
	 * @throws DBUnexpectedError
1430
	 */
1431
	function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
1432
		global $wgContLang;
1433
1434
		$table = $this->tableName( $table );
1435
		$opts = $this->makeUpdateOptions( $options );
1436
		$sql = "UPDATE $opts $table SET ";
1437
1438
		$first = true;
1439
		foreach ( $values as $col => &$val ) {
1440
			$sqlSet = $this->fieldBindStatement( $table, $col, $val, true );
1441
1442
			if ( !$first ) {
1443
				$sqlSet = ', ' . $sqlSet;
1444
			} else {
1445
				$first = false;
1446
			}
1447
			$sql .= $sqlSet;
1448
		}
1449
1450
		if ( $conds !== [] && $conds !== '*' ) {
1451
			$conds = $this->wrapConditionsForWhere( $table, $conds );
1452
			$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
1453
		}
1454
1455
		$this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
1456 View Code Duplication
		if ( $stmt === false ) {
1457
			$e = oci_error( $this->mConn );
1458
			$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1459
1460
			return false;
1461
		}
1462
		foreach ( $values as $col => &$val ) {
1463
			$col_info = $this->fieldInfoMulti( $table, $col );
1464
			$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
0 ignored issues
show
Bug introduced by
The method type does only exist in ORAField, but not in ORAResult.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1465
1466
			if ( $val === null ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
1467
				// do nothing ... null was inserted in statement creation
1468
			} elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
1469
				if ( is_object( $val ) ) {
1470
					$val = $val->getData();
1471
				}
1472
1473
				if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
1474
					$val = '31-12-2030 12:00:00.000000';
1475
				}
1476
1477
				$val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
1478
				if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
1479
					$e = oci_error( $stmt );
1480
					$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1481
1482
					return false;
1483
				}
1484 View Code Duplication
			} else {
1485
				/** @var OCI_Lob[] $lob */
1486
				$lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$lob was never initialized. Although not strictly required by PHP, it is generally a good practice to add $lob = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1487
				if ( $lob[$col] === false ) {
1488
					$e = oci_error( $stmt );
1489
					throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
1490
				}
1491
1492
				if ( is_object( $val ) ) {
1493
					$val = $val->getData();
1494
				}
1495
1496
				if ( $col_type == 'BLOB' ) {
1497
					$lob[$col]->writeTemporary( $val );
1498
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, SQLT_BLOB );
1499
				} else {
1500
					$lob[$col]->writeTemporary( $val );
1501
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
1502
				}
1503
			}
1504
		}
1505
1506
		MediaWiki\suppressWarnings();
1507
1508 View Code Duplication
		if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
1509
			$e = oci_error( $stmt );
1510
			if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
1511
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1512
1513
				return false;
1514
			} else {
1515
				$this->mAffectedRows = oci_num_rows( $stmt );
1516
			}
1517
		} else {
1518
			$this->mAffectedRows = oci_num_rows( $stmt );
1519
		}
1520
1521
		MediaWiki\restoreWarnings();
1522
1523
		if ( isset( $lob ) ) {
1524
			foreach ( $lob as $lob_v ) {
1525
				$lob_v->free();
1526
			}
1527
		}
1528
1529
		if ( !$this->mTrxLevel ) {
1530
			oci_commit( $this->mConn );
1531
		}
1532
1533
		return oci_free_statement( $stmt );
1534
	}
1535
1536
	function bitNot( $field ) {
1537
		// expecting bit-fields smaller than 4bytes
1538
		return 'BITNOT(' . $field . ')';
1539
	}
1540
1541
	function bitAnd( $fieldLeft, $fieldRight ) {
1542
		return 'BITAND(' . $fieldLeft . ', ' . $fieldRight . ')';
1543
	}
1544
1545
	function bitOr( $fieldLeft, $fieldRight ) {
1546
		return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
1547
	}
1548
1549
	function getDBname() {
1550
		return $this->mDBname;
1551
	}
1552
1553
	function getServer() {
1554
		return $this->mServer;
1555
	}
1556
1557 View Code Duplication
	public function buildGroupConcatField(
1558
		$delim, $table, $field, $conds = '', $join_conds = []
1559
	) {
1560
		$fld = "LISTAGG($field," . $this->addQuotes( $delim ) . ") WITHIN GROUP (ORDER BY $field)";
1561
1562
		return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1563
	}
1564
1565
	/**
1566
	 * @param string $field Field or column to cast
1567
	 * @return string
1568
	 * @since 1.28
1569
	 */
1570
	public function buildStringCast( $field ) {
1571
		return 'CAST ( ' . $field . ' AS VARCHAR2 )';
1572
	}
1573
1574
	public function getSearchEngine() {
1575
		return 'SearchOracle';
1576
	}
1577
1578
	public function getInfinity() {
1579
		return '31-12-2030 12:00:00.000000';
1580
	}
1581
}
1582