Completed
Branch master (62f6c6)
by
unknown
21:31
created

DatabaseOracle   F

Complexity

Total Complexity 259

Size/Duplication

Total Lines 1366
Duplicated Lines 10.1 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 138
loc 1366
rs 3.9999
c 0
b 0
f 0
wmc 259
lcom 1
cbo 11

78 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 insertSelect() 10 47 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
C makeSelectOptions() 5 31 8
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 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|resource $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
The method free cannot be called on $res (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
443
	}
444
445
	/**
446
	 * @param ResultWrapper|stdClass $res
447
	 * @return mixed
448
	 */
449
	function fetchObject( $res ) {
450
		if ( $res instanceof ResultWrapper ) {
451
			$res = $res->result;
452
		}
453
454
		return $res->fetchObject();
455
	}
456
457
	function fetchRow( $res ) {
458
		if ( $res instanceof ResultWrapper ) {
459
			$res = $res->result;
460
		}
461
462
		return $res->fetchRow();
0 ignored issues
show
Bug introduced by
The method fetchRow cannot be called on $res (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
463
	}
464
465
	function numRows( $res ) {
466
		if ( $res instanceof ResultWrapper ) {
467
			$res = $res->result;
468
		}
469
470
		return $res->numRows();
471
	}
472
473
	function numFields( $res ) {
474
		if ( $res instanceof ResultWrapper ) {
475
			$res = $res->result;
476
		}
477
478
		return $res->numFields();
479
	}
480
481
	function fieldName( $stmt, $n ) {
482
		return oci_field_name( $stmt, $n );
483
	}
484
485
	/**
486
	 * This must be called after nextSequenceVal
487
	 * @return null|int
488
	 */
489
	function insertId() {
490
		return $this->mInsertId;
491
	}
492
493
	/**
494
	 * @param mixed $res
495
	 * @param int $row
496
	 */
497
	function dataSeek( $res, $row ) {
498
		if ( $res instanceof ORAResult ) {
499
			$res->seek( $row );
500
		} else {
501
			$res->result->seek( $row );
502
		}
503
	}
504
505 View Code Duplication
	function lastError() {
506
		if ( $this->mConn === false ) {
507
			$e = oci_error();
508
		} else {
509
			$e = oci_error( $this->mConn );
510
		}
511
512
		return $e['message'];
513
	}
514
515 View Code Duplication
	function lastErrno() {
516
		if ( $this->mConn === false ) {
517
			$e = oci_error();
518
		} else {
519
			$e = oci_error( $this->mConn );
520
		}
521
522
		return $e['code'];
523
	}
524
525
	function affectedRows() {
526
		return $this->mAffectedRows;
527
	}
528
529
	/**
530
	 * Returns information about an index
531
	 * If errors are explicitly ignored, returns NULL on failure
532
	 * @param string $table
533
	 * @param string $index
534
	 * @param string $fname
535
	 * @return bool
536
	 */
537
	function indexInfo( $table, $index, $fname = __METHOD__ ) {
538
		return false;
539
	}
540
541
	function indexUnique( $table, $index, $fname = __METHOD__ ) {
542
		return false;
543
	}
544
545
	function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
546
		if ( !count( $a ) ) {
547
			return true;
548
		}
549
550
		if ( !is_array( $options ) ) {
551
			$options = [ $options ];
552
		}
553
554
		if ( in_array( 'IGNORE', $options ) ) {
555
			$this->ignoreDupValOnIndex = true;
556
		}
557
558
		if ( !is_array( reset( $a ) ) ) {
559
			$a = [ $a ];
560
		}
561
562
		foreach ( $a as &$row ) {
563
			$this->insertOneRow( $table, $row, $fname );
564
		}
565
		$retVal = true;
566
567
		if ( in_array( 'IGNORE', $options ) ) {
568
			$this->ignoreDupValOnIndex = false;
569
		}
570
571
		return $retVal;
572
	}
573
574
	private function fieldBindStatement( $table, $col, &$val, $includeCol = false ) {
575
		$col_info = $this->fieldInfoMulti( $table, $col );
576
		$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...
577
578
		$bind = '';
579
		if ( is_numeric( $col ) ) {
580
			$bind = $val;
581
			$val = null;
582
583
			return $bind;
584
		} elseif ( $includeCol ) {
585
			$bind = "$col = ";
586
		}
587
588
		if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) {
589
			$val = null;
590
		}
591
592
		if ( $val === 'NULL' ) {
593
			$val = null;
594
		}
595
596
		if ( $val === null ) {
597
			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...
598
				$bind .= 'DEFAULT';
599
			} else {
600
				$bind .= 'NULL';
601
			}
