Completed
Branch master (939199)
by
unknown
39:35
created

includes/db/DatabaseOracle.php (12 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 IDatabase $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;
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
 * @ingroup Database
133
 */
134
class DatabaseOracle extends DatabaseBase {
135
	/** @var resource */
136
	protected $mLastResult = null;
137
138
	/** @var int The number of rows affected as an integer */
139
	protected $mAffectedRows;
140
141
	/** @var int */
142
	private $mInsertId = null;
143
144
	/** @var bool */
145
	private $ignoreDupValOnIndex = false;
146
147
	/** @var bool|array */
148
	private $sequenceData = null;
149
150
	/** @var string Character set for Oracle database */
151
	private $defaultCharset = 'AL32UTF8';
152
153
	/** @var array */
154
	private $mFieldInfoCache = [];
155
156
	function __construct( array $p ) {
157
		global $wgDBprefix;
158
159
		if ( $p['tablePrefix'] == 'get from global' ) {
160
			$p['tablePrefix'] = $wgDBprefix;
161
		}
162
		$p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
163
		parent::__construct( $p );
164
		Hooks::run( 'DatabaseOraclePostInit', [ $this ] );
165
	}
166
167
	function __destruct() {
168
		if ( $this->mOpened ) {
169
			MediaWiki\suppressWarnings();
170
			$this->close();
171
			MediaWiki\restoreWarnings();
172
		}
173
	}
174
175
	function getType() {
176
		return 'oracle';
177
	}
178
179
	function implicitGroupby() {
180
		return false;
181
	}
182
183
	function implicitOrderby() {
184
		return false;
185
	}
186
187
	/**
188
	 * Usually aborts on failure
189
	 * @param string $server
190
	 * @param string $user
191
	 * @param string $password
192
	 * @param string $dbName
193
	 * @throws DBConnectionError
194
	 * @return resource|null
195
	 */
196
	function open( $server, $user, $password, $dbName ) {
197
		global $wgDBOracleDRCP;
198
		if ( !function_exists( 'oci_connect' ) ) {
199
			throw new DBConnectionError(
200
				$this,
201
				"Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n " .
202
					"(Note: if you recently installed PHP, you may need to restart your webserver\n " .
203
					"and database)\n" );
204
		}
205
206
		$this->close();
207
		$this->mUser = $user;
208
		$this->mPassword = $password;
209
		// changed internal variables functions
210
		// mServer now holds the TNS endpoint
211
		// mDBname is schema name if different from username
212
		if ( !$server ) {
213
			// backward compatibillity (server used to be null and TNS was supplied in dbname)
214
			$this->mServer = $dbName;
215
			$this->mDBname = $user;
216
		} else {
217
			$this->mServer = $server;
218
			if ( !$dbName ) {
219
				$this->mDBname = $user;
220
			} else {
221
				$this->mDBname = $dbName;
222
			}
223
		}
224
225
		if ( !strlen( $user ) ) { # e.g. the class is being loaded
226
			return null;
227
		}
228
229
		if ( $wgDBOracleDRCP ) {
230
			$this->setFlag( DBO_PERSISTENT );
231
		}
232
233
		$session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
234
235
		MediaWiki\suppressWarnings();
236
		if ( $this->mFlags & DBO_PERSISTENT ) {
237
			$this->mConn = oci_pconnect(
238
				$this->mUser,
239
				$this->mPassword,
240
				$this->mServer,
241
				$this->defaultCharset,
242
				$session_mode
243
			);
244
		} elseif ( $this->mFlags & DBO_DEFAULT ) {
245
			$this->mConn = oci_new_connect(
246
				$this->mUser,
247
				$this->mPassword,
248
				$this->mServer,
249
				$this->defaultCharset,
250
				$session_mode
251
			);
252
		} else {
253
			$this->mConn = oci_connect(
254
				$this->mUser,
255
				$this->mPassword,
256
				$this->mServer,
257
				$this->defaultCharset,
258
				$session_mode
259
			);
260
		}
261
		MediaWiki\restoreWarnings();
262
263
		if ( $this->mUser != $this->mDBname ) {
264
			// change current schema in session
265
			$this->selectDB( $this->mDBname );
266
		}
267
268
		if ( !$this->mConn ) {
269
			throw new DBConnectionError( $this, $this->lastError() );
270
		}
271
272
		$this->mOpened = true;
273
274
		# removed putenv calls because they interfere with the system globaly
275
		$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
276
		$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
277
		$this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
278
279
		return $this->mConn;
280
	}
281
282
	/**
283
	 * Closes a database connection, if it is open
284
	 * Returns success, true if already closed
285
	 * @return bool
286
	 */
287
	protected function closeConnection() {
288
		return oci_close( $this->mConn );
289
	}
290
291
	function execFlags() {
292
		return $this->mTrxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
293
	}
294
295
	protected function doQuery( $sql ) {
296
		wfDebug( "SQL: [$sql]\n" );
297
		if ( !StringUtils::isUtf8( $sql ) ) {
298
			throw new InvalidArgumentException( "SQL encoding is invalid\n$sql" );
299
		}
300
301
		// handle some oracle specifics
302
		// remove AS column/table/subquery namings
303
		if ( !$this->getFlag( DBO_DDLMODE ) ) {
304
			$sql = preg_replace( '/ as /i', ' ', $sql );
305
		}
306
307
		// Oracle has issues with UNION clause if the statement includes LOB fields
308
		// So we do a UNION ALL and then filter the results array with array_unique
309
		$union_unique = ( preg_match( '/\/\* UNION_UNIQUE \*\/ /', $sql ) != 0 );
310
		// EXPLAIN syntax in Oracle is EXPLAIN PLAN FOR and it return nothing
311
		// you have to select data from plan table after explain
312
		$explain_id = MWTimestamp::getLocalInstance()->format( 'dmYHis' );
313
314
		$sql = preg_replace(
315
			'/^EXPLAIN /',
316
			'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR',
317
			$sql,
318
			1,
319
			$explain_count
320
		);
321
322
		MediaWiki\suppressWarnings();
323
324
		$this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
325 View Code Duplication
		if ( $stmt === false ) {
326
			$e = oci_error( $this->mConn );
327
			$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
328
329
			return false;
330
		}
331
332
		if ( !oci_execute( $stmt, $this->execFlags() ) ) {
333
			$e = oci_error( $stmt );
334
			if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
335
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
336
337
				return false;
338
			}
339
		}
340
341
		MediaWiki\restoreWarnings();
342
343
		if ( $explain_count > 0 ) {
344
			return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table ' .
345
				'WHERE statement_id = \'' . $explain_id . '\'' );
346
		} elseif ( oci_statement_type( $stmt ) == 'SELECT' ) {
347
			return new ORAResult( $this, $stmt, $union_unique );
348
		} else {
349
			$this->mAffectedRows = oci_num_rows( $stmt );
350
351
			return true;
352
		}
353
	}
