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