602
		} else {
603
			$bind .= ':' . $col;
604
		}
605
606
		return $bind;
607
	}
608
609
	/**
610
	 * @param string $table
611
	 * @param array $row
612
	 * @param string $fname
613
	 * @return bool
614
	 * @throws DBUnexpectedError
615
	 */
616
	private function insertOneRow( $table, $row, $fname ) {
617
		global $wgContLang;
618
619
		$table = $this->tableName( $table );
620
		// "INSERT INTO tables (a, b, c)"
621
		$sql = "INSERT INTO " . $table . " (" . implode( ',', array_keys( $row ) ) . ')';
622
		$sql .= " VALUES (";
623
624
		// for each value, append ":key"
625
		$first = true;
626
		foreach ( $row as $col => &$val ) {
627
			if ( !$first ) {
628
				$sql .= ', ';
629
			} else {
630
				$first = false;
631
			}
632
			if ( $this->isQuotedIdentifier( $val ) ) {
633
				$sql .= $this->removeIdentifierQuotes( $val );
634
				unset( $row[$col] );
635
			} else {
636
				$sql .= $this->fieldBindStatement( $table, $col, $val );
637
			}
638
		}
639
		$sql .= ')';
640
641
		$this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
642 View Code Duplication
		if ( $stmt === false ) {
643
			$e = oci_error( $this->mConn );
644
			$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
645
646
			return false;
647
		}
648
		foreach ( $row as $col => &$val ) {
649
			$col_info = $this->fieldInfoMulti( $table, $col );
650
			$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...
651
652
			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...
653
				// do nothing ... null was inserted in statement creation
654
			} elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
655
				if ( is_object( $val ) ) {
656
					$val = $val->fetch();
657
				}
658
659
				// backward compatibility
660
				if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
661
					$val = $this->getInfinity();
662
				}
663
664
				$val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
665
				if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
666
					$e = oci_error( $stmt );
667
					$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
668
669
					return false;
670
				}
671 View Code Duplication
			} else {
672
				/** @var OCI_Lob[] $lob */
673
				$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...
674
				if ( $lob[$col] === false ) {
675
					$e = oci_error( $stmt );
676
					throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
677
				}
678
679
				if ( is_object( $val ) ) {
680
					$val = $val->fetch();
681
				}
682
683
				if ( $col_type == 'BLOB' ) {
684
					$lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
685
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_BLOB );
686
				} else {
687
					$lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
688
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
689
				}
690
			}
691
		}
692
693
		MediaWiki\suppressWarnings();
694
695 View Code Duplication
		if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
696
			$e = oci_error( $stmt );
697
			if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
698
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
699
700
				return false;
701
			} else {
702
				$this->mAffectedRows = oci_num_rows( $stmt );
703
			}
704
		} else {
705
			$this->mAffectedRows = oci_num_rows( $stmt );
706
		}
707
708
		MediaWiki\restoreWarnings();
709
710
		if ( isset( $lob ) ) {
711
			foreach ( $lob as $lob_v ) {
712
				$lob_v->free();
713
			}
714
		}
715
716
		if ( !$this->mTrxLevel ) {
717
			oci_commit( $this->mConn );
718
		}
719
720
		return oci_free_statement( $stmt );
721
	}
722
723
	function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
724
		$insertOptions = [], $selectOptions = []
725
	) {
726
		$destTable = $this->tableName( $destTable );
727
		if ( !is_array( $selectOptions ) ) {
728
			$selectOptions = [ $selectOptions ];
729
		}
730
		list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
731 View Code Duplication
		if ( is_array( $srcTable ) ) {
732
			$srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
733
		} else {
734
			$srcTable = $this->tableName( $srcTable );
735
		}
736
737
		$sequenceData = $this->getSequenceData( $destTable );
738 View Code Duplication
		if ( $sequenceData !== false &&
739
			!isset( $varMap[$sequenceData['column']] )
740
		) {
741
			$varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
742
		}
743
744
		// count-alias subselect fields to avoid abigious definition errors
745
		$i = 0;
746
		foreach ( $varMap as &$val ) {
747
			$val = $val . ' field' . ( $i++ );
748
		}
749
750
		$sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
751
			" SELECT $startOpts " . implode( ',', $varMap ) .
752
			" FROM $srcTable $useIndex ";
753
		if ( $conds != '*' ) {
754
			$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
755
		}
756
		$sql .= " $tailOpts";
757
758
		if ( in_array( 'IGNORE', $insertOptions ) ) {
759
			$this->ignoreDupValOnIndex = true;
760
		}
761
762
		$retval = $this->query( $sql, $fname );
763
764
		if ( in_array( 'IGNORE', $insertOptions ) ) {
765
			$this->ignoreDupValOnIndex = false;
766
		}
767
768
		return $retval;
769
	}