354
355
	function queryIgnore( $sql, $fname = '' ) {
356
		return $this->query( $sql, $fname, true );
357
	}
358
359
	/**
360
	 * Frees resources associated with the LOB descriptor
361
	 * @param ResultWrapper|ORAResult $res
362
	 */
363
	function freeResult( $res ) {
364
		if ( $res instanceof ResultWrapper ) {
365
			$res = $res->result;
366
		}
367
368
		$res->free();
0 ignored issues
show
It seems like $res is not always an object, but can also be of type resource|array|null. 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...
369
	}
370
371
	/**
372
	 * @param ResultWrapper|ORAResult $res
373
	 * @return mixed
374
	 */
375
	function fetchObject( $res ) {
376
		if ( $res instanceof ResultWrapper ) {
377
			$res = $res->result;
378
		}
379
380
		return $res->fetchObject();
0 ignored issues
show
It seems like $res is not always an object, but can also be of type resource|array|null. 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...
381
	}
382
383
	/**
384
	 * @param ResultWrapper|ORAResult $res
385
	 * @return mixed
386
	 */
387
	function fetchRow( $res ) {
388
		if ( $res instanceof ResultWrapper ) {
389
			$res = $res->result;
390
		}
391
392
		return $res->fetchRow();
0 ignored issues
show
It seems like $res is not always an object, but can also be of type resource|array|null. 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...
393
	}
394
395
	/**
396
	 * @param ResultWrapper|ORAResult $res
397
	 * @return int
398
	 */
399
	function numRows( $res ) {
400
		if ( $res instanceof ResultWrapper ) {
401
			$res = $res->result;
402
		}
403
404
		return $res->numRows();
0 ignored issues
show
It seems like $res is not always an object, but can also be of type resource|array|null. 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...
405
	}
406
407
	/**
408
	 * @param ResultWrapper|ORAResult $res
409
	 * @return int
410
	 */
411
	function numFields( $res ) {
412
		if ( $res instanceof ResultWrapper ) {
413
			$res = $res->result;
414
		}
415
416
		return $res->numFields();
0 ignored issues
show
It seems like $res is not always an object, but can also be of type resource|array|null. 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...
417
	}
418
419
	function fieldName( $stmt, $n ) {
420
		return oci_field_name( $stmt, $n );
421
	}
422
423
	/**
424
	 * This must be called after nextSequenceVal
425
	 * @return null|int
426
	 */
427
	function insertId() {
428
		return $this->mInsertId;
429
	}
430
431
	/**
432
	 * @param mixed $res
433
	 * @param int $row
434
	 */
435
	function dataSeek( $res, $row ) {
436
		if ( $res instanceof ORAResult ) {
437
			$res->seek( $row );
438
		} else {
439
			$res->result->seek( $row );
440
		}
441
	}
442
443 View Code Duplication
	function lastError() {
444
		if ( $this->mConn === false ) {
445
			$e = oci_error();
446
		} else {
447
			$e = oci_error( $this->mConn );
448
		}
449
450
		return $e['message'];
451
	}
452
453 View Code Duplication
	function lastErrno() {
454
		if ( $this->mConn === false ) {
455
			$e = oci_error();
456
		} else {
457
			$e = oci_error( $this->mConn );
458
		}
459
460
		return $e['code'];
461
	}
462
463
	function affectedRows() {
464
		return $this->mAffectedRows;
465
	}
466
467
	/**
468
	 * Returns information about an index
469
	 * If errors are explicitly ignored, returns NULL on failure
470
	 * @param string $table
471
	 * @param string $index
472
	 * @param string $fname
473
	 * @return bool
474
	 */
475
	function indexInfo( $table, $index, $fname = __METHOD__ ) {
476
		return false;
477
	}
478
479
	function indexUnique( $table, $index, $fname = __METHOD__ ) {
480
		return false;
481
	}
482
483
	function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
484
		if ( !count( $a ) ) {
485
			return true;
486
		}
487
488
		if ( !is_array( $options ) ) {
489
			$options = [ $options ];
490
		}
491
492
		if ( in_array( 'IGNORE', $options ) ) {
493
			$this->ignoreDupValOnIndex = true;
494
		}
495
496
		if ( !is_array( reset( $a ) ) ) {
497
			$a = [ $a ];
498
		}
