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