MediaWiki
REL1_19
|
00001 <?php 00015 class ORAResult { 00016 private $rows; 00017 private $cursor; 00018 private $nrows; 00019 00020 private $columns = array(); 00021 00022 private function array_unique_md( $array_in ) { 00023 $array_out = array(); 00024 $array_hashes = array(); 00025 00026 foreach ( $array_in as $item ) { 00027 $hash = md5( serialize( $item ) ); 00028 if ( !isset( $array_hashes[$hash] ) ) { 00029 $array_hashes[$hash] = $hash; 00030 $array_out[] = $item; 00031 } 00032 } 00033 00034 return $array_out; 00035 } 00036 00042 function __construct( &$db, $stmt, $unique = false ) { 00043 $this->db =& $db; 00044 00045 if ( ( $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, - 1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM ) ) === false ) { 00046 $e = oci_error( $stmt ); 00047 $db->reportQueryError( $e['message'], $e['code'], '', __METHOD__ ); 00048 $this->free(); 00049 return; 00050 } 00051 00052 if ( $unique ) { 00053 $this->rows = $this->array_unique_md( $this->rows ); 00054 $this->nrows = count( $this->rows ); 00055 } 00056 00057 if ($this->nrows > 0) { 00058 foreach ( $this->rows[0] as $k => $v ) { 00059 $this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) ); 00060 } 00061 } 00062 00063 $this->cursor = 0; 00064 oci_free_statement( $stmt ); 00065 } 00066 00067 public function free() { 00068 unset($this->db); 00069 } 00070 00071 public function seek( $row ) { 00072 $this->cursor = min( $row, $this->nrows ); 00073 } 00074 00075 public function numRows() { 00076 return $this->nrows; 00077 } 00078 00079 public function numFields() { 00080 return count($this->columns); 00081 } 00082 00083 public function fetchObject() { 00084 if ( $this->cursor >= $this->nrows ) { 00085 return false; 00086 } 00087 $row = $this->rows[$this->cursor++]; 00088 $ret = new stdClass(); 00089 foreach ( $row as $k => $v ) { 00090 $lc = $this->columns[$k]; 00091 $ret->$lc = $v; 00092 } 00093 00094 return $ret; 00095 } 00096 00097 public function fetchRow() { 00098 if ( $this->cursor >= $this->nrows ) { 00099 return false; 00100 } 00101 00102 $row = $this->rows[$this->cursor++]; 00103 $ret = array(); 00104 foreach ( $row as $k => $v ) { 00105 $lc = $this->columns[$k]; 00106 $ret[$lc] = $v; 00107 $ret[$k] = $v; 00108 } 00109 return $ret; 00110 } 00111 } 00112 00117 class ORAField implements Field { 00118 private $name, $tablename, $default, $max_length, $nullable, 00119 $is_pk, $is_unique, $is_multiple, $is_key, $type; 00120 00121 function __construct( $info ) { 00122 $this->name = $info['column_name']; 00123 $this->tablename = $info['table_name']; 00124 $this->default = $info['data_default']; 00125 $this->max_length = $info['data_length']; 00126 $this->nullable = $info['not_null']; 00127 $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0; 00128 $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0; 00129 $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0; 00130 $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple ); 00131 $this->type = $info['data_type']; 00132 } 00133 00134 function name() { 00135 return $this->name; 00136 } 00137 00138 function tableName() { 00139 return $this->tablename; 00140 } 00141 00142 function defaultValue() { 00143 return $this->default; 00144 } 00145 00146 function maxLength() { 00147 return $this->max_length; 00148 } 00149 00150 function isNullable() { 00151 return $this->nullable; 00152 } 00153 00154 function isKey() { 00155 return $this->is_key; 00156 } 00157 00158 function isMultipleKey() { 00159 return $this->is_multiple; 00160 } 00161 00162 function type() { 00163 return $this->type; 00164 } 00165 } 00166 00170 class DatabaseOracle extends DatabaseBase { 00171 var $mInsertId = null; 00172 var $mLastResult = null; 00173 var $lastResult = null; 00174 var $cursor = 0; 00175 var $mAffectedRows; 00176 00177 var $ignore_DUP_VAL_ON_INDEX = false; 00178 var $sequenceData = null; 00179 00180 var $defaultCharset = 'AL32UTF8'; 00181 00182 var $mFieldInfoCache = array(); 00183 00184 function __construct( $server = false, $user = false, $password = false, $dbName = false, 00185 $flags = 0, $tablePrefix = 'get from global' ) 00186 { 00187 global $wgDBprefix; 00188 $tablePrefix = $tablePrefix == 'get from global' ? strtoupper( $wgDBprefix ) : strtoupper( $tablePrefix ); 00189 parent::__construct( $server, $user, $password, $dbName, $flags, $tablePrefix ); 00190 wfRunHooks( 'DatabaseOraclePostInit', array( $this ) ); 00191 } 00192 00193 function __destruct() { 00194 if ($this->mOpened) { 00195 wfSuppressWarnings(); 00196 $this->close(); 00197 wfRestoreWarnings(); 00198 } 00199 } 00200 00201 function getType() { 00202 return 'oracle'; 00203 } 00204 00205 function cascadingDeletes() { 00206 return true; 00207 } 00208 function cleanupTriggers() { 00209 return true; 00210 } 00211 function strictIPs() { 00212 return true; 00213 } 00214 function realTimestamps() { 00215 return true; 00216 } 00217 function implicitGroupby() { 00218 return false; 00219 } 00220 function implicitOrderby() { 00221 return false; 00222 } 00223 function searchableIPs() { 00224 return true; 00225 } 00226 00230 function open( $server, $user, $password, $dbName ) { 00231 if ( !function_exists( 'oci_connect' ) ) { 00232 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" ); 00233 } 00234 00235 $this->close(); 00236 $this->mUser = $user; 00237 $this->mPassword = $password; 00238 // changed internal variables functions 00239 // mServer now holds the TNS endpoint 00240 // mDBname is schema name if different from username 00241 if ( !$server ) { 00242 // backward compatibillity (server used to be null and TNS was supplied in dbname) 00243 $this->mServer = $dbName; 00244 $this->mDBname = $user; 00245 } else { 00246 $this->mServer = $server; 00247 if ( !$dbName ) { 00248 $this->mDBname = $user; 00249 } else { 00250 $this->mDBname = $dbName; 00251 } 00252 } 00253 00254 if ( !strlen( $user ) ) { # e.g. the class is being loaded 00255 return; 00256 } 00257 00258 $session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT; 00259 wfSuppressWarnings(); 00260 if ( $this->mFlags & DBO_DEFAULT ) { 00261 $this->mConn = oci_new_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode ); 00262 } else { 00263 $this->mConn = oci_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode ); 00264 } 00265 wfRestoreWarnings(); 00266 00267 if ( $this->mUser != $this->mDBname ) { 00268 //change current schema in session 00269 $this->selectDB( $this->mDBname ); 00270 } 00271 00272 if ( !$this->mConn ) { 00273 throw new DBConnectionError( $this, $this->lastError() ); 00274 } 00275 00276 $this->mOpened = true; 00277 00278 # removed putenv calls because they interfere with the system globaly 00279 $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' ); 00280 $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' ); 00281 $this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' ); 00282 return $this->mConn; 00283 } 00284 00289 function close() { 00290 $this->mOpened = false; 00291 if ( $this->mConn ) { 00292 if ( $this->mTrxLevel ) { 00293 $this->commit(); 00294 } 00295 return oci_close( $this->mConn ); 00296 } else { 00297 return true; 00298 } 00299 } 00300 00301 function execFlags() { 00302 return $this->mTrxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS; 00303 } 00304 00305 protected function doQuery( $sql ) { 00306 wfDebug( "SQL: [$sql]\n" ); 00307 if ( !mb_check_encoding( $sql ) ) { 00308 throw new MWException( "SQL encoding is invalid\n$sql" ); 00309 } 00310 00311 // handle some oracle specifics 00312 // remove AS column/table/subquery namings 00313 if( !$this->getFlag( DBO_DDLMODE ) ) { 00314 $sql = preg_replace( '/ as /i', ' ', $sql ); 00315 } 00316 00317 // Oracle has issues with UNION clause if the statement includes LOB fields 00318 // So we do a UNION ALL and then filter the results array with array_unique 00319 $union_unique = ( preg_match( '/\/\* UNION_UNIQUE \*\/ /', $sql ) != 0 ); 00320 // EXPLAIN syntax in Oracle is EXPLAIN PLAN FOR and it return nothing 00321 // you have to select data from plan table after explain 00322 $explain_id = date( 'dmYHis' ); 00323 00324 $sql = preg_replace( '/^EXPLAIN /', 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR', $sql, 1, $explain_count ); 00325 00326 wfSuppressWarnings(); 00327 00328 if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) { 00329 $e = oci_error( $this->mConn ); 00330 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ ); 00331 return false; 00332 } 00333 00334 if ( !oci_execute( $stmt, $this->execFlags() ) ) { 00335 $e = oci_error( $stmt ); 00336 if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) { 00337 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ ); 00338 return false; 00339 } 00340 } 00341 00342 wfRestoreWarnings(); 00343 00344 if ( $explain_count > 0 ) { 00345 return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table WHERE statement_id = \'' . $explain_id . '\'' ); 00346 } elseif ( oci_statement_type( $stmt ) == 'SELECT' ) { 00347 return new ORAResult( $this, $stmt, $union_unique ); 00348 } else { 00349 $this->mAffectedRows = oci_num_rows( $stmt ); 00350 return true; 00351 } 00352 } 00353 00354 function queryIgnore( $sql, $fname = '' ) { 00355 return $this->query( $sql, $fname, true ); 00356 } 00357 00358 function freeResult( $res ) { 00359 if ( $res instanceof ResultWrapper ) { 00360 $res = $res->result; 00361 } 00362 00363 $res->free(); 00364 } 00365 00366 function fetchObject( $res ) { 00367 if ( $res instanceof ResultWrapper ) { 00368 $res = $res->result; 00369 } 00370 00371 return $res->fetchObject(); 00372 } 00373 00374 function fetchRow( $res ) { 00375 if ( $res instanceof ResultWrapper ) { 00376 $res = $res->result; 00377 } 00378 00379 return $res->fetchRow(); 00380 } 00381 00382 function numRows( $res ) { 00383 if ( $res instanceof ResultWrapper ) { 00384 $res = $res->result; 00385 } 00386 00387 return $res->numRows(); 00388 } 00389 00390 function numFields( $res ) { 00391 if ( $res instanceof ResultWrapper ) { 00392 $res = $res->result; 00393 } 00394 00395 return $res->numFields(); 00396 } 00397 00398 function fieldName( $stmt, $n ) { 00399 return oci_field_name( $stmt, $n ); 00400 } 00401 00405 function insertId() { 00406 return $this->mInsertId; 00407 } 00408 00409 function dataSeek( $res, $row ) { 00410 if ( $res instanceof ORAResult ) { 00411 $res->seek( $row ); 00412 } else { 00413 $res->result->seek( $row ); 00414 } 00415 } 00416 00417 function lastError() { 00418 if ( $this->mConn === false ) { 00419 $e = oci_error(); 00420 } else { 00421 $e = oci_error( $this->mConn ); 00422 } 00423 return $e['message']; 00424 } 00425 00426 function lastErrno() { 00427 if ( $this->mConn === false ) { 00428 $e = oci_error(); 00429 } else { 00430 $e = oci_error( $this->mConn ); 00431 } 00432 return $e['code']; 00433 } 00434 00435 function affectedRows() { 00436 return $this->mAffectedRows; 00437 } 00438 00443 function indexInfo( $table, $index, $fname = 'DatabaseOracle::indexExists' ) { 00444 return false; 00445 } 00446 00447 function indexUnique( $table, $index, $fname = 'DatabaseOracle::indexUnique' ) { 00448 return false; 00449 } 00450 00451 function insert( $table, $a, $fname = 'DatabaseOracle::insert', $options = array() ) { 00452 if ( !count( $a ) ) { 00453 return true; 00454 } 00455 00456 if ( !is_array( $options ) ) { 00457 $options = array( $options ); 00458 } 00459 00460 if ( in_array( 'IGNORE', $options ) ) { 00461 $this->ignore_DUP_VAL_ON_INDEX = true; 00462 } 00463 00464 if ( !is_array( reset( $a ) ) ) { 00465 $a = array( $a ); 00466 } 00467 00468 foreach ( $a as &$row ) { 00469 $this->insertOneRow( $table, $row, $fname ); 00470 } 00471 $retVal = true; 00472 00473 if ( in_array( 'IGNORE', $options ) ) { 00474 $this->ignore_DUP_VAL_ON_INDEX = false; 00475 } 00476 00477 return $retVal; 00478 } 00479 00480 private function fieldBindStatement ( $table, $col, &$val, $includeCol = false ) { 00481 $col_info = $this->fieldInfoMulti( $table, $col ); 00482 $col_type = $col_info != false ? $col_info->type() : 'CONSTANT'; 00483 00484 $bind = ''; 00485 if ( is_numeric( $col ) ) { 00486 $bind = $val; 00487 $val = null; 00488 return $bind; 00489 } elseif ( $includeCol ) { 00490 $bind = "$col = "; 00491 } 00492 00493 if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) { 00494 $val = null; 00495 } 00496 00497 if ( $val === 'NULL' ) { 00498 $val = null; 00499 } 00500 00501 if ( $val === null ) { 00502 if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) { 00503 $bind .= 'DEFAULT'; 00504 } else { 00505 $bind .= 'NULL'; 00506 } 00507 } else { 00508 $bind .= ':' . $col; 00509 } 00510 00511 return $bind; 00512 } 00513 00514 private function insertOneRow( $table, $row, $fname ) { 00515 global $wgContLang; 00516 00517 $table = $this->tableName( $table ); 00518 // "INSERT INTO tables (a, b, c)" 00519 $sql = "INSERT INTO " . $table . " (" . join( ',', array_keys( $row ) ) . ')'; 00520 $sql .= " VALUES ("; 00521 00522 // for each value, append ":key" 00523 $first = true; 00524 foreach ( $row as $col => &$val ) { 00525 if ( !$first ) { 00526 $sql .= ', '; 00527 } else { 00528 $first = false; 00529 } 00530 00531 $sql .= $this->fieldBindStatement( $table, $col, $val ); 00532 } 00533 $sql .= ')'; 00534 00535 if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) { 00536 $e = oci_error( $this->mConn ); 00537 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ ); 00538 return false; 00539 } 00540 foreach ( $row as $col => &$val ) { 00541 $col_info = $this->fieldInfoMulti( $table, $col ); 00542 $col_type = $col_info != false ? $col_info->type() : 'CONSTANT'; 00543 00544 if ( $val === null ) { 00545 // do nothing ... null was inserted in statement creation 00546 } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) { 00547 if ( is_object( $val ) ) { 00548 $val = $val->fetch(); 00549 } 00550 00551 // backward compatibility 00552 if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) { 00553 $val = $this->getInfinity(); 00554 } 00555 00556 $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val; 00557 if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) { 00558 $e = oci_error( $stmt ); 00559 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ ); 00560 return false; 00561 } 00562 } else { 00563 if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) { 00564 $e = oci_error( $stmt ); 00565 throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] ); 00566 } 00567 00568 if ( is_object( $val ) ) { 00569 $val = $val->fetch(); 00570 } 00571 00572 if ( $col_type == 'BLOB' ) { 00573 $lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB ); 00574 oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_BLOB ); 00575 } else { 00576 $lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB ); 00577 oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB ); 00578 } 00579 } 00580 } 00581 00582 wfSuppressWarnings(); 00583 00584 if ( oci_execute( $stmt, $this->execFlags() ) === false ) { 00585 $e = oci_error( $stmt ); 00586 if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) { 00587 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ ); 00588 return false; 00589 } else { 00590 $this->mAffectedRows = oci_num_rows( $stmt ); 00591 } 00592 } else { 00593 $this->mAffectedRows = oci_num_rows( $stmt ); 00594 } 00595 00596 wfRestoreWarnings(); 00597 00598 if ( isset( $lob ) ) { 00599 foreach ( $lob as $lob_v ) { 00600 $lob_v->free(); 00601 } 00602 } 00603 00604 if ( !$this->mTrxLevel ) { 00605 oci_commit( $this->mConn ); 00606 } 00607 00608 oci_free_statement( $stmt ); 00609 } 00610 00611 function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseOracle::insertSelect', 00612 $insertOptions = array(), $selectOptions = array() ) 00613 { 00614 $destTable = $this->tableName( $destTable ); 00615 if ( !is_array( $selectOptions ) ) { 00616 $selectOptions = array( $selectOptions ); 00617 } 00618 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); 00619 if ( is_array( $srcTable ) ) { 00620 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); 00621 } else { 00622 $srcTable = $this->tableName( $srcTable ); 00623 } 00624 00625 if ( ( $sequenceData = $this->getSequenceData( $destTable ) ) !== false && 00626 !isset( $varMap[$sequenceData['column']] ) ) 00627 { 00628 $varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')'; 00629 } 00630 00631 // count-alias subselect fields to avoid abigious definition errors 00632 $i = 0; 00633 foreach ( $varMap as &$val ) { 00634 $val = $val . ' field' . ( $i++ ); 00635 } 00636 00637 $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . 00638 " SELECT $startOpts " . implode( ',', $varMap ) . 00639 " FROM $srcTable $useIndex "; 00640 if ( $conds != '*' ) { 00641 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); 00642 } 00643 $sql .= " $tailOpts"; 00644 00645 if ( in_array( 'IGNORE', $insertOptions ) ) { 00646 $this->ignore_DUP_VAL_ON_INDEX = true; 00647 } 00648 00649 $retval = $this->query( $sql, $fname ); 00650 00651 if ( in_array( 'IGNORE', $insertOptions ) ) { 00652 $this->ignore_DUP_VAL_ON_INDEX = false; 00653 } 00654 00655 return $retval; 00656 } 00657 00658 function tableName( $name, $format = 'quoted' ) { 00659 /* 00660 Replace reserved words with better ones 00661 Using uppercase because that's the only way Oracle can handle 00662 quoted tablenames 00663 */ 00664 switch( $name ) { 00665 case 'user': 00666 $name = 'MWUSER'; 00667 break; 00668 case 'text': 00669 $name = 'PAGECONTENT'; 00670 break; 00671 } 00672 00673 return parent::tableName( strtoupper( $name ), $format ); 00674 } 00675 00676 function tableNameInternal( $name ) { 00677 $name = $this->tableName( $name ); 00678 return preg_replace( '/.*\.(.*)/', '$1', $name); 00679 } 00683 function nextSequenceValue( $seqName ) { 00684 $res = $this->query( "SELECT $seqName.nextval FROM dual" ); 00685 $row = $this->fetchRow( $res ); 00686 $this->mInsertId = $row[0]; 00687 return $this->mInsertId; 00688 } 00689 00693 private function getSequenceData( $table ) { 00694 if ( $this->sequenceData == null ) { 00695 $result = $this->doQuery( "SELECT lower(asq.sequence_name), 00696 lower(atc.table_name), 00697 lower(atc.column_name) 00698 FROM all_sequences asq, all_tab_columns atc 00699 WHERE decode(atc.table_name, '{$this->mTablePrefix}MWUSER', '{$this->mTablePrefix}USER', atc.table_name) || '_' || 00700 atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name 00701 AND asq.sequence_owner = upper('{$this->mDBname}') 00702 AND atc.owner = upper('{$this->mDBname}')" ); 00703 00704 while ( ( $row = $result->fetchRow() ) !== false ) { 00705 $this->sequenceData[$row[1]] = array( 00706 'sequence' => $row[0], 00707 'column' => $row[2] 00708 ); 00709 } 00710 } 00711 $table = strtolower( $this->removeIdentifierQuotes( $this->tableName( $table ) ) ); 00712 return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false; 00713 } 00714 00715 # Returns the size of a text field, or -1 for "unlimited" 00716 function textFieldSize( $table, $field ) { 00717 $fieldInfoData = $this->fieldInfo( $table, $field ); 00718 return $fieldInfoData->maxLength(); 00719 } 00720 00721 function limitResult( $sql, $limit, $offset = false ) { 00722 if ( $offset === false ) { 00723 $offset = 0; 00724 } 00725 return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)"; 00726 } 00727 00728 function encodeBlob( $b ) { 00729 return new Blob( $b ); 00730 } 00731 00732 function decodeBlob( $b ) { 00733 if ( $b instanceof Blob ) { 00734 $b = $b->fetch(); 00735 } 00736 return $b; 00737 } 00738 00739 function unionQueries( $sqls, $all ) { 00740 $glue = ' UNION ALL '; 00741 return 'SELECT * ' . ( $all ? '':'/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')' ; 00742 } 00743 00744 function wasDeadlock() { 00745 return $this->lastErrno() == 'OCI-00060'; 00746 } 00747 00748 function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseOracle::duplicateTableStructure' ) { 00749 $temporary = $temporary ? 'TRUE' : 'FALSE'; 00750 00751 $newName = strtoupper( $newName ); 00752 $oldName = strtoupper( $oldName ); 00753 00754 $tabName = substr( $newName, strlen( $this->mTablePrefix ) ); 00755 $oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) ); 00756 $newPrefix = strtoupper( $this->mTablePrefix ); 00757 00758 return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', '$oldPrefix', '$newPrefix', $temporary ); END;" ); 00759 } 00760 00761 function listTables( $prefix = null, $fname = 'DatabaseOracle::listTables' ) { 00762 $listWhere = ''; 00763 if (!empty($prefix)) { 00764 $listWhere = ' AND table_name LIKE \''.strtoupper($prefix).'%\''; 00765 } 00766 00767 $owner = strtoupper( $this->mDBname ); 00768 $result = $this->doQuery( "SELECT table_name FROM all_tables WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" ); 00769 00770 // dirty code ... i know 00771 $endArray = array(); 00772 $endArray[] = strtoupper($prefix.'MWUSER'); 00773 $endArray[] = strtoupper($prefix.'PAGE'); 00774 $endArray[] = strtoupper($prefix.'IMAGE'); 00775 $fixedOrderTabs = $endArray; 00776 while (($row = $result->fetchRow()) !== false) { 00777 if (!in_array($row['table_name'], $fixedOrderTabs)) 00778 $endArray[] = $row['table_name']; 00779 } 00780 00781 return $endArray; 00782 } 00783 00784 public function dropTable( $tableName, $fName = 'DatabaseOracle::dropTable' ) { 00785 $tableName = $this->tableName($tableName); 00786 if( !$this->tableExists( $tableName ) ) { 00787 return false; 00788 } 00789 00790 return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" ); 00791 } 00792 00793 function timestamp( $ts = 0 ) { 00794 return wfTimestamp( TS_ORACLE, $ts ); 00795 } 00796 00800 function aggregateValue ( $valuedata, $valuename = 'value' ) { 00801 return $valuedata; 00802 } 00803 00804 function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { 00805 # Ignore errors during error handling to avoid infinite 00806 # recursion 00807 $ignore = $this->ignoreErrors( true ); 00808 ++$this->mErrorCount; 00809 00810 if ( $ignore || $tempIgnore ) { 00811 wfDebug( "SQL ERROR (ignored): $error\n" ); 00812 $this->ignoreErrors( $ignore ); 00813 } else { 00814 throw new DBQueryError( $this, $error, $errno, $sql, $fname ); 00815 } 00816 } 00817 00821 public static function getSoftwareLink() { 00822 return '[http://www.oracle.com/ Oracle]'; 00823 } 00824 00828 function getServerVersion() { 00829 //better version number, fallback on driver 00830 $rset = $this->doQuery( 'SELECT version FROM product_component_version WHERE UPPER(product) LIKE \'ORACLE DATABASE%\'' ); 00831 if ( !( $row = $rset->fetchRow() ) ) { 00832 return oci_server_version( $this->mConn ); 00833 } 00834 return $row['version']; 00835 } 00836 00840 function indexExists( $table, $index, $fname = 'DatabaseOracle::indexExists' ) { 00841 $table = $this->tableName( $table ); 00842 $table = strtoupper( $this->removeIdentifierQuotes( $table ) ); 00843 $index = strtoupper( $index ); 00844 $owner = strtoupper( $this->mDBname ); 00845 $SQL = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'"; 00846 $res = $this->doQuery( $SQL ); 00847 if ( $res ) { 00848 $count = $res->numRows(); 00849 $res->free(); 00850 } else { 00851 $count = 0; 00852 } 00853 return $count != 0; 00854 } 00855 00859 function tableExists( $table, $fname = __METHOD__ ) { 00860 $table = $this->tableName( $table ); 00861 $table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) ); 00862 $owner = $this->addQuotes( strtoupper( $this->mDBname ) ); 00863 $SQL = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table"; 00864 $res = $this->doQuery( $SQL ); 00865 if ( $res ) { 00866 $count = $res->numRows(); 00867 $res->free(); 00868 } else { 00869 $count = 0; 00870 } 00871 return $count; 00872 } 00873 00884 private function fieldInfoMulti( $table, $field ) { 00885 $field = strtoupper( $field ); 00886 if ( is_array( $table ) ) { 00887 $table = array_map( array( &$this, 'tableNameInternal' ), $table ); 00888 $tableWhere = 'IN ('; 00889 foreach( $table as &$singleTable ) { 00890 $singleTable = $this->removeIdentifierQuotes($singleTable); 00891 if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) { 00892 return $this->mFieldInfoCache["$singleTable.$field"]; 00893 } 00894 $tableWhere .= '\'' . $singleTable . '\','; 00895 } 00896 $tableWhere = rtrim( $tableWhere, ',' ) . ')'; 00897 } else { 00898 $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) ); 00899 if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) { 00900 return $this->mFieldInfoCache["$table.$field"]; 00901 } 00902 $tableWhere = '= \''.$table.'\''; 00903 } 00904 00905 $fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name '.$tableWhere.' and column_name = \''.$field.'\'' ); 00906 if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) { 00907 $e = oci_error( $fieldInfoStmt ); 00908 $this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ ); 00909 return false; 00910 } 00911 $res = new ORAResult( $this, $fieldInfoStmt ); 00912 if ( $res->numRows() == 0 ) { 00913 if ( is_array( $table ) ) { 00914 foreach( $table as &$singleTable ) { 00915 $this->mFieldInfoCache["$singleTable.$field"] = false; 00916 } 00917 } else { 00918 $this->mFieldInfoCache["$table.$field"] = false; 00919 } 00920 $fieldInfoTemp = null; 00921 } else { 00922 $fieldInfoTemp = new ORAField( $res->fetchRow() ); 00923 $table = $fieldInfoTemp->tableName(); 00924 $this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp; 00925 } 00926 $res->free(); 00927 return $fieldInfoTemp; 00928 } 00929 00936 function fieldInfo( $table, $field ) { 00937 if ( is_array( $table ) ) { 00938 throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' ); 00939 } 00940 return $this->fieldInfoMulti ($table, $field); 00941 } 00942 00943 function begin( $fname = 'DatabaseOracle::begin' ) { 00944 $this->mTrxLevel = 1; 00945 $this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' ); 00946 } 00947 00948 function commit( $fname = 'DatabaseOracle::commit' ) { 00949 if ( $this->mTrxLevel ) { 00950 $ret = oci_commit( $this->mConn ); 00951 if ( !$ret ) { 00952 throw new DBUnexpectedError( $this, $this->lastError() ); 00953 } 00954 $this->mTrxLevel = 0; 00955 $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' ); 00956 } 00957 } 00958 00959 function rollback( $fname = 'DatabaseOracle::rollback' ) { 00960 if ( $this->mTrxLevel ) { 00961 oci_rollback( $this->mConn ); 00962 $this->mTrxLevel = 0; 00963 $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' ); 00964 } 00965 } 00966 00967 /* Not even sure why this is used in the main codebase... */ 00968 function limitResultForUpdate( $sql, $num ) { 00969 return $sql; 00970 } 00971 00972 /* defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; */ 00973 function sourceStream( $fp, $lineCallback = false, $resultCallback = false, 00974 $fname = 'DatabaseOracle::sourceStream', $inputCallback = false ) { 00975 $cmd = ''; 00976 $done = false; 00977 $dollarquote = false; 00978 00979 $replacements = array(); 00980 00981 while ( ! feof( $fp ) ) { 00982 if ( $lineCallback ) { 00983 call_user_func( $lineCallback ); 00984 } 00985 $line = trim( fgets( $fp, 1024 ) ); 00986 $sl = strlen( $line ) - 1; 00987 00988 if ( $sl < 0 ) { 00989 continue; 00990 } 00991 if ( '-' == $line { 0 } && '-' == $line { 1 } ) { 00992 continue; 00993 } 00994 00995 // Allow dollar quoting for function declarations 00996 if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) { 00997 if ( $dollarquote ) { 00998 $dollarquote = false; 00999 $line = str_replace( '/*$mw$*/', '', $line ); // remove dollarquotes 01000 $done = true; 01001 } else { 01002 $dollarquote = true; 01003 } 01004 } elseif ( !$dollarquote ) { 01005 if ( ';' == $line { $sl } && ( $sl < 2 || ';' != $line { $sl - 1 } ) ) { 01006 $done = true; 01007 $line = substr( $line, 0, $sl ); 01008 } 01009 } 01010 01011 if ( $cmd != '' ) { 01012 $cmd .= ' '; 01013 } 01014 $cmd .= "$line\n"; 01015 01016 if ( $done ) { 01017 $cmd = str_replace( ';;', ";", $cmd ); 01018 if ( strtolower( substr( $cmd, 0, 6 ) ) == 'define' ) { 01019 if ( preg_match( '/^define\s*([^\s=]*)\s*=\s*\'\{\$([^\}]*)\}\'/', $cmd, $defines ) ) { 01020 $replacements[$defines[2]] = $defines[1]; 01021 } 01022 } else { 01023 foreach ( $replacements as $mwVar => $scVar ) { 01024 $cmd = str_replace( '&' . $scVar . '.', '`{$' . $mwVar . '}`', $cmd ); 01025 } 01026 01027 $cmd = $this->replaceVars( $cmd ); 01028 if ( $inputCallback ) { 01029 call_user_func( $inputCallback, $cmd ); 01030 } 01031 $res = $this->doQuery( $cmd ); 01032 if ( $resultCallback ) { 01033 call_user_func( $resultCallback, $res, $this ); 01034 } 01035 01036 if ( false === $res ) { 01037 $err = $this->lastError(); 01038 return "Query \"{$cmd}\" failed with error code \"$err\".\n"; 01039 } 01040 } 01041 01042 $cmd = ''; 01043 $done = false; 01044 } 01045 } 01046 return true; 01047 } 01048 01049 function selectDB( $db ) { 01050 $this->mDBname = $db; 01051 if ( $db == null || $db == $this->mUser ) { 01052 return true; 01053 } 01054 $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper($db); 01055 $stmt = oci_parse( $this->mConn, $sql ); 01056 wfSuppressWarnings(); 01057 $success = oci_execute( $stmt ); 01058 wfRestoreWarnings(); 01059 if ( !$success ) { 01060 $e = oci_error( $stmt ); 01061 if ( $e['code'] != '1435' ) { 01062 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ ); 01063 } 01064 return false; 01065 } 01066 return true; 01067 } 01068 01069 function strencode( $s ) { 01070 return str_replace( "'", "''", $s ); 01071 } 01072 01073 function addQuotes( $s ) { 01074 global $wgContLang; 01075 if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) { 01076 $s = $wgContLang->checkTitleEncoding( $s ); 01077 } 01078 return "'" . $this->strencode( $s ) . "'"; 01079 } 01080 01081 public function addIdentifierQuotes( $s ) { 01082 if ( !$this->getFlag( DBO_DDLMODE ) ) { 01083 $s = '/*Q*/' . $s; 01084 } 01085 return $s; 01086 } 01087 01088 public function removeIdentifierQuotes( $s ) { 01089 return strpos($s, '/*Q*/') === FALSE ? $s : substr($s, 5); 01090 } 01091 01092 public function isQuotedIdentifier( $s ) { 01093 return strpos($s, '/*Q*/') !== FALSE; 01094 } 01095 01096 private function wrapFieldForWhere( $table, &$col, &$val ) { 01097 global $wgContLang; 01098 01099 $col_info = $this->fieldInfoMulti( $table, $col ); 01100 $col_type = $col_info != false ? $col_info->type() : 'CONSTANT'; 01101 if ( $col_type == 'CLOB' ) { 01102 $col = 'TO_CHAR(' . $col . ')'; 01103 $val = $wgContLang->checkTitleEncoding( $val ); 01104 } elseif ( $col_type == 'VARCHAR2' && !mb_check_encoding( $val ) ) { 01105 $val = $wgContLang->checkTitleEncoding( $val ); 01106 } 01107 } 01108 01109 private function wrapConditionsForWhere ( $table, $conds, $parentCol = null ) { 01110 $conds2 = array(); 01111 foreach ( $conds as $col => $val ) { 01112 if ( is_array( $val ) ) { 01113 $conds2[$col] = $this->wrapConditionsForWhere ( $table, $val, $col ); 01114 } else { 01115 if ( is_numeric( $col ) && $parentCol != null ) { 01116 $this->wrapFieldForWhere ( $table, $parentCol, $val ); 01117 } else { 01118 $this->wrapFieldForWhere ( $table, $col, $val ); 01119 } 01120 $conds2[$col] = $val; 01121 } 01122 } 01123 return $conds2; 01124 } 01125 01126 function selectRow( $table, $vars, $conds, $fname = 'DatabaseOracle::selectRow', $options = array(), $join_conds = array() ) { 01127 if ( is_array($conds) ) { 01128 $conds = $this->wrapConditionsForWhere( $table, $conds ); 01129 } 01130 return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds ); 01131 } 01132 01143 function makeSelectOptions( $options ) { 01144 $preLimitTail = $postLimitTail = ''; 01145 $startOpts = ''; 01146 01147 $noKeyOptions = array(); 01148 foreach ( $options as $key => $option ) { 01149 if ( is_numeric( $key ) ) { 01150 $noKeyOptions[$option] = true; 01151 } 01152 } 01153 01154 if ( isset( $options['GROUP BY'] ) ) { 01155 $preLimitTail .= " GROUP BY {$options['GROUP BY']}"; 01156 } 01157 if ( isset( $options['ORDER BY'] ) ) { 01158 $preLimitTail .= " ORDER BY {$options['ORDER BY']}"; 01159 } 01160 01161 # if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE'; 01162 # if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE'; 01163 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { 01164 $startOpts .= 'DISTINCT'; 01165 } 01166 01167 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) { 01168 $useIndex = $this->useIndexClause( $options['USE INDEX'] ); 01169 } else { 01170 $useIndex = ''; 01171 } 01172 01173 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); 01174 } 01175 01176 public function delete( $table, $conds, $fname = 'DatabaseOracle::delete' ) { 01177 if ( is_array($conds) ) { 01178 $conds = $this->wrapConditionsForWhere( $table, $conds ); 01179 } 01180 // a hack for deleting pages, users and images (which have non-nullable FKs) 01181 // all deletions on these tables have transactions so final failure rollbacks these updates 01182 $table = $this->tableName( $table ); 01183 if ( $table == $this->tableName( 'user' ) ) { 01184 $this->update( 'archive', array( 'ar_user' => 0 ), array( 'ar_user' => $conds['user_id'] ), $fname ); 01185 $this->update( 'ipblocks', array( 'ipb_user' => 0 ), array( 'ipb_user' => $conds['user_id'] ), $fname ); 01186 $this->update( 'image', array( 'img_user' => 0 ), array( 'img_user' => $conds['user_id'] ), $fname ); 01187 $this->update( 'oldimage', array( 'oi_user' => 0 ), array( 'oi_user' => $conds['user_id'] ), $fname ); 01188 $this->update( 'filearchive', array( 'fa_deleted_user' => 0 ), array( 'fa_deleted_user' => $conds['user_id'] ), $fname ); 01189 $this->update( 'filearchive', array( 'fa_user' => 0 ), array( 'fa_user' => $conds['user_id'] ), $fname ); 01190 $this->update( 'uploadstash', array( 'us_user' => 0 ), array( 'us_user' => $conds['user_id'] ), $fname ); 01191 $this->update( 'recentchanges', array( 'rc_user' => 0 ), array( 'rc_user' => $conds['user_id'] ), $fname ); 01192 $this->update( 'logging', array( 'log_user' => 0 ), array( 'log_user' => $conds['user_id'] ), $fname ); 01193 } elseif ( $table == $this->tableName( 'image' ) ) { 01194 $this->update( 'oldimage', array( 'oi_name' => 0 ), array( 'oi_name' => $conds['img_name'] ), $fname ); 01195 } 01196 return parent::delete( $table, $conds, $fname ); 01197 } 01198 01199 function update( $table, $values, $conds, $fname = 'DatabaseOracle::update', $options = array() ) { 01200 global $wgContLang; 01201 01202 $table = $this->tableName( $table ); 01203 $opts = $this->makeUpdateOptions( $options ); 01204 $sql = "UPDATE $opts $table SET "; 01205 01206 $first = true; 01207 foreach ( $values as $col => &$val ) { 01208 $sqlSet = $this->fieldBindStatement( $table, $col, $val, true ); 01209 01210 if ( !$first ) { 01211 $sqlSet = ', ' . $sqlSet; 01212 } else { 01213 $first = false; 01214 } 01215 $sql .= $sqlSet; 01216 } 01217 01218 if ( $conds !== array() && $conds !== '*' ) { 01219 $conds = $this->wrapConditionsForWhere( $table, $conds ); 01220 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); 01221 } 01222 01223 if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) { 01224 $e = oci_error( $this->mConn ); 01225 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ ); 01226 return false; 01227 } 01228 foreach ( $values as $col => &$val ) { 01229 $col_info = $this->fieldInfoMulti( $table, $col ); 01230 $col_type = $col_info != false ? $col_info->type() : 'CONSTANT'; 01231 01232 if ( $val === null ) { 01233 // do nothing ... null was inserted in statement creation 01234 } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) { 01235 if ( is_object( $val ) ) { 01236 $val = $val->getData(); 01237 } 01238 01239 if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) { 01240 $val = '31-12-2030 12:00:00.000000'; 01241 } 01242 01243 $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val; 01244 if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) { 01245 $e = oci_error( $stmt ); 01246 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ ); 01247 return false; 01248 } 01249 } else { 01250 if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) { 01251 $e = oci_error( $stmt ); 01252 throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] ); 01253 } 01254 01255 if ( $col_type == 'BLOB' ) { 01256 $lob[$col]->writeTemporary( $val ); 01257 oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, SQLT_BLOB ); 01258 } else { 01259 $lob[$col]->writeTemporary( $val ); 01260 oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB ); 01261 } 01262 } 01263 } 01264 01265 wfSuppressWarnings(); 01266 01267 if ( oci_execute( $stmt, $this->execFlags() ) === false ) { 01268 $e = oci_error( $stmt ); 01269 if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) { 01270 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ ); 01271 return false; 01272 } else { 01273 $this->mAffectedRows = oci_num_rows( $stmt ); 01274 } 01275 } else { 01276 $this->mAffectedRows = oci_num_rows( $stmt ); 01277 } 01278 01279 wfRestoreWarnings(); 01280 01281 if ( isset( $lob ) ) { 01282 foreach ( $lob as $lob_v ) { 01283 $lob_v->free(); 01284 } 01285 } 01286 01287 if ( !$this->mTrxLevel ) { 01288 oci_commit( $this->mConn ); 01289 } 01290 01291 oci_free_statement( $stmt ); 01292 } 01293 01294 function bitNot( $field ) { 01295 // expecting bit-fields smaller than 4bytes 01296 return 'BITNOT(' . $field . ')'; 01297 } 01298 01299 function bitAnd( $fieldLeft, $fieldRight ) { 01300 return 'BITAND(' . $fieldLeft . ', ' . $fieldRight . ')'; 01301 } 01302 01303 function bitOr( $fieldLeft, $fieldRight ) { 01304 return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')'; 01305 } 01306 01307 function setFakeMaster( $enabled = true ) { 01308 } 01309 01310 function getDBname() { 01311 return $this->mDBname; 01312 } 01313 01314 function getServer() { 01315 return $this->mServer; 01316 } 01317 01318 public function getSearchEngine() { 01319 return 'SearchOracle'; 01320 } 01321 01322 public function getInfinity() { 01323 return '31-12-2030 12:00:00.000000'; 01324 } 01325 01326 } // end DatabaseOracle class