499
500
		foreach ( $a as &$row ) {
501
			$this->insertOneRow( $table, $row, $fname );
502
		}
503
		$retVal = true;
504
505
		if ( in_array( 'IGNORE', $options ) ) {
506
			$this->ignoreDupValOnIndex = false;
507
		}
508
509
		return $retVal;
510
	}
511
512
	private function fieldBindStatement( $table, $col, &$val, $includeCol = false ) {
513
		$col_info = $this->fieldInfoMulti( $table, $col );
514
		$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
0 ignored issues
show
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...
515
516
		$bind = '';
517
		if ( is_numeric( $col ) ) {
518
			$bind = $val;
519
			$val = null;
520
521
			return $bind;
522
		} elseif ( $includeCol ) {
523
			$bind = "$col = ";
524
		}
525
526
		if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) {
527
			$val = null;
528
		}
529
530
		if ( $val === 'NULL' ) {
531
			$val = null;
532
		}
533
534
		if ( $val === null ) {
535
			if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
0 ignored issues
show
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...
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...
536
				$bind .= 'DEFAULT';
537
			} else {
538
				$bind .= 'NULL';
539
			}
540
		} else {
541
			$bind .= ':' . $col;
542
		}
543
544
		return $bind;
545
	}
546
547
	/**
548
	 * @param string $table
549
	 * @param array $row
550
	 * @param string $fname
551
	 * @return bool
552
	 * @throws DBUnexpectedError
553
	 */
554
	private function insertOneRow( $table, $row, $fname ) {
555
		global $wgContLang;
556
557
		$table = $this->tableName( $table );
558
		// "INSERT INTO tables (a, b, c)"
559
		$sql = "INSERT INTO " . $table . " (" . implode( ',', array_keys( $row ) ) . ')';
560
		$sql .= " VALUES (";
561
562
		// for each value, append ":key"
563
		$first = true;
564
		foreach ( $row as $col => &$val ) {
565
			if ( !$first ) {
566
				$sql .= ', ';
567
			} else {
568
				$first = false;
569
			}
570
			if ( $this->isQuotedIdentifier( $val ) ) {
571
				$sql .= $this->removeIdentifierQuotes( $val );
572
				unset( $row[$col] );
573
			} else {
574
				$sql .= $this->fieldBindStatement( $table, $col, $val );
575
			}
576
		}
577
		$sql .= ')';
578
579
		$this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
580 View Code Duplication
		if ( $stmt === false ) {
581
			$e = oci_error( $this->mConn );
582
			$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
583
584
			return false;
585
		}
586
		foreach ( $row as $col => &$val ) {
587
			$col_info = $this->fieldInfoMulti( $table, $col );
588
			$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
0 ignored issues
show
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
			if ( $val === null ) {
591
				// do nothing ... null was inserted in statement creation
592
			} elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
593
				if ( is_object( $val ) ) {
594
					$val = $val->fetch();
595
				}
596
597
				// backward compatibility
598
				if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
599
					$val = $this->getInfinity();
600
				}
601
602
				$val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
603
				if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
604
					$e = oci_error( $stmt );
605
					$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
606
607
					return false;
608
				}
609 View Code Duplication
			} else {
610
				/** @var OCI_Lob[] $lob */
611
				$lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB );
612
				if ( $lob[$col] === false ) {
613
					$e = oci_error( $stmt );
614
					throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
615
				}
616
617
				if ( is_object( $val ) ) {
618
					$val = $val->fetch();
619
				}
620
621
				if ( $col_type == 'BLOB' ) {
622
					$lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
623
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_BLOB );
624
				} else {
625
					$lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
626
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
627
				}
628
			}
629
		}
630
631
		MediaWiki\suppressWarnings();
632
633 View Code Duplication
		if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
634
			$e = oci_error( $stmt );
635
			if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
636
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
637
638
				return false;
639
			} else {
640
				$this->mAffectedRows = oci_num_rows( $stmt );
641
			}
642
		} else {
643
			$this->mAffectedRows = oci_num_rows( $stmt );
644
		}
645
646
		MediaWiki\restoreWarnings();
647
648
		if ( isset( $lob ) ) {
649
			foreach ( $lob as $lob_v ) {
650
				$lob_v->free();
651
			}
652
		}
653
654
		if ( !$this->mTrxLevel ) {
655
			oci_commit( $this->mConn );
656
		}
657
658
		return oci_free_statement( $stmt );
659
	}
660
661
	function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
662
		$insertOptions = [], $selectOptions = []
663
	) {
664
		$destTable = $this->tableName( $destTable );
665
		if ( !is_array( $selectOptions ) ) {
666
			$selectOptions = [ $selectOptions ];
667
		}
668
		list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) =
669
			$this->makeSelectOptions( $selectOptions );
670 View Code Duplication
		if ( is_array( $srcTable ) ) {
671
			$srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
672
		} else {
673
			$srcTable = $this->tableName( $srcTable );
674
		}
675
676
		$sequenceData = $this->getSequenceData( $destTable );
677 View Code Duplication
		if ( $sequenceData !== false &&
678
			!isset( $varMap[$sequenceData['column']] )
679
		) {
680
			$varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
681
		}
682
683
		// count-alias subselect fields to avoid abigious definition errors
684
		$i = 0;
685
		foreach ( $varMap as &$val ) {
686
			$val = $val . ' field' . ( $i++ );
687
		}
688
689
		$sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
690
			" SELECT $startOpts " . implode( ',', $varMap ) .
691
			" FROM $srcTable $useIndex $ignoreIndex ";
692
		if ( $conds != '*' ) {
693
			$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
694
		}
695
		$sql .= " $tailOpts";
