]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/db/DatabaseOracle.php
MediaWiki 1.17.4
[autoinstalls/mediawiki.git] / includes / db / DatabaseOracle.php
1 <?php
2 /**
3  * This is the Oracle database abstraction layer.
4  *
5  * @file
6  * @ingroup Database
7  */
8
9 /**
10  * The oci8 extension is fairly weak and doesn't support oci_num_rows, among
11  * other things.  We use a wrapper class to handle that and other
12  * Oracle-specific bits, like converting column names back to lowercase.
13  * @ingroup Database
14  */
15 class ORAResult {
16         private $rows;
17         private $cursor;
18         private $nrows;
19         
20         private $columns = array();
21
22         private function array_unique_md( $array_in ) {
23                 $array_out = array();
24                 $array_hashes = array();
25
26                 foreach ( $array_in as $item ) {
27                         $hash = md5( serialize( $item ) );
28                         if ( !isset( $array_hashes[$hash] ) ) {
29                                 $array_hashes[$hash] = $hash;
30                                 $array_out[] = $item;
31                         }
32                 }
33
34                 return $array_out;
35         }
36
37         function __construct( &$db, $stmt, $unique = false ) {
38                 $this->db =& $db;
39
40                 if ( ( $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, - 1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM ) ) === false ) {
41                         $e = oci_error( $stmt );
42                         $db->reportQueryError( $e['message'], $e['code'], '', __METHOD__ );
43                         $this->free();
44                         return;
45                 }
46
47                 if ( $unique ) {
48                         $this->rows = $this->array_unique_md( $this->rows );
49                         $this->nrows = count( $this->rows );
50                 }
51
52                 if ($this->nrows > 0) {
53                         foreach ( $this->rows[0] as $k => $v ) {
54                                 $this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) );
55                         }
56                 }
57
58                 $this->cursor = 0;
59                 oci_free_statement( $stmt );
60         }
61
62         public function free() {
63                 unset($this->db);
64         }
65
66         public function seek( $row ) {
67                 $this->cursor = min( $row, $this->nrows );
68         }
69
70         public function numRows() {
71                 return $this->nrows;
72         }
73
74         public function numFields() {
75                 return count($this->columns);
76         }
77
78         public function fetchObject() {
79                 if ( $this->cursor >= $this->nrows ) {
80                         return false;
81                 }
82                 $row = $this->rows[$this->cursor++];
83                 $ret = new stdClass();
84                 foreach ( $row as $k => $v ) {
85                         $lc = $this->columns[$k];
86                         $ret->$lc = $v;
87                 }
88
89                 return $ret;
90         }
91
92         public function fetchRow() {
93                 if ( $this->cursor >= $this->nrows ) {
94                         return false;
95                 }
96
97                 $row = $this->rows[$this->cursor++];
98                 $ret = array();
99                 foreach ( $row as $k => $v ) {
100                         $lc = $this->columns[$k];
101                         $ret[$lc] = $v;
102                         $ret[$k] = $v;
103                 }
104                 return $ret;
105         }
106 }
107
108 /**
109  * Utility class.
110  * @ingroup Database
111  */
112 class ORAField implements Field {
113         private $name, $tablename, $default, $max_length, $nullable,
114                 $is_pk, $is_unique, $is_multiple, $is_key, $type;
115
116         function __construct( $info ) {
117                 $this->name = $info['column_name'];
118                 $this->tablename = $info['table_name'];
119                 $this->default = $info['data_default'];
120                 $this->max_length = $info['data_length'];
121                 $this->nullable = $info['not_null'];
122                 $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
123                 $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
124                 $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
125                 $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
126                 $this->type = $info['data_type'];
127         }
128
129         function name() {
130                 return $this->name;
131         }
132
133         function tableName() {
134                 return $this->tablename;
135         }
136
137         function defaultValue() {
138                 return $this->default;
139         }
140
141         function maxLength() {
142                 return $this->max_length;
143         }
144
145         function isNullable() {
146                 return $this->nullable;
147         }
148
149         function isKey() {
150                 return $this->is_key;
151         }
152
153         function isMultipleKey() {
154                 return $this->is_multiple;
155         }
156
157         function type() {
158                 return $this->type;
159         }
160 }
161
162 /**
163  * @ingroup Database
164  */
165 class DatabaseOracle extends DatabaseBase {
166         var $mInsertId = null;
167         var $mLastResult = null;
168         var $numeric_version = null;
169         var $lastResult = null;
170         var $cursor = 0;
171         var $mAffectedRows;
172
173         var $ignore_DUP_VAL_ON_INDEX = false;
174         var $sequenceData = null;
175
176         var $defaultCharset = 'AL32UTF8';
177
178         var $mFieldInfoCache = array();
179
180         function __construct( $server = false, $user = false, $password = false, $dbName = false,
181                 $flags = 0, $tablePrefix = 'get from global' )
182         {
183                 $tablePrefix = $tablePrefix == 'get from global' ? $tablePrefix : strtoupper( $tablePrefix );
184                 parent::__construct( $server, $user, $password, $dbName, $flags, $tablePrefix );
185                 wfRunHooks( 'DatabaseOraclePostInit', array( $this ) );
186         }
187
188         function __destruct() {
189                 if ($this->mOpened) {
190                         wfSuppressWarnings();
191                         $this->close();
192                         wfRestoreWarnings();
193                 }
194         }
195
196         function getType() {
197                 return 'oracle';
198         }
199
200         function cascadingDeletes() {
201                 return true;
202         }
203         function cleanupTriggers() {
204                 return true;
205         }
206         function strictIPs() {
207                 return true;
208         }
209         function realTimestamps() {
210                 return true;
211         }
212         function implicitGroupby() {
213                 return false;
214         }
215         function implicitOrderby() {
216                 return false;
217         }
218         function searchableIPs() {
219                 return true;
220         }
221
222         static function newFromParams( $server, $user, $password, $dbName, $flags = 0 )
223         {
224                 return new DatabaseOracle( $server, $user, $password, $dbName, $flags );
225         }
226
227         /**
228          * Usually aborts on failure
229          */
230         function open( $server, $user, $password, $dbName ) {
231                 if ( !function_exists( 'oci_connect' ) ) {
232                         throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
233                 }
234
235                 $this->mUser = $user;
236                 $this->mPassword = $password;
237                 // changed internal variables functions
238                 // mServer now holds the TNS endpoint
239                 // mDBname is schema name if different from username
240                 if ( !$server ) {
241                         // backward compatibillity (server used to be null and TNS was supplied in dbname)
242                         $this->mServer = $dbName;
243                         $this->mDBname = $user;
244                 } else {
245                         $this->mServer = $server;
246                         if ( !$dbName ) {
247                                 $this->mDBname = $user;
248                         } else {        
249                                 $this->mDBname = $dbName;
250                         }
251                 }
252
253                 if ( !strlen( $user ) ) { # e.g. the class is being loaded
254                         return;
255                 }
256
257                 $session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
258                 if ( $this->mFlags & DBO_DEFAULT ) {
259                         $this->mConn = oci_new_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
260                 } else {
261                         $this->mConn = oci_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
262                 }
263
264                 if ( $this->mUser != $this->mDBname ) {
265                         //change current schema in session
266                         $this->selectDB( $this->mDBname );
267                 }
268
269                 if ( !$this->mConn ) {
270                         throw new DBConnectionError( $this, $this->lastError() );
271                 }
272
273                 $this->mOpened = true;
274
275                 # removed putenv calls because they interfere with the system globaly
276                 $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
277                 $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
278                 $this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
279                 
280                 return $this->mConn;
281         }
282
283         /**
284          * Closes a database connection, if it is open
285          * Returns success, true if already closed
286          */
287         function close() {
288                 $this->mOpened = false;
289                 if ( $this->mConn ) {
290                         if ( $this->mTrxLevel ) {
291                                 $this->commit();
292                         }
293                         return oci_close( $this->mConn );
294                 } else {
295                         return true;
296                 }
297         }
298
299         function execFlags() {
300                 return $this->mTrxLevel ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS;
301         }
302
303         function doQuery( $sql ) {
304                 wfDebug( "SQL: [$sql]\n" );
305                 if ( !mb_check_encoding( $sql ) ) {
306                         throw new MWException( "SQL encoding is invalid\n$sql" );
307                 }
308
309                 // handle some oracle specifics
310                 // remove AS column/table/subquery namings
311                 if( !$this->getFlag( DBO_DDLMODE ) ) {
312                         $sql = preg_replace( '/ as /i', ' ', $sql );
313                 }
314
315                 // Oracle has issues with UNION clause if the statement includes LOB fields
316                 // So we do a UNION ALL and then filter the results array with array_unique
317                 $union_unique = ( preg_match( '/\/\* UNION_UNIQUE \*\/ /', $sql ) != 0 );
318                 // EXPLAIN syntax in Oracle is EXPLAIN PLAN FOR and it return nothing
319                 // you have to select data from plan table after explain
320                 $explain_id = date( 'dmYHis' );
321
322                 $sql = preg_replace( '/^EXPLAIN /', 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR', $sql, 1, $explain_count );
323
324                 wfSuppressWarnings();
325
326                 if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
327                         $e = oci_error( $this->mConn );
328                         $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
329                         return false;
330                 }
331
332                 if ( !oci_execute( $stmt, $this->execFlags() ) ) {
333                         $e = oci_error( $stmt );
334                         if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
335                                 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
336                                 return false;
337                         }
338                 }
339
340                 wfRestoreWarnings();
341
342                 if ( $explain_count > 0 ) {
343                         return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table WHERE statement_id = \'' . $explain_id . '\'' );
344                 } elseif ( oci_statement_type( $stmt ) == 'SELECT' ) {
345                         return new ORAResult( $this, $stmt, $union_unique );
346                 } else {
347                         $this->mAffectedRows = oci_num_rows( $stmt );
348                         return true;
349                 }
350         }
351
352         function queryIgnore( $sql, $fname = '' ) {
353                 return $this->query( $sql, $fname, true );
354         }
355
356         function freeResult( $res ) {
357                 if ( $res instanceof ResultWrapper ) {
358                         $res = $res->result;
359                 }
360                 
361                 $res->free();
362         }
363
364         function fetchObject( $res ) {
365                 if ( $res instanceof ResultWrapper ) {
366                         $res = $res->result;
367                 }
368                 
369                 return $res->fetchObject();
370         }
371
372         function fetchRow( $res ) {
373                 if ( $res instanceof ResultWrapper ) {
374                         $res = $res->result;
375                 }
376
377                 return $res->fetchRow();
378         }
379
380         function numRows( $res ) {
381                 if ( $res instanceof ResultWrapper ) {
382                         $res = $res->result;
383                 }
384
385                 return $res->numRows();
386         }
387
388         function numFields( $res ) {
389                 if ( $res instanceof ResultWrapper ) {
390                         $res = $res->result;
391                 }
392
393                 return $res->numFields();
394         }
395
396         function fieldName( $stmt, $n ) {
397                 return oci_field_name( $stmt, $n );
398         }
399
400         /**
401          * This must be called after nextSequenceVal
402          */
403         function insertId() {
404                 return $this->mInsertId;
405         }
406
407         function dataSeek( $res, $row ) {
408                 if ( $res instanceof ORAResult ) {
409                         $res->seek( $row );
410                 } else {
411                         $res->result->seek( $row );
412                 }
413         }
414
415         function lastError() {
416                 if ( $this->mConn === false ) {
417                         $e = oci_error();
418                 } else {
419                         $e = oci_error( $this->mConn );
420                 }
421                 return $e['message'];
422         }
423
424         function lastErrno() {
425                 if ( $this->mConn === false ) {
426                         $e = oci_error();
427                 } else {
428                         $e = oci_error( $this->mConn );
429                 }
430                 return $e['code'];
431         }
432
433         function affectedRows() {
434                 return $this->mAffectedRows;
435         }
436
437         /**
438          * Returns information about an index
439          * If errors are explicitly ignored, returns NULL on failure
440          */
441         function indexInfo( $table, $index, $fname = 'DatabaseOracle::indexExists' ) {
442                 return false;
443         }
444
445         function indexUnique( $table, $index, $fname = 'DatabaseOracle::indexUnique' ) {
446                 return false;
447         }
448
449         function insert( $table, $a, $fname = 'DatabaseOracle::insert', $options = array() ) {
450                 if ( !count( $a ) ) {
451                         return true;
452                 }
453
454                 if ( !is_array( $options ) ) {
455                         $options = array( $options );
456                 }
457
458                 if ( in_array( 'IGNORE', $options ) ) {
459                         $this->ignore_DUP_VAL_ON_INDEX = true;
460                 }
461
462                 if ( !is_array( reset( $a ) ) ) {
463                         $a = array( $a );
464                 }
465
466                 foreach ( $a as &$row ) {
467                         $this->insertOneRow( $table, $row, $fname );
468                 }
469                 $retVal = true;
470
471                 if ( in_array( 'IGNORE', $options ) ) {
472                         $this->ignore_DUP_VAL_ON_INDEX = false;
473                 }
474
475                 return $retVal;
476         }
477
478         private function fieldBindStatement ( $table, $col, &$val, $includeCol = false ) {
479                 $col_info = $this->fieldInfoMulti( $table, $col );
480                 $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
481                 
482                 $bind = '';
483                 if ( is_numeric( $col ) ) {
484                         $bind = $val;
485                         $val = null;
486                         return $bind; 
487                 } else if ( $includeCol ) {
488                         $bind = "$col = ";
489                 }
490                 
491                 if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) {
492                         $val = null;
493                 }
494
495                 if ( $val === 'NULL' ) {
496                         $val = null;
497                 }
498
499                 if ( $val === null ) {
500                         if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
501                                 $bind .= 'DEFAULT';
502                         } else {
503                                 $bind .= 'NULL';
504                         }
505                 } else {
506                         $bind .= ':' . $col;
507                 }
508                 
509                 return $bind;
510         }
511
512         private function insertOneRow( $table, $row, $fname ) {
513                 global $wgContLang;
514
515                 $table = $this->tableName( $table );
516                 // "INSERT INTO tables (a, b, c)"
517                 $sql = "INSERT INTO " . $table . " (" . join( ',', array_keys( $row ) ) . ')';
518                 $sql .= " VALUES (";
519
520                 // for each value, append ":key"
521                 $first = true;
522                 foreach ( $row as $col => &$val ) {
523                         if ( !$first ) {
524                                 $sql .= ', ';
525                         } else {
526                                 $first = false;
527                         }
528                         
529                         $sql .= $this->fieldBindStatement( $table, $col, $val );
530                 }
531                 $sql .= ')';
532
533                 if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
534                         $e = oci_error( $this->mConn );
535                         $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
536                         return false;
537                 }
538                 foreach ( $row as $col => &$val ) {
539                         $col_info = $this->fieldInfoMulti( $table, $col );
540                         $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
541
542                         if ( $val === null ) {
543                                 // do nothing ... null was inserted in statement creation
544                         } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
545                                 if ( is_object( $val ) ) {
546                                         $val = $val->fetch();
547                                 }
548
549                                 if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
550                                         $val = '31-12-2030 12:00:00.000000';
551                                 }
552
553                                 $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
554                                 if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
555                                         $e = oci_error( $stmt );
556                                         $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
557                                         return false;
558                                 }
559                         } else {
560                                 if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) {
561                                         $e = oci_error( $stmt );
562                                         throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
563                                 }
564
565                                 if ( is_object( $val ) ) {
566                                         $val = $val->fetch();
567                                 }
568
569                                 if ( $col_type == 'BLOB' ) {
570                                         $lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
571                                         oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_BLOB );
572                                 } else {
573                                         $lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
574                                         oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB );
575                                 }
576                         }
577                 }
578
579                 wfSuppressWarnings();
580
581                 if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
582                         $e = oci_error( $stmt );
583                         if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
584                                 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
585                                 return false;
586                         } else {
587                                 $this->mAffectedRows = oci_num_rows( $stmt );
588                         }
589                 } else {
590                         $this->mAffectedRows = oci_num_rows( $stmt );
591                 }
592
593                 wfRestoreWarnings();
594
595                 if ( isset( $lob ) ) {
596                         foreach ( $lob as $lob_v ) {
597                                 $lob_v->free();
598                         }
599                 }
600
601                 if ( !$this->mTrxLevel ) {
602                         oci_commit( $this->mConn );
603                 }
604
605                 oci_free_statement( $stmt );
606         }
607
608         function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseOracle::insertSelect',
609                 $insertOptions = array(), $selectOptions = array() )
610         {
611                 $destTable = $this->tableName( $destTable );
612                 if ( !is_array( $selectOptions ) ) {
613                         $selectOptions = array( $selectOptions );
614                 }
615                 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
616                 if ( is_array( $srcTable ) ) {
617                         $srcTable =  implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
618                 } else {
619                         $srcTable = $this->tableName( $srcTable );
620                 }
621
622                 if ( ( $sequenceData = $this->getSequenceData( $destTable ) ) !== false &&
623                                 !isset( $varMap[$sequenceData['column']] ) )
624                 {
625                         $varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
626                 }
627
628                 // count-alias subselect fields to avoid abigious definition errors
629                 $i = 0;
630                 foreach ( $varMap as &$val ) {
631                         $val = $val . ' field' . ( $i++ );
632                 }
633
634                 $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
635                         " SELECT $startOpts " . implode( ',', $varMap ) .
636                         " FROM $srcTable $useIndex ";
637                 if ( $conds != '*' ) {
638                         $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
639                 }
640                 $sql .= " $tailOpts";
641
642                 if ( in_array( 'IGNORE', $insertOptions ) ) {
643                         $this->ignore_DUP_VAL_ON_INDEX = true;
644                 }
645
646                 $retval = $this->query( $sql, $fname );
647
648                 if ( in_array( 'IGNORE', $insertOptions ) ) {
649                         $this->ignore_DUP_VAL_ON_INDEX = false;
650                 }
651
652                 return $retval;
653         }
654
655         function tableName( $name ) {
656                 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
657                 /*
658                 Replace reserved words with better ones
659                 Using uppercase because that's the only way Oracle can handle
660                 quoted tablenames
661                 */
662                 switch( $name ) {
663                         case 'user':
664                                 $name = 'MWUSER';
665                                 break;
666                         case 'text':
667                                 $name = 'PAGECONTENT';
668                                 break;
669                 }
670
671                 /*
672                         The rest of procedure is equal to generic Databse class
673                         except for the quoting style
674                 */
675                 if ( $name[0] == '"' && substr( $name, - 1, 1 ) == '"' ) {
676                         return $name;
677                 }
678                 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
679                         return $name;
680                 }
681                 $dbDetails = array_reverse( explode( '.', $name, 2 ) );
682                 if ( isset( $dbDetails[1] ) ) {
683                         @list( $table, $database ) = $dbDetails;
684                 } else {
685                         @list( $table ) = $dbDetails;
686                 }
687
688                 $prefix = $this->mTablePrefix;
689
690                 if ( isset( $database ) ) {
691                         $table = ( $table[0] == '`' ? $table : "`{$table}`" );
692                 }
693
694                 if ( !isset( $database ) && isset( $wgSharedDB ) && $table[0] != '"'
695                         && isset( $wgSharedTables )
696                         && is_array( $wgSharedTables )
697                         && in_array( $table, $wgSharedTables )
698                 ) {
699                         $database = $wgSharedDB;
700                         $prefix   = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
701                 }
702
703                 if ( isset( $database ) ) {
704                         $database = ( $database[0] == '"' ? $database : "\"{$database}\"" );
705                 }
706                 $table = ( $table[0] == '"') ? $table : "\"{$prefix}{$table}\"" ;
707
708                 $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
709
710                 return strtoupper( $tableName );
711         }
712
713         function tableNameInternal( $name ) {
714                 $name = $this->tableName( $name );
715                 return preg_replace( '/.*\."(.*)"/', '$1', $name);
716         }
717
718         /**
719          * Return the next in a sequence, save the value for retrieval via insertId()
720          */
721         function nextSequenceValue( $seqName ) {
722                 $res = $this->query( "SELECT $seqName.nextval FROM dual" );
723                 $row = $this->fetchRow( $res );
724                 $this->mInsertId = $row[0];
725                 return $this->mInsertId;
726         }
727
728         /**
729          * Return sequence_name if table has a sequence
730          */
731         private function getSequenceData( $table ) {
732                 if ( $this->sequenceData == null ) {
733                         $result = $this->doQuery( 'SELECT lower(us.sequence_name), lower(utc.table_name), lower(utc.column_name) from user_sequences us, user_tab_columns utc where us.sequence_name = utc.table_name||\'_\'||utc.column_name||\'_SEQ\'' );
734
735                         while ( ( $row = $result->fetchRow() ) !== false ) {
736                                 $this->sequenceData[$this->tableName( $row[1] )] = array(
737                                         'sequence' => $row[0],
738                                         'column' => $row[2]
739                                 );
740                         }
741                 }
742
743                 return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
744         }
745
746         /**
747          * REPLACE query wrapper
748          * Oracle simulates this with a DELETE followed by INSERT
749          * $row is the row to insert, an associative array
750          * $uniqueIndexes is an array of indexes. Each element may be either a
751          * field name or an array of field names
752          *
753          * It may be more efficient to leave off unique indexes which are unlikely to collide.
754          * However if you do this, you run the risk of encountering errors which wouldn't have
755          * occurred in MySQL.
756          *
757          * @param $table String: table name
758          * @param $uniqueIndexes Array: array of indexes. Each element may be
759          *                       either a field name or an array of field names
760          * @param $rows Array: rows to insert to $table
761          * @param $fname String: function name, you can use __METHOD__ here
762          */
763         function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseOracle::replace' ) {
764                 $table = $this->tableName( $table );
765
766                 if ( count( $rows ) == 0 ) {
767                         return;
768                 }
769
770                 # Single row case
771                 if ( !is_array( reset( $rows ) ) ) {
772                         $rows = array( $rows );
773                 }
774
775                 $sequenceData = $this->getSequenceData( $table );
776
777                 foreach ( $rows as $row ) {
778                         # Delete rows which collide
779                         if ( $uniqueIndexes ) {
780                                 $deleteConds = array();
781                                 foreach ( $uniqueIndexes as $key=>$index ) {
782                                         if ( is_array( $index ) ) {
783                                                 $deleteConds2 = array();
784                                                 foreach ( $index as $col ) {
785                                                         $deleteConds2[$col] = $row[$col];
786                                                 }
787                                                 $deleteConds[$key] = $this->makeList( $deleteConds2, LIST_AND );
788                                         } else {
789                                                 $deleteConds[$index] = $row[$index];
790                                         }
791                                 }
792                                 $deleteConds = array( $this->makeList( $deleteConds, LIST_OR ) );
793                                 $this->delete( $table, $deleteConds, $fname );
794                         }
795
796                         
797                         if ( $sequenceData !== false && !isset( $row[$sequenceData['column']] ) ) {
798                                 $row[$sequenceData['column']] = $this->nextSequenceValue( $sequenceData['sequence'] );
799                         }
800
801                         # Now insert the row
802                         $this->insert( $table, $row, $fname );
803                 }
804         }
805
806         # DELETE where the condition is a join
807         function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseOracle::deleteJoin' ) {
808                 if ( !$conds ) {
809                         throw new DBUnexpectedError( $this, 'DatabaseOracle::deleteJoin() called with empty $conds' );
810                 }
811
812                 $delTable = $this->tableName( $delTable );
813                 $joinTable = $this->tableName( $joinTable );
814                 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
815                 if ( $conds != '*' ) {
816                         $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
817                 }
818                 $sql .= ')';
819
820                 $this->query( $sql, $fname );
821         }
822
823         # Returns the size of a text field, or -1 for "unlimited"
824         function textFieldSize( $table, $field ) {
825                 $fieldInfoData = $this->fieldInfo( $table, $field );
826                 return $fieldInfoData->maxLength();
827         }
828
829         function limitResult( $sql, $limit, $offset = false ) {
830                 if ( $offset === false ) {
831                         $offset = 0;
832                 }
833                 return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
834         }
835
836         function encodeBlob( $b ) {
837                 return new Blob( $b );
838         }
839
840         function decodeBlob( $b ) {
841                 if ( $b instanceof Blob ) {
842                         $b = $b->fetch();
843                 }
844                 return $b;
845         }
846
847         function unionQueries( $sqls, $all ) {
848                 $glue = ' UNION ALL ';
849                 return 'SELECT * ' . ( $all ? '':'/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')' ;
850         }
851
852         public function unixTimestamp( $field ) {
853                 return "((trunc($field) - to_date('19700101','YYYYMMDD')) * 86400)";
854         }
855
856         function wasDeadlock() {
857                 return $this->lastErrno() == 'OCI-00060';
858         }
859
860         function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseOracle::duplicateTableStructure' ) {
861                 global $wgDBprefix;
862                 
863                 $temporary = $temporary ? 'TRUE' : 'FALSE';
864
865                 $newName = trim( strtoupper( $newName ), '"');
866                 $oldName = trim( strtoupper( $oldName ), '"');
867
868                 $tabName = substr( $newName, strlen( $wgDBprefix ) );
869                 $oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
870
871                 return $this->doQuery( 'BEGIN DUPLICATE_TABLE(\'' . $tabName . '\', \'' . $oldPrefix . '\', \'' . strtoupper( $wgDBprefix ) . '\', ' . $temporary . '); END;' );
872         }
873
874         function listTables( $prefix = null, $fname = 'DatabaseOracle::listTables' ) {
875                 $listWhere = '';
876                 if (!empty($prefix)) {
877                         $listWhere = ' AND table_name LIKE \''.strtoupper($prefix).'%\'';
878                 }
879                 
880                 $result = $this->doQuery( "SELECT table_name FROM user_tables WHERE table_name NOT LIKE '%!_IDX$_' ESCAPE '!' $listWhere" );
881
882                 // dirty code ... i know
883                 $endArray = array();
884                 $endArray[] = $prefix.'MWUSER';
885                 $endArray[] = $prefix.'PAGE';
886                 $endArray[] = $prefix.'IMAGE';
887                 $fixedOrderTabs = $endArray;
888                 while (($row = $result->fetchRow()) !== false) {
889                         if (!in_array($row['table_name'], $fixedOrderTabs))
890                                 $endArray[] = $row['table_name'];
891                 }
892
893                 return $endArray;
894         }
895
896         public function dropTable( $tableName, $fName = 'DatabaseOracle::dropTable' ) {
897                 $tableName = $this->tableName($tableName);
898                 if( !$this->tableExists( $tableName ) ) {
899                         return false;
900                 }
901                 
902                 return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
903         }
904
905         function timestamp( $ts = 0 ) {
906                 return wfTimestamp( TS_ORACLE, $ts );
907         }
908
909         /**
910          * Return aggregated value function call
911          */
912         function aggregateValue ( $valuedata, $valuename = 'value' ) {
913                 return $valuedata;
914         }
915
916         function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
917                 # Ignore errors during error handling to avoid infinite
918                 # recursion
919                 $ignore = $this->ignoreErrors( true );
920                 ++$this->mErrorCount;
921
922                 if ( $ignore || $tempIgnore ) {
923                         wfDebug( "SQL ERROR (ignored): $error\n" );
924                         $this->ignoreErrors( $ignore );
925                 } else {
926                         throw new DBQueryError( $this, $error, $errno, $sql, $fname );
927                 }
928         }
929
930         /**
931          * @return string wikitext of a link to the server software's web site
932          */
933         public static function getSoftwareLink() {
934                 return '[http://www.oracle.com/ Oracle]';
935         }
936
937         /**
938          * @return string Version information from the database
939          */
940         function getServerVersion() {
941                 //better version number, fallback on driver
942                 $rset = $this->doQuery( 'SELECT version FROM product_component_version WHERE UPPER(product) LIKE \'ORACLE DATABASE%\'' );
943                 if ( !( $row =  $rset->fetchRow() ) ) {
944                         return oci_server_version( $this->mConn );
945                 } 
946                 return $row['version'];
947         }
948
949         /**
950          * Query whether a given table exists (in the given schema, or the default mw one if not given)
951          */
952         function tableExists( $table ) {
953                 $table = $this->addQuotes( trim( $this->tableName($table), '"' ) );
954                 $owner = $this->addQuotes( strtoupper( $this->mDBname ) );
955                 $SQL = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
956                 $res = $this->doQuery( $SQL );
957                 if ( $res ) {
958                         $count = $res->numRows();
959                         $res->free();
960                 } else {
961                         $count = 0;
962                 }
963                 return $count!=0;
964         }
965
966         /**
967          * Function translates mysql_fetch_field() functionality on ORACLE.
968          * Caching is present for reducing query time.
969          * For internal calls. Use fieldInfo for normal usage.
970          * Returns false if the field doesn't exist
971          *
972          * @param $table Array
973          * @param $field String
974          */
975         private function fieldInfoMulti( $table, $field ) {
976                 $field = strtoupper( $field );
977                 if ( is_array( $table ) ) {
978                         $table = array_map( array( &$this, 'tableNameInternal' ), $table );
979                         $tableWhere = 'IN (';
980                         foreach( $table as &$singleTable ) {
981                                 $singleTable = strtoupper( trim( $singleTable, '"' ) );
982                                 if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
983                                         return $this->mFieldInfoCache["$singleTable.$field"];
984                                 }
985                                 $tableWhere .= '\'' . $singleTable . '\',';
986                         }
987                         $tableWhere = rtrim( $tableWhere, ',' ) . ')';
988                 } else {
989                         $table = strtoupper( trim( $this->tableNameInternal( $table ), '"' ) );
990                         if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
991                                 return $this->mFieldInfoCache["$table.$field"];
992                         }
993                         $tableWhere = '= \''.$table.'\'';
994                 }
995
996                 $fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name '.$tableWhere.' and column_name = \''.$field.'\'' );
997                 if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
998                         $e = oci_error( $fieldInfoStmt );
999                         $this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
1000                         return false;
1001                 }
1002                 $res = new ORAResult( $this, $fieldInfoStmt );
1003                 if ( $res->numRows() == 0 ) {
1004                         if ( is_array( $table ) ) {
1005                                 foreach( $table as &$singleTable ) {
1006                                         $this->mFieldInfoCache["$singleTable.$field"] = false;
1007                                 }
1008                         } else {
1009                                 $this->mFieldInfoCache["$table.$field"] = false;
1010                         }
1011                         $fieldInfoTemp = null;
1012                 } else {
1013                         $fieldInfoTemp = new ORAField( $res->fetchRow() );
1014                         $table = $fieldInfoTemp->tableName();
1015                         $this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
1016                 }
1017                 $res->free();
1018                 return $fieldInfoTemp;
1019         }
1020
1021         function fieldInfo( $table, $field ) {
1022                 if ( is_array( $table ) ) {
1023                         throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
1024                 }
1025                 return $this->fieldInfoMulti ($table, $field);
1026         }
1027
1028         function begin( $fname = 'DatabaseOracle::begin' ) {
1029                 $this->mTrxLevel = 1;
1030         }
1031
1032         function commit( $fname = 'DatabaseOracle::commit' ) {
1033                 if ( $this->mTrxLevel ) {
1034                         oci_commit( $this->mConn );
1035                         $this->mTrxLevel = 0;
1036                 }
1037         }
1038
1039         function rollback( $fname = 'DatabaseOracle::rollback' ) {
1040                 if ( $this->mTrxLevel ) {
1041                         oci_rollback( $this->mConn );
1042                         $this->mTrxLevel = 0;
1043                 }
1044         }
1045
1046         /* Not even sure why this is used in the main codebase... */
1047         function limitResultForUpdate( $sql, $num ) {
1048                 return $sql;
1049         }
1050
1051         /* defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; */
1052         function sourceStream( $fp, $lineCallback = false, $resultCallback = false, $fname = 'DatabaseOracle::sourceStream' ) {
1053                 $cmd = '';
1054                 $done = false;
1055                 $dollarquote = false;
1056
1057                 $replacements = array();
1058
1059                 while ( ! feof( $fp ) ) {
1060                         if ( $lineCallback ) {
1061                                 call_user_func( $lineCallback );
1062                         }
1063                         $line = trim( fgets( $fp, 1024 ) );
1064                         $sl = strlen( $line ) - 1;
1065
1066                         if ( $sl < 0 ) {
1067                                 continue;
1068                         }
1069                         if ( '-' == $line { 0 } && '-' == $line { 1 } ) {
1070                                 continue;
1071                         }
1072
1073                         // Allow dollar quoting for function declarations
1074                         if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) {
1075                                 if ( $dollarquote ) {
1076                                         $dollarquote = false;
1077                                         $line = str_replace( '/*$mw$*/', '', $line ); // remove dollarquotes
1078                                         $done = true;
1079                                 } else {
1080                                         $dollarquote = true;
1081                                 }
1082                         } elseif ( !$dollarquote ) {
1083                                 if ( ';' == $line { $sl } && ( $sl < 2 || ';' != $line { $sl - 1 } ) ) {
1084                                         $done = true;
1085                                         $line = substr( $line, 0, $sl );
1086                                 }
1087                         }
1088
1089                         if ( $cmd != '' ) {
1090                                 $cmd .= ' ';
1091                         }
1092                         $cmd .= "$line\n";
1093
1094                         if ( $done ) {
1095                                 $cmd = str_replace( ';;', ";", $cmd );
1096                                 if ( strtolower( substr( $cmd, 0, 6 ) ) == 'define' ) {
1097                                         if ( preg_match( '/^define\s*([^\s=]*)\s*=\s*\'\{\$([^\}]*)\}\'/', $cmd, $defines ) ) {
1098                                                 $replacements[$defines[2]] = $defines[1];
1099                                         }
1100                                 } else {
1101                                         foreach ( $replacements as $mwVar => $scVar ) {
1102                                                 $cmd = str_replace( '&' . $scVar . '.', '`{$' . $mwVar . '}`', $cmd );
1103                                         }
1104
1105                                         $cmd = $this->replaceVars( $cmd );
1106                                         $res = $this->doQuery( $cmd );
1107                                         if ( $resultCallback ) {
1108                                                 call_user_func( $resultCallback, $res, $this );
1109                                         }
1110
1111                                         if ( false === $res ) {
1112                                                 $err = $this->lastError();
1113                                                 return "Query \"{$cmd}\" failed with error code \"$err\".\n";
1114                                         }
1115                                 }
1116
1117                                 $cmd = '';
1118                                 $done = false;
1119                         }
1120                 }
1121                 return true;
1122         }
1123
1124         function selectDB( $db ) {
1125                 $this->mDBname = $db;
1126                 if ( $db == null || $db == $this->mUser ) {
1127                         return true;
1128                 }
1129                 $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper($db);
1130                 $stmt = oci_parse( $this->mConn, $sql );
1131                 wfSuppressWarnings();
1132                 $success = oci_execute( $stmt );
1133                 wfRestoreWarnings();
1134                 if ( !$success ) {
1135                         $e = oci_error( $stmt );
1136                         if ( $e['code'] != '1435' ) {
1137                                 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1138                         }
1139                         return false;
1140                 }
1141                 return true;
1142         }
1143
1144         function strencode( $s ) {
1145                 return str_replace( "'", "''", $s );
1146         }
1147
1148         function addQuotes( $s ) {
1149                 global $wgContLang;
1150                 if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
1151                         $s = $wgContLang->checkTitleEncoding( $s );
1152                 }
1153                 return "'" . $this->strencode( $s ) . "'";
1154         }
1155
1156         public function addIdentifierQuotes( $s ) {
1157                 if ( !$this->mFlags & DBO_DDLMODE ) {
1158                         $s = '"' . str_replace( '"', '""', $s ) . '"';
1159                 }
1160                 return $s;
1161         }
1162
1163         private function wrapFieldForWhere( $table, &$col, &$val ) {
1164                 global $wgContLang;
1165                 
1166                 $col_info = $this->fieldInfoMulti( $table, $col );
1167                 $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
1168                 if ( $col_type == 'CLOB' ) {
1169                         $col = 'TO_CHAR(' . $col . ')';
1170                         $val = $wgContLang->checkTitleEncoding( $val );
1171                 } elseif ( $col_type == 'VARCHAR2' && !mb_check_encoding( $val ) ) {
1172                         $val = $wgContLang->checkTitleEncoding( $val );
1173                 }
1174         }
1175
1176         private function wrapConditionsForWhere ( $table, $conds, $parentCol = null ) {
1177                 $conds2 = array();
1178                 foreach ( $conds as $col => $val ) {
1179                         if ( is_array( $val ) ) {
1180                                 $conds2[$col] = $this->wrapConditionsForWhere ( $table, $val, $col );
1181                         } else {
1182                                 if ( is_numeric( $col ) && $parentCol != null ) {
1183                                         $this->wrapFieldForWhere ( $table, $parentCol, $val );
1184                                 } else {
1185                                         $this->wrapFieldForWhere ( $table, $col, $val );
1186                                 }
1187                                 $conds2[$col] = $val;
1188                         }
1189                 }
1190                 return $conds2;
1191         }
1192
1193         function selectRow( $table, $vars, $conds, $fname = 'DatabaseOracle::selectRow', $options = array(), $join_conds = array() ) {
1194                 if ( is_array($conds) ) {
1195                         $conds = $this->wrapConditionsForWhere( $table, $conds );
1196                 }
1197                 return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
1198         }
1199
1200         /**
1201          * Returns an optional USE INDEX clause to go after the table, and a
1202          * string to go at the end of the query
1203          *
1204          * @private
1205          *
1206          * @param $options Array: an associative array of options to be turned into
1207          *              an SQL query, valid keys are listed in the function.
1208          * @return array
1209          */
1210         function makeSelectOptions( $options ) {
1211                 $preLimitTail = $postLimitTail = '';
1212                 $startOpts = '';
1213
1214                 $noKeyOptions = array();
1215                 foreach ( $options as $key => $option ) {
1216                         if ( is_numeric( $key ) ) {
1217                                 $noKeyOptions[$option] = true;
1218                         }
1219                 }
1220
1221                 if ( isset( $options['GROUP BY'] ) ) {
1222                         $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
1223                 }
1224                 if ( isset( $options['ORDER BY'] ) ) {
1225                         $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
1226                 }
1227
1228                 # if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE';
1229                 # if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE';
1230                 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1231                         $startOpts .= 'DISTINCT';
1232                 }
1233
1234                 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
1235                         $useIndex = $this->useIndexClause( $options['USE INDEX'] );
1236                 } else {
1237                         $useIndex = '';
1238                 }
1239
1240                 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
1241         }
1242
1243         public function delete( $table, $conds, $fname = 'DatabaseOracle::delete' ) {
1244                 if ( is_array($conds) ) {
1245                         $conds = $this->wrapConditionsForWhere( $table, $conds );
1246                 }
1247                 return parent::delete( $table, $conds, $fname );
1248         }
1249
1250         function update( $table, $values, $conds, $fname = 'DatabaseOracle::update', $options = array() ) {
1251                 global $wgContLang;
1252                 
1253                 $table = $this->tableName( $table );
1254                 $opts = $this->makeUpdateOptions( $options );
1255                 $sql = "UPDATE $opts $table SET ";
1256                 
1257                 $first = true;
1258                 foreach ( $values as $col => &$val ) {
1259                         $sqlSet = $this->fieldBindStatement( $table, $col, $val, true );
1260                         
1261                         if ( !$first ) {
1262                                 $sqlSet = ', ' . $sqlSet;
1263                         } else {
1264                                 $first = false;
1265                         }
1266                         $sql .= $sqlSet;
1267                 }
1268
1269                 if ( $conds != '*' ) {
1270                         $conds = $this->wrapConditionsForWhere( $table, $conds );
1271                         $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
1272                 }
1273
1274                 if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
1275                         $e = oci_error( $this->mConn );
1276                         $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1277                         return false;
1278                 }
1279                 foreach ( $values as $col => &$val ) {
1280                         $col_info = $this->fieldInfoMulti( $table, $col );
1281                         $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
1282
1283                         if ( $val === null ) {
1284                                 // do nothing ... null was inserted in statement creation
1285                         } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
1286                                 if ( is_object( $val ) ) {
1287                                         $val = $val->getData();
1288                                 }
1289
1290                                 if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
1291                                         $val = '31-12-2030 12:00:00.000000';
1292                                 }
1293
1294                                 $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
1295                                 if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
1296                                         $e = oci_error( $stmt );
1297                                         $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1298                                         return false;
1299                                 }
1300                         } else {
1301                                 if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) {
1302                                         $e = oci_error( $stmt );
1303                                         throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
1304                                 }
1305
1306                                 if ( $col_type == 'BLOB' ) { 
1307                                         $lob[$col]->writeTemporary( $val ); 
1308                                         oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, SQLT_BLOB );
1309                                 } else {
1310                                         $lob[$col]->writeTemporary( $val );
1311                                         oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB );
1312                                 }
1313                         }
1314                 }
1315
1316                 wfSuppressWarnings();
1317
1318                 if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
1319                         $e = oci_error( $stmt );
1320                         if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
1321                                 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1322                                 return false;
1323                         } else {
1324                                 $this->mAffectedRows = oci_num_rows( $stmt );
1325                         }
1326                 } else {
1327                         $this->mAffectedRows = oci_num_rows( $stmt );
1328                 }
1329
1330                 wfRestoreWarnings();
1331
1332                 if ( isset( $lob ) ) {
1333                         foreach ( $lob as $lob_v ) {
1334                                 $lob_v->free();
1335                         }
1336                 }
1337
1338                 if ( !$this->mTrxLevel ) {
1339                         oci_commit( $this->mConn );
1340                 }
1341
1342                 oci_free_statement( $stmt );
1343         }
1344
1345         function bitNot( $field ) {
1346                 // expecting bit-fields smaller than 4bytes
1347                 return 'BITNOT(' . $field . ')';
1348         }
1349
1350         function bitAnd( $fieldLeft, $fieldRight ) {
1351                 return 'BITAND(' . $fieldLeft . ', ' . $fieldRight . ')';
1352         }
1353
1354         function bitOr( $fieldLeft, $fieldRight ) {
1355                 return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
1356         }
1357
1358         function setFakeMaster( $enabled = true ) { }
1359
1360         function getDBname() {
1361                 return $this->mDBname;
1362         }
1363
1364         function getServer() {
1365                 return $this->mServer;
1366         }
1367
1368         public function getSearchEngine() {
1369                 return 'SearchOracle';
1370         }
1371 } // end DatabaseOracle class