Use AutoLoader to load classes:
[lhc/web/wiklou.git] / includes / DatabaseOracle.php
1 <?php
2
3 /**
4 * Oracle.
5 *
6 * @package MediaWiki
7 */
8
9 class OracleBlob extends DBObject {
10 function isLOB() {
11 return true;
12 }
13 function data() {
14 return $this->mData;
15 }
16 };
17
18 /**
19 *
20 * @package MediaWiki
21 */
22 class DatabaseOracle extends Database {
23 var $mInsertId = NULL;
24 var $mLastResult = NULL;
25 var $mFetchCache = array();
26 var $mFetchID = array();
27 var $mNcols = array();
28 var $mFieldNames = array(), $mFieldTypes = array();
29 var $mAffectedRows = array();
30 var $mErr;
31
32 function DatabaseOracle($server = false, $user = false, $password = false, $dbName = false,
33 $failFunction = false, $flags = 0, $tablePrefix = 'get from global' )
34 {
35 Database::Database( $server, $user, $password, $dbName, $failFunction, $flags, $tablePrefix );
36 }
37
38 /* static */ function newFromParams( $server = false, $user = false, $password = false, $dbName = false,
39 $failFunction = false, $flags = 0, $tablePrefix = 'get from global' )
40 {
41 return new DatabaseOracle( $server, $user, $password, $dbName, $failFunction, $flags, $tablePrefix );
42 }
43
44 /**
45 * Usually aborts on failure
46 * If the failFunction is set to a non-zero integer, returns success
47 */
48 function open( $server, $user, $password, $dbName ) {
49 if ( !function_exists( 'oci_connect' ) ) {
50 wfDie( "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n" );
51 }
52 $this->close();
53 $this->mServer = $server;
54 $this->mUser = $user;
55 $this->mPassword = $password;
56 $this->mDBname = $dbName;
57
58 $success = false;
59
60 $hstring="";
61 $this->mConn = oci_new_connect($user, $password, $dbName, "AL32UTF8");
62 if ( $this->mConn === false ) {
63 wfDebug( "DB connection error\n" );
64 wfDebug( "Server: $server, Database: $dbName, User: $user, Password: "
65 . substr( $password, 0, 3 ) . "...\n" );
66 wfDebug( $this->lastError()."\n" );
67 } else {
68 $this->mOpened = true;
69 }
70 return $this->mConn;
71 }
72
73 /**
74 * Closes a database connection, if it is open
75 * Returns success, true if already closed
76 */
77 function close() {
78 $this->mOpened = false;
79 if ($this->mConn) {
80 return oci_close($this->mConn);
81 } else {
82 return true;
83 }
84 }
85
86 function parseStatement($sql) {
87 $this->mErr = $this->mLastResult = false;
88 if (($stmt = oci_parse($this->mConn, $sql)) === false) {
89 $this->lastError();
90 return $this->mLastResult = false;
91 }
92 $this->mAffectedRows[$stmt] = 0;
93 return $this->mLastResult = $stmt;
94 }
95
96 function doQuery($sql) {
97 if (($stmt = $this->parseStatement($sql)) === false)
98 return false;
99 return $this->executeStatement($stmt);
100 }
101
102 function executeStatement($stmt) {
103 if (!oci_execute($stmt, OCI_DEFAULT)) {
104 $this->lastError();
105 oci_free_statement($stmt);
106 return false;
107 }
108 $this->mAffectedRows[$stmt] = oci_num_rows($stmt);
109 $this->mFetchCache[$stmt] = array();
110 $this->mFetchID[$stmt] = 0;
111 $this->mNcols[$stmt] = oci_num_fields($stmt);
112 if ($this->mNcols[$stmt] == 0)
113 return $this->mLastResult;
114 for ($i = 1; $i <= $this->mNcols[$stmt]; $i++) {
115 $this->mFieldNames[$stmt][$i] = oci_field_name($stmt, $i);
116 $this->mFieldTypes[$stmt][$i] = oci_field_type($stmt, $i);
117 }
118 while (($o = oci_fetch_array($stmt)) !== false) {
119 foreach ($o as $key => $value) {
120 if (is_object($value)) {
121 $o[$key] = $value->load();
122 }
123 }
124 $this->mFetchCache[$stmt][] = $o;
125 }
126 return $this->mLastResult;
127 }
128
129 function queryIgnore( $sql, $fname = '' ) {
130 return $this->query( $sql, $fname, true );
131 }
132
133 function freeResult( $res ) {
134 if (!oci_free_statement($res)) {
135 wfDebugDieBacktrace( "Unable to free Oracle result\n" );
136 }
137 unset($this->mFetchID[$res]);
138 unset($this->mFetchCache[$res]);
139 unset($this->mNcols[$res]);
140 unset($this->mFieldNames[$res]);
141 unset($this->mFieldTypes[$res]);
142 }
143
144 function fetchAssoc($res) {
145 if ($this->mFetchID[$res] >= count($this->mFetchCache[$res]))
146 return false;
147
148 for ($i = 1; $i <= $this->mNcols[$res]; $i++) {
149 $name = $this->mFieldNames[$res][$i];
150 $type = $this->mFieldTypes[$res][$i];
151 if (isset($this->mFetchCache[$res][$this->mFetchID[$res]][$name]))
152 $value = $this->mFetchCache[$res][$this->mFetchID[$res]][$name];
153 else $value = NULL;
154 $key = strtolower($name);
155 wfdebug("'$key' => '$value'\n");
156 $ret[$key] = $value;
157 }
158 $this->mFetchID[$res]++;
159 return $ret;
160 }
161
162 function fetchRow($res) {
163 $r = $this->fetchAssoc($res);
164 if (!$r)
165 return false;
166 $i = 0;
167 $ret = array();
168 foreach ($r as $key => $value) {
169 wfdebug("ret[$i]=[$value]\n");
170 $ret[$i++] = $value;
171 }
172 return $ret;
173 }
174
175 function fetchObject($res) {
176 $row = $this->fetchAssoc($res);
177 if (!$row)
178 return false;
179 $ret = new stdClass;
180 foreach ($row as $key => $value)
181 $ret->$key = $value;
182 return $ret;
183 }
184
185 function numRows($res) {
186 return count($this->mFetchCache[$res]);
187 }
188 function numFields( $res ) { return pg_num_fields( $res ); }
189 function fieldName( $res, $n ) { return pg_field_name( $res, $n ); }
190
191 /**
192 * This must be called after nextSequenceVal
193 */
194 function insertId() {
195 return $this->mInsertId;
196 }
197
198 function dataSeek($res, $row) {
199 $this->mFetchID[$res] = $row;
200 }
201
202 function lastError() {
203 if ($this->mErr === false) {
204 if ($this->mLastResult !== false) $what = $this->mLastResult;
205 else if ($this->mConn !== false) $what = $this->mConn;
206 else $what = false;
207 $err = ($what !== false) ? oci_error($what) : oci_error();
208 if ($err === false)
209 $this->mErr = 'no error';
210 else
211 $this->mErr = $err['message'];
212 }
213 return str_replace("\n", '<br />', $this->mErr);
214 }
215 function lastErrno() {
216 return 0;
217 }
218
219 function affectedRows() {
220 return $this->mAffectedRows[$this->mLastResult];
221 }
222
223 /**
224 * Returns information about an index
225 * If errors are explicitly ignored, returns NULL on failure
226 */
227 function indexInfo ($table, $index, $fname = 'Database::indexInfo' ) {
228 $table = $this->tableName($table, true);
229 if ($index == 'PRIMARY')
230 $index = "${table}_pk";
231 $sql = "SELECT uniqueness FROM all_indexes WHERE table_name='" .
232 $table . "' AND index_name='" .
233 $this->strencode(strtoupper($index)) . "'";
234 $res = $this->query($sql, $fname);
235 if (!$res)
236 return NULL;
237 if (($row = $this->fetchObject($res)) == NULL)
238 return false;
239 $this->freeResult($res);
240 $row->Non_unique = !$row->uniqueness;
241 return $row;
242 }
243
244 function indexUnique ($table, $index, $fname = 'indexUnique') {
245 if (!($i = $this->indexInfo($table, $index, $fname)))
246 return $i;
247 return $i->uniqueness == 'UNIQUE';
248 }
249
250 function fieldInfo( $table, $field ) {
251 $o = new stdClass;
252 $o->multiple_key = true; /* XXX */
253 return $o;
254 }
255
256 function getColumnInformation($table, $field) {
257 $table = $this->tableName($table, true);
258 $field = strtoupper($field);
259
260 $res = $this->doQuery("SELECT * FROM all_tab_columns " .
261 "WHERE table_name='".$table."' " .
262 "AND column_name='".$field."'");
263 if (!$res)
264 return false;
265 $o = $this->fetchObject($res);
266 $this->freeResult($res);
267 return $o;
268 }
269
270 function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) {
271 $column = $this->getColumnInformation($table, $field);
272 if (!$column)
273 return false;
274 return true;
275 }
276
277 function tableName($name, $forddl = false) {
278 # First run any transformations from the parent object
279 $name = parent::tableName( $name );
280
281 # Replace backticks into empty
282 # Note: "foo" and foo are not the same in Oracle!
283 $name = str_replace('`', '', $name);
284
285 # Now quote Oracle reserved keywords
286 switch( $name ) {
287 case 'user':
288 case 'group':
289 case 'validate':
290 if ($forddl)
291 return $name;
292 else
293 return '"' . $name . '"';
294
295 default:
296 return strtoupper($name);
297 }
298 }
299
300 function strencode( $s ) {
301 return str_replace("'", "''", $s);
302 }
303
304 /**
305 * Return the next in a sequence, save the value for retrieval via insertId()
306 */
307 function nextSequenceValue( $seqName ) {
308 $r = $this->doQuery("SELECT $seqName.nextval AS val FROM dual");
309 $o = $this->fetchObject($r);
310 $this->freeResult($r);
311 return $this->mInsertId = (int)$o->val;
312 }
313
314 /**
315 * USE INDEX clause
316 * PostgreSQL doesn't have them and returns ""
317 */
318 function useIndexClause( $index ) {
319 return '';
320 }
321
322 # REPLACE query wrapper
323 # PostgreSQL simulates this with a DELETE followed by INSERT
324 # $row is the row to insert, an associative array
325 # $uniqueIndexes is an array of indexes. Each element may be either a
326 # field name or an array of field names
327 #
328 # It may be more efficient to leave off unique indexes which are unlikely to collide.
329 # However if you do this, you run the risk of encountering errors which wouldn't have
330 # occurred in MySQL
331 function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
332 $table = $this->tableName( $table );
333
334 if (count($rows)==0) {
335 return;
336 }
337
338 # Single row case
339 if ( !is_array( reset( $rows ) ) ) {
340 $rows = array( $rows );
341 }
342
343 foreach( $rows as $row ) {
344 # Delete rows which collide
345 if ( $uniqueIndexes ) {
346 $sql = "DELETE FROM $table WHERE ";
347 $first = true;
348 foreach ( $uniqueIndexes as $index ) {
349 if ( $first ) {
350 $first = false;
351 $sql .= "(";
352 } else {
353 $sql .= ') OR (';
354 }
355 if ( is_array( $index ) ) {
356 $first2 = true;
357 foreach ( $index as $col ) {
358 if ( $first2 ) {
359 $first2 = false;
360 } else {
361 $sql .= ' AND ';
362 }
363 $sql .= $col.'=' . $this->addQuotes( $row[$col] );
364 }
365 } else {
366 $sql .= $index.'=' . $this->addQuotes( $row[$index] );
367 }
368 }
369 $sql .= ')';
370 $this->query( $sql, $fname );
371 }
372
373 # Now insert the row
374 $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' .
375 $this->makeList( $row, LIST_COMMA ) . ')';
376 $this->query( $sql, $fname );
377 }
378 }
379
380 # DELETE where the condition is a join
381 function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) {
382 if ( !$conds ) {
383 wfDebugDieBacktrace( 'Database::deleteJoin() called with empty $conds' );
384 }
385
386 $delTable = $this->tableName( $delTable );
387 $joinTable = $this->tableName( $joinTable );
388 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
389 if ( $conds != '*' ) {
390 $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
391 }
392 $sql .= ')';
393
394 $this->query( $sql, $fname );
395 }
396
397 # Returns the size of a text field, or -1 for "unlimited"
398 function textFieldSize( $table, $field ) {
399 $table = $this->tableName( $table );
400 $sql = "SELECT t.typname as ftype,a.atttypmod as size
401 FROM pg_class c, pg_attribute a, pg_type t
402 WHERE relname='$table' AND a.attrelid=c.oid AND
403 a.atttypid=t.oid and a.attname='$field'";
404 $res =$this->query($sql);
405 $row=$this->fetchObject($res);
406 if ($row->ftype=="varchar") {
407 $size=$row->size-4;
408 } else {
409 $size=$row->size;
410 }
411 $this->freeResult( $res );
412 return $size;
413 }
414
415 function lowPriorityOption() {
416 return '';
417 }
418
419 function limitResult($sql, $limit, $offset) {
420 $ret = "SELECT * FROM ($sql) WHERE ROWNUM < " . ((int)$limit + (int)($offset+1));
421 if (is_numeric($offset))
422 $ret .= " AND ROWNUM >= " . (int)$offset;
423 return $ret;
424 }
425 function limitResultForUpdate($sql, $limit) {
426 return $sql;
427 }
428 /**
429 * Returns an SQL expression for a simple conditional.
430 * Uses CASE on PostgreSQL.
431 *
432 * @param string $cond SQL expression which will result in a boolean value
433 * @param string $trueVal SQL expression to return if true
434 * @param string $falseVal SQL expression to return if false
435 * @return string SQL fragment
436 */
437 function conditional( $cond, $trueVal, $falseVal ) {
438 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
439 }
440
441 # FIXME: actually detecting deadlocks might be nice
442 function wasDeadlock() {
443 return false;
444 }
445
446 # Return DB-style timestamp used for MySQL schema
447 function timestamp($ts = 0) {
448 return $this->strencode(wfTimestamp(TS_ORACLE, $ts));
449 # return "TO_TIMESTAMP('" . $this->strencode(wfTimestamp(TS_DB, $ts)) . "', 'RRRR-MM-DD HH24:MI:SS')";
450 }
451
452 /**
453 * Return aggregated value function call
454 */
455 function aggregateValue ($valuedata,$valuename='value') {
456 return $valuedata;
457 }
458
459
460 function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
461 $message = "A database error has occurred\n" .
462 "Query: $sql\n" .
463 "Function: $fname\n" .
464 "Error: $errno $error\n";
465 wfDebugDieBacktrace($message);
466 }
467
468 /**
469 * @return string wikitext of a link to the server software's web site
470 */
471 function getSoftwareLink() {
472 return "[http://www.oracle.com/ Oracle]";
473 }
474
475 /**
476 * @return string Version information from the database
477 */
478 function getServerVersion() {
479 return oci_server_version($this->mConn);
480 }
481
482 function setSchema($schema=false) {
483 $schemas=$this->mSchemas;
484 if ($schema) { array_unshift($schemas,$schema); }
485 $searchpath=$this->makeList($schemas,LIST_NAMES);
486 $this->query("SET search_path = $searchpath");
487 }
488
489 function begin() {
490 }
491
492 function immediateCommit( $fname = 'Database::immediateCommit' ) {
493 oci_commit($this->mConn);
494 $this->mTrxLevel = 0;
495 }
496 function rollback( $fname = 'Database::rollback' ) {
497 oci_rollback($this->mConn);
498 $this->mTrxLevel = 0;
499 }
500 function getLag() {
501 return false;
502 }
503 function getStatus($which=null) {
504 $result = array('Threads_running' => 0, 'Threads_connected' => 0);
505 return $result;
506 }
507
508 /**
509 * Returns an optional USE INDEX clause to go after the table, and a
510 * string to go at the end of the query
511 *
512 * @access private
513 *
514 * @param array $options an associative array of options to be turned into
515 * an SQL query, valid keys are listed in the function.
516 * @return array
517 */
518 function makeSelectOptions($options) {
519 $tailOpts = '';
520
521 if (isset( $options['ORDER BY'])) {
522 $tailOpts .= " ORDER BY {$options['ORDER BY']}";
523 }
524
525 return array('', $tailOpts);
526 }
527
528 function maxListLen() {
529 return 1000;
530 }
531
532 /**
533 * Query whether a given table exists
534 */
535 function tableExists( $table ) {
536 $table = $this->tableName($table, true);
537 $res = $this->query( "SELECT COUNT(*) as NUM FROM user_tables WHERE table_name='"
538 . $table . "'" );
539 if (!$res)
540 return false;
541 $row = $this->fetchObject($res);
542 $this->freeResult($res);
543 return $row->num >= 1;
544 }
545
546 /**
547 * UPDATE wrapper, takes a condition array and a SET array
548 */
549 function update( $table, $values, $conds, $fname = 'Database::update' ) {
550 $table = $this->tableName( $table );
551
552 $sql = "UPDATE $table SET ";
553 $first = true;
554 foreach ($values as $field => $v) {
555 if ($first)
556 $first = false;
557 else
558 $sql .= ", ";
559 $sql .= "$field = :n$field ";
560 }
561 if ( $conds != '*' ) {
562 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
563 }
564 $stmt = $this->parseStatement($sql);
565 if ($stmt === false) {
566 $this->reportQueryError( $this->lastError(), $this->lastErrno(), $stmt );
567 return false;
568 }
569 if ($this->debug())
570 wfDebug("SQL: $sql\n");
571 $s = '';
572 foreach ($values as $field => $v) {
573 oci_bind_by_name($stmt, ":n$field", $values[$field]);
574 if ($this->debug())
575 $s .= " [$field] = [$v]\n";
576 }
577 if ($this->debug())
578 wfdebug(" PH: $s\n");
579 $ret = $this->executeStatement($stmt);
580 return $ret;
581 }
582
583 /**
584 * INSERT wrapper, inserts an array into a table
585 *
586 * $a may be a single associative array, or an array of these with numeric keys, for
587 * multi-row insert.
588 *
589 * Usually aborts on failure
590 * If errors are explicitly ignored, returns success
591 */
592 function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
593 # No rows to insert, easy just return now
594 if ( !count( $a ) ) {
595 return true;
596 }
597
598 $table = $this->tableName( $table );
599 if (!is_array($options))
600 $options = array($options);
601
602 $oldIgnore = false;
603 if (in_array('IGNORE', $options))
604 $oldIgnore = $this->ignoreErrors( true );
605
606 if ( isset( $a[0] ) && is_array( $a[0] ) ) {
607 $multi = true;
608 $keys = array_keys( $a[0] );
609 } else {
610 $multi = false;
611 $keys = array_keys( $a );
612 }
613
614 $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES (';
615 $return = '';
616 $first = true;
617 foreach ($a as $key => $value) {
618 if ($first)
619 $first = false;
620 else
621 $sql .= ", ";
622 if (is_object($value) && $value->isLOB()) {
623 $sql .= "EMPTY_BLOB()";
624 $return = "RETURNING $key INTO :bobj";
625 } else
626 $sql .= ":$key";
627 }
628 $sql .= ") $return";
629
630 if ($this->debug()) {
631 wfDebug("SQL: $sql\n");
632 }
633
634 if (($stmt = $this->parseStatement($sql)) === false) {
635 $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname);
636 $this->ignoreErrors($oldIgnore);
637 return false;
638 }
639
640 /*
641 * If we're inserting multiple rows, parse the statement once and
642 * execute it for each set of values. Otherwise, convert it into an
643 * array and pretend.
644 */
645 if (!$multi)
646 $a = array($a);
647
648 foreach ($a as $key => $row) {
649 $blob = false;
650 $bdata = false;
651 $s = '';
652 foreach ($row as $k => $value) {
653 if (is_object($value) && $value->isLOB()) {
654 $blob = oci_new_descriptor($this->mConn, OCI_D_LOB);
655 $bdata = $value->data();
656 oci_bind_by_name($stmt, ":bobj", $blob, -1, OCI_B_BLOB);
657 } else
658 oci_bind_by_name($stmt, ":$k", $a[$key][$k], -1);
659 if ($this->debug())
660 $s .= " [$k] = {$row[$k]}";
661 }
662 if ($this->debug())
663 wfDebug(" PH: $s\n");
664 if (($s = $this->executeStatement($stmt)) === false) {
665 $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname);
666 $this->ignoreErrors($oldIgnore);
667 return false;
668 }
669
670 if ($blob) {
671 $blob->save($bdata);
672 }
673 }
674 $this->ignoreErrors($oldIgnore);
675 return $this->mLastResult = $s;
676 }
677
678 function ping() {
679 return true;
680 }
681
682 function encodeBlob($b) {
683 return new OracleBlob($b);
684 }
685 }
686
687 ?>