696
697
		if ( in_array( 'IGNORE', $insertOptions ) ) {
698
			$this->ignoreDupValOnIndex = true;
699
		}
700
701
		$retval = $this->query( $sql, $fname );
702
703
		if ( in_array( 'IGNORE', $insertOptions ) ) {
704
			$this->ignoreDupValOnIndex = false;
705
		}
706
707
		return $retval;
708
	}
709
710
	public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
711
		$fname = __METHOD__
712
	) {
713
		if ( !count( $rows ) ) {
714
			return true; // nothing to do
715
		}
716
717
		if ( !is_array( reset( $rows ) ) ) {
718
			$rows = [ $rows ];
719
		}
720
721
		$sequenceData = $this->getSequenceData( $table );
722 View Code Duplication
		if ( $sequenceData !== false ) {
723
			// add sequence column to each list of columns, when not set
724
			foreach ( $rows as &$row ) {
725
				if ( !isset( $row[$sequenceData['column']] ) ) {
726
					$row[$sequenceData['column']] =
727
						$this->addIdentifierQuotes( 'GET_SEQUENCE_VALUE(\'' .
728
							$sequenceData['sequence'] . '\')' );
729
				}
730
			}
731
		}
732
733
		return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname );
734
	}
735
736
	function tableName( $name, $format = 'quoted' ) {
737
		/*
738
		Replace reserved words with better ones
739
		Using uppercase because that's the only way Oracle can handle
740
		quoted tablenames
741
		*/
742
		switch ( $name ) {
743
			case 'user':
744
				$name = 'MWUSER';
745
				break;
746
			case 'text':
747
				$name = 'PAGECONTENT';
748
				break;
749
		}
750
751
		return strtoupper( parent::tableName( $name, $format ) );
752
	}
753
754
	function tableNameInternal( $name ) {
755
		$name = $this->tableName( $name );
756
757
		return preg_replace( '/.*\.(.*)/', '$1', $name );
758
	}
759
760
	/**
761
	 * Return the next in a sequence, save the value for retrieval via insertId()
762
	 *
763
	 * @param string $seqName
764
	 * @return null|int
765
	 */
766
	function nextSequenceValue( $seqName ) {
767
		$res = $this->query( "SELECT $seqName.nextval FROM dual" );
768
		$row = $this->fetchRow( $res );
769
		$this->mInsertId = $row[0];
770
771
		return $this->mInsertId;
772
	}
773
774
	/**
775
	 * Return sequence_name if table has a sequence
776
	 *
777
	 * @param string $table
778
	 * @return bool
779
	 */
780
	private function getSequenceData( $table ) {
781
		if ( $this->sequenceData == null ) {
782
			$result = $this->doQuery( "SELECT lower(asq.sequence_name),
783
				lower(atc.table_name),
784
				lower(atc.column_name)
785
			FROM all_sequences asq, all_tab_columns atc
786
			WHERE decode(
787
					atc.table_name,
788
					'{$this->mTablePrefix}MWUSER',
789
					'{$this->mTablePrefix}USER',
790
					atc.table_name
791
				) || '_' ||
792
				atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
793
				AND asq.sequence_owner = upper('{$this->mDBname}')
794
				AND atc.owner = upper('{$this->mDBname}')" );
795
796
			while ( ( $row = $result->fetchRow() ) !== false ) {
797
				$this->sequenceData[$row[1]] = [
798
					'sequence' => $row[0],
799
					'column' => $row[2]
800
				];
801
			}
802
		}
803
		$table = strtolower( $this->removeIdentifierQuotes( $this->tableName( $table ) ) );
804
805
		return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
806
	}
807
808
	/**
809
	 * Returns the size of a text field, or -1 for "unlimited"
810
	 *
811
	 * @param string $table
812
	 * @param string $field
813
	 * @return mixed
814
	 */
815
	function textFieldSize( $table, $field ) {
816
		$fieldInfoData = $this->fieldInfo( $table, $field );
817
818
		return $fieldInfoData->maxLength();
819
	}
820
821
	function limitResult( $sql, $limit, $offset = false ) {
822
		if ( $offset === false ) {
823
			$offset = 0;
824
		}
825
826
		return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
827
	}
828
829
	function encodeBlob( $b ) {
830
		return new Blob( $b );
831
	}
832
833
	function decodeBlob( $b ) {
834
		if ( $b instanceof Blob ) {
835
			$b = $b->fetch();
836
		}
837
838
		return $b;
839
	}
840
841
	function unionQueries( $sqls, $all ) {
842
		$glue = ' UNION ALL ';
843
844
		return 'SELECT * ' . ( $all ? '' : '/* UNION_UNIQUE */ ' ) .
845
			'FROM (' . implode( $glue, $sqls ) . ')';
846
	}
847
848
	function wasDeadlock() {
849
		return $this->lastErrno() == 'OCI-00060';
850
	}
851
852
	function duplicateTableStructure( $oldName, $newName, $temporary = false,
853
		$fname = __METHOD__
854
	) {
855
		$temporary = $temporary ? 'TRUE' : 'FALSE';
856
857
		$newName = strtoupper( $newName );
858
		$oldName = strtoupper( $oldName );
859
860
		$tabName = substr( $newName, strlen( $this->mTablePrefix ) );
861
		$oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
862
		$newPrefix = strtoupper( $this->mTablePrefix );
863
864
		return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
865
			"'$oldPrefix', '$newPrefix', $temporary ); END;" );
866
	}