770
771
	public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
772
		$fname = __METHOD__
773
	) {
774
		if ( !count( $rows ) ) {
775
			return true; // nothing to do
776
		}
777
778
		if ( !is_array( reset( $rows ) ) ) {
779
			$rows = [ $rows ];
780
		}
781
782
		$sequenceData = $this->getSequenceData( $table );
783 View Code Duplication
		if ( $sequenceData !== false ) {
784
			// add sequence column to each list of columns, when not set
785
			foreach ( $rows as &$row ) {
786
				if ( !isset( $row[$sequenceData['column']] ) ) {
787
					$row[$sequenceData['column']] =
788
						$this->addIdentifierQuotes( 'GET_SEQUENCE_VALUE(\'' .
789
							$sequenceData['sequence'] . '\')' );
790
				}
791
			}
792
		}
793
794
		return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname );
795
	}
796
797
	function tableName( $name, $format = 'quoted' ) {
798
		/*
799
		Replace reserved words with better ones
800
		Using uppercase because that's the only way Oracle can handle
801
		quoted tablenames
802
		*/
803
		switch ( $name ) {
804
			case 'user':
805
				$name = 'MWUSER';
806
				break;
807
			case 'text':
808
				$name = 'PAGECONTENT';
809
				break;
810
		}
811
812
		return strtoupper( parent::tableName( $name, $format ) );
813
	}
814
815
	function tableNameInternal( $name ) {
816
		$name = $this->tableName( $name );
817
818
		return preg_replace( '/.*\.(.*)/', '$1', $name );
819
	}
820
821
	/**
822
	 * Return the next in a sequence, save the value for retrieval via insertId()
823
	 *
824
	 * @param string $seqName
825
	 * @return null|int
826
	 */
827
	function nextSequenceValue( $seqName ) {
828
		$res = $this->query( "SELECT $seqName.nextval FROM dual" );
829
		$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 828 can also be of type boolean; however, DatabaseOracle::fetchRow() does only seem to accept object<ResultWrapper>, 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...
830
		$this->mInsertId = $row[0];
831
832
		return $this->mInsertId;
833
	}
834
835
	/**
836
	 * Return sequence_name if table has a sequence
837
	 *
838
	 * @param string $table
839
	 * @return bool
840
	 */
841
	private function getSequenceData( $table ) {
842
		if ( $this->sequenceData == null ) {
843
			$result = $this->doQuery( "SELECT lower(asq.sequence_name),
844
				lower(atc.table_name),
845
				lower(atc.column_name)
846
			FROM all_sequences asq, all_tab_columns atc
847
			WHERE decode(
848
					atc.table_name,
849
					'{$this->mTablePrefix}MWUSER',
850
					'{$this->mTablePrefix}USER',
851
					atc.table_name
852
				) || '_' ||
853
				atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
854
				AND asq.sequence_owner = upper('{$this->mDBname}')
855
				AND atc.owner = upper('{$this->mDBname}')" );
856
857
			while ( ( $row = $result->fetchRow() ) !== false ) {
858
				$this->sequenceData[$row[1]] = [
859
					'sequence' => $row[0],
860
					'column' => $row[2]
861
				];
862
			}
863
		}
864
		$table = strtolower( $this->removeIdentifierQuotes( $this->tableName( $table ) ) );
865
866
		return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
867
	}
868
869
	/**
870
	 * Returns the size of a text field, or -1 for "unlimited"
871
	 *
872
	 * @param string $table
873
	 * @param string $field
874
	 * @return mixed
875
	 */
876
	function textFieldSize( $table, $field ) {
877
		$fieldInfoData = $this->fieldInfo( $table, $field );
878
879
		return $fieldInfoData->maxLength();
880
	}
881
882
	function limitResult( $sql, $limit, $offset = false ) {
883
		if ( $offset === false ) {
884
			$offset = 0;
885
		}
886
887
		return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
888
	}
889
890
	function encodeBlob( $b ) {
891
		return new Blob( $b );
892
	}
893
894
	function decodeBlob( $b ) {
895
		if ( $b instanceof Blob ) {
896
			$b = $b->fetch();
897
		}
898
899
		return $b;
900
	}
901
902
	function unionQueries( $sqls, $all ) {
903
		$glue = ' UNION ALL ';
904
905
		return 'SELECT * ' . ( $all ? '' : '/* UNION_UNIQUE */ ' ) .
906
			'FROM (' . implode( $glue, $sqls ) . ')';
907
	}
908
909
	function wasDeadlock() {
910
		return $this->lastErrno() == 'OCI-00060';
911
	}
912
913
	function duplicateTableStructure( $oldName, $newName, $temporary = false,
914
		$fname = __METHOD__
915
	) {
916
		$temporary = $temporary ? 'TRUE' : 'FALSE';
917
918
		$newName = strtoupper( $newName );
919
		$oldName = strtoupper( $oldName );
920
921
		$tabName = substr( $newName, strlen( $this->mTablePrefix ) );
922
		$oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
923
		$newPrefix = strtoupper( $this->mTablePrefix );
924
925
		return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
926
			"'$oldPrefix', '$newPrefix', $temporary ); END;" );
927
	}
