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