867
868
	function listTables( $prefix = null, $fname = __METHOD__ ) {
869
		$listWhere = '';
870
		if ( !empty( $prefix ) ) {
871
			$listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
872
		}
873
874
		$owner = strtoupper( $this->mDBname );
875
		$result = $this->doQuery( "SELECT table_name FROM all_tables " .
876
			"WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
877
878
		// dirty code ... i know
879
		$endArray = [];
880
		$endArray[] = strtoupper( $prefix . 'MWUSER' );
881
		$endArray[] = strtoupper( $prefix . 'PAGE' );
882
		$endArray[] = strtoupper( $prefix . 'IMAGE' );
883
		$fixedOrderTabs = $endArray;
884
		while ( ( $row = $result->fetchRow() ) !== false ) {
885
			if ( !in_array( $row['table_name'], $fixedOrderTabs ) ) {
886
				$endArray[] = $row['table_name'];
887
			}
888
		}
889
890
		return $endArray;
891
	}
892
893
	public function dropTable( $tableName, $fName = __METHOD__ ) {
894
		$tableName = $this->tableName( $tableName );
895
		if ( !$this->tableExists( $tableName ) ) {
896
			return false;
897
		}
898
899
		return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
900
	}
901
902
	function timestamp( $ts = 0 ) {
903
		return wfTimestamp( TS_ORACLE, $ts );
904
	}
905
906
	/**
907
	 * Return aggregated value function call
908
	 *
909
	 * @param array $valuedata
910
	 * @param string $valuename
911
	 * @return mixed
912
	 */
913
	public function aggregateValue( $valuedata, $valuename = 'value' ) {
914
		return $valuedata;
915
	}
916
917
	/**
918
	 * @return string Wikitext of a link to the server software's web site
919
	 */
920
	public function getSoftwareLink() {
921
		return '[{{int:version-db-oracle-url}} Oracle]';
922
	}
923
924
	/**
925
	 * @return string Version information from the database
926
	 */
927
	function getServerVersion() {
928
		// better version number, fallback on driver
929
		$rset = $this->doQuery(
930
			'SELECT version FROM product_component_version ' .
931
				'WHERE UPPER(product) LIKE \'ORACLE DATABASE%\''
932
		);
933
		$row = $rset->fetchRow();
934
		if ( !$row ) {
935
			return oci_server_version( $this->mConn );
936
		}
937
938
		return $row['version'];
939
	}
940
941
	/**
942
	 * Query whether a given index exists
943
	 * @param string $table
944
	 * @param string $index
945
	 * @param string $fname
946
	 * @return bool
947
	 */
948
	function indexExists( $table, $index, $fname = __METHOD__ ) {
949
		$table = $this->tableName( $table );
950
		$table = strtoupper( $this->removeIdentifierQuotes( $table ) );
951
		$index = strtoupper( $index );
952
		$owner = strtoupper( $this->mDBname );
953
		$sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
954
		$res = $this->doQuery( $sql );
955
		if ( $res ) {
956
			$count = $res->numRows();
957
			$res->free();
958
		} else {
959
			$count = 0;
960
		}
961
962
		return $count != 0;
963
	}
964
965
	/**
966
	 * Query whether a given table exists (in the given schema, or the default mw one if not given)
967
	 * @param string $table
968
	 * @param string $fname
969
	 * @return bool
970
	 */
971
	function tableExists( $table, $fname = __METHOD__ ) {
972
		$table = $this->tableName( $table );
973
		$table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
974
		$owner = $this->addQuotes( strtoupper( $this->mDBname ) );
975
		$sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
976
		$res = $this->doQuery( $sql );
977
		if ( $res && $res->numRows() > 0 ) {
978
			$exists = true;
979
		} else {
980
			$exists = false;
981
		}
982
983
		$res->free();
984
985
		return $exists;
986
	}
987
988
	/**
989
	 * Function translates mysql_fetch_field() functionality on ORACLE.
990
	 * Caching is present for reducing query time.
991
	 * For internal calls. Use fieldInfo for normal usage.
992
	 * Returns false if the field doesn't exist
993
	 *
994
	 * @param array|string $table
995
	 * @param string $field
996
	 * @return ORAField|ORAResult
997
	 */
998
	private function fieldInfoMulti( $table, $field ) {
999
		$field = strtoupper( $field );
1000
		if ( is_array( $table ) ) {
1001
			$table = array_map( [ &$this, 'tableNameInternal' ], $table );
1002
			$tableWhere = 'IN (';
1003
			foreach ( $table as &$singleTable ) {
1004
				$singleTable = $this->removeIdentifierQuotes( $singleTable );
1005
				if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
1006
					return $this->mFieldInfoCache["$singleTable.$field"];
1007
				}
1008
				$tableWhere .= '\'' . $singleTable . '\',';
1009
			}
1010
			$tableWhere = rtrim( $tableWhere, ',' ) . ')';
1011
		} else {
1012
			$table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
1013
			if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
1014
				return $this->mFieldInfoCache["$table.$field"];
1015
			}
1016
			$tableWhere = '= \'' . $table . '\'';
1017
		}
1018
1019
		$fieldInfoStmt = oci_parse(
1020
			$this->mConn,
1021
			'SELECT * FROM wiki_field_info_full WHERE table_name ' .
1022
				$tableWhere . ' and column_name = \'' . $field . '\''
1023
		);
1024 View Code Duplication
		if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
1025
			$e = oci_error( $fieldInfoStmt );
1026
			$this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
1027
1028
			return false;
1029
		}
1030
		$res = new ORAResult( $this, $fieldInfoStmt );