928
929
	function listTables( $prefix = null, $fname = __METHOD__ ) {
930
		$listWhere = '';
931
		if ( !empty( $prefix ) ) {
932
			$listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
933
		}
934
935
		$owner = strtoupper( $this->mDBname );
936
		$result = $this->doQuery( "SELECT table_name FROM all_tables " .
937
			"WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
938
939
		// dirty code ... i know
940
		$endArray = [];
941
		$endArray[] = strtoupper( $prefix . 'MWUSER' );
942
		$endArray[] = strtoupper( $prefix . 'PAGE' );
943
		$endArray[] = strtoupper( $prefix . 'IMAGE' );
944
		$fixedOrderTabs = $endArray;
945
		while ( ( $row = $result->fetchRow() ) !== false ) {
946
			if ( !in_array( $row['table_name'], $fixedOrderTabs ) ) {
947
				$endArray[] = $row['table_name'];
948
			}
949
		}
950
951
		return $endArray;
952
	}
953
954
	public function dropTable( $tableName, $fName = __METHOD__ ) {
955
		$tableName = $this->tableName( $tableName );
956
		if ( !$this->tableExists( $tableName ) ) {
957
			return false;
958
		}
959
960
		return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
961
	}
962
963
	function timestamp( $ts = 0 ) {
964
		return wfTimestamp( TS_ORACLE, $ts );
965
	}
966
967
	/**
968
	 * Return aggregated value function call
969
	 *
970
	 * @param array $valuedata
971
	 * @param string $valuename
972
	 * @return mixed
973
	 */
974
	public function aggregateValue( $valuedata, $valuename = 'value' ) {
975
		return $valuedata;
976
	}
977
978
	/**
979
	 * @return string Wikitext of a link to the server software's web site
980
	 */
981
	public function getSoftwareLink() {
982
		return '[{{int:version-db-oracle-url}} Oracle]';
983
	}
984
985
	/**
986
	 * @return string Version information from the database
987
	 */
988
	function getServerVersion() {
989
		// better version number, fallback on driver
990
		$rset = $this->doQuery(
991
			'SELECT version FROM product_component_version ' .
992
				'WHERE UPPER(product) LIKE \'ORACLE DATABASE%\''
993
		);
994
		$row = $rset->fetchRow();
995
		if ( !$row ) {
996
			return oci_server_version( $this->mConn );
997
		}
998
999
		return $row['version'];
1000
	}
1001
1002
	/**
1003
	 * Query whether a given index exists
1004
	 * @param string $table
1005
	 * @param string $index
1006
	 * @param string $fname
1007
	 * @return bool
1008
	 */
1009
	function indexExists( $table, $index, $fname = __METHOD__ ) {
1010
		$table = $this->tableName( $table );
1011
		$table = strtoupper( $this->removeIdentifierQuotes( $table ) );
1012
		$index = strtoupper( $index );
1013
		$owner = strtoupper( $this->mDBname );
1014
		$sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
1015
		$res = $this->doQuery( $sql );
1016
		if ( $res ) {
1017
			$count = $res->numRows();
1018
			$res->free();
1019
		} else {
1020
			$count = 0;
1021
		}
1022
1023
		return $count != 0;
1024
	}
1025
1026
	/**
1027
	 * Query whether a given table exists (in the given schema, or the default mw one if not given)
1028
	 * @param string $table
1029
	 * @param string $fname
1030
	 * @return bool
1031
	 */
1032
	function tableExists( $table, $fname = __METHOD__ ) {
1033
		$table = $this->tableName( $table );
1034
		$table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
1035
		$owner = $this->addQuotes( strtoupper( $this->mDBname ) );
1036
		$sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
1037
		$res = $this->doQuery( $sql );
1038
		if ( $res && $res->numRows() > 0 ) {
1039
			$exists = true;
1040
		} else {
1041
			$exists = false;
1042
		}
1043
1044
		$res->free();
1045
1046
		return $exists;
1047
	}
1048
1049
	/**
1050
	 * Function translates mysql_fetch_field() functionality on ORACLE.
1051
	 * Caching is present for reducing query time.
1052
	 * For internal calls. Use fieldInfo for normal usage.
1053
	 * Returns false if the field doesn't exist
1054
	 *
1055
	 * @param array|string $table
1056
	 * @param string $field
1057
	 * @return ORAField|ORAResult
1058
	 */
1059
	private function fieldInfoMulti( $table, $field ) {
1060
		$field = strtoupper( $field );
1061
		if ( is_array( $table ) ) {
1062
			$table = array_map( [ &$this, 'tableNameInternal' ], $table );
1063
			$tableWhere = 'IN (';
1064
			foreach ( $table as &$singleTable ) {
1065
				$singleTable = $this->removeIdentifierQuotes( $singleTable );
1066
				if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
1067
					return $this->mFieldInfoCache["$singleTable.$field"];
1068
				}
1069
				$tableWhere .= '\'' . $singleTable . '\',';
1070
			}
1071
			$tableWhere = rtrim( $tableWhere, ',' ) . ')';
1072
		} else {
1073
			$table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
1074
			if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
1075
				return $this->mFieldInfoCache["$table.$field"];
1076
			}
1077
			$tableWhere = '= \'' . $table . '\'';
1078
		}
