DatabaseOracle.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. <?php
  2. /**
  3. * @ingroup Database
  4. * @file
  5. */
  6. /**
  7. * This is the Oracle database abstraction layer.
  8. * @ingroup Database
  9. */
  10. class ORABlob {
  11. var $mData;
  12. function __construct($data) {
  13. $this->mData = $data;
  14. }
  15. function getData() {
  16. return $this->mData;
  17. }
  18. }
  19. /**
  20. * The oci8 extension is fairly weak and doesn't support oci_num_rows, among
  21. * other things. We use a wrapper class to handle that and other
  22. * Oracle-specific bits, like converting column names back to lowercase.
  23. * @ingroup Database
  24. */
  25. class ORAResult {
  26. private $rows;
  27. private $cursor;
  28. private $stmt;
  29. private $nrows;
  30. private $db;
  31. function __construct(&$db, $stmt) {
  32. $this->db =& $db;
  33. if (($this->nrows = oci_fetch_all($stmt, $this->rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM)) === false) {
  34. $e = oci_error($stmt);
  35. $db->reportQueryError($e['message'], $e['code'], '', __FUNCTION__);
  36. return;
  37. }
  38. $this->cursor = 0;
  39. $this->stmt = $stmt;
  40. }
  41. function free() {
  42. oci_free_statement($this->stmt);
  43. }
  44. function seek($row) {
  45. $this->cursor = min($row, $this->nrows);
  46. }
  47. function numRows() {
  48. return $this->nrows;
  49. }
  50. function numFields() {
  51. return oci_num_fields($this->stmt);
  52. }
  53. function fetchObject() {
  54. if ($this->cursor >= $this->nrows)
  55. return false;
  56. $row = $this->rows[$this->cursor++];
  57. $ret = new stdClass();
  58. foreach ($row as $k => $v) {
  59. $lc = strtolower(oci_field_name($this->stmt, $k + 1));
  60. $ret->$lc = $v;
  61. }
  62. return $ret;
  63. }
  64. function fetchAssoc() {
  65. if ($this->cursor >= $this->nrows)
  66. return false;
  67. $row = $this->rows[$this->cursor++];
  68. $ret = array();
  69. foreach ($row as $k => $v) {
  70. $lc = strtolower(oci_field_name($this->stmt, $k + 1));
  71. $ret[$lc] = $v;
  72. $ret[$k] = $v;
  73. }
  74. return $ret;
  75. }
  76. }
  77. /**
  78. * @ingroup Database
  79. */
  80. class DatabaseOracle extends Database {
  81. var $mInsertId = NULL;
  82. var $mLastResult = NULL;
  83. var $numeric_version = NULL;
  84. var $lastResult = null;
  85. var $cursor = 0;
  86. var $mAffectedRows;
  87. function DatabaseOracle($server = false, $user = false, $password = false, $dbName = false,
  88. $failFunction = false, $flags = 0 )
  89. {
  90. global $wgOut;
  91. # Can't get a reference if it hasn't been set yet
  92. if ( !isset( $wgOut ) ) {
  93. $wgOut = NULL;
  94. }
  95. $this->mOut =& $wgOut;
  96. $this->mFailFunction = $failFunction;
  97. $this->mFlags = $flags;
  98. $this->open( $server, $user, $password, $dbName);
  99. }
  100. function cascadingDeletes() {
  101. return true;
  102. }
  103. function cleanupTriggers() {
  104. return true;
  105. }
  106. function strictIPs() {
  107. return true;
  108. }
  109. function realTimestamps() {
  110. return true;
  111. }
  112. function implicitGroupby() {
  113. return false;
  114. }
  115. function implicitOrderby() {
  116. return false;
  117. }
  118. function searchableIPs() {
  119. return true;
  120. }
  121. static function newFromParams( $server = false, $user = false, $password = false, $dbName = false,
  122. $failFunction = false, $flags = 0)
  123. {
  124. return new DatabaseOracle( $server, $user, $password, $dbName, $failFunction, $flags );
  125. }
  126. /**
  127. * Usually aborts on failure
  128. * If the failFunction is set to a non-zero integer, returns success
  129. */
  130. function open( $server, $user, $password, $dbName ) {
  131. if ( !function_exists( 'oci_connect' ) ) {
  132. 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" );
  133. }
  134. # Needed for proper UTF-8 functionality
  135. putenv("NLS_LANG=AMERICAN_AMERICA.AL32UTF8");
  136. $this->close();
  137. $this->mServer = $server;
  138. $this->mUser = $user;
  139. $this->mPassword = $password;
  140. $this->mDBname = $dbName;
  141. if (!strlen($user)) { ## e.g. the class is being loaded
  142. return;
  143. }
  144. error_reporting( E_ALL );
  145. $this->mConn = oci_connect($user, $password, $dbName);
  146. if ($this->mConn == false) {
  147. wfDebug("DB connection error\n");
  148. wfDebug("Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n");
  149. wfDebug($this->lastError()."\n");
  150. return false;
  151. }
  152. $this->mOpened = true;
  153. return $this->mConn;
  154. }
  155. /**
  156. * Closes a database connection, if it is open
  157. * Returns success, true if already closed
  158. */
  159. function close() {
  160. $this->mOpened = false;
  161. if ( $this->mConn ) {
  162. return oci_close( $this->mConn );
  163. } else {
  164. return true;
  165. }
  166. }
  167. function execFlags() {
  168. return $this->mTrxLevel ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS;
  169. }
  170. function doQuery($sql) {
  171. wfDebug("SQL: [$sql]\n");
  172. if (!mb_check_encoding($sql)) {
  173. throw new MWException("SQL encoding is invalid");
  174. }
  175. if (($this->mLastResult = $stmt = oci_parse($this->mConn, $sql)) === false) {
  176. $e = oci_error($this->mConn);
  177. $this->reportQueryError($e['message'], $e['code'], $sql, __FUNCTION__);
  178. }
  179. if (oci_execute($stmt, $this->execFlags()) == false) {
  180. $e = oci_error($stmt);
  181. $this->reportQueryError($e['message'], $e['code'], $sql, __FUNCTION__);
  182. }
  183. if (oci_statement_type($stmt) == "SELECT")
  184. return new ORAResult($this, $stmt);
  185. else {
  186. $this->mAffectedRows = oci_num_rows($stmt);
  187. return true;
  188. }
  189. }
  190. function queryIgnore($sql, $fname = '') {
  191. return $this->query($sql, $fname, true);
  192. }
  193. function freeResult($res) {
  194. $res->free();
  195. }
  196. function fetchObject($res) {
  197. return $res->fetchObject();
  198. }
  199. function fetchRow($res) {
  200. return $res->fetchAssoc();
  201. }
  202. function numRows($res) {
  203. return $res->numRows();
  204. }
  205. function numFields($res) {
  206. return $res->numFields();
  207. }
  208. function fieldName($stmt, $n) {
  209. return pg_field_name($stmt, $n);
  210. }
  211. /**
  212. * This must be called after nextSequenceVal
  213. */
  214. function insertId() {
  215. return $this->mInsertId;
  216. }
  217. function dataSeek($res, $row) {
  218. $res->seek($row);
  219. }
  220. function lastError() {
  221. if ($this->mConn === false)
  222. $e = oci_error();
  223. else
  224. $e = oci_error($this->mConn);
  225. return $e['message'];
  226. }
  227. function lastErrno() {
  228. if ($this->mConn === false)
  229. $e = oci_error();
  230. else
  231. $e = oci_error($this->mConn);
  232. return $e['code'];
  233. }
  234. function affectedRows() {
  235. return $this->mAffectedRows;
  236. }
  237. /**
  238. * Returns information about an index
  239. * If errors are explicitly ignored, returns NULL on failure
  240. */
  241. function indexInfo( $table, $index, $fname = 'Database::indexExists' ) {
  242. return false;
  243. }
  244. function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) {
  245. return false;
  246. }
  247. function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
  248. if (!is_array($options))
  249. $options = array($options);
  250. #if (in_array('IGNORE', $options))
  251. # $oldIgnore = $this->ignoreErrors(true);
  252. # IGNORE is performed using single-row inserts, ignoring errors in each
  253. # FIXME: need some way to distiguish between key collision and other types of error
  254. //$oldIgnore = $this->ignoreErrors(true);
  255. if (!is_array(reset($a))) {
  256. $a = array($a);
  257. }
  258. foreach ($a as $row) {
  259. $this->insertOneRow($table, $row, $fname);
  260. }
  261. //$this->ignoreErrors($oldIgnore);
  262. $retVal = true;
  263. //if (in_array('IGNORE', $options))
  264. // $this->ignoreErrors($oldIgnore);
  265. return $retVal;
  266. }
  267. function insertOneRow($table, $row, $fname) {
  268. // "INSERT INTO tables (a, b, c)"
  269. $sql = "INSERT INTO " . $this->tableName($table) . " (" . join(',', array_keys($row)) . ')';
  270. $sql .= " VALUES (";
  271. // for each value, append ":key"
  272. $first = true;
  273. $returning = '';
  274. foreach ($row as $col => $val) {
  275. if (is_object($val)) {
  276. $what = "EMPTY_BLOB()";
  277. assert($returning === '');
  278. $returning = " RETURNING $col INTO :bval";
  279. $blobcol = $col;
  280. } else
  281. $what = ":$col";
  282. if ($first)
  283. $sql .= "$what";
  284. else
  285. $sql.= ", $what";
  286. $first = false;
  287. }
  288. $sql .= ") $returning";
  289. $stmt = oci_parse($this->mConn, $sql);
  290. foreach ($row as $col => $val) {
  291. if (!is_object($val)) {
  292. if (oci_bind_by_name($stmt, ":$col", $row[$col]) === false)
  293. $this->reportQueryError($this->lastErrno(), $this->lastError(), $sql, __METHOD__);
  294. }
  295. }
  296. if (($bval = oci_new_descriptor($this->mConn, OCI_D_LOB)) === false) {
  297. $e = oci_error($stmt);
  298. throw new DBUnexpectedError($this, "Cannot create LOB descriptor: " . $e['message']);
  299. }
  300. if (strlen($returning))
  301. oci_bind_by_name($stmt, ":bval", $bval, -1, SQLT_BLOB);
  302. if (oci_execute($stmt, OCI_DEFAULT) === false) {
  303. $e = oci_error($stmt);
  304. $this->reportQueryError($e['message'], $e['code'], $sql, __METHOD__);
  305. }
  306. if (strlen($returning)) {
  307. $bval->save($row[$blobcol]->getData());
  308. $bval->free();
  309. }
  310. if (!$this->mTrxLevel)
  311. oci_commit($this->mConn);
  312. oci_free_statement($stmt);
  313. }
  314. function tableName( $name ) {
  315. # Replace reserved words with better ones
  316. switch( $name ) {
  317. case 'user':
  318. return 'mwuser';
  319. case 'text':
  320. return 'pagecontent';
  321. default:
  322. return $name;
  323. }
  324. }
  325. /**
  326. * Return the next in a sequence, save the value for retrieval via insertId()
  327. */
  328. function nextSequenceValue($seqName) {
  329. $res = $this->query("SELECT $seqName.nextval FROM dual");
  330. $row = $this->fetchRow($res);
  331. $this->mInsertId = $row[0];
  332. $this->freeResult($res);
  333. return $this->mInsertId;
  334. }
  335. /**
  336. * Oracle does not have a "USE INDEX" clause, so return an empty string
  337. */
  338. function useIndexClause($index) {
  339. return '';
  340. }
  341. # REPLACE query wrapper
  342. # Oracle simulates this with a DELETE followed by INSERT
  343. # $row is the row to insert, an associative array
  344. # $uniqueIndexes is an array of indexes. Each element may be either a
  345. # field name or an array of field names
  346. #
  347. # It may be more efficient to leave off unique indexes which are unlikely to collide.
  348. # However if you do this, you run the risk of encountering errors which wouldn't have
  349. # occurred in MySQL
  350. function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
  351. $table = $this->tableName($table);
  352. if (count($rows)==0) {
  353. return;
  354. }
  355. # Single row case
  356. if (!is_array(reset($rows))) {
  357. $rows = array($rows);
  358. }
  359. foreach( $rows as $row ) {
  360. # Delete rows which collide
  361. if ( $uniqueIndexes ) {
  362. $sql = "DELETE FROM $table WHERE ";
  363. $first = true;
  364. foreach ( $uniqueIndexes as $index ) {
  365. if ( $first ) {
  366. $first = false;
  367. $sql .= "(";
  368. } else {
  369. $sql .= ') OR (';
  370. }
  371. if ( is_array( $index ) ) {
  372. $first2 = true;
  373. foreach ( $index as $col ) {
  374. if ( $first2 ) {
  375. $first2 = false;
  376. } else {
  377. $sql .= ' AND ';
  378. }
  379. $sql .= $col.'=' . $this->addQuotes( $row[$col] );
  380. }
  381. } else {
  382. $sql .= $index.'=' . $this->addQuotes( $row[$index] );
  383. }
  384. }
  385. $sql .= ')';
  386. $this->query( $sql, $fname );
  387. }
  388. # Now insert the row
  389. $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' .
  390. $this->makeList( $row, LIST_COMMA ) . ')';
  391. $this->query($sql, $fname);
  392. }
  393. }
  394. # DELETE where the condition is a join
  395. function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) {
  396. if ( !$conds ) {
  397. throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' );
  398. }
  399. $delTable = $this->tableName( $delTable );
  400. $joinTable = $this->tableName( $joinTable );
  401. $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
  402. if ( $conds != '*' ) {
  403. $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
  404. }
  405. $sql .= ')';
  406. $this->query( $sql, $fname );
  407. }
  408. # Returns the size of a text field, or -1 for "unlimited"
  409. function textFieldSize( $table, $field ) {
  410. $table = $this->tableName( $table );
  411. $sql = "SELECT t.typname as ftype,a.atttypmod as size
  412. FROM pg_class c, pg_attribute a, pg_type t
  413. WHERE relname='$table' AND a.attrelid=c.oid AND
  414. a.atttypid=t.oid and a.attname='$field'";
  415. $res =$this->query($sql);
  416. $row=$this->fetchObject($res);
  417. if ($row->ftype=="varchar") {
  418. $size=$row->size-4;
  419. } else {
  420. $size=$row->size;
  421. }
  422. $this->freeResult( $res );
  423. return $size;
  424. }
  425. function lowPriorityOption() {
  426. return '';
  427. }
  428. function limitResult($sql, $limit, $offset) {
  429. if ($offset === false)
  430. $offset = 0;
  431. return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < 1 + $limit + $offset";
  432. }
  433. /**
  434. * Returns an SQL expression for a simple conditional.
  435. * Uses CASE on Oracle
  436. *
  437. * @param $cond String: SQL expression which will result in a boolean value
  438. * @param $trueVal String: SQL expression to return if true
  439. * @param $falseVal String: SQL expression to return if false
  440. * @return String: SQL fragment
  441. */
  442. function conditional( $cond, $trueVal, $falseVal ) {
  443. return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
  444. }
  445. function wasDeadlock() {
  446. return $this->lastErrno() == 'OCI-00060';
  447. }
  448. function timestamp($ts = 0) {
  449. return wfTimestamp(TS_ORACLE, $ts);
  450. }
  451. /**
  452. * Return aggregated value function call
  453. */
  454. function aggregateValue ($valuedata,$valuename='value') {
  455. return $valuedata;
  456. }
  457. function reportQueryError($error, $errno, $sql, $fname, $tempIgnore = false) {
  458. # Ignore errors during error handling to avoid infinite
  459. # recursion
  460. $ignore = $this->ignoreErrors(true);
  461. ++$this->mErrorCount;
  462. if ($ignore || $tempIgnore) {
  463. echo "error ignored! query = [$sql]\n";
  464. wfDebug("SQL ERROR (ignored): $error\n");
  465. $this->ignoreErrors( $ignore );
  466. }
  467. else {
  468. echo "error!\n";
  469. $message = "A database error has occurred\n" .
  470. "Query: $sql\n" .
  471. "Function: $fname\n" .
  472. "Error: $errno $error\n";
  473. throw new DBUnexpectedError($this, $message);
  474. }
  475. }
  476. /**
  477. * @return string wikitext of a link to the server software's web site
  478. */
  479. function getSoftwareLink() {
  480. return "[http://www.oracle.com/ Oracle]";
  481. }
  482. /**
  483. * @return string Version information from the database
  484. */
  485. function getServerVersion() {
  486. return oci_server_version($this->mConn);
  487. }
  488. /**
  489. * Query whether a given table exists (in the given schema, or the default mw one if not given)
  490. */
  491. function tableExists($table) {
  492. $etable= $this->addQuotes($table);
  493. $SQL = "SELECT 1 FROM user_tables WHERE table_name='$etable'";
  494. $res = $this->query($SQL);
  495. $count = $res ? oci_num_rows($res) : 0;
  496. if ($res)
  497. $this->freeResult($res);
  498. return $count;
  499. }
  500. /**
  501. * Query whether a given column exists in the mediawiki schema
  502. */
  503. function fieldExists( $table, $field ) {
  504. return true; // XXX
  505. }
  506. function fieldInfo( $table, $field ) {
  507. return false; // XXX
  508. }
  509. function begin( $fname = '' ) {
  510. $this->mTrxLevel = 1;
  511. }
  512. function immediateCommit( $fname = '' ) {
  513. return true;
  514. }
  515. function commit( $fname = '' ) {
  516. oci_commit($this->mConn);
  517. $this->mTrxLevel = 0;
  518. }
  519. /* Not even sure why this is used in the main codebase... */
  520. function limitResultForUpdate($sql, $num) {
  521. return $sql;
  522. }
  523. function strencode($s) {
  524. return str_replace("'", "''", $s);
  525. }
  526. function encodeBlob($b) {
  527. return new ORABlob($b);
  528. }
  529. function decodeBlob($b) {
  530. return $b; //return $b->load();
  531. }
  532. function addQuotes( $s ) {
  533. global $wgLang;
  534. $s = $wgLang->checkTitleEncoding($s);
  535. return "'" . $this->strencode($s) . "'";
  536. }
  537. function quote_ident( $s ) {
  538. return $s;
  539. }
  540. /* For now, does nothing */
  541. function selectDB( $db ) {
  542. return true;
  543. }
  544. /**
  545. * Returns an optional USE INDEX clause to go after the table, and a
  546. * string to go at the end of the query
  547. *
  548. * @private
  549. *
  550. * @param $options Array: an associative array of options to be turned into
  551. * an SQL query, valid keys are listed in the function.
  552. * @return array
  553. */
  554. function makeSelectOptions( $options ) {
  555. $preLimitTail = $postLimitTail = '';
  556. $startOpts = '';
  557. $noKeyOptions = array();
  558. foreach ( $options as $key => $option ) {
  559. if ( is_numeric( $key ) ) {
  560. $noKeyOptions[$option] = true;
  561. }
  562. }
  563. if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
  564. if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
  565. if (isset($options['LIMIT'])) {
  566. // $tailOpts .= $this->limitResult('', $options['LIMIT'],
  567. // isset($options['OFFSET']) ? $options['OFFSET']
  568. // : false);
  569. }
  570. #if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE';
  571. #if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE';
  572. if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
  573. if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
  574. $useIndex = $this->useIndexClause( $options['USE INDEX'] );
  575. } else {
  576. $useIndex = '';
  577. }
  578. return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
  579. }
  580. public function setTimeout( $timeout ) {
  581. // @todo fixme no-op
  582. }
  583. function ping() {
  584. wfDebug( "Function ping() not written for DatabaseOracle.php yet");
  585. return true;
  586. }
  587. /**
  588. * How lagged is this slave?
  589. *
  590. * @return int
  591. */
  592. public function getLag() {
  593. # Not implemented for Oracle
  594. return 0;
  595. }
  596. function setFakeSlaveLag( $lag ) {}
  597. function setFakeMaster( $enabled = true ) {}
  598. function getDBname() {
  599. return $this->mDBname;
  600. }
  601. function getServer() {
  602. return $this->mServer;
  603. }
  604. /**
  605. * No-op lock functions
  606. */
  607. public function lock( $lockName, $method ) {
  608. return true;
  609. }
  610. public function unlock( $lockName, $method ) {
  611. return true;
  612. }
  613. public function getSearchEngine() {
  614. return "SearchOracle";
  615. }
  616. } // end DatabaseOracle class