1031
		if ( $res->numRows() == 0 ) {
1032
			if ( is_array( $table ) ) {
1033
				foreach ( $table as &$singleTable ) {
1034
					$this->mFieldInfoCache["$singleTable.$field"] = false;
1035
				}
1036
			} else {
1037
				$this->mFieldInfoCache["$table.$field"] = false;
1038
			}
1039
			$fieldInfoTemp = null;
1040
		} else {
1041
			$fieldInfoTemp = new ORAField( $res->fetchRow() );
1042
			$table = $fieldInfoTemp->tableName();
1043
			$this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
1044
		}
1045
		$res->free();
1046
1047
		return $fieldInfoTemp;
1048
	}
1049
1050
	/**
1051
	 * @throws DBUnexpectedError
1052
	 * @param string $table
1053
	 * @param string $field
1054
	 * @return ORAField
1055
	 */
1056
	function fieldInfo( $table, $field ) {
1057
		if ( is_array( $table ) ) {
1058
			throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
1059
		}
1060
1061
		return $this->fieldInfoMulti( $table, $field );
1062
	}
1063
1064
	protected function doBegin( $fname = __METHOD__ ) {
1065
		$this->mTrxLevel = 1;
1066
		$this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
1067
	}
1068
1069
	protected function doCommit( $fname = __METHOD__ ) {
1070
		if ( $this->mTrxLevel ) {
1071
			$ret = oci_commit( $this->mConn );
1072
			if ( !$ret ) {
1073
				throw new DBUnexpectedError( $this, $this->lastError() );
1074
			}
1075
			$this->mTrxLevel = 0;
1076
			$this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
1077
		}
1078
	}
1079
1080
	protected function doRollback( $fname = __METHOD__ ) {
1081
		if ( $this->mTrxLevel ) {
1082
			oci_rollback( $this->mConn );
1083
			$this->mTrxLevel = 0;
1084
			$this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
1085
		}
1086
	}
1087
1088
	/**
1089
	 * defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
1090
	 *
1091
	 * @param resource $fp
1092
	 * @param bool|string $lineCallback
1093
	 * @param bool|callable $resultCallback
1094
	 * @param string $fname
1095
	 * @param bool|callable $inputCallback
1096
	 * @return bool|string
1097
	 */
1098
	function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
1099
		$fname = __METHOD__, $inputCallback = false ) {
1100
		$cmd = '';
1101
		$done = false;
1102
		$dollarquote = false;
1103
1104
		$replacements = [];
1105
1106
		while ( !feof( $fp ) ) {
1107
			if ( $lineCallback ) {
1108
				call_user_func( $lineCallback );
1109
			}
1110
			$line = trim( fgets( $fp, 1024 ) );
1111
			$sl = strlen( $line ) - 1;
1112
1113
			if ( $sl < 0 ) {
1114
				continue;
1115
			}
1116
			if ( '-' == $line[0] && '-' == $line[1] ) {
1117
				continue;
1118
			}
1119
1120
			// Allow dollar quoting for function declarations
1121
			if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) {
1122
				if ( $dollarquote ) {
1123
					$dollarquote = false;
1124
					$line = str_replace( '/*$mw$*/', '', $line ); // remove dollarquotes
1125
					$done = true;
1126
				} else {
1127
					$dollarquote = true;
1128
				}
1129
			} elseif ( !$dollarquote ) {
1130
				if ( ';' == $line[$sl] && ( $sl < 2 || ';' != $line[$sl - 1] ) ) {
1131
					$done = true;
1132
					$line = substr( $line, 0, $sl );
1133
				}
1134
			}
1135
1136
			if ( $cmd != '' ) {
1137
				$cmd .= ' ';
1138
			}
1139
			$cmd .= "$line\n";
1140
1141
			if ( $done ) {
1142
				$cmd = str_replace( ';;', ";", $cmd );
1143
				if ( strtolower( substr( $cmd, 0, 6 ) ) == 'define' ) {
1144
					if ( preg_match( '/^define\s*([^\s=]*)\s*=\s*\'\{\$([^\}]*)\}\'/', $cmd, $defines ) ) {
1145
						$replacements[$defines[2]] = $defines[1];
1146
					}
1147
				} else {
1148
					foreach ( $replacements as $mwVar => $scVar ) {
1149
						$cmd = str_replace( '&' . $scVar . '.', '`{$' . $mwVar . '}`', $cmd );
1150
					}
1151
1152
					$cmd = $this->replaceVars( $cmd );
1153
					if ( $inputCallback ) {
1154
						call_user_func( $inputCallback, $cmd );
1155
					}
1156
					$res = $this->doQuery( $cmd );
1157
					if ( $resultCallback ) {
1158
						call_user_func( $resultCallback, $res, $this );
1159
					}
1160
1161
					if ( false === $res ) {
1162
						$err = $this->lastError();
1163
1164
						return "Query \"{$cmd}\" failed with error code \"$err\".\n";
1165
					}
1166
				}
1167
1168
				$cmd = '';
1169
				$done = false;
1170
			}
1171
		}
1172
1173
		return true;
1174
	}
1175
1176
	function selectDB( $db ) {
1177
		$this->mDBname = $db;
1178
		if ( $db == null || $db == $this->mUser ) {
1179
			return true;
1180
		}
1181
		$sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
1182
		$stmt = oci_parse( $this->mConn, $sql );
1183
		MediaWiki\suppressWarnings();
1184
		$success = oci_execute( $stmt );
1185
		MediaWiki\restoreWarnings();
1186
		if ( !$success ) {
1187
			$e = oci_error( $stmt );
1188
			if ( $e['code'] != '1435' ) {
1189
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1190
			}
1191
1192
			return false;
1193
		}
1194
1195
		return true;
1196
	}
1197
1198
	function strencode( $s ) {
1199
		return str_replace( "'", "''", $s );
1200
	}