1079
1080
		$fieldInfoStmt = oci_parse(
1081
			$this->mConn,
1082
			'SELECT * FROM wiki_field_info_full WHERE table_name ' .
1083
				$tableWhere . ' and column_name = \'' . $field . '\''
1084
		);
1085 View Code Duplication
		if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
1086
			$e = oci_error( $fieldInfoStmt );
1087
			$this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
1088
1089
			return false;
1090
		}
1091
		$res = new ORAResult( $this, $fieldInfoStmt );
1092
		if ( $res->numRows() == 0 ) {
1093
			if ( is_array( $table ) ) {
1094
				foreach ( $table as &$singleTable ) {
1095
					$this->mFieldInfoCache["$singleTable.$field"] = false;
1096
				}
1097
			} else {
1098
				$this->mFieldInfoCache["$table.$field"] = false;
1099
			}
1100
			$fieldInfoTemp = null;
1101
		} else {
1102
			$fieldInfoTemp = new ORAField( $res->fetchRow() );
1103
			$table = $fieldInfoTemp->tableName();
1104
			$this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
1105
		}
1106
		$res->free();
1107
1108
		return $fieldInfoTemp;
1109
	}
1110
1111
	/**
1112
	 * @throws DBUnexpectedError
1113
	 * @param string $table
1114
	 * @param string $field
1115
	 * @return ORAField
1116
	 */
1117
	function fieldInfo( $table, $field ) {
1118
		if ( is_array( $table ) ) {
1119
			throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
1120
		}
1121
1122
		return $this->fieldInfoMulti( $table, $field );
1123
	}
1124
1125
	protected function doBegin( $fname = __METHOD__ ) {
1126
		$this->mTrxLevel = 1;
1127
		$this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
1128
	}
1129
1130
	protected function doCommit( $fname = __METHOD__ ) {
1131
		if ( $this->mTrxLevel ) {
1132
			$ret = oci_commit( $this->mConn );
1133
			if ( !$ret ) {
1134
				throw new DBUnexpectedError( $this, $this->lastError() );
1135
			}
1136
			$this->mTrxLevel = 0;
1137
			$this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
1138
		}
1139
	}
1140
1141
	protected function doRollback( $fname = __METHOD__ ) {
1142
		if ( $this->mTrxLevel ) {
1143
			oci_rollback( $this->mConn );
1144
			$this->mTrxLevel = 0;
1145
			$this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
1146
		}
1147
	}
1148
1149
	/**
1150
	 * defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
1151
	 *
1152
	 * @param resource $fp
1153
	 * @param bool|string $lineCallback
1154
	 * @param bool|callable $resultCallback
1155
	 * @param string $fname
1156
	 * @param bool|callable $inputCallback
1157
	 * @return bool|string
1158
	 */
1159
	function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
