74 $this->serverPort =
$params[
'port'];
75 $this->useWindowsAuth =
$params[
'UseWindowsAuth'];
90 # Test for driver support, to avoid suppressed fatal error
91 if ( !function_exists(
'sqlsrv_connect' ) ) {
94 "Microsoft SQL Server Native (sqlsrv) functions missing.
95 You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
99 # e.g. the class is being loaded
100 if ( !strlen( $user ) ) {
110 $connectionInfo = [];
113 $connectionInfo[
'Database'] =
$dbName;
118 if ( !$this->useWindowsAuth ) {
119 $connectionInfo[
'UID'] =
$user;
123 Wikimedia\suppressWarnings();
124 $this->conn = sqlsrv_connect(
$server, $connectionInfo );
125 Wikimedia\restoreWarnings();
127 if ( $this->conn ===
false ) {
131 $this->opened =
true;
142 return sqlsrv_close( $this->conn );
154 } elseif ( $result ===
true ) {
173 if ( preg_match(
'/\bLIMIT\s*/i', $sql ) ) {
179 if ( preg_match(
'#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql,
$matches ) ) {
181 $sql = str_replace(
$matches[0],
"DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
191 $scrollArr = [
'Scrollable' => SQLSRV_CURSOR_STATIC ];
198 $stmt = sqlsrv_prepare( $this->conn, $sql, [], $scrollArr );
201 $stmt = sqlsrv_query( $this->conn, $sql, [], $scrollArr );
208 if ( $this->ignoreDupKeyErrors ) {
217 $errors = sqlsrv_errors();
220 foreach ( $errors as $err ) {
232 $this->lastAffectedRowCount = sqlsrv_rows_affected( $stmt );
242 sqlsrv_free_stmt(
$res );
251 return $res->fetchObject();
259 return $res->fetchRow();
273 if (
$ret ===
false ) {
276 $ret = (int)sqlsrv_has_rows(
$res );
291 return sqlsrv_num_fields(
$res );
304 return sqlsrv_field_metadata(
$res )[$n][
'Name'];
321 return $res->seek( $row );
329 $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
330 if ( $retErrors !=
null ) {
331 foreach ( $retErrors as $arrError ) {
335 $strRet =
"No errors found";
346 return '[SQLSTATE ' .
347 $err[
'SQLSTATE'] .
'][Error Code ' . $err[
'code'] .
']' . $err[
'message'];
354 $err = sqlsrv_errors( SQLSRV_ERR_ALL );
355 if ( $err !==
null && isset( $err[0] ) ) {
356 return $err[0][
'code'];
363 $errors = sqlsrv_errors( SQLSRV_ERR_ALL );
370 $statementOnly =
false;
371 $codeWhitelist = [
'2601',
'2627',
'547' ];
372 foreach ( $errors as $error ) {
373 if ( $error[
'code'] ==
'3621' ) {
374 $statementOnly =
true;
375 } elseif ( !in_array( $error[
'code'], $codeWhitelist ) ) {
376 $statementOnly =
false;
381 return $statementOnly;
413 if ( isset(
$options[
'EXPLAIN'] ) ) {
417 $this->
query(
"SET SHOWPLAN_ALL ON" );
419 $this->
query(
"SET SHOWPLAN_ALL OFF" );
421 if ( isset(
$options[
'FOR COUNT'] ) ) {
423 $this->
query(
"SET SHOWPLAN_ALL OFF" );
427 'COUNT(*) AS EstimateRows',
464 if ( isset(
$options[
'EXPLAIN'] ) ) {
471 if ( strpos( $sql,
'MAX(' ) !==
false || strpos( $sql,
'MIN(' ) !==
false ) {
473 if ( is_array( $table ) ) {
477 if ( is_array(
$t ) ) {
487 foreach ( $bitColumns as $col => $info ) {
489 "MAX({$col})" =>
"MAX(CAST({$col} AS tinyint))",
490 "MIN({$col})" =>
"MIN(CAST({$col} AS tinyint))",
492 $sql = str_replace( array_keys( $replace ), array_values( $replace ), $sql );
499 public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
504 parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
$fname );
505 }
catch ( Exception
$e ) {
512 public function delete( $table, $conds,
$fname = __METHOD__ ) {
515 parent::delete( $table, $conds,
$fname );
516 }
catch ( Exception
$e ) {
542 if ( is_string( $column ) && !in_array( $column, [
'*',
'1' ] ) ) {
543 $conds[] =
"$column IS NOT NULL";
555 if ( isset( $row[
'EstimateRows'] ) ) {
556 $rows = (int)$row[
'EstimateRows'];
572 # This does not return the same info as MYSQL would, but that's OK
573 # because MediaWiki never uses the returned value except to check for
574 # the existence of indexes.
575 $sql =
"sp_helpindex '" . $this->
tableName( $table ) .
"'";
583 foreach (
$res as $row ) {
584 if ( $row->index_name == $index ) {
585 $row->Non_unique = !stristr( $row->index_description,
"unique" );
586 $cols = explode(
", ", $row->index_keys );
587 foreach ( $cols as $col ) {
588 $row->Column_name = trim( $col );
589 $result[] = clone $row;
591 } elseif ( $index ==
'PRIMARY' && stristr( $row->index_description,
'PRIMARY' ) ) {
592 $row->Non_unique = 0;
593 $cols = explode(
", ", $row->index_keys );
594 foreach ( $cols as $col ) {
595 $row->Column_name = trim( $col );
596 $result[] = clone $row;
601 return $result ?:
false;
620 # No rows to insert, easy just return now
621 if ( !count( $arrToInsert ) ) {
631 if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) {
632 $arrToInsert = [ 0 => $arrToInsert ];
638 $tableRawArr = explode(
'.', preg_replace(
'#\[([^\]]*)\]#',
'$1', $table ) );
639 $tableRaw = array_pop( $tableRawArr );
641 "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
642 "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
644 if (
$res && sqlsrv_has_rows(
$res ) ) {
646 $identityArr = sqlsrv_fetch_array(
$res, SQLSRV_FETCH_ASSOC );
647 $identity = array_pop( $identityArr );
649 sqlsrv_free_stmt(
$res );
656 if ( in_array(
'IGNORE',
$options ) ) {
658 $this->ignoreDupKeyErrors =
true;
662 foreach ( $arrToInsert as $a ) {
667 $identityClause =
'';
672 foreach ( $a as $k => $v ) {
673 if ( $k == $identity ) {
674 if ( !is_null( $v ) ) {
677 $sqlPre =
"SET IDENTITY_INSERT $table ON;";
678 $sqlPost =
";SET IDENTITY_INSERT $table OFF;";
688 $identityClause =
"OUTPUT INSERTED.$identity ";
691 $keys = array_keys( $a );
694 $sql = $sqlPre .
'INSERT ' . implode(
' ',
$options ) .
695 " INTO $table (" . implode(
',',
$keys ) .
") $identityClause VALUES (";
698 foreach ( $a as $key =>
$value ) {
699 if ( isset( $binaryColumns[$key] ) ) {
707 if ( is_null(
$value ) ) {
709 } elseif ( is_array(
$value ) || is_object(
$value ) ) {
719 $sql .=
')' . $sqlPost;
725 }
catch ( Exception
$e ) {
727 $this->ignoreDupKeyErrors =
false;
734 $row =
$ret->fetchObject();
735 if ( is_object( $row ) ) {
736 $this->lastInsertId = $row->$identity;
740 if ( $this->lastAffectedRowCount == -1 ) {
741 $this->lastAffectedRowCount = 1;
747 $this->ignoreDupKeyErrors =
false;
769 $insertOptions = [], $selectOptions = [], $selectJoinConds = []
773 $ret = parent::nativeInsertSelect(
783 }
catch ( Exception
$e ) {
822 $sql =
"UPDATE $opts $table SET " . $this->
makeList( $values,
LIST_SET, $binaryColumns );
824 if ( $conds !== [] && $conds !==
'*' ) {
830 $this->
query( $sql );
831 }
catch ( Exception
$e ) {
856 if ( !is_array( $a ) ) {
857 throw new DBUnexpectedError( $this, __METHOD__ .
' called with incorrect parameters' );
864 foreach ( array_keys( $a ) as $field ) {
865 if ( !isset( $binaryColumns[$field] ) ) {
869 if ( is_array( $a[$field] ) ) {
870 foreach ( $a[$field] as &$v ) {
875 $a[$field] =
new MssqlBlob( $a[$field] );
880 return parent::makeList( $a, $mode );
890 $sql =
"SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
891 WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
895 if ( strtolower( $row[
'DATA_TYPE'] ) !=
'text' ) {
896 $size = $row[
'CHARACTER_MAXIMUM_LENGTH'];
913 if ( $offset ===
false || $offset == 0 ) {
914 if ( strpos( $sql,
"SELECT" ) ===
false ) {
915 return "TOP {$limit} " . $sql;
917 return preg_replace(
'/\bSELECT(\s+DISTINCT)?\b/Dsi',
918 'SELECT$1 TOP ' . $limit, $sql, 1 );
922 $select = $orderby = [];
923 $s1 = preg_match(
'#SELECT\s+(.+?)\s+FROM#Dis', $sql, $select );
924 $s2 = preg_match(
'#(ORDER BY\s+.+?)(\s*FOR XML .*)?$#Dis', $sql, $orderby );
926 $first = $offset + 1;
927 $last = $offset + $limit;
929 $sub2 =
'sub_' . ( $this->subqueryId + 1 );
930 $this->subqueryId += 2;
933 throw new DBUnexpectedError( $this,
"Attempting to LIMIT a non-SELECT query\n" );
937 $overOrder =
'ORDER BY (SELECT 1)';
939 if ( !isset( $orderby[2] ) || !$orderby[2] ) {
941 $sql = str_replace( $orderby[1],
'', $sql );
943 $overOrder = $orderby[1];
944 $postOrder =
' ' . $overOrder;
946 $sql =
"SELECT {$select[1]}
948 SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
949 FROM ({$sql}) {$sub1}
951 WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
969 $pattern =
'/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
970 if ( preg_match( $pattern, $sql,
$matches ) ) {
975 $sql = str_replace(
$matches[0],
'', $sql );
977 return $this->
limitResult( $sql, $row_count, $offset );
987 return "[{{int:version-db-mssql-url}} MS SQL Server]";
994 $server_info = sqlsrv_server_info( $this->conn );
996 if ( isset( $server_info[
'SQLServerVersion'] ) ) {
997 $version = $server_info[
'SQLServerVersion'];
1011 if ( $db !==
false ) {
1013 $this->queryLogger->error(
"Attempting to call tableExists on a remote table" );
1021 $res = $this->
query(
"SELECT 1 FROM INFORMATION_SCHEMA.TABLES
1022 WHERE TABLE_TYPE = 'BASE TABLE'
1023 AND TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table'" );
1025 if (
$res->numRows() ) {
1042 if ( $db !==
false ) {
1044 $this->queryLogger->error(
"Attempting to call fieldExists on a remote table" );
1048 $res = $this->
query(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
1049 WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1051 if (
$res->numRows() ) {
1061 if ( $db !==
false ) {
1063 $this->queryLogger->error(
"Attempting to call fieldInfo on a remote table" );
1067 $res = $this->
query(
"SELECT * FROM INFORMATION_SCHEMA.COLUMNS
1068 WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1070 $meta =
$res->fetchRow();
1096 sqlsrv_begin_transaction( $this->conn );
1105 sqlsrv_commit( $this->conn );
1115 sqlsrv_rollback( $this->conn );
1125 return str_replace(
"'",
"''",
$s );
1135 } elseif (
$s instanceof
Blob ) {
1139 return $blob->fetch();
1141 if ( is_bool(
$s ) ) {
1144 return parent::addQuotes(
$s );
1154 return '[' .
$s .
']';
1162 return strlen( $name ) && $name[0] ==
'[' && substr( $name, -1, 1 ) ==
']';
1173 return str_replace( [ $escapeChar,
'%',
'_',
'[',
']',
'^' ],
1174 [
"{$escapeChar}{$escapeChar}",
"{$escapeChar}%",
"{$escapeChar}_",
1175 "{$escapeChar}[",
"{$escapeChar}]",
"{$escapeChar}^" ],
1185 $this->dbName = $db;
1186 $this->
query(
"USE $db" );
1188 }
catch ( Exception
$e ) {
1203 foreach (
$options as $key => $option ) {
1204 if ( is_numeric( $key ) ) {
1205 $noKeyOptions[$option] =
true;
1213 if ( isset( $noKeyOptions[
'DISTINCT'] ) || isset( $noKeyOptions[
'DISTINCTROW'] ) ) {
1214 $startOpts .=
'DISTINCT';
1217 if ( isset( $noKeyOptions[
'FOR XML'] ) ) {
1219 $tailOpts .=
" FOR XML PATH('')";
1223 return [ $startOpts,
'', $tailOpts,
'',
'' ];
1235 return implode(
' + ', $stringList );
1259 $this->subqueryId++;
1261 $delimLen = strlen( $delim );
1262 $fld =
"{$field} + {$this->addQuotes( $delim )}";
1263 $sql =
"(SELECT LEFT({$field}, LEN({$field}) - {$delimLen}) FROM ("
1264 . $this->
selectSQLText( $table, $fld, $conds,
null, [
'FOR XML' ], $join_conds )
1265 .
") {$gcsq} ({$field}))";
1272 if ( $length ===
null ) {
1278 $length = 2147483647;
1280 return 'SUBSTRING(' . implode(
',', [
$input, $startPosition, $length ] ) .
')';
1290 $tableRawArr = explode(
'.', preg_replace(
'#\[([^\]]*)\]#',
'$1', $table ) );
1291 $tableRaw = array_pop( $tableRawArr );
1293 if ( $this->binaryColumnCache ===
null ) {
1297 return isset( $this->binaryColumnCache[$tableRaw] )
1298 ? $this->binaryColumnCache[$tableRaw]
1307 $tableRawArr = explode(
'.', preg_replace(
'#\[([^\]]*)\]#',
'$1', $table ) );
1308 $tableRaw = array_pop( $tableRawArr );
1310 if ( $this->bitColumnCache ===
null ) {
1314 return isset( $this->bitColumnCache[$tableRaw] )
1315 ? $this->bitColumnCache[$tableRaw]
1320 $res = $this->
select(
'INFORMATION_SCHEMA.COLUMNS',
'*',
1322 'TABLE_CATALOG' => $this->dbName,
1323 'TABLE_SCHEMA' => $this->schema,
1324 'DATA_TYPE' => [
'varbinary',
'binary',
'image',
'bit' ]
1327 $this->binaryColumnCache = [];
1328 $this->bitColumnCache = [];
1329 foreach (
$res as $row ) {
1330 if ( $row->DATA_TYPE ==
'bit' ) {
1331 $this->bitColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
1333 $this->binaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
1344 # Replace reserved words with better ones
1360 $table = parent::tableName( $name, $format );
1361 if ( $format ==
'split' ) {
1364 $table = explode(
'.', $table );
1365 while ( count( $table ) < 3 ) {
1366 array_unshift( $table,
false );
1379 public function dropTable( $tableName, $fName = __METHOD__ ) {
1380 if ( !$this->
tableExists( $tableName, $fName ) ) {
1385 $sql =
"DROP TABLE " . $this->
tableName( $tableName );
1387 return $this->
query( $sql, $fName );
1421class_alias( DatabaseMssql::class,
'DatabaseMssql' );
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
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
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
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
namespace being checked & $result
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
this hook is for auditing only RecentChangesLinked and Watchlist 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
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
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
returning false will NOT prevent logging $e
if(is_array($mode)) switch( $mode) $input