1201
1202
	function addQuotes( $s ) {
1203
		global $wgContLang;
1204
		if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
1205
			$s = $wgContLang->checkTitleEncoding( $s );
1206
		}
1207
1208
		return "'" . $this->strencode( $s ) . "'";
1209
	}
1210
1211
	public function addIdentifierQuotes( $s ) {
1212
		if ( !$this->getFlag( DBO_DDLMODE ) ) {
1213
			$s = '/*Q*/' . $s;
1214
		}
1215
1216
		return $s;
1217
	}
1218
1219
	public function removeIdentifierQuotes( $s ) {
1220
		return strpos( $s, '/*Q*/' ) === false ? $s : substr( $s, 5 );
1221
	}
1222
1223
	public function isQuotedIdentifier( $s ) {
1224
		return strpos( $s, '/*Q*/' ) !== false;
1225
	}
1226
1227
	private function wrapFieldForWhere( $table, &$col, &$val ) {
1228
		global $wgContLang;
1229
1230
		$col_info = $this->fieldInfoMulti( $table, $col );
1231
		$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
0 ignored issues
show
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...
1232
		if ( $col_type == 'CLOB' ) {
1233
			$col = 'TO_CHAR(' . $col . ')';
1234
			$val = $wgContLang->checkTitleEncoding( $val );
1235
		} elseif ( $col_type == 'VARCHAR2' ) {
1236
			$val = $wgContLang->checkTitleEncoding( $val );
1237
		}
1238
	}
1239
1240
	private function wrapConditionsForWhere( $table, $conds, $parentCol = null ) {
1241
		$conds2 = [];
1242
		foreach ( $conds as $col => $val ) {
1243
			if ( is_array( $val ) ) {
1244
				$conds2[$col] = $this->wrapConditionsForWhere( $table, $val, $col );
1245
			} else {
1246
				if ( is_numeric( $col ) && $parentCol != null ) {
1247
					$this->wrapFieldForWhere( $table, $parentCol, $val );
1248
				} else {
1249
					$this->wrapFieldForWhere( $table, $col, $val );
1250
				}
1251
				$conds2[$col] = $val;
1252
			}
1253
		}
1254
1255
		return $conds2;
1256
	}
1257
1258
	function selectRow( $table, $vars, $conds, $fname = __METHOD__,
1259
		$options = [], $join_conds = []
1260
	) {
1261
		if ( is_array( $conds ) ) {
1262
			$conds = $this->wrapConditionsForWhere( $table, $conds );
1263
		}
1264
1265
		return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
1266
	}
1267
1268
	/**
1269
	 * Returns an optional USE INDEX clause to go after the table, and a
1270
	 * string to go at the end of the query
1271
	 *
1272
	 * @param array $options An associative array of options to be turned into
1273
	 *   an SQL query, valid keys are listed in the function.
1274
	 * @return array
1275
	 */
1276
	function makeSelectOptions( $options ) {
1277
		$preLimitTail = $postLimitTail = '';
1278
		$startOpts = '';
1279
1280
		$noKeyOptions = [];
1281
		foreach ( $options as $key => $option ) {
1282
			if ( is_numeric( $key ) ) {
1283
				$noKeyOptions[$option] = true;
1284
			}
1285
		}
1286
1287
		$preLimitTail .= $this->makeGroupByWithHaving( $options );
1288
1289
		$preLimitTail .= $this->makeOrderBy( $options );
1290
1291
		if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1292
			$postLimitTail .= ' FOR UPDATE';
1293
		}
1294
1295
		if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1296
			$startOpts .= 'DISTINCT';
1297
		}
1298
1299 View Code Duplication
		if ( isset( $options['USE INDEX'] ) && !is_array( $options['USE INDEX'] ) ) {
1300
			$useIndex = $this->useIndexClause( $options['USE INDEX'] );
1301
		} else {
1302
			$useIndex = '';
1303
		}
1304
1305 View Code Duplication
		if ( isset( $options['IGNORE INDEX'] ) && !is_array( $options['IGNORE INDEX'] ) ) {
1306
			$ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
1307
		} else {
1308
			$ignoreIndex = '';
1309
		}
1310
1311
		return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1312
	}
1313
1314
	public function delete( $table, $conds, $fname = __METHOD__ ) {
1315
		if ( is_array( $conds ) ) {
1316
			$conds = $this->wrapConditionsForWhere( $table, $conds );
1317
		}
1318
		// a hack for deleting pages, users and images (which have non-nullable FKs)
1319
		// all deletions on these tables have transactions so final failure rollbacks these updates
1320
		$table = $this->tableName( $table );
1321
		if ( $table == $this->tableName( 'user' ) ) {
1322
			$this->update( 'archive', [ 'ar_user' => 0 ],
1323
				[ 'ar_user' => $conds['user_id'] ], $fname );
1324
			$this->update( 'ipblocks', [ 'ipb_user' => 0 ],
1325
				[ 'ipb_user' => $conds['user_id'] ], $fname );
1326
			$this->update( 'image', [ 'img_user' => 0 ],
1327
				[ 'img_user' => $conds['user_id'] ], $fname );
1328
			$this->update( 'oldimage', [ 'oi_user' => 0 ],
1329
				[ 'oi_user' => $conds['user_id'] ], $fname );
1330
			$this->update( 'filearchive', [ 'fa_deleted_user' => 0 ],
1331
				[ 'fa_deleted_user' => $conds['user_id'] ], $fname );
1332
			$this->update( 'filearchive', [ 'fa_user' => 0 ],
1333
				[ 'fa_user' => $conds['user_id'] ], $fname );
1334
			$this->update( 'uploadstash', [ 'us_user' => 0 ],
1335
				[ 'us_user' => $conds['user_id'] ], $fname );
1336
			$this->update( 'recentchanges', [ 'rc_user' => 0 ],
1337
				[ 'rc_user' => $conds['user_id'] ], $fname );
1338
			$this->update( 'logging', [ 'log_user' => 0 ],
1339
				[ 'log_user' => $conds['user_id'] ], $fname );
1340
		} elseif ( $table == $this->tableName( 'image' ) ) {
1341
			$this->update( 'oldimage', [ 'oi_name' => 0 ],
1342
				[ 'oi_name' => $conds['img_name'] ], $fname );
1343
		}
1344
1345
		return parent::delete( $table, $conds, $fname );
1346
	}
