MediaWiki REL1_27
DatabaseOracle.php
Go to the documentation of this file.
1<?php
30class ORAResult {
31 private $rows;
32 private $cursor;
33 private $nrows;
34
35 private $columns = [];
36
37 private function array_unique_md( $array_in ) {
38 $array_out = [];
39 $array_hashes = [];
40
41 foreach ( $array_in as $item ) {
42 $hash = md5( serialize( $item ) );
43 if ( !isset( $array_hashes[$hash] ) ) {
44 $array_hashes[$hash] = $hash;
45 $array_out[] = $item;
46 }
47 }
48
49 return $array_out;
50 }
51
57 function __construct( &$db, $stmt, $unique = false ) {
58 $this->db =& $db;
59
60 $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM );
61 if ( $this->nrows === false ) {
62 $e = oci_error( $stmt );
63 $db->reportQueryError( $e['message'], $e['code'], '', __METHOD__ );
64 $this->free();
65
66 return;
67 }
68
69 if ( $unique ) {
70 $this->rows = $this->array_unique_md( $this->rows );
71 $this->nrows = count( $this->rows );
72 }
73
74 if ( $this->nrows > 0 ) {
75 foreach ( $this->rows[0] as $k => $v ) {
76 $this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) );
77 }
78 }
79
80 $this->cursor = 0;
81 oci_free_statement( $stmt );
82 }
83
84 public function free() {
85 unset( $this->db );
86 }
87
88 public function seek( $row ) {
89 $this->cursor = min( $row, $this->nrows );
90 }
91
92 public function numRows() {
93 return $this->nrows;
94 }
95
96 public function numFields() {
97 return count( $this->columns );
98 }
99
100 public function fetchObject() {
101 if ( $this->cursor >= $this->nrows ) {
102 return false;
103 }
104 $row = $this->rows[$this->cursor++];
105 $ret = new stdClass();
106 foreach ( $row as $k => $v ) {
107 $lc = $this->columns[$k];
108 $ret->$lc = $v;
109 }
110
111 return $ret;
112 }
113
114 public function fetchRow() {
115 if ( $this->cursor >= $this->nrows ) {
116 return false;
117 }
118
119 $row = $this->rows[$this->cursor++];
120 $ret = [];
121 foreach ( $row as $k => $v ) {
122 $lc = $this->columns[$k];
123 $ret[$lc] = $v;
124 $ret[$k] = $v;
125 }
126
127 return $ret;
128 }
129}
130
135class ORAField implements Field {
138
139 function __construct( $info ) {
140 $this->name = $info['column_name'];
141 $this->tablename = $info['table_name'];
142 $this->default = $info['data_default'];
143 $this->max_length = $info['data_length'];
144 $this->nullable = $info['not_null'];
145 $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
146 $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
147 $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
148 $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
149 $this->type = $info['data_type'];
150 }
151
152 function name() {
153 return $this->name;
154 }
155
156 function tableName() {
157 return $this->tablename;
158 }
159
160 function defaultValue() {
161 return $this->default;
162 }
163
164 function maxLength() {
165 return $this->max_length;
166 }
167
168 function isNullable() {
169 return $this->nullable;
170 }
171
172 function isKey() {
173 return $this->is_key;
174 }
175
176 function isMultipleKey() {
177 return $this->is_multiple;
178 }
179
180 function type() {
181 return $this->type;
182 }
183}
184
190 protected $mLastResult = null;
191
193 protected $mAffectedRows;
194
196 private $mInsertId = null;
197
199 private $ignoreDupValOnIndex = false;
200
202 private $sequenceData = null;
203
205 private $defaultCharset = 'AL32UTF8';
206
208 private $mFieldInfoCache = [];
209
210 function __construct( array $p ) {
212
213 if ( $p['tablePrefix'] == 'get from global' ) {
214 $p['tablePrefix'] = $wgDBprefix;
215 }
216 $p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
217 parent::__construct( $p );
218 Hooks::run( 'DatabaseOraclePostInit', [ $this ] );
219 }
220
221 function __destruct() {
222 if ( $this->mOpened ) {
223 MediaWiki\suppressWarnings();
224 $this->close();
225 MediaWiki\restoreWarnings();
226 }
227 }
228
229 function getType() {
230 return 'oracle';
231 }
232
233 function cascadingDeletes() {
234 return true;
235 }
236
237 function cleanupTriggers() {
238 return true;
239 }
240
241 function strictIPs() {
242 return true;
243 }
244
245 function realTimestamps() {
246 return true;
247 }
248
249 function implicitGroupby() {
250 return false;
251 }
252
253 function implicitOrderby() {
254 return false;
255 }
256
257 function searchableIPs() {
258 return true;
259 }
260
270 function open( $server, $user, $password, $dbName ) {
272 if ( !function_exists( 'oci_connect' ) ) {
273 throw new DBConnectionError(
274 $this,
275 "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n " .
276 "(Note: if you recently installed PHP, you may need to restart your webserver\n " .
277 "and database)\n" );
278 }
279
280 $this->close();
281 $this->mUser = $user;
282 $this->mPassword = $password;
283 // changed internal variables functions
284 // mServer now holds the TNS endpoint
285 // mDBname is schema name if different from username
286 if ( !$server ) {
287 // backward compatibillity (server used to be null and TNS was supplied in dbname)
288 $this->mServer = $dbName;
289 $this->mDBname = $user;
290 } else {
291 $this->mServer = $server;
292 if ( !$dbName ) {
293 $this->mDBname = $user;
294 } else {
295 $this->mDBname = $dbName;
296 }
297 }
298
299 if ( !strlen( $user ) ) { # e.g. the class is being loaded
300 return null;
301 }
302
303 if ( $wgDBOracleDRCP ) {
304 $this->setFlag( DBO_PERSISTENT );
305 }
306
307 $session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
308
309 MediaWiki\suppressWarnings();
310 if ( $this->mFlags & DBO_PERSISTENT ) {
311 $this->mConn = oci_pconnect(
312 $this->mUser,
313 $this->mPassword,
314 $this->mServer,
315 $this->defaultCharset,
316 $session_mode
317 );
318 } elseif ( $this->mFlags & DBO_DEFAULT ) {
319 $this->mConn = oci_new_connect(
320 $this->mUser,
321 $this->mPassword,
322 $this->mServer,
323 $this->defaultCharset,
324 $session_mode
325 );
326 } else {
327 $this->mConn = oci_connect(
328 $this->mUser,
329 $this->mPassword,
330 $this->mServer,
331 $this->defaultCharset,
332 $session_mode
333 );
334 }
335 MediaWiki\restoreWarnings();
336
337 if ( $this->mUser != $this->mDBname ) {
338 // change current schema in session
339 $this->selectDB( $this->mDBname );
340 }
341
342 if ( !$this->mConn ) {
343 throw new DBConnectionError( $this, $this->lastError() );
344 }
345
346 $this->mOpened = true;
347
348 # removed putenv calls because they interfere with the system globaly
349 $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
350 $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
351 $this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
352
353 return $this->mConn;
354 }
355
361 protected function closeConnection() {
362 return oci_close( $this->mConn );
363 }
364
365 function execFlags() {
366 return $this->mTrxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
367 }
368
369 protected function doQuery( $sql ) {
370 wfDebug( "SQL: [$sql]\n" );
371 if ( !StringUtils::isUtf8( $sql ) ) {
372 throw new MWException( "SQL encoding is invalid\n$sql" );
373 }
374
375 // handle some oracle specifics
376 // remove AS column/table/subquery namings
377 if ( !$this->getFlag( DBO_DDLMODE ) ) {
378 $sql = preg_replace( '/ as /i', ' ', $sql );
379 }
380
381 // Oracle has issues with UNION clause if the statement includes LOB fields
382 // So we do a UNION ALL and then filter the results array with array_unique
383 $union_unique = ( preg_match( '/\/\* UNION_UNIQUE \*\/ /', $sql ) != 0 );
384 // EXPLAIN syntax in Oracle is EXPLAIN PLAN FOR and it return nothing
385 // you have to select data from plan table after explain
386 $explain_id = MWTimestamp::getLocalInstance()->format( 'dmYHis' );
387
388 $sql = preg_replace(
389 '/^EXPLAIN /',
390 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR',
391 $sql,
392 1,
393 $explain_count
394 );
395
396 MediaWiki\suppressWarnings();
397
398 $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
399 if ( $stmt === false ) {
400 $e = oci_error( $this->mConn );
401 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
402
403 return false;
404 }
405
406 if ( !oci_execute( $stmt, $this->execFlags() ) ) {
407 $e = oci_error( $stmt );
408 if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
409 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
410
411 return false;
412 }
413 }
414
415 MediaWiki\restoreWarnings();
416
417 if ( $explain_count > 0 ) {
418 return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table ' .
419 'WHERE statement_id = \'' . $explain_id . '\'' );
420 } elseif ( oci_statement_type( $stmt ) == 'SELECT' ) {
421 return new ORAResult( $this, $stmt, $union_unique );
422 } else {
423 $this->mAffectedRows = oci_num_rows( $stmt );
424
425 return true;
426 }
427 }
428
429 function queryIgnore( $sql, $fname = '' ) {
430 return $this->query( $sql, $fname, true );
431 }
432
437 function freeResult( $res ) {
438 if ( $res instanceof ResultWrapper ) {
439 $res = $res->result;
440 }
441
442 $res->free();
443 }
444
449 function fetchObject( $res ) {
450 if ( $res instanceof ResultWrapper ) {
451 $res = $res->result;
452 }
453
454 return $res->fetchObject();
455 }
456
457 function fetchRow( $res ) {
458 if ( $res instanceof ResultWrapper ) {
459 $res = $res->result;
460 }
461
462 return $res->fetchRow();
463 }
464
465 function numRows( $res ) {
466 if ( $res instanceof ResultWrapper ) {
467 $res = $res->result;
468 }
469
470 return $res->numRows();
471 }
472
473 function numFields( $res ) {
474 if ( $res instanceof ResultWrapper ) {
475 $res = $res->result;
476 }
477
478 return $res->numFields();
479 }
480
481 function fieldName( $stmt, $n ) {
482 return oci_field_name( $stmt, $n );
483 }
484
489 function insertId() {
490 return $this->mInsertId;
491 }
492
497 function dataSeek( $res, $row ) {
498 if ( $res instanceof ORAResult ) {
499 $res->seek( $row );
500 } else {
501 $res->result->seek( $row );
502 }
503 }
504
505 function lastError() {
506 if ( $this->mConn === false ) {
507 $e = oci_error();
508 } else {
509 $e = oci_error( $this->mConn );
510 }
511
512 return $e['message'];
513 }
514
515 function lastErrno() {
516 if ( $this->mConn === false ) {
517 $e = oci_error();
518 } else {
519 $e = oci_error( $this->mConn );
520 }
521
522 return $e['code'];
523 }
524
525 function affectedRows() {
526 return $this->mAffectedRows;
527 }
528
537 function indexInfo( $table, $index, $fname = __METHOD__ ) {
538 return false;
539 }
540
541 function indexUnique( $table, $index, $fname = __METHOD__ ) {
542 return false;
543 }
544
545 function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
546 if ( !count( $a ) ) {
547 return true;
548 }
549
550 if ( !is_array( $options ) ) {
551 $options = [ $options ];
552 }
553
554 if ( in_array( 'IGNORE', $options ) ) {
555 $this->ignoreDupValOnIndex = true;
556 }
557
558 if ( !is_array( reset( $a ) ) ) {
559 $a = [ $a ];
560 }
561
562 foreach ( $a as &$row ) {
563 $this->insertOneRow( $table, $row, $fname );
564 }
565 $retVal = true;
566
567 if ( in_array( 'IGNORE', $options ) ) {
568 $this->ignoreDupValOnIndex = false;
569 }
570
571 return $retVal;
572 }
573
574 private function fieldBindStatement( $table, $col, &$val, $includeCol = false ) {
575 $col_info = $this->fieldInfoMulti( $table, $col );
576 $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
577
578 $bind = '';
579 if ( is_numeric( $col ) ) {
580 $bind = $val;
581 $val = null;
582
583 return $bind;
584 } elseif ( $includeCol ) {
585 $bind = "$col = ";
586 }
587
588 if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) {
589 $val = null;
590 }
591
592 if ( $val === 'NULL' ) {
593 $val = null;
594 }
595
596 if ( $val === null ) {
597 if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
598 $bind .= 'DEFAULT';
599 } else {
600 $bind .= 'NULL';
601 }
602 } else {
603 $bind .= ':' . $col;
604 }
605
606 return $bind;
607 }
608
616 private function insertOneRow( $table, $row, $fname ) {
618
619 $table = $this->tableName( $table );
620 // "INSERT INTO tables (a, b, c)"
621 $sql = "INSERT INTO " . $table . " (" . implode( ',', array_keys( $row ) ) . ')';
622 $sql .= " VALUES (";
623
624 // for each value, append ":key"
625 $first = true;
626 foreach ( $row as $col => &$val ) {
627 if ( !$first ) {
628 $sql .= ', ';
629 } else {
630 $first = false;
631 }
632 if ( $this->isQuotedIdentifier( $val ) ) {
633 $sql .= $this->removeIdentifierQuotes( $val );
634 unset( $row[$col] );
635 } else {
636 $sql .= $this->fieldBindStatement( $table, $col, $val );
637 }
638 }
639 $sql .= ')';
640
641 $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
642 if ( $stmt === false ) {
643 $e = oci_error( $this->mConn );
644 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
645
646 return false;
647 }
648 foreach ( $row as $col => &$val ) {
649 $col_info = $this->fieldInfoMulti( $table, $col );
650 $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
651
652 if ( $val === null ) {
653 // do nothing ... null was inserted in statement creation
654 } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
655 if ( is_object( $val ) ) {
656 $val = $val->fetch();
657 }
658
659 // backward compatibility
660 if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
661 $val = $this->getInfinity();
662 }
663
664 $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
665 if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
666 $e = oci_error( $stmt );
667 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
668
669 return false;
670 }
671 } else {
673 $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB );
674 if ( $lob[$col] === false ) {
675 $e = oci_error( $stmt );
676 throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
677 }
678
679 if ( is_object( $val ) ) {
680 $val = $val->fetch();
681 }
682
683 if ( $col_type == 'BLOB' ) {
684 $lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
685 oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_BLOB );
686 } else {
687 $lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
688 oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
689 }
690 }
691 }
692
693 MediaWiki\suppressWarnings();
694
695 if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
696 $e = oci_error( $stmt );
697 if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
698 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
699
700 return false;
701 } else {
702 $this->mAffectedRows = oci_num_rows( $stmt );
703 }
704 } else {
705 $this->mAffectedRows = oci_num_rows( $stmt );
706 }
707
708 MediaWiki\restoreWarnings();
709
710 if ( isset( $lob ) ) {
711 foreach ( $lob as $lob_v ) {
712 $lob_v->free();
713 }
714 }
715
716 if ( !$this->mTrxLevel ) {
717 oci_commit( $this->mConn );
718 }
719
720 return oci_free_statement( $stmt );
721 }
722
723 function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
724 $insertOptions = [], $selectOptions = []
725 ) {
726 $destTable = $this->tableName( $destTable );
727 if ( !is_array( $selectOptions ) ) {
728 $selectOptions = [ $selectOptions ];
729 }
730 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
731 if ( is_array( $srcTable ) ) {
732 $srcTable = implode( ',', array_map( [ $this, 'tableName' ], $srcTable ) );
733 } else {
734 $srcTable = $this->tableName( $srcTable );
735 }
736
737 $sequenceData = $this->getSequenceData( $destTable );
738 if ( $sequenceData !== false &&
739 !isset( $varMap[$sequenceData['column']] )
740 ) {
741 $varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
742 }
743
744 // count-alias subselect fields to avoid abigious definition errors
745 $i = 0;
746 foreach ( $varMap as &$val ) {
747 $val = $val . ' field' . ( $i++ );
748 }
749
750 $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
751 " SELECT $startOpts " . implode( ',', $varMap ) .
752 " FROM $srcTable $useIndex ";
753 if ( $conds != '*' ) {
754 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
755 }
756 $sql .= " $tailOpts";
757
758 if ( in_array( 'IGNORE', $insertOptions ) ) {
759 $this->ignoreDupValOnIndex = true;
760 }
761
762 $retval = $this->query( $sql, $fname );
763
764 if ( in_array( 'IGNORE', $insertOptions ) ) {
765 $this->ignoreDupValOnIndex = false;
766 }
767
768 return $retval;
769 }
770
771 public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
772 $fname = __METHOD__
773 ) {
774 if ( !count( $rows ) ) {
775 return true; // nothing to do
776 }
777
778 if ( !is_array( reset( $rows ) ) ) {
779 $rows = [ $rows ];
780 }
781
782 $sequenceData = $this->getSequenceData( $table );
783 if ( $sequenceData !== false ) {
784 // add sequence column to each list of columns, when not set
785 foreach ( $rows as &$row ) {
786 if ( !isset( $row[$sequenceData['column']] ) ) {
787 $row[$sequenceData['column']] =
788 $this->addIdentifierQuotes( 'GET_SEQUENCE_VALUE(\'' .
789 $sequenceData['sequence'] . '\')' );
790 }
791 }
792 }
793
794 return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname );
795 }
796
797 function tableName( $name, $format = 'quoted' ) {
798 /*
799 Replace reserved words with better ones
800 Using uppercase because that's the only way Oracle can handle
801 quoted tablenames
802 */
803 switch ( $name ) {
804 case 'user':
805 $name = 'MWUSER';
806 break;
807 case 'text':
808 $name = 'PAGECONTENT';
809 break;
810 }
811
812 return strtoupper( parent::tableName( $name, $format ) );
813 }
814
815 function tableNameInternal( $name ) {
816 $name = $this->tableName( $name );
817
818 return preg_replace( '/.*\.(.*)/', '$1', $name );
819 }
820
827 function nextSequenceValue( $seqName ) {
828 $res = $this->query( "SELECT $seqName.nextval FROM dual" );
829 $row = $this->fetchRow( $res );
830 $this->mInsertId = $row[0];
831
832 return $this->mInsertId;
833 }
834
841 private function getSequenceData( $table ) {
842 if ( $this->sequenceData == null ) {
843 $result = $this->doQuery( "SELECT lower(asq.sequence_name),
844 lower(atc.table_name),
845 lower(atc.column_name)
846 FROM all_sequences asq, all_tab_columns atc
847 WHERE decode(
848 atc.table_name,
849 '{$this->mTablePrefix}MWUSER',
850 '{$this->mTablePrefix}USER',
851 atc.table_name
852 ) || '_' ||
853 atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
854 AND asq.sequence_owner = upper('{$this->mDBname}')
855 AND atc.owner = upper('{$this->mDBname}')" );
856
857 while ( ( $row = $result->fetchRow() ) !== false ) {
858 $this->sequenceData[$row[1]] = [
859 'sequence' => $row[0],
860 'column' => $row[2]
861 ];
862 }
863 }
864 $table = strtolower( $this->removeIdentifierQuotes( $this->tableName( $table ) ) );
865
866 return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
867 }
868
876 function textFieldSize( $table, $field ) {
877 $fieldInfoData = $this->fieldInfo( $table, $field );
878
879 return $fieldInfoData->maxLength();
880 }
881
882 function limitResult( $sql, $limit, $offset = false ) {
883 if ( $offset === false ) {
884 $offset = 0;
885 }
886
887 return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
888 }
889
890 function encodeBlob( $b ) {
891 return new Blob( $b );
892 }
893
894 function decodeBlob( $b ) {
895 if ( $b instanceof Blob ) {
896 $b = $b->fetch();
897 }
898
899 return $b;
900 }
901
902 function unionQueries( $sqls, $all ) {
903 $glue = ' UNION ALL ';
904
905 return 'SELECT * ' . ( $all ? '' : '/* UNION_UNIQUE */ ' ) .
906 'FROM (' . implode( $glue, $sqls ) . ')';
907 }
908
909 function wasDeadlock() {
910 return $this->lastErrno() == 'OCI-00060';
911 }
912
913 function duplicateTableStructure( $oldName, $newName, $temporary = false,
914 $fname = __METHOD__
915 ) {
916 $temporary = $temporary ? 'TRUE' : 'FALSE';
917
918 $newName = strtoupper( $newName );
919 $oldName = strtoupper( $oldName );
920
921 $tabName = substr( $newName, strlen( $this->mTablePrefix ) );
922 $oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
923 $newPrefix = strtoupper( $this->mTablePrefix );
924
925 return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
926 "'$oldPrefix', '$newPrefix', $temporary ); END;" );
927 }
928
929 function listTables( $prefix = null, $fname = __METHOD__ ) {
930 $listWhere = '';
931 if ( !empty( $prefix ) ) {
932 $listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
933 }
934
935 $owner = strtoupper( $this->mDBname );
936 $result = $this->doQuery( "SELECT table_name FROM all_tables " .
937 "WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
938
939 // dirty code ... i know
940 $endArray = [];
941 $endArray[] = strtoupper( $prefix . 'MWUSER' );
942 $endArray[] = strtoupper( $prefix . 'PAGE' );
943 $endArray[] = strtoupper( $prefix . 'IMAGE' );
944 $fixedOrderTabs = $endArray;
945 while ( ( $row = $result->fetchRow() ) !== false ) {
946 if ( !in_array( $row['table_name'], $fixedOrderTabs ) ) {
947 $endArray[] = $row['table_name'];
948 }
949 }
950
951 return $endArray;
952 }
953
954 public function dropTable( $tableName, $fName = __METHOD__ ) {
955 $tableName = $this->tableName( $tableName );
956 if ( !$this->tableExists( $tableName ) ) {
957 return false;
958 }
959
960 return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
961 }
962
963 function timestamp( $ts = 0 ) {
964 return wfTimestamp( TS_ORACLE, $ts );
965 }
966
974 public function aggregateValue( $valuedata, $valuename = 'value' ) {
975 return $valuedata;
976 }
977
981 public function getSoftwareLink() {
982 return '[{{int:version-db-oracle-url}} Oracle]';
983 }
984
988 function getServerVersion() {
989 // better version number, fallback on driver
990 $rset = $this->doQuery(
991 'SELECT version FROM product_component_version ' .
992 'WHERE UPPER(product) LIKE \'ORACLE DATABASE%\''
993 );
994 $row = $rset->fetchRow();
995 if ( !$row ) {
996 return oci_server_version( $this->mConn );
997 }
998
999 return $row['version'];
1000 }
1001
1009 function indexExists( $table, $index, $fname = __METHOD__ ) {
1010 $table = $this->tableName( $table );
1011 $table = strtoupper( $this->removeIdentifierQuotes( $table ) );
1012 $index = strtoupper( $index );
1013 $owner = strtoupper( $this->mDBname );
1014 $sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
1015 $res = $this->doQuery( $sql );
1016 if ( $res ) {
1017 $count = $res->numRows();
1018 $res->free();
1019 } else {
1020 $count = 0;
1021 }
1022
1023 return $count != 0;
1024 }
1025
1032 function tableExists( $table, $fname = __METHOD__ ) {
1033 $table = $this->tableName( $table );
1034 $table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
1035 $owner = $this->addQuotes( strtoupper( $this->mDBname ) );
1036 $sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
1037 $res = $this->doQuery( $sql );
1038 if ( $res && $res->numRows() > 0 ) {
1039 $exists = true;
1040 } else {
1041 $exists = false;
1042 }
1043
1044 $res->free();
1045
1046 return $exists;
1047 }
1048
1059 private function fieldInfoMulti( $table, $field ) {
1060 $field = strtoupper( $field );
1061 if ( is_array( $table ) ) {
1062 $table = array_map( [ $this, 'tableNameInternal' ], $table );
1063 $tableWhere = 'IN (';
1064 foreach ( $table as &$singleTable ) {
1065 $singleTable = $this->removeIdentifierQuotes( $singleTable );
1066 if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
1067 return $this->mFieldInfoCache["$singleTable.$field"];
1068 }
1069 $tableWhere .= '\'' . $singleTable . '\',';
1070 }
1071 $tableWhere = rtrim( $tableWhere, ',' ) . ')';
1072 } else {
1073 $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
1074 if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
1075 return $this->mFieldInfoCache["$table.$field"];
1076 }
1077 $tableWhere = '= \'' . $table . '\'';
1078 }
1079
1080 $fieldInfoStmt = oci_parse(
1081 $this->mConn,
1082 'SELECT * FROM wiki_field_info_full WHERE table_name ' .
1083 $tableWhere . ' and column_name = \'' . $field . '\''
1084 );
1085 if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
1086 $e = oci_error( $fieldInfoStmt );
1087 $this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
1088
1089 return false;
1090 }
1091 $res = new ORAResult( $this, $fieldInfoStmt );
1092 if ( $res->numRows() == 0 ) {
1093 if ( is_array( $table ) ) {
1094 foreach ( $table as &$singleTable ) {
1095 $this->mFieldInfoCache["$singleTable.$field"] = false;
1096 }
1097 } else {
1098 $this->mFieldInfoCache["$table.$field"] = false;
1099 }
1100 $fieldInfoTemp = null;
1101 } else {
1102 $fieldInfoTemp = new ORAField( $res->fetchRow() );
1103 $table = $fieldInfoTemp->tableName();
1104 $this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
1105 }
1106 $res->free();
1107
1108 return $fieldInfoTemp;
1109 }
1110
1117 function fieldInfo( $table, $field ) {
1118 if ( is_array( $table ) ) {
1119 throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
1120 }
1121
1122 return $this->fieldInfoMulti( $table, $field );
1123 }
1124
1125 protected function doBegin( $fname = __METHOD__ ) {
1126 $this->mTrxLevel = 1;
1127 $this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
1128 }
1129
1130 protected function doCommit( $fname = __METHOD__ ) {
1131 if ( $this->mTrxLevel ) {
1132 $ret = oci_commit( $this->mConn );
1133 if ( !$ret ) {
1134 throw new DBUnexpectedError( $this, $this->lastError() );
1135 }
1136 $this->mTrxLevel = 0;
1137 $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
1138 }
1139 }
1140
1141 protected function doRollback( $fname = __METHOD__ ) {
1142 if ( $this->mTrxLevel ) {
1143 oci_rollback( $this->mConn );
1144 $this->mTrxLevel = 0;
1145 $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
1146 }
1147 }
1148
1159 function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
1160 $fname = __METHOD__, $inputCallback = false ) {
1161 $cmd = '';
1162 $done = false;
1163 $dollarquote = false;
1164
1165 $replacements = [];
1166
1167 while ( !feof( $fp ) ) {
1168 if ( $lineCallback ) {
1169 call_user_func( $lineCallback );
1170 }
1171 $line = trim( fgets( $fp, 1024 ) );
1172 $sl = strlen( $line ) - 1;
1173
1174 if ( $sl < 0 ) {
1175 continue;
1176 }
1177 if ( '-' == $line[0] && '-' == $line[1] ) {
1178 continue;
1179 }
1180
1181 // Allow dollar quoting for function declarations
1182 if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) {
1183 if ( $dollarquote ) {
1184 $dollarquote = false;
1185 $line = str_replace( '/*$mw$*/', '', $line ); // remove dollarquotes
1186 $done = true;
1187 } else {
1188 $dollarquote = true;
1189 }
1190 } elseif ( !$dollarquote ) {
1191 if ( ';' == $line[$sl] && ( $sl < 2 || ';' != $line[$sl - 1] ) ) {
1192 $done = true;
1193 $line = substr( $line, 0, $sl );
1194 }
1195 }
1196
1197 if ( $cmd != '' ) {
1198 $cmd .= ' ';
1199 }
1200 $cmd .= "$line\n";
1201
1202 if ( $done ) {
1203 $cmd = str_replace( ';;', ";", $cmd );
1204 if ( strtolower( substr( $cmd, 0, 6 ) ) == 'define' ) {
1205 if ( preg_match( '/^define\s*([^\s=]*)\s*=\s*\'\{\$([^\}]*)\}\'/', $cmd, $defines ) ) {
1206 $replacements[$defines[2]] = $defines[1];
1207 }
1208 } else {
1209 foreach ( $replacements as $mwVar => $scVar ) {
1210 $cmd = str_replace( '&' . $scVar . '.', '`{$' . $mwVar . '}`', $cmd );
1211 }
1212
1213 $cmd = $this->replaceVars( $cmd );
1214 if ( $inputCallback ) {
1215 call_user_func( $inputCallback, $cmd );
1216 }
1217 $res = $this->doQuery( $cmd );
1218 if ( $resultCallback ) {
1219 call_user_func( $resultCallback, $res, $this );
1220 }
1221
1222 if ( false === $res ) {
1223 $err = $this->lastError();
1224
1225 return "Query \"{$cmd}\" failed with error code \"$err\".\n";
1226 }
1227 }
1228
1229 $cmd = '';
1230 $done = false;
1231 }
1232 }
1233
1234 return true;
1235 }
1236
1237 function selectDB( $db ) {
1238 $this->mDBname = $db;
1239 if ( $db == null || $db == $this->mUser ) {
1240 return true;
1241 }
1242 $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
1243 $stmt = oci_parse( $this->mConn, $sql );
1244 MediaWiki\suppressWarnings();
1245 $success = oci_execute( $stmt );
1246 MediaWiki\restoreWarnings();
1247 if ( !$success ) {
1248 $e = oci_error( $stmt );
1249 if ( $e['code'] != '1435' ) {
1250 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1251 }
1252
1253 return false;
1254 }
1255
1256 return true;
1257 }
1258
1259 function strencode( $s ) {
1260 return str_replace( "'", "''", $s );
1261 }
1262
1263 function addQuotes( $s ) {
1265 if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
1266 $s = $wgContLang->checkTitleEncoding( $s );
1267 }
1268
1269 return "'" . $this->strencode( $s ) . "'";
1270 }
1271
1272 public function addIdentifierQuotes( $s ) {
1273 if ( !$this->getFlag( DBO_DDLMODE ) ) {
1274 $s = '/*Q*/' . $s;
1275 }
1276
1277 return $s;
1278 }
1279
1280 public function removeIdentifierQuotes( $s ) {
1281 return strpos( $s, '/*Q*/' ) === false ? $s : substr( $s, 5 );
1282 }
1283
1284 public function isQuotedIdentifier( $s ) {
1285 return strpos( $s, '/*Q*/' ) !== false;
1286 }
1287
1288 private function wrapFieldForWhere( $table, &$col, &$val ) {
1290
1291 $col_info = $this->fieldInfoMulti( $table, $col );
1292 $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
1293 if ( $col_type == 'CLOB' ) {
1294 $col = 'TO_CHAR(' . $col . ')';
1295 $val = $wgContLang->checkTitleEncoding( $val );
1296 } elseif ( $col_type == 'VARCHAR2' ) {
1297 $val = $wgContLang->checkTitleEncoding( $val );
1298 }
1299 }
1300
1301 private function wrapConditionsForWhere( $table, $conds, $parentCol = null ) {
1302 $conds2 = [];
1303 foreach ( $conds as $col => $val ) {
1304 if ( is_array( $val ) ) {
1305 $conds2[$col] = $this->wrapConditionsForWhere( $table, $val, $col );
1306 } else {
1307 if ( is_numeric( $col ) && $parentCol != null ) {
1308 $this->wrapFieldForWhere( $table, $parentCol, $val );
1309 } else {
1310 $this->wrapFieldForWhere( $table, $col, $val );
1311 }
1312 $conds2[$col] = $val;
1313 }
1314 }
1315
1316 return $conds2;
1317 }
1318
1319 function selectRow( $table, $vars, $conds, $fname = __METHOD__,
1320 $options = [], $join_conds = []
1321 ) {
1322 if ( is_array( $conds ) ) {
1323 $conds = $this->wrapConditionsForWhere( $table, $conds );
1324 }
1325
1326 return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
1327 }
1328
1338 $preLimitTail = $postLimitTail = '';
1339 $startOpts = '';
1340
1341 $noKeyOptions = [];
1342 foreach ( $options as $key => $option ) {
1343 if ( is_numeric( $key ) ) {
1344 $noKeyOptions[$option] = true;
1345 }
1346 }
1347
1348 $preLimitTail .= $this->makeGroupByWithHaving( $options );
1349
1350 $preLimitTail .= $this->makeOrderBy( $options );
1351
1352 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1353 $postLimitTail .= ' FOR UPDATE';
1354 }
1355
1356 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1357 $startOpts .= 'DISTINCT';
1358 }
1359
1360 if ( isset( $options['USE INDEX'] ) && !is_array( $options['USE INDEX'] ) ) {
1361 $useIndex = $this->useIndexClause( $options['USE INDEX'] );
1362 } else {
1363 $useIndex = '';
1364 }
1365
1366 return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail ];
1367 }
1368
1369 public function delete( $table, $conds, $fname = __METHOD__ ) {
1370 if ( is_array( $conds ) ) {
1371 $conds = $this->wrapConditionsForWhere( $table, $conds );
1372 }
1373 // a hack for deleting pages, users and images (which have non-nullable FKs)
1374 // all deletions on these tables have transactions so final failure rollbacks these updates
1375 $table = $this->tableName( $table );
1376 if ( $table == $this->tableName( 'user' ) ) {
1377 $this->update( 'archive', [ 'ar_user' => 0 ],
1378 [ 'ar_user' => $conds['user_id'] ], $fname );
1379 $this->update( 'ipblocks', [ 'ipb_user' => 0 ],
1380 [ 'ipb_user' => $conds['user_id'] ], $fname );
1381 $this->update( 'image', [ 'img_user' => 0 ],
1382 [ 'img_user' => $conds['user_id'] ], $fname );
1383 $this->update( 'oldimage', [ 'oi_user' => 0 ],
1384 [ 'oi_user' => $conds['user_id'] ], $fname );
1385 $this->update( 'filearchive', [ 'fa_deleted_user' => 0 ],
1386 [ 'fa_deleted_user' => $conds['user_id'] ], $fname );
1387 $this->update( 'filearchive', [ 'fa_user' => 0 ],
1388 [ 'fa_user' => $conds['user_id'] ], $fname );
1389 $this->update( 'uploadstash', [ 'us_user' => 0 ],
1390 [ 'us_user' => $conds['user_id'] ], $fname );
1391 $this->update( 'recentchanges', [ 'rc_user' => 0 ],
1392 [ 'rc_user' => $conds['user_id'] ], $fname );
1393 $this->update( 'logging', [ 'log_user' => 0 ],
1394 [ 'log_user' => $conds['user_id'] ], $fname );
1395 } elseif ( $table == $this->tableName( 'image' ) ) {
1396 $this->update( 'oldimage', [ 'oi_name' => 0 ],
1397 [ 'oi_name' => $conds['img_name'] ], $fname );
1398 }
1399
1400 return parent::delete( $table, $conds, $fname );
1401 }
1402
1412 function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
1414
1415 $table = $this->tableName( $table );
1416 $opts = $this->makeUpdateOptions( $options );
1417 $sql = "UPDATE $opts $table SET ";
1418
1419 $first = true;
1420 foreach ( $values as $col => &$val ) {
1421 $sqlSet = $this->fieldBindStatement( $table, $col, $val, true );
1422
1423 if ( !$first ) {
1424 $sqlSet = ', ' . $sqlSet;
1425 } else {
1426 $first = false;
1427 }
1428 $sql .= $sqlSet;
1429 }
1430
1431 if ( $conds !== [] && $conds !== '*' ) {
1432 $conds = $this->wrapConditionsForWhere( $table, $conds );
1433 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
1434 }
1435
1436 $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
1437 if ( $stmt === false ) {
1438 $e = oci_error( $this->mConn );
1439 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1440
1441 return false;
1442 }
1443 foreach ( $values as $col => &$val ) {
1444 $col_info = $this->fieldInfoMulti( $table, $col );
1445 $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
1446
1447 if ( $val === null ) {
1448 // do nothing ... null was inserted in statement creation
1449 } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
1450 if ( is_object( $val ) ) {
1451 $val = $val->getData();
1452 }
1453
1454 if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
1455 $val = '31-12-2030 12:00:00.000000';
1456 }
1457
1458 $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
1459 if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
1460 $e = oci_error( $stmt );
1461 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1462
1463 return false;
1464 }
1465 } else {
1467 $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB );
1468 if ( $lob[$col] === false ) {
1469 $e = oci_error( $stmt );
1470 throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
1471 }
1472
1473 if ( is_object( $val ) ) {
1474 $val = $val->getData();
1475 }
1476
1477 if ( $col_type == 'BLOB' ) {
1478 $lob[$col]->writeTemporary( $val );
1479 oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, SQLT_BLOB );
1480 } else {
1481 $lob[$col]->writeTemporary( $val );
1482 oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
1483 }
1484 }
1485 }
1486
1487 MediaWiki\suppressWarnings();
1488
1489 if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
1490 $e = oci_error( $stmt );
1491 if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
1492 $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
1493
1494 return false;
1495 } else {
1496 $this->mAffectedRows = oci_num_rows( $stmt );
1497 }
1498 } else {
1499 $this->mAffectedRows = oci_num_rows( $stmt );
1500 }
1501
1502 MediaWiki\restoreWarnings();
1503
1504 if ( isset( $lob ) ) {
1505 foreach ( $lob as $lob_v ) {
1506 $lob_v->free();
1507 }
1508 }
1509
1510 if ( !$this->mTrxLevel ) {
1511 oci_commit( $this->mConn );
1512 }
1513
1514 return oci_free_statement( $stmt );
1515 }
1516
1517 function bitNot( $field ) {
1518 // expecting bit-fields smaller than 4bytes
1519 return 'BITNOT(' . $field . ')';
1520 }
1521
1522 function bitAnd( $fieldLeft, $fieldRight ) {
1523 return 'BITAND(' . $fieldLeft . ', ' . $fieldRight . ')';
1524 }
1525
1526 function bitOr( $fieldLeft, $fieldRight ) {
1527 return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
1528 }
1529
1530 function getDBname() {
1531 return $this->mDBname;
1532 }
1533
1534 function getServer() {
1535 return $this->mServer;
1536 }
1537
1538 public function buildGroupConcatField(
1539 $delim, $table, $field, $conds = '', $join_conds = []
1540 ) {
1541 $fld = "LISTAGG($field," . $this->addQuotes( $delim ) . ") WITHIN GROUP (ORDER BY $field)";
1542
1543 return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1544 }
1545
1546 public function getSearchEngine() {
1547 return 'SearchOracle';
1548 }
1549
1550 public function getInfinity() {
1551 return '31-12-2030 12:00:00.000000';
1552 }
1553}
serialize()
$wgDBprefix
Table name prefix.
$wgDBOracleDRCP
Set true to enable Oracle DCRP (supported from 11gR1 onward)
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
const TS_ORACLE
Oracle format time.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
return[ 'SiteStore'=> function(MediaWikiServices $services) { $loadBalancer=wfGetLB();$rawSiteStore=new DBSiteStore( $loadBalancer);$cache=wfGetCache(wfIsHHVM() ? CACHE_ACCEL :CACHE_ANYTHING);return new CachingSiteStore( $rawSiteStore, $cache);}, 'SiteLookup'=> function(MediaWikiServices $services) { return $services->getSiteStore();}, 'ConfigFactory'=> function(MediaWikiServices $services) { $registry=$services->getBootstrapConfig() ->get( 'ConfigRegistry');$factory=new ConfigFactory();foreach( $registry as $name=> $callback) { $factory->register( $name, $callback);} return $factory;}, 'MainConfig'=> function(MediaWikiServices $services) { return $services->getConfigFactory() ->makeConfig( 'main');}, 'StatsdDataFactory'=> function(MediaWikiServices $services) { return new BufferingStatsdDataFactory(rtrim( $services->getMainConfig() ->get( 'StatsdMetricPrefix'), '.'));}, 'EventRelayerGroup'=> function(MediaWikiServices $services) { return new EventRelayerGroup( $services->getMainConfig() ->get( 'EventRelayerConfig'));}, 'SearchEngineFactory'=> function(MediaWikiServices $services) { return new SearchEngineFactory( $services->getSearchEngineConfig());}, 'SearchEngineConfig'=> function(MediaWikiServices $services) { global $wgContLang;return new SearchEngineConfig( $services->getMainConfig(), $wgContLang);}, 'SkinFactory'=> function(MediaWikiServices $services) { return new SkinFactory();},]
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition Setup.php:35
$line
Definition cdb.php:59
Utility class.
resource $mConn
Database connection.
Definition Database.php:52
close()
Closes a database connection.
Definition Database.php:694
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition Database.php:416
setFlag( $flag)
Set a flag for this connection.
Definition Database.php:408
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Creates a new table with structure copied from existing table Note that unlike most database abstract...
doBegin( $fname=__METHOD__)
Issues the BEGIN command to the database server.
lastErrno()
Get the last error number.
addIdentifierQuotes( $s)
Quotes an identifier using backticks or "double quotes" depending on the database type.
bitAnd( $fieldLeft, $fieldRight)
wrapFieldForWhere( $table, &$col, &$val)
indexExists( $table, $index, $fname=__METHOD__)
Query whether a given index exists.
numFields( $res)
Get the number of fields in a result object.
sourceStream( $fp, $lineCallback=false, $resultCallback=false, $fname=__METHOD__, $inputCallback=false)
defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
indexInfo( $table, $index, $fname=__METHOD__)
Returns information about an index If errors are explicitly ignored, returns NULL on failure.
numRows( $res)
Get the number of rows in a result object.
bitOr( $fieldLeft, $fieldRight)
buildGroupConcatField( $delim, $table, $field, $conds='', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
fetchRow( $res)
Fetch the next row from the given result object, in associative array form.
selectDB( $db)
Change the current database.
doRollback( $fname=__METHOD__)
Issues the ROLLBACK command to the database server.
strencode( $s)
Wrapper for addslashes()
affectedRows()
Get the number of rows affected by the last write query.
insertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[])
INSERT SELECT wrapper.
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
makeSelectOptions( $options)
Returns an optional USE INDEX clause to go after the table, and a string to go at the end of the quer...
fieldInfoMulti( $table, $field)
Function translates mysql_fetch_field() functionality on ORACLE.
fieldInfo( $table, $field)
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited".
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
encodeBlob( $b)
Some DBMSs have a special format for inserting into blob fields, they don't allow simple quoted strin...
int $mAffectedRows
The number of rows affected as an integer.
cleanupTriggers()
Returns true if this database supports (and uses) triggers (e.g.
addQuotes( $s)
Adds quotes and backslashes.
doCommit( $fname=__METHOD__)
Issues the COMMIT command to the database server.
queryIgnore( $sql, $fname='')
getSequenceData( $table)
Return sequence_name if table has a sequence.
lastError()
Get a description of the last error.
getDBname()
Get the current DB name.
bool array $sequenceData
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects.
strictIPs()
Returns true if this database is strict about what can be put into an IP field.
insertOneRow( $table, $row, $fname)
string $defaultCharset
Character set for Oracle database.
__destruct()
Run a few simple sanity checks.
dropTable( $tableName, $fName=__METHOD__)
Delete a table.
implicitGroupby()
Returns true if this database does an implicit sort when doing GROUP BY.
insertId()
This must be called after nextSequenceVal.
closeConnection()
Closes a database connection, if it is open Returns success, true if already closed.
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.
realTimestamps()
Returns true if this database uses timestamps rather than integers.
getServer()
Get the server hostname or IP address.
wasDeadlock()
Determines if the last failure was due to a deadlock STUB.
tableExists( $table, $fname=__METHOD__)
Query whether a given table exists (in the given schema, or the default mw one if not given)
dataSeek( $res, $row)
getSearchEngine()
Get search engine class.
tableNameInternal( $name)
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
indexUnique( $table, $index, $fname=__METHOD__)
fieldBindStatement( $table, $col, &$val, $includeCol=false)
searchableIPs()
Returns true if this database can do a native search on IP columns e.g.
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
nextSequenceValue( $seqName)
Return the next in a sequence, save the value for retrieval via insertId()
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
doQuery( $sql)
The DBMS-dependent part of query()
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Single row SELECT wrapper.
wrapConditionsForWhere( $table, $conds, $parentCol=null)
open( $server, $user, $password, $dbName)
Usually aborts on failure.
fieldName( $stmt, $n)
Get a field name in a result object.
freeResult( $res)
Frees resources associated with the LOB descriptor.
__construct(array $p)
Constructor.
unionQueries( $sqls, $all)
Construct a UNION query This is used for providing overload point for other DB abstractions not compa...
aggregateValue( $valuedata, $valuename='value')
Return aggregated value function call.
cascadingDeletes()
Returns true if this database supports (and uses) cascading deletes.
isQuotedIdentifier( $s)
Returns if the given identifier looks quoted or not according to the database convention for quoting ...
getInfinity()
Find out when 'infinity' is.
MediaWiki exception.
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
Utility class.
tableName()
Name of table this field belongs to.
name()
Field name.
__construct( $info)
type()
Database type.
isNullable()
Whether this field can store NULL values.
The oci8 extension is fairly weak and doesn't support oci_num_rows, among other things.
__construct(&$db, $stmt, $unique=false)
array_unique_md( $array_in)
Result wrapper for grabbing data queried by someone else.
static isUtf8( $value)
Test whether a string is valid UTF-8.
$res
Definition database.txt:21
We use the convention $dbr for read and $dbw for write to help you keep track of whether the database object is a the world will explode Or to be a subsequent write query which succeeded on the master may fail when replicated to the slave due to a unique key collision Replication on the slave will stop and it may take hours to repair the database and get it back online Setting read_only in my cnf on the slave will avoid this but given the dire we prefer to have as many checks as possible We provide a but the wrapper functions like please read the documentation for except in special pages derived from QueryPage It s a common pitfall for new developers to submit code containing SQL queries which examine huge numbers of rows Remember that COUNT * is(N), counting rows in atable is like counting beans in a bucket.------------------------------------------------------------------------ Replication------------------------------------------------------------------------The largest installation of MediaWiki, Wikimedia, uses a large set ofslave MySQL servers replicating writes made to a master MySQL server. Itis important to understand the issues associated with this setup if youwant to write code destined for Wikipedia.It 's often the case that the best algorithm to use for a given taskdepends on whether or not replication is in use. Due to our unabashedWikipedia-centrism, we often just use the replication-friendly version, but if you like, you can use wfGetLB() ->getServerCount() > 1 tocheck to see if replication is in use.===Lag===Lag primarily occurs when large write queries are sent to the master.Writes on the master are executed in parallel, but they are executed inserial when they are replicated to the slaves. The master writes thequery to the binlog when the transaction is committed. The slaves pollthe binlog and start executing the query as soon as it appears. They canservice reads while they are performing a write query, but will not readanything more from the binlog and thus will perform no more writes. Thismeans that if the write query runs for a long time, the slaves will lagbehind the master for the time it takes for the write query to complete.Lag can be exacerbated by high read load. MediaWiki 's load balancer willstop sending reads to a slave when it is lagged by more than 30 seconds.If the load ratios are set incorrectly, or if there is too much loadgenerally, this may lead to a slave permanently hovering around 30seconds lag.If all slaves are lagged by more than 30 seconds, MediaWiki will stopwriting to the database. All edits and other write operations will berefused, with an error returned to the user. This gives the slaves achance to catch up. Before we had this mechanism, the slaves wouldregularly lag by several minutes, making review of recent editsdifficult.In addition to this, MediaWiki attempts to ensure that the user seesevents occurring on the wiki in chronological order. A few seconds of lagcan be tolerated, as long as the user sees a consistent picture fromsubsequent requests. This is done by saving the master binlog positionin the session, and then at the start of each request, waiting for theslave to catch up to that position before doing any reads from it. Ifthis wait times out, reads are allowed anyway, but the request isconsidered to be in "lagged slave mode". Lagged slave mode can bechecked by calling wfGetLB() ->getLaggedSlaveMode(). The onlypractical consequence at present is a warning displayed in the pagefooter.===Lag avoidance===To avoid excessive lag, queries which write large numbers of rows shouldbe split up, generally to write one row at a time. Multi-row INSERT ...SELECT queries are the worst offenders should be avoided altogether.Instead do the select first and then the insert.===Working with lag===Despite our best efforts, it 's not practical to guarantee a low-lagenvironment. Lag will usually be less than one second, but mayoccasionally be up to 30 seconds. For scalability, it 's very importantto keep load on the master low, so simply sending all your queries tothe master is not the answer. So when you have a genuine need forup-to-date data, the following approach is advised:1) Do a quick query to the master for a sequence number or timestamp 2) Run the full query on the slave and check if it matches the data you gotfrom the master 3) If it doesn 't, run the full query on the masterTo avoid swamping the master every time the slaves lag, use of thisapproach should be kept to a minimum. In most cases you should just readfrom the slave and let the user deal with the delay.------------------------------------------------------------------------ Lock contention------------------------------------------------------------------------Due to the high write rate on Wikipedia(and some other wikis), MediaWiki developers need to be very careful to structure their writesto avoid long-lasting locks. By default, MediaWiki opens a transactionat the first query, and commits it before the output is sent. Locks willbe held from the time when the query is done until the commit. So youcan reduce lock time by doing as much processing as possible before youdo your write queries.Often this approach is not good enough, and it becomes necessary toenclose small groups of queries in their own transaction. Use thefollowing syntax:$dbw=wfGetDB(DB_MASTER
For a write query
Definition database.txt:26
We use the convention $dbr for read and $dbw for write to help you keep track of whether the database object is a the world will explode Or to be a subsequent write query which succeeded on the master may fail when replicated to the slave due to a unique key collision Replication on the slave will stop and it may take hours to repair the database and get it back online Setting read_only in my cnf on the slave will avoid this but given the dire we prefer to have as many checks as possible We provide a but the wrapper functions like please read the documentation for tableName() and addQuotes(). You will need both of them. ------------------------------------------------------------------------ Basic query optimisation ------------------------------------------------------------------------ MediaWiki developers who need to write DB queries should have some understanding of databases and the performance issues associated with them. Patches containing unacceptably slow features will not be accepted. Unindexed queries are generally not welcome in MediaWiki
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition design.txt:57
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
const DBO_DDLMODE
Definition Defines.php:37
const DBO_SYSDBA
Definition Defines.php:36
const DBO_DEFAULT
Definition Defines.php:34
const DBO_PERSISTENT
Definition Defines.php:35
const LIST_AND
Definition Defines.php:194
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of or an object and a method hook function The function part of a third party developers and local administrators to define code that will be run at certain points in the mainline code
Definition hooks.txt:28
the array() calling protocol came about after MediaWiki 1.4rc1.
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:1999
in this case you re responsible for computing and outputting the entire conflict i e
Definition hooks.txt:1243
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:249
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition hooks.txt:1799
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1042
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition hooks.txt:1810
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition hooks.txt:1081
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a message
Definition hooks.txt:1927
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:314
processing should stop and the error should be shown to the user * false
Definition hooks.txt:189
returning false will NOT prevent logging $e
Definition hooks.txt:1940
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to and or sell copies of the and to permit persons to whom the Software is furnished to do subject to the following WITHOUT WARRANTY OF ANY EXPRESS OR INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DAMAGES OR OTHER WHETHER IN AN ACTION OF TORT OR ARISING FROM
Definition LICENSE.txt:24
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Base for all database-specific classes representing information about database fields.
#define the
table suitable for use with IDatabase::select()
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN boolean columns are always mapped to as the code does not always treat the column as a and VARBINARY columns should simply be TEXT The only exception is when VARBINARY is used to store true binary such as the math_inputhash column
Definition postgres.txt:44