1160
		$fname = __METHOD__, $inputCallback = false ) {
1161
		$cmd = '';
1162
		$done = false;
1163
		$dollarquote = false;
1164
1165
		$replacements = [];
1166
1167
		while ( !feof( $fp ) ) {
1168
			if ( $lineCallback ) {
1169
				call_user_func( $lineCallback );
1170
			}
1171
			$line = trim( fgets( $fp, 1024 ) );
1172
			$sl = strlen( $line ) - 1;
1173
1174
			if ( $sl < 0 ) {
1175
				continue;
1176
			}
1177
			if ( '-' == $line[0] && '-' == $line[1] ) {
1178
				continue;
1179
			}
1180
1181
			// Allow dollar quoting for function declarations
1182
			if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) {
1183
				if ( $dollarquote ) {
1184
					$dollarquote = false;
1185
					$line = str_replace( '/*$mw$*/', '', $line ); // remove dollarquotes
1186
					$done = true;
1187
				} else {
1188
					$dollarquote = true;
1189
				}
1190
			} elseif ( !$dollarquote ) {
1191
				if ( ';' == $line[$sl] && ( $sl < 2 || ';' != $line[$sl - 1] ) ) {
1192
					$done = true;
1193
					$line = substr( $line, 0, $sl );
1194
				}
1195
			}
1196
1197
			if ( $cmd != '' ) {
1198
				$cmd .= ' ';
1199
			}
1200
			$cmd .= "$line\n";
1201
1202
			if ( $done ) {
1203
				$cmd = str_replace( ';;', ";", $cmd );
1204
				if ( strtolower( substr( $cmd, 0, 6 ) ) == 'define' ) {
1205
					if ( preg_match( '/^define\s*([^\s=]*)\s*=\s*\'\{\$([^\}]*)\}\'/', $cmd, $defines ) ) {
1206
						$replacements[$defines[2]] = $defines[1];
1207
					}
1208
				} else {
1209
					foreach ( $replacements as $mwVar => $scVar ) {
1210
						$cmd = str_replace( '&' . $scVar . '.', '`{$' . $mwVar . '}`', $cmd );
1211
					}
1212
1213
					$cmd = $this->replaceVars( $cmd );
1214
					if ( $inputCallback ) {
1215
						call_user_func( $inputCallback, $cmd );
1216
					}
1217
					$res = $this->doQuery( $cmd );
1218
					if ( $resultCallback ) {
1219
						call_user_func( $resultCallback, $res, $this );
1220
					}
1221
1222
					if ( false === $res ) {
1223
						$err = $this->lastError();
1224
1225
						return "Query \"{$cmd}\" failed with error code \"$err\".\n";
1226
					}
1227
				}
1228
1229
				$cmd = '';
1230
				$done = false;
1231
			}
1232
		}
1233
1234
		return true;
1235
	}
1236
1237
	function selectDB( $db ) {
1238
		$this->mDBname = $db;
1239
		if ( $db == null || $db == $this->mUser ) {
1240
			return true;
1241
		}
1242
		$sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
1243
		$stmt = oci_parse( $this->mConn, $sql );
1244
		MediaWiki\suppressWarnings();
1245
		$success = oci_execute( $stmt );
1246
		MediaWiki\restoreWarnings();
1247
		if ( !$success ) {
1248
			$e = oci_error( $stmt );
1249
			if ( $e['code'] != '1435' ) {
1250
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1251
			}
1252
1253
			return false;
1254
		}
1255
1256
		return true;
1257
	}
1258
1259
	function strencode( $s ) {
1260
		return str_replace( "'", "''", $s );
1261
	}
1262
1263
	function addQuotes( $s ) {
1264
		global $wgContLang;
1265
		if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
1266
			$s = $wgContLang->checkTitleEncoding( $s );
1267
		}
1268
1269
		return "'" . $this->strencode( $s ) . "'";
1270
	}
1271
1272
	public function addIdentifierQuotes( $s ) {
1273
		if ( !$this->getFlag( DBO_DDLMODE ) ) {
1274
			$s = '/*Q*/' . $s;
1275
		}
1276
1277
		return $s;
1278
	}
1279
1280
	public function removeIdentifierQuotes( $s ) {
1281
		return strpos( $s, '/*Q*/' ) === false ? $s : substr( $s, 5 );
1282
	}
1283
1284
	public function isQuotedIdentifier( $s ) {
1285
		return strpos( $s, '/*Q*/' ) !== false;
1286
	}
1287
1288
	private function wrapFieldForWhere( $table, &$col, &$val ) {
1289
		global $wgContLang;
1290
1291
		$col_info = $this->fieldInfoMulti( $table, $col );
1292
		$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...
1293
		if ( $col_type == 'CLOB' ) {
1294
			$col = 'TO_CHAR(' . $col . ')';
1295
			$val = $wgContLang->checkTitleEncoding( $val );
1296
		} elseif ( $col_type == 'VARCHAR2' ) {
1297
			$val = $wgContLang->checkTitleEncoding( $val );
1298
		}
1299
	}