1347
1348
	/**
1349
	 * @param string $table
1350
	 * @param array $values
1351
	 * @param array $conds
1352
	 * @param string $fname
1353
	 * @param array $options
1354
	 * @return bool
1355
	 * @throws DBUnexpectedError
1356
	 */
1357
	function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
1358
		global $wgContLang;
1359
1360
		$table = $this->tableName( $table );
1361
		$opts = $this->makeUpdateOptions( $options );
1362
		$sql = "UPDATE $opts $table SET ";
1363
1364
		$first = true;
1365
		foreach ( $values as $col => &$val ) {
1366
			$sqlSet = $this->fieldBindStatement( $table, $col, $val, true );
1367
1368
			if ( !$first ) {
1369
				$sqlSet = ', ' . $sqlSet;
1370
			} else {
1371
				$first = false;
1372
			}
1373
			$sql .= $sqlSet;
1374
		}
1375
1376
		if ( $conds !== [] && $conds !== '*' ) {
1377
			$conds = $this->wrapConditionsForWhere( $table, $conds );
1378
			$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
1379
		}
1380
1381
		$this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
1382 View Code Duplication
		if ( $stmt === false ) {
1383
			$e = oci_error( $this->mConn );
1384
			$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1385
1386
			return false;
1387
		}
1388
		foreach ( $values as $col => &$val ) {
1389
			$col_info = $this->fieldInfoMulti( $table, $col );
1390
			$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
0 ignored issues
show
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...
1391
1392
			if ( $val === null ) {
0 ignored issues
show
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...
1393
				// do nothing ... null was inserted in statement creation
1394
			} elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
1395
				if ( is_object( $val ) ) {
1396
					$val = $val->getData();
1397
				}
1398
1399
				if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
1400
					$val = '31-12-2030 12:00:00.000000';
1401
				}
1402
1403
				$val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
1404
				if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
1405
					$e = oci_error( $stmt );
1406
					$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1407
1408
					return false;
1409
				}
1410 View Code Duplication
			} else {
1411
				/** @var OCI_Lob[] $lob */
1412
				$lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB );
1413
				if ( $lob[$col] === false ) {
1414
					$e = oci_error( $stmt );
1415
					throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
1416
				}
1417
1418
				if ( is_object( $val ) ) {
1419
					$val = $val->getData();
1420
				}
1421
1422
				if ( $col_type == 'BLOB' ) {
1423
					$lob[$col]->writeTemporary( $val );
1424
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, SQLT_BLOB );
1425
				} else {
1426
					$lob[$col]->writeTemporary( $val );
1427
					oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
1428
				}
1429
			}
1430
		}
1431
1432
		MediaWiki\suppressWarnings();
1433
1434 View Code Duplication
		if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
1435
			$e = oci_error( $stmt );
1436
			if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
1437
				$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1438
1439
				return false;
1440
			} else {
1441
				$this->mAffectedRows = oci_num_rows( $stmt );
1442
			}
1443
		} else {
1444
			$this->mAffectedRows = oci_num_rows( $stmt );
1445
		}
1446
1447
		MediaWiki\restoreWarnings();
1448
1449
		if ( isset( $lob ) ) {
1450
			foreach ( $lob as $lob_v ) {
1451
				$lob_v->free();
1452
			}
1453
		}
1454
1455
		if ( !$this->mTrxLevel ) {
1456
			oci_commit( $this->mConn );
1457
		}
1458
1459
		return oci_free_statement( $stmt );
1460
	}
1461
1462
	function bitNot( $field ) {
1463
		// expecting bit-fields smaller than 4bytes
1464
		return 'BITNOT(' . $field . ')';
1465
	}
1466
1467
	function bitAnd( $fieldLeft, $fieldRight ) {
1468
		return 'BITAND(' . $fieldLeft . ', ' . $fieldRight . ')';
1469
	}
1470
1471
	function bitOr( $fieldLeft, $fieldRight ) {
1472
		return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
1473
	}
1474
1475
	function getDBname() {
1476
		return $this->mDBname;
1477
	}
1478
1479
	function getServer() {
1480
		return $this->mServer;
1481
	}
1482
1483 View Code Duplication
	public function buildGroupConcatField(
1484
		$delim, $table, $field, $conds = '', $join_conds = []
1485
	) {
1486
		$fld = "LISTAGG($field," . $this->addQuotes( $delim ) . ") WITHIN GROUP (ORDER BY $field)";
1487
1488
		return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1489
	}
1490
1491
	/**
1492
	 * @param string $field Field or column to cast
1493
	 * @return string
1494
	 * @since 1.28
1495
	 */
1496
	public function buildStringCast( $field ) {
1497
		return 'CAST ( ' . $field . ' AS VARCHAR2 )';
1498
	}
1499
1500
	public function getInfinity() {
1501
		return '31-12-2030 12:00:00.000000';
1502
	}
1503
}
1504