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