1300
1301
	private function wrapConditionsForWhere( $table, $conds, $parentCol = null ) {
1302
		$conds2 = [];
1303
		foreach ( $conds as $col => $val ) {
1304
			if ( is_array( $val ) ) {
1305
				$conds2[$col] = $this->wrapConditionsForWhere( $table, $val, $col );
1306
			} else {
1307
				if ( is_numeric( $col ) && $parentCol != null ) {
1308
					$this->wrapFieldForWhere( $table, $parentCol, $val );
1309
				} else {
1310
					$this->wrapFieldForWhere( $table, $col, $val );
1311
				}
1312
				$conds2[$col] = $val;
1313
			}
1314
		}
1315
1316
		return $conds2;
1317
	}
1318
1319
	function selectRow( $table, $vars, $conds, $fname = __METHOD__,
1320
		$options = [], $join_conds = []
1321
	) {
1322
		if ( is_array( $conds ) ) {
1323
			$conds = $this->wrapConditionsForWhere( $table, $conds );
1324
		}
1325
1326
		return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
1327
	}
1328
1329
	/**
1330
	 * Returns an optional USE INDEX clause to go after the table, and a
1331
	 * string to go at the end of the query
1332
	 *
1333
	 * @param array $options An associative array of options to be turned into
1334
	 *   an SQL query, valid keys are listed in the function.
1335
	 * @return array
1336
	 */
1337
	function makeSelectOptions( $options ) {
1338
		$preLimitTail = $postLimitTail = '';
1339
		$startOpts = '';
1340
1341
		$noKeyOptions = [];
1342
		foreach ( $options as $key => $option ) {
1343
			if ( is_numeric( $key ) ) {
1344
				$noKeyOptions[$option] = true;
1345
			}
1346
		}
1347
1348
		$preLimitTail .= $this->makeGroupByWithHaving( $options );
1349
1350
		$preLimitTail .= $this->makeOrderBy( $options );
1351
1352
		if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1353
			$postLimitTail .= ' FOR UPDATE';
1354
		}
1355
1356
		if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1357
			$startOpts .= 'DISTINCT';
1358
		}
1359
1360 View Code Duplication
		if ( isset( $options['USE INDEX'] ) && !is_array( $options['USE INDEX'] ) ) {
1361
			$useIndex = $this->useIndexClause( $options['USE INDEX'] );
1362
		} else {
1363
			$useIndex = '';
1364
		}
1365
1366
		return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail ];
1367
	}
1368
1369
	public function delete( $table, $conds, $fname = __METHOD__ ) {
1370
		if ( is_array( $conds ) ) {
1371
			$conds = $this->wrapConditionsForWhere( $table, $conds );
1372
		}
1373
		// a hack for deleting pages, users and images (which have non-nullable FKs)
1374
		// all deletions on these tables have transactions so final failure rollbacks these updates
1375
		$table = $this->tableName( $table );
1376
		if ( $table == $this->tableName( 'user' ) ) {
1377
			$this->update( 'archive', [ 'ar_user' => 0 ],
1378
				[ 'ar_user' => $conds['user_id'] ], $fname );
1379
			$this->update( 'ipblocks', [ 'ipb_user' => 0 ],
1380
				[ 'ipb_user' => $conds['user_id'] ], $fname );
1381
			$this->update( 'image', [ 'img_user' => 0 ],
1382
				[ 'img_user' => $conds['user_id'] ], $fname );
1383
			$this->update( 'oldimage', [ 'oi_user' => 0 ],
1384
				[ 'oi_user' => $conds['user_id'] ], $fname );
1385
			$this->update( 'filearchive', [ 'fa_deleted_user' => 0 ],
1386
				[ 'fa_deleted_user' => $conds['user_id'] ], $fname );
1387
			$this->update( 'filearchive', [ 'fa_user' => 0 ],
1388
				[ 'fa_user' => $conds['user_id'] ], $fname );
1389
			$this->update( 'uploadstash', [ 'us_user' => 0 ],
1390
				[ 'us_user' => $conds['user_id'] ], $fname );
1391
			$this->update( 'recentchanges', [ 'rc_user' => 0 ],
1392
				[ 'rc_user' => $conds['user_id'] ], $fname );
1393
			$this->update( 'logging', [ 'log_user' => 0 ],
1394
				[ 'log_user' => $conds['user_id'] ], $fname );
1395
		} elseif ( $table == $this->tableName( 'image' ) ) {
1396
			$this->update( 'oldimage', [ 'oi_name' => 0 ],
1397
				[ 'oi_name' => $conds['img_name'] ], $fname );
1398
		}
1399
1400
		return parent::delete( $table, $conds, $fname );
1401
	}
