MediaWiki REL1_33
DatabaseMssql.php
Go to the documentation of this file.
1<?php
28namespace Wikimedia\Rdbms;
29
31use Exception;
32use stdClass;
33
37class DatabaseMssql extends Database {
39 protected $serverPort;
41 protected $useWindowsAuth = false;
43 protected $lastInsertId = null;
45 protected $lastAffectedRowCount = null;
47 protected $subqueryId = 0;
49 protected $scrollableCursor = true;
51 protected $prepareStatements = true;
53 protected $binaryColumnCache = null;
55 protected $bitColumnCache = null;
57 protected $ignoreDupKeyErrors = false;
59 protected $ignoreErrors = [];
60
61 public function implicitGroupby() {
62 return false;
63 }
64
65 public function implicitOrderby() {
66 return false;
67 }
68
69 public function unionSupportsOrderAndLimit() {
70 return false;
71 }
72
73 public function __construct( array $params ) {
74 $this->serverPort = $params['port'];
75 $this->useWindowsAuth = $params['UseWindowsAuth'];
76
77 parent::__construct( $params );
78 }
79
80 protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
81 # Test for driver support, to avoid suppressed fatal error
82 if ( !function_exists( 'sqlsrv_connect' ) ) {
83 throw new DBConnectionError(
84 $this,
85 "Microsoft SQL Server Native (sqlsrv) functions missing.
86 You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
87 );
88 }
89
90 # e.g. the class is being loaded
91 if ( !strlen( $user ) ) {
92 return null;
93 }
94
95 $this->close();
96 $this->server = $server;
97 $this->user = $user;
98 $this->password = $password;
99
100 $connectionInfo = [];
101
102 if ( $dbName != '' ) {
103 $connectionInfo['Database'] = $dbName;
104 }
105
106 // Decide which auth scenerio to use
107 // if we are using Windows auth, then don't add credentials to $connectionInfo
108 if ( !$this->useWindowsAuth ) {
109 $connectionInfo['UID'] = $user;
110 $connectionInfo['PWD'] = $password;
111 }
112
113 Wikimedia\suppressWarnings();
114 $this->conn = sqlsrv_connect( $server, $connectionInfo );
115 Wikimedia\restoreWarnings();
116
117 if ( $this->conn === false ) {
118 throw new DBConnectionError( $this, $this->lastError() );
119 }
120
121 $this->opened = true;
122 $this->currentDomain = new DatabaseDomain(
123 ( $dbName != '' ) ? $dbName : null,
124 null,
125 $tablePrefix
126 );
127
128 return (bool)$this->conn;
129 }
130
136 protected function closeConnection() {
137 return sqlsrv_close( $this->conn );
138 }
139
144 protected function resultObject( $result ) {
145 if ( !$result ) {
146 return false;
147 } elseif ( $result instanceof MssqlResultWrapper ) {
148 return $result;
149 } elseif ( $result === true ) {
150 // Successful write query
151 return $result;
152 } else {
153 return new MssqlResultWrapper( $this, $result );
154 }
155 }
156
161 protected function doQuery( $sql ) {
162 // several extensions seem to think that all databases support limits
163 // via LIMIT N after the WHERE clause, but MSSQL uses SELECT TOP N,
164 // so to catch any of those extensions we'll do a quick check for a
165 // LIMIT clause and pass $sql through $this->LimitToTopN() which parses
166 // the LIMIT clause and passes the result to $this->limitResult();
167 if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
168 // massage LIMIT -> TopN
169 $sql = $this->LimitToTopN( $sql );
170 }
171
172 // MSSQL doesn't have EXTRACT(epoch FROM XXX)
173 if ( preg_match( '#\bEXTRACT\s*?\‍(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
174 // This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970
175 $sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
176 }
177
178 // perform query
179
180 // SQLSRV_CURSOR_STATIC is slower than SQLSRV_CURSOR_CLIENT_BUFFERED (one of the two is
181 // needed if we want to be able to seek around the result set), however CLIENT_BUFFERED
182 // has a bug in the sqlsrv driver where wchar_t types (such as nvarchar) that are empty
183 // strings make php throw a fatal error "Severe error translating Unicode"
184 if ( $this->scrollableCursor ) {
185 $scrollArr = [ 'Scrollable' => SQLSRV_CURSOR_STATIC ];
186 } else {
187 $scrollArr = [];
188 }
189
190 if ( $this->prepareStatements ) {
191 // we do prepare + execute so we can get its field metadata for later usage if desired
192 $stmt = sqlsrv_prepare( $this->conn, $sql, [], $scrollArr );
193 $success = sqlsrv_execute( $stmt );
194 } else {
195 $stmt = sqlsrv_query( $this->conn, $sql, [], $scrollArr );
196 $success = (bool)$stmt;
197 }
198
199 // Make a copy to ensure what we add below does not get reflected in future queries
201
202 if ( $this->ignoreDupKeyErrors ) {
203 // ignore duplicate key errors
204 // this emulates INSERT IGNORE in MySQL
205 $ignoreErrors[] = '2601'; // duplicate key error caused by unique index
206 $ignoreErrors[] = '2627'; // duplicate key error caused by primary key
207 $ignoreErrors[] = '3621'; // generic "the statement has been terminated" error
208 }
209
210 if ( $success === false ) {
211 $errors = sqlsrv_errors();
212 $success = true;
213
214 foreach ( $errors as $err ) {
215 if ( !in_array( $err['code'], $ignoreErrors ) ) {
216 $success = false;
217 break;
218 }
219 }
220
221 if ( $success === false ) {
222 return false;
223 }
224 }
225 // remember number of rows affected
226 $this->lastAffectedRowCount = sqlsrv_rows_affected( $stmt );
227
228 return $stmt;
229 }
230
231 public function freeResult( $res ) {
232 if ( $res instanceof ResultWrapper ) {
233 $res = $res->result;
234 }
235
236 sqlsrv_free_stmt( $res );
237 }
238
243 public function fetchObject( $res ) {
244 // $res is expected to be an instance of MssqlResultWrapper here
245 return $res->fetchObject();
246 }
247
252 public function fetchRow( $res ) {
253 return $res->fetchRow();
254 }
255
260 public function numRows( $res ) {
261 if ( $res instanceof ResultWrapper ) {
262 $res = $res->result;
263 }
264
265 $ret = sqlsrv_num_rows( $res );
266
267 if ( $ret === false ) {
268 // we cannot get an amount of rows from this cursor type
269 // has_rows returns bool true/false if the result has rows
270 $ret = (int)sqlsrv_has_rows( $res );
271 }
272
273 return $ret;
274 }
275
280 public function numFields( $res ) {
281 if ( $res instanceof ResultWrapper ) {
282 $res = $res->result;
283 }
284
285 return sqlsrv_num_fields( $res );
286 }
287
293 public function fieldName( $res, $n ) {
294 if ( $res instanceof ResultWrapper ) {
295 $res = $res->result;
296 }
297
298 return sqlsrv_field_metadata( $res )[$n]['Name'];
299 }
300
305 public function insertId() {
306 return $this->lastInsertId;
307 }
308
314 public function dataSeek( $res, $row ) {
315 return $res->seek( $row );
316 }
317
321 public function lastError() {
322 $strRet = '';
323 $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
324 if ( $retErrors != null ) {
325 foreach ( $retErrors as $arrError ) {
326 $strRet .= $this->formatError( $arrError ) . "\n";
327 }
328 } else {
329 $strRet = "No errors found";
330 }
331
332 return $strRet;
333 }
334
339 private function formatError( $err ) {
340 return '[SQLSTATE ' .
341 $err['SQLSTATE'] . '][Error Code ' . $err['code'] . ']' . $err['message'];
342 }
343
347 public function lastErrno() {
348 $err = sqlsrv_errors( SQLSRV_ERR_ALL );
349 if ( $err !== null && isset( $err[0] ) ) {
350 return $err[0]['code'];
351 } else {
352 return 0;
353 }
354 }
355
356 protected function wasKnownStatementRollbackError() {
357 $errors = sqlsrv_errors( SQLSRV_ERR_ALL );
358 if ( !$errors ) {
359 return false;
360 }
361 // The transaction vs statement rollback behavior depends on XACT_ABORT, so make sure
362 // that the "statement has been terminated" error (3621) is specifically present.
363 // https://docs.microsoft.com/en-us/sql/t-sql/statements/set-xact-abort-transact-sql
364 $statementOnly = false;
365 $codeWhitelist = [ '2601', '2627', '547' ];
366 foreach ( $errors as $error ) {
367 if ( $error['code'] == '3621' ) {
368 $statementOnly = true;
369 } elseif ( !in_array( $error['code'], $codeWhitelist ) ) {
370 $statementOnly = false;
371 break;
372 }
373 }
374
375 return $statementOnly;
376 }
377
381 protected function fetchAffectedRowCount() {
383 }
384
403 public function select( $table, $vars, $conds = '', $fname = __METHOD__,
404 $options = [], $join_conds = []
405 ) {
406 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
407 if ( isset( $options['EXPLAIN'] ) ) {
408 try {
409 $this->scrollableCursor = false;
410 $this->prepareStatements = false;
411 $this->query( "SET SHOWPLAN_ALL ON" );
412 $ret = $this->query( $sql, $fname );
413 $this->query( "SET SHOWPLAN_ALL OFF" );
414 } catch ( DBQueryError $dqe ) {
415 if ( isset( $options['FOR COUNT'] ) ) {
416 // likely don't have privs for SHOWPLAN, so run a select count instead
417 $this->query( "SET SHOWPLAN_ALL OFF" );
418 unset( $options['EXPLAIN'] );
419 $ret = $this->select(
420 $table,
421 'COUNT(*) AS EstimateRows',
422 $conds,
423 $fname,
424 $options,
425 $join_conds
426 );
427 } else {
428 // someone actually wanted the query plan instead of an est row count
429 // let them know of the error
430 $this->scrollableCursor = true;
431 $this->prepareStatements = true;
432 throw $dqe;
433 }
434 }
435 $this->scrollableCursor = true;
436 $this->prepareStatements = true;
437 return $ret;
438 }
439 return $this->query( $sql, $fname );
440 }
441
455 public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
456 $options = [], $join_conds = []
457 ) {
458 if ( isset( $options['EXPLAIN'] ) ) {
459 unset( $options['EXPLAIN'] );
460 }
461
462 $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
463
464 // try to rewrite aggregations of bit columns (currently MAX and MIN)
465 if ( strpos( $sql, 'MAX(' ) !== false || strpos( $sql, 'MIN(' ) !== false ) {
466 $bitColumns = [];
467 if ( is_array( $table ) ) {
468 $tables = $table;
469 while ( $tables ) {
470 $t = array_pop( $tables );
471 if ( is_array( $t ) ) {
472 $tables = array_merge( $tables, $t );
473 } else {
474 $bitColumns += $this->getBitColumns( $this->tableName( $t ) );
475 }
476 }
477 } else {
478 $bitColumns = $this->getBitColumns( $this->tableName( $table ) );
479 }
480
481 foreach ( $bitColumns as $col => $info ) {
482 $replace = [
483 "MAX({$col})" => "MAX(CAST({$col} AS tinyint))",
484 "MIN({$col})" => "MIN(CAST({$col} AS tinyint))",
485 ];
486 $sql = str_replace( array_keys( $replace ), array_values( $replace ), $sql );
487 }
488 }
489
490 return $sql;
491 }
492
493 public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
494 $fname = __METHOD__
495 ) {
496 $this->scrollableCursor = false;
497 try {
498 parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname );
499 } catch ( Exception $e ) {
500 $this->scrollableCursor = true;
501 throw $e;
502 }
503 $this->scrollableCursor = true;
504 }
505
506 public function delete( $table, $conds, $fname = __METHOD__ ) {
507 $this->scrollableCursor = false;
508 try {
509 parent::delete( $table, $conds, $fname );
510 } catch ( Exception $e ) {
511 $this->scrollableCursor = true;
512 throw $e;
513 }
514 $this->scrollableCursor = true;
515
516 return true;
517 }
518
533 public function estimateRowCount( $table, $var = '*', $conds = '',
534 $fname = __METHOD__, $options = [], $join_conds = []
535 ) {
536 $conds = $this->normalizeConditions( $conds, $fname );
537 $column = $this->extractSingleFieldFromList( $var );
538 if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
539 $conds[] = "$column IS NOT NULL";
540 }
541
542 // http://msdn2.microsoft.com/en-us/library/aa259203.aspx
543 $options['EXPLAIN'] = true;
544 $options['FOR COUNT'] = true;
545 $res = $this->select( $table, $var, $conds, $fname, $options, $join_conds );
546
547 $rows = -1;
548 if ( $res ) {
549 $row = $this->fetchRow( $res );
550
551 if ( isset( $row['EstimateRows'] ) ) {
552 $rows = (int)$row['EstimateRows'];
553 }
554 }
555
556 return $rows;
557 }
558
567 public function indexInfo( $table, $index, $fname = __METHOD__ ) {
568 # This does not return the same info as MYSQL would, but that's OK
569 # because MediaWiki never uses the returned value except to check for
570 # the existence of indexes.
571 $sql = "sp_helpindex '" . $this->tableName( $table ) . "'";
572 $res = $this->query( $sql, $fname );
573
574 if ( !$res ) {
575 return null;
576 }
577
578 $result = [];
579 foreach ( $res as $row ) {
580 if ( $row->index_name == $index ) {
581 $row->Non_unique = !stristr( $row->index_description, "unique" );
582 $cols = explode( ", ", $row->index_keys );
583 foreach ( $cols as $col ) {
584 $row->Column_name = trim( $col );
585 $result[] = clone $row;
586 }
587 } elseif ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) {
588 $row->Non_unique = 0;
589 $cols = explode( ", ", $row->index_keys );
590 foreach ( $cols as $col ) {
591 $row->Column_name = trim( $col );
592 $result[] = clone $row;
593 }
594 }
595 }
596
597 return $result ?: false;
598 }
599
615 public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = [] ) {
616 # No rows to insert, easy just return now
617 if ( !count( $arrToInsert ) ) {
618 return true;
619 }
620
621 if ( !is_array( $options ) ) {
622 $options = [ $options ];
623 }
624
625 $table = $this->tableName( $table );
626
627 if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) { // Not multi row
628 $arrToInsert = [ 0 => $arrToInsert ]; // make everything multi row compatible
629 }
630
631 // We know the table we're inserting into, get its identity column
632 $identity = null;
633 // strip matching square brackets and the db/schema from table name
634 $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
635 $tableRaw = array_pop( $tableRawArr );
636 $res = $this->doQuery(
637 "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
638 "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
639 );
640 if ( $res && sqlsrv_has_rows( $res ) ) {
641 // There is an identity for this table.
642 $identityArr = sqlsrv_fetch_array( $res, SQLSRV_FETCH_ASSOC );
643 $identity = array_pop( $identityArr );
644 }
645 sqlsrv_free_stmt( $res );
646
647 // Determine binary/varbinary fields so we can encode data as a hex string like 0xABCDEF
648 $binaryColumns = $this->getBinaryColumns( $table );
649
650 // INSERT IGNORE is not supported by SQL Server
651 // remove IGNORE from options list and set ignore flag to true
652 if ( in_array( 'IGNORE', $options ) ) {
653 $options = array_diff( $options, [ 'IGNORE' ] );
654 $this->ignoreDupKeyErrors = true;
655 }
656
657 $ret = null;
658 foreach ( $arrToInsert as $a ) {
659 // start out with empty identity column, this is so we can return
660 // it as a result of the INSERT logic
661 $sqlPre = '';
662 $sqlPost = '';
663 $identityClause = '';
664
665 // if we have an identity column
666 if ( $identity ) {
667 // iterate through
668 foreach ( $a as $k => $v ) {
669 if ( $k == $identity ) {
670 if ( !is_null( $v ) ) {
671 // there is a value being passed to us,
672 // we need to turn on and off inserted identity
673 $sqlPre = "SET IDENTITY_INSERT $table ON;";
674 $sqlPost = ";SET IDENTITY_INSERT $table OFF;";
675 } else {
676 // we can't insert NULL into an identity column,
677 // so remove the column from the insert.
678 unset( $a[$k] );
679 }
680 }
681 }
682
683 // we want to output an identity column as result
684 $identityClause = "OUTPUT INSERTED.$identity ";
685 }
686
687 $keys = array_keys( $a );
688
689 // Build the actual query
690 $sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
691 " INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
692
693 $first = true;
694 foreach ( $a as $key => $value ) {
695 if ( isset( $binaryColumns[$key] ) ) {
696 $value = new MssqlBlob( $value );
697 }
698 if ( $first ) {
699 $first = false;
700 } else {
701 $sql .= ',';
702 }
703 if ( is_null( $value ) ) {
704 $sql .= 'null';
705 } else {
706 $sql .= $this->addQuotes( $value );
707 }
708 }
709 $sql .= ')' . $sqlPost;
710
711 // Run the query
712 $this->scrollableCursor = false;
713 try {
714 $ret = $this->query( $sql );
715 } catch ( Exception $e ) {
716 $this->scrollableCursor = true;
717 $this->ignoreDupKeyErrors = false;
718 throw $e;
719 }
720 $this->scrollableCursor = true;
721
722 if ( $ret instanceof ResultWrapper && !is_null( $identity ) ) {
723 // Then we want to get the identity column value we were assigned and save it off
724 $row = $ret->fetchObject();
725 if ( is_object( $row ) ) {
726 $this->lastInsertId = $row->$identity;
727 // It seems that mAffectedRows is -1 sometimes when OUTPUT INSERTED.identity is
728 // used if we got an identity back, we know for sure a row was affected, so
729 // adjust that here
730 if ( $this->lastAffectedRowCount == -1 ) {
731 $this->lastAffectedRowCount = 1;
732 }
733 }
734 }
735 }
736
737 $this->ignoreDupKeyErrors = false;
738
739 return true;
740 }
741
757 protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
758 $insertOptions = [], $selectOptions = [], $selectJoinConds = []
759 ) {
760 $this->scrollableCursor = false;
761 try {
762 parent::nativeInsertSelect(
763 $destTable,
764 $srcTable,
765 $varMap,
766 $conds,
767 $fname,
768 $insertOptions,
769 $selectOptions,
770 $selectJoinConds
771 );
772 } catch ( Exception $e ) {
773 $this->scrollableCursor = true;
774 throw $e;
775 }
776 $this->scrollableCursor = true;
777 }
778
804 function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
805 $table = $this->tableName( $table );
806 $binaryColumns = $this->getBinaryColumns( $table );
807
808 $opts = $this->makeUpdateOptions( $options );
809 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET, $binaryColumns );
810
811 if ( $conds !== [] && $conds !== '*' ) {
812 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND, $binaryColumns );
813 }
814
815 $this->scrollableCursor = false;
816 try {
817 $this->query( $sql );
818 } catch ( Exception $e ) {
819 $this->scrollableCursor = true;
820 throw $e;
821 }
822 $this->scrollableCursor = true;
823 return true;
824 }
825
842 public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = [] ) {
843 if ( !is_array( $a ) ) {
844 throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
845 }
846
847 if ( $mode != LIST_NAMES ) {
848 // In MS SQL, values need to be specially encoded when they are
849 // inserted into binary fields. Perform this necessary encoding
850 // for the specified set of columns.
851 foreach ( array_keys( $a ) as $field ) {
852 if ( !isset( $binaryColumns[$field] ) ) {
853 continue;
854 }
855
856 if ( is_array( $a[$field] ) ) {
857 foreach ( $a[$field] as &$v ) {
858 $v = new MssqlBlob( $v );
859 }
860 unset( $v );
861 } else {
862 $a[$field] = new MssqlBlob( $a[$field] );
863 }
864 }
865 }
866
867 return parent::makeList( $a, $mode );
868 }
869
875 public function textFieldSize( $table, $field ) {
876 $table = $this->tableName( $table );
877 $sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
878 WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
879 $res = $this->query( $sql );
880 $row = $this->fetchRow( $res );
881 $size = -1;
882 if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) {
883 $size = $row['CHARACTER_MAXIMUM_LENGTH'];
884 }
885
886 return $size;
887 }
888
899 public function limitResult( $sql, $limit, $offset = false ) {
900 if ( $offset === false || $offset == 0 ) {
901 if ( strpos( $sql, "SELECT" ) === false ) {
902 return "TOP {$limit} " . $sql;
903 } else {
904 return preg_replace( '/\bSELECT(\s+DISTINCT)?\b/Dsi',
905 'SELECT$1 TOP ' . $limit, $sql, 1 );
906 }
907 } else {
908 // This one is fun, we need to pull out the select list as well as any ORDER BY clause
909 $select = $orderby = [];
910 $s1 = preg_match( '#SELECT\s+(.+?)\s+FROM#Dis', $sql, $select );
911 $s2 = preg_match( '#(ORDER BY\s+.+?)(\s*FOR XML .*)?$#Dis', $sql, $orderby );
912 $postOrder = '';
913 $first = $offset + 1;
914 $last = $offset + $limit;
915 $sub1 = 'sub_' . $this->subqueryId;
916 $sub2 = 'sub_' . ( $this->subqueryId + 1 );
917 $this->subqueryId += 2;
918 if ( !$s1 ) {
919 // wat
920 throw new DBUnexpectedError( $this, "Attempting to LIMIT a non-SELECT query\n" );
921 }
922 if ( !$s2 ) {
923 // no ORDER BY
924 $overOrder = 'ORDER BY (SELECT 1)';
925 } else {
926 if ( !isset( $orderby[2] ) || !$orderby[2] ) {
927 // don't need to strip it out if we're using a FOR XML clause
928 $sql = str_replace( $orderby[1], '', $sql );
929 }
930 $overOrder = $orderby[1];
931 $postOrder = ' ' . $overOrder;
932 }
933 $sql = "SELECT {$select[1]}
934 FROM (
935 SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
936 FROM ({$sql}) {$sub1}
937 ) {$sub2}
938 WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
939
940 return $sql;
941 }
942 }
943
954 public function LimitToTopN( $sql ) {
955 // Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
956 $pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
957 if ( preg_match( $pattern, $sql, $matches ) ) {
958 $row_count = $matches[4];
959 $offset = $matches[3] ?: $matches[6] ?: false;
960
961 // strip the matching LIMIT clause out
962 $sql = str_replace( $matches[0], '', $sql );
963
964 return $this->limitResult( $sql, $row_count, $offset );
965 }
966
967 return $sql;
968 }
969
973 public function getSoftwareLink() {
974 return "[{{int:version-db-mssql-url}} MS SQL Server]";
975 }
976
980 public function getServerVersion() {
981 $server_info = sqlsrv_server_info( $this->conn );
982 $version = $server_info['SQLServerVersion'] ?? 'Error';
983
984 return $version;
985 }
986
992 public function tableExists( $table, $fname = __METHOD__ ) {
993 list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
994
995 if ( $db !== false ) {
996 // remote database
997 $this->queryLogger->error( "Attempting to call tableExists on a remote table" );
998 return false;
999 }
1000
1001 if ( $schema === false ) {
1002 $schema = $this->dbSchema();
1003 }
1004
1005 $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
1006 WHERE TABLE_TYPE = 'BASE TABLE'
1007 AND TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table'" );
1008
1009 if ( $res->numRows() ) {
1010 return true;
1011 } else {
1012 return false;
1013 }
1014 }
1015
1023 public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1024 list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
1025
1026 if ( $db !== false ) {
1027 // remote database
1028 $this->queryLogger->error( "Attempting to call fieldExists on a remote table" );
1029 return false;
1030 }
1031
1032 $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
1033 WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1034
1035 if ( $res->numRows() ) {
1036 return true;
1037 } else {
1038 return false;
1039 }
1040 }
1041
1042 public function fieldInfo( $table, $field ) {
1043 list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
1044
1045 if ( $db !== false ) {
1046 // remote database
1047 $this->queryLogger->error( "Attempting to call fieldInfo on a remote table" );
1048 return false;
1049 }
1050
1051 $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS
1052 WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1053
1054 $meta = $res->fetchRow();
1055 if ( $meta ) {
1056 return new MssqlField( $meta );
1057 }
1058
1059 return false;
1060 }
1061
1062 protected function doSavepoint( $identifier, $fname ) {
1063 $this->query( 'SAVE TRANSACTION ' . $this->addIdentifierQuotes( $identifier ), $fname );
1064 }
1065
1066 protected function doReleaseSavepoint( $identifier, $fname ) {
1067 // Not supported. Also not really needed, a new doSavepoint() for the
1068 // same identifier will overwrite the old.
1069 }
1070
1071 protected function doRollbackToSavepoint( $identifier, $fname ) {
1072 $this->query( 'ROLLBACK TRANSACTION ' . $this->addIdentifierQuotes( $identifier ), $fname );
1073 }
1074
1079 protected function doBegin( $fname = __METHOD__ ) {
1080 sqlsrv_begin_transaction( $this->conn );
1081 $this->trxLevel = 1;
1082 }
1083
1088 protected function doCommit( $fname = __METHOD__ ) {
1089 sqlsrv_commit( $this->conn );
1090 $this->trxLevel = 0;
1091 }
1092
1098 protected function doRollback( $fname = __METHOD__ ) {
1099 sqlsrv_rollback( $this->conn );
1100 $this->trxLevel = 0;
1101 }
1102
1107 public function strencode( $s ) {
1108 // Should not be called by us
1109 return str_replace( "'", "''", $s );
1110 }
1111
1116 public function addQuotes( $s ) {
1117 if ( $s instanceof MssqlBlob ) {
1118 return $s->fetch();
1119 } elseif ( $s instanceof Blob ) {
1120 // this shouldn't really ever be called, but it's here if needed
1121 // (and will quite possibly make the SQL error out)
1122 $blob = new MssqlBlob( $s->fetch() );
1123 return $blob->fetch();
1124 } else {
1125 if ( is_bool( $s ) ) {
1126 $s = $s ? 1 : 0;
1127 }
1128 return parent::addQuotes( $s );
1129 }
1130 }
1131
1136 public function addIdentifierQuotes( $s ) {
1137 // http://msdn.microsoft.com/en-us/library/aa223962.aspx
1138 return '[' . $s . ']';
1139 }
1140
1145 public function isQuotedIdentifier( $name ) {
1146 return strlen( $name ) && $name[0] == '[' && substr( $name, -1, 1 ) == ']';
1147 }
1148
1156 protected function escapeLikeInternal( $s, $escapeChar = '`' ) {
1157 return str_replace( [ $escapeChar, '%', '_', '[', ']', '^' ],
1158 [ "{$escapeChar}{$escapeChar}", "{$escapeChar}%", "{$escapeChar}_",
1159 "{$escapeChar}[", "{$escapeChar}]", "{$escapeChar}^" ],
1160 $s );
1161 }
1162
1163 protected function doSelectDomain( DatabaseDomain $domain ) {
1164 if ( $domain->getSchema() !== null ) {
1165 throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
1166 }
1167
1168 $database = $domain->getDatabase();
1169 if ( $database !== $this->getDBname() ) {
1170 $encDatabase = $this->addIdentifierQuotes( $database );
1171 $res = $this->doQuery( "USE $encDatabase" );
1172 if ( !$res ) {
1173 throw new DBExpectedError( $this, "Could not select database '$database'." );
1174 }
1175 }
1176 // Update that domain fields on success (no exception thrown)
1177 $this->currentDomain = $domain;
1178
1179 return true;
1180 }
1181
1187 public function makeSelectOptions( $options ) {
1188 $tailOpts = '';
1189 $startOpts = '';
1190
1191 $noKeyOptions = [];
1192 foreach ( $options as $key => $option ) {
1193 if ( is_numeric( $key ) ) {
1194 $noKeyOptions[$option] = true;
1195 }
1196 }
1197
1198 $tailOpts .= $this->makeGroupByWithHaving( $options );
1199
1200 $tailOpts .= $this->makeOrderBy( $options );
1201
1202 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1203 $startOpts .= 'DISTINCT';
1204 }
1205
1206 if ( isset( $noKeyOptions['FOR XML'] ) ) {
1207 // used in group concat field emulation
1208 $tailOpts .= " FOR XML PATH('')";
1209 }
1210
1211 // we want this to be compatible with the output of parent::makeSelectOptions()
1212 return [ $startOpts, '', $tailOpts, '', '' ];
1213 }
1214
1215 public function getType() {
1216 return 'mssql';
1217 }
1218
1223 public function buildConcat( $stringList ) {
1224 return implode( ' + ', $stringList );
1225 }
1226
1244 public function buildGroupConcatField( $delim, $table, $field, $conds = '',
1245 $join_conds = []
1246 ) {
1247 $gcsq = 'gcsq_' . $this->subqueryId;
1248 $this->subqueryId++;
1249
1250 $delimLen = strlen( $delim );
1251 $fld = "{$field} + {$this->addQuotes( $delim )}";
1252 $sql = "(SELECT LEFT({$field}, LEN({$field}) - {$delimLen}) FROM ("
1253 . $this->selectSQLText( $table, $fld, $conds, null, [ 'FOR XML' ], $join_conds )
1254 . ") {$gcsq} ({$field}))";
1255
1256 return $sql;
1257 }
1258
1259 public function buildSubstring( $input, $startPosition, $length = null ) {
1260 $this->assertBuildSubstringParams( $startPosition, $length );
1261 if ( $length === null ) {
1267 $length = 2147483647;
1268 }
1269 return 'SUBSTRING(' . implode( ',', [ $input, $startPosition, $length ] ) . ')';
1270 }
1271
1278 private function getBinaryColumns( $table ) {
1279 $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
1280 $tableRaw = array_pop( $tableRawArr );
1281
1282 if ( $this->binaryColumnCache === null ) {
1283 $this->populateColumnCaches();
1284 }
1285
1286 return $this->binaryColumnCache[$tableRaw] ?? [];
1287 }
1288
1293 private function getBitColumns( $table ) {
1294 $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
1295 $tableRaw = array_pop( $tableRawArr );
1296
1297 if ( $this->bitColumnCache === null ) {
1298 $this->populateColumnCaches();
1299 }
1300
1301 return $this->bitColumnCache[$tableRaw] ?? [];
1302 }
1303
1304 private function populateColumnCaches() {
1305 $res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
1306 [
1307 'TABLE_CATALOG' => $this->getDBname(),
1308 'TABLE_SCHEMA' => $this->dbSchema(),
1309 'DATA_TYPE' => [ 'varbinary', 'binary', 'image', 'bit' ]
1310 ] );
1311
1312 $this->binaryColumnCache = [];
1313 $this->bitColumnCache = [];
1314 foreach ( $res as $row ) {
1315 if ( $row->DATA_TYPE == 'bit' ) {
1316 $this->bitColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
1317 } else {
1318 $this->binaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
1319 }
1320 }
1321 }
1322
1329 function tableName( $name, $format = 'quoted' ) {
1330 # Replace reserved words with better ones
1331 switch ( $name ) {
1332 case 'user':
1333 return $this->realTableName( 'mwuser', $format );
1334 default:
1335 return $this->realTableName( $name, $format );
1336 }
1337 }
1338
1347 function realTableName( $name, $format = 'quoted' ) {
1348 $table = parent::tableName( $name, $format );
1349 if ( $format == 'split' ) {
1350 // Used internally, we want the schema split off from the table name and returned
1351 // as a list with 3 elements (database, schema, table)
1352 return array_pad( explode( '.', $table, 3 ), -3, false );
1353 }
1354 return $table;
1355 }
1356
1364 public function dropTable( $tableName, $fName = __METHOD__ ) {
1365 if ( !$this->tableExists( $tableName, $fName ) ) {
1366 return false;
1367 }
1368
1369 // parent function incorrectly appends CASCADE, which we don't want
1370 $sql = "DROP TABLE " . $this->tableName( $tableName );
1371
1372 return $this->query( $sql, $fName );
1373 }
1374
1381 public function prepareStatements( $value = null ) {
1383 if ( $value !== null ) {
1384 $this->prepareStatements = $value;
1385 }
1386
1387 return $old;
1388 }
1389
1396 public function scrollableCursor( $value = null ) {
1398 if ( $value !== null ) {
1399 $this->scrollableCursor = $value;
1400 }
1401
1402 return $old;
1403 }
1404
1405 public function buildStringCast( $field ) {
1406 return "CAST( $field AS NVARCHAR )";
1407 }
1408
1409 public static function getAttributes() {
1410 return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS => true ];
1411 }
1412}
1413
1417class_alias( DatabaseMssql::class, 'DatabaseMssql' );
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition Setup.php:123
Base class for the more common types of database errors.
Class to handle database/prefix specification for IDatabase domains.
insert( $table, $arrToInsert, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
buildSubstring( $input, $startPosition, $length=null)
escapeLikeInternal( $s, $escapeChar='`')
MS SQL supports more pattern operators than other databases (ex: [,],^)
closeConnection()
Closes a database connection, if it is open Returns success, true if already closed.
doCommit( $fname=__METHOD__)
End a transaction.
fieldInfo( $table, $field)
mysql_fetch_field() wrapper Returns false if the field doesn't exist
doSavepoint( $identifier, $fname)
Create a savepoint.
prepareStatements( $value=null)
Called in the installer and updater.
insertId()
This must be called after nextSequenceVal.
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
estimateRowCount( $table, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Estimate rows in dataset Returns estimated count, based on SHOWPLAN_ALL output This is not necessaril...
LimitToTopN( $sql)
If there is a limit clause, parse it, strip it, and pass the remaining SQL through limitResult() with...
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
SELECT wrapper.
realTableName( $name, $format='quoted')
call this instead of tableName() in the updater when renaming tables
buildGroupConcatField( $delim, $table, $field, $conds='', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[], $selectJoinConds=[])
INSERT SELECT wrapper $varMap must be an associative array of the form [ 'dest1' => 'source1',...
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset This is used for query pages.
dropTable( $tableName, $fName=__METHOD__)
Delete a table.
doRollbackToSavepoint( $identifier, $fname)
Rollback to a savepoint.
freeResult( $res)
Free a result object returned by query() or select().
fieldExists( $table, $field, $fname=__METHOD__)
Query whether a given column exists in the mediawiki schema.
makeList( $a, $mode=LIST_COMMA, $binaryColumns=[])
Makes an encoded list of strings from an array.
indexInfo( $table, $index, $fname=__METHOD__)
Returns information about an index If errors are explicitly ignored, returns NULL on failure.
tableName( $name, $format='quoted')
scrollableCursor( $value=null)
Called in the installer and updater.
open( $server, $user, $password, $dbName, $schema, $tablePrefix)
Open a new connection to the database (closing any existing one)
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
SELECT wrapper.
doRollback( $fname=__METHOD__)
Rollback a transaction.
doBegin( $fname=__METHOD__)
Begin a transaction, committing any previously open transaction.
stdClass[][] null $bitColumnCache
unionSupportsOrderAndLimit()
Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries within th...
tableExists( $table, $fname=__METHOD__)
deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
implicitGroupby()
Returns true if this database does an implicit sort when doing GROUP BY.
doReleaseSavepoint( $identifier, $fname)
Release a savepoint.
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
stdClass[][] null $binaryColumnCache
doSelectDomain(DatabaseDomain $domain)
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
getBinaryColumns( $table)
Returns an associative array for fields that are of type varbinary, binary, or image $table can be ei...
Relational database abstraction object.
Definition Database.php:49
makeUpdateOptions( $options)
Make UPDATE options for the Database::update function.
object resource null $conn
Database connection.
Definition Database.php:109
dbSchema( $schema=null)
Get/set the db schema.
Definition Database.php:620
string $user
User that this instance is currently connected under the name of.
Definition Database.php:84
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
trxLevel()
Gets the current transaction level.
Definition Database.php:591
assertBuildSubstringParams( $startPosition, $length)
Check type and bounds for parameters to self::buildSubstring()
string $password
Password used to establish the current connection.
Definition Database.php:86
string $server
Server that this instance is currently connected to.
Definition Database.php:82
normalizeConditions( $conds, $fname)
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query and return the result.
makeOrderBy( $options)
Returns an optional ORDER BY.
getDBname()
Get the current DB name.
close()
Close the database connection.
Definition Database.php:943
Result wrapper for grabbing data queried from an IDatabase object.
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 select() and insert() are usually more convenient. They take care of things like table prefixes and escaping for you. If you really need to make your own SQL
$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 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 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
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 and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of and they certainly aren t ideal for someone who s installing MediaWiki as MediaWiki does not conform to normal Unix filesystem layout Hopefully we ll offer direct support for standard layouts in the but for now *any change to the location of files is unsupported *Moving things and leaving symlinks will *probably *not break but it is *strongly *advised not to try any more intrusive changes to get MediaWiki to conform more closely to your filesystem hierarchy Any such attempt will almost certainly result in unnecessary bugs The standard recommended location to install relative to the web is it should be possible to enable the appropriate rewrite rules by if you can reconfigure the web server
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 and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same user
Wikitext formatted, in the key only.
const LIST_NAMES
Definition Defines.php:54
const LIST_COMMA
Definition Defines.php:51
const LIST_SET
Definition Defines.php:53
const LIST_AND
Definition Defines.php:52
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2228
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition hooks.txt:2818
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. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. '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 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name '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. '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 '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:Array with elements of the form "language:title" in the order that they will be output. & $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':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. 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:1991
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 & $options
Definition hooks.txt:1999
this hook is for auditing only RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition hooks.txt:996
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 true
Definition hooks.txt:2004
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:2003
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:271
returning false will NOT prevent logging $e
Definition hooks.txt:2175
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
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...
if(is_array($mode)) switch( $mode) $input
$last
$params