1402
1403
	/**
1404
	 * @param string $table
1405
	 * @param array $values
1406
	 * @param array $conds
1407
	 * @param string $fname
1408
	 * @param array $options
1409
	 * @return bool
1410
	 * @throws DBUnexpectedError
1411
	 */
1412
	function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
1413
		global $wgContLang;
1414
1415
		$table = $this->tableName( $table );
1416
		$opts = $this->makeUpdateOptions( $options );
1417
		$sql = "UPDATE $opts $table SET ";
1418
1419
		$first = true;
1420
		foreach ( $values as $col => &$val ) {
1421
			$sqlSet = $this->fieldBindStatement( $table, $col, $val, true );
1422
1423
			if ( !$first ) {
1424
				$sqlSet = ', ' . $sqlSet;
1425
			} else {
1426
				$first = false;
1427
			}
1428
			$sql .= $sqlSet;
1429
		}
1430
1431
		if ( $conds !== [] && $conds !== '*' ) {
1432
			$conds = $this->wrapConditionsForWhere( $table, $conds );
1433
			$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
1434
		}
1435
1436
		$this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
1437 View Code Duplication
		if ( $stmt === false ) {
1438
			$e = oci_error( $this->mConn );
1439
			$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1440
1441
			return false;
1442
		}
1443
		foreach ( $values as $col => &$val ) {
1444
			$col_info = $this->fieldInfoMulti( $table, $col );
1445
			$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...
1446
1447
			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...
1448
				// do nothing ... null was inserted in statement creation
1449
			} elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
1450
				if ( is_object( $val ) ) {
1451
					$val = $val->getData();
1452
				}
1453
1454
				if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
1455
					$val = '31-12-2030 12:00:00.000000';
1456
				}
1457
1458
				$val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
1459
				if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
1460
					$e = oci_error( $stmt );
1461
					$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1462
1463
					return false;
1464
				}
1465 View Code Duplication
			} else {
1466
				/** @var OCI_Lob[] $lob */
1467
				$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...
1468
				if ( $lob[$col] === false ) {
1469
					$e = oci_error( $stmt );
1470
					throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
1471
				}
1472
1473
				if ( is_object( $val ) ) {
1474
					$val = $val->getData();
1475
				}
1476
1477
				if ( $col_type == 'BLOB' ) {
1478
					$lob[$col]->writeTemporary( $val );
1479
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, SQLT_BLOB );
1480
				} else {
1481
					$lob[$col]->writeTemporary( $val );
1482
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
1483
				}
1484
			}
1485
		}
1486
1487
		MediaWiki\suppressWarnings();
1488
1489 View Code Duplication
		if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
1490
			$e = oci_error( $stmt );
1491
			if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
1492
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1493
1494
				return false;
1495
			} else {
1496
				$this->mAffectedRows = oci_num_rows( $stmt );
1497
			}
1498
		} else {
1499
			$this->mAffectedRows = oci_num_rows( $stmt );
1500
		}
1501
1502
		MediaWiki\restoreWarnings();
1503
1504
		if ( isset( $lob ) ) {
1505
			foreach ( $lob as $lob_v ) {
1506
				$lob_v->free();
1507
			}
1508
		}
1509
1510
		if ( !$this->mTrxLevel ) {
1511
			oci_commit( $this->mConn );
1512
		}
1513
1514
		return oci_free_statement( $stmt );
1515
	}
1516
1517
	function bitNot( $field ) {
1518
		// expecting bit-fields smaller than 4bytes
1519
		return 'BITNOT(' . $field . ')';
1520
	}
1521
1522
	function bitAnd( $fieldLeft, $fieldRight ) {
1523
		return 'BITAND(' . $fieldLeft . ', ' . $fieldRight . ')';
1524
	}
1525
1526
	function bitOr( $fieldLeft, $fieldRight ) {
1527
		return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
1528
	}
1529
1530
	function getDBname() {
1531
		return $this->mDBname;
1532
	}
1533
1534
	function getServer() {
1535
		return $this->mServer;
1536
	}
1537
1538 View Code Duplication
	public function buildGroupConcatField(
1539
		$delim, $table, $field, $conds = '', $join_conds = []
1540
	) {
1541
		$fld = "LISTAGG($field," . $this->addQuotes( $delim ) . ") WITHIN GROUP (ORDER BY $field)";
1542
1543
		return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1544
	}
1545
1546
	public function getSearchEngine() {
1547
		return 'SearchOracle';
1548
	}
1549
1550
	public function getInfinity() {
1551
		return '31-12-2030 12:00:00.000000';
1552
	}
1553
}
1554