74 $this->serverPort =
$params[
'port'];
75 $this->useWindowsAuth =
$params[
'UseWindowsAuth'];
81 # Test for driver support, to avoid suppressed fatal error
85 "Microsoft SQL Server Native (sqlsrv) functions missing.
86 You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
90 # e.g. the class is being loaded
100 $connectionInfo = [];
102 if ( $dbName !=
'' ) {
103 $connectionInfo[
'Database'] = $dbName;
108 if ( !$this->useWindowsAuth ) {
109 $connectionInfo[
'UID'] =
$user;
117 if ( $this->conn ===
false ) {
121 $this->opened =
true;
123 ( $dbName !=
'' ) ? $dbName :
null,
149 }
elseif ( $result ===
true ) {
195 $stmt =
sqlsrv_query( $this->conn, $sql, [], $scrollArr );
202 if ( $this->ignoreDupKeyErrors ) {
214 foreach ( $errors as $err ) {
245 return $res->fetchObject();
253 return $res->fetchRow();
267 if (
$ret ===
false ) {
315 return $res->seek( $row );
324 if ( $retErrors !=
null ) {
325 foreach ( $retErrors as $arrError ) {
329 $strRet =
"No errors found";
340 return '[SQLSTATE ' .
341 $err[
'SQLSTATE'] .
'][Error Code ' . $err[
'code'] .
']' . $err[
'message'];
349 if ( $err !==
null &&
isset( $err[0] ) ) {
350 return $err[0][
'code'];
364 $statementOnly =
false;
365 $codeWhitelist = [
'2601',
'2627',
'547' ];
366 foreach ( $errors as $error ) {
367 if ( $error[
'code'] ==
'3621' ) {
368 $statementOnly =
true;
370 $statementOnly =
false;
411 $this->
query(
"SET SHOWPLAN_ALL ON" );
413 $this->
query(
"SET SHOWPLAN_ALL OFF" );
417 $this->
query(
"SET SHOWPLAN_ALL OFF" );
421 'COUNT(*) AS EstimateRows',
465 if (
strpos( $sql,
'MAX(' ) !==
false ||
strpos( $sql,
'MIN(' ) !==
false ) {
481 foreach ( $bitColumns as $col => $info ) {
483 "MAX({$col})" =>
"MAX(CAST({$col} AS tinyint))",
484 "MIN({$col})" =>
"MIN(CAST({$col} AS tinyint))",
493 public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
498 parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
$fname );
499 }
catch ( Exception
$e ) {
509 parent::delete( $table, $conds,
$fname );
510 }
catch ( Exception
$e ) {
539 $conds[] =
"$column IS NOT NULL";
551 if (
isset( $row[
'EstimateRows'] ) ) {
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 ) .
"'";
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;
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;
597 return $result ?:
false;
616 # No rows to insert, easy just return now
617 if ( !count( $arrToInsert ) ) {
627 if ( !(
isset( $arrToInsert[0] ) &&
is_array( $arrToInsert[0] ) ) ) {
634 $tableRawArr = explode(
'.',
preg_replace(
'#\[([^\]]*)\]#',
'$1', $table ) );
637 "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
638 "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
654 $this->ignoreDupKeyErrors =
true;
658 foreach ( $arrToInsert as $a ) {
663 $identityClause =
'';
668 foreach ( $a as $k => $v ) {
669 if ( $k == $identity ) {
673 $sqlPre =
"SET IDENTITY_INSERT $table ON;";
674 $sqlPost =
";SET IDENTITY_INSERT $table OFF;";
684 $identityClause =
"OUTPUT INSERTED.$identity ";
690 $sql = $sqlPre .
'INSERT ' . implode(
' ',
$options ) .
691 " INTO $table (" . implode(
',',
$keys ) .
") $identityClause VALUES (";
694 foreach ( $a as $key =>
$value ) {
695 if (
isset( $binaryColumns[$key] ) ) {
715 }
catch ( Exception
$e ) {
717 $this->ignoreDupKeyErrors =
false;
724 $row =
$ret->fetchObject();
726 $this->lastInsertId = $row->$identity;
730 if ( $this->lastAffectedRowCount == -1 ) {
731 $this->lastAffectedRowCount = 1;
737 $this->ignoreDupKeyErrors =
false;
758 $insertOptions = [], $selectOptions = [], $selectJoinConds = []
762 parent::nativeInsertSelect(
772 }
catch ( Exception
$e ) {
809 $sql =
"UPDATE $opts $table SET " . $this->
makeList( $values,
LIST_SET, $binaryColumns );
811 if ( $conds !== [] && $conds !==
'*' ) {
817 $this->
query( $sql );
818 }
catch ( Exception
$e ) {
844 throw new DBUnexpectedError( $this, __METHOD__ .
' called with incorrect parameters' );
852 if ( !
isset( $binaryColumns[$field] ) ) {
857 foreach ( $a[$field] as &$v ) {
867 return parent::makeList( $a, $mode );
877 $sql =
"SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
878 WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
882 if (
strtolower( $row[
'DATA_TYPE'] ) !=
'text' ) {
883 $size = $row[
'CHARACTER_MAXIMUM_LENGTH'];
900 if ( $offset ===
false || $offset == 0 ) {
901 if (
strpos( $sql,
"SELECT" ) ===
false ) {
902 return "TOP {$limit} " . $sql;
905 'SELECT$1 TOP ' . $limit, $sql, 1 );
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 );
913 $first = $offset + 1;
914 $last = $offset + $limit;
916 $sub2 =
'sub_' . ( $this->subqueryId + 1 );
917 $this->subqueryId += 2;
920 throw new DBUnexpectedError( $this,
"Attempting to LIMIT a non-SELECT query\n" );
924 $overOrder =
'ORDER BY (SELECT 1)';
926 if ( !
isset( $orderby[2] ) || !$orderby[2] ) {
930 $overOrder = $orderby[1];
933 $sql =
"SELECT {$select[1]}
935 SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
936 FROM ({$sql}) {$sub1}
938 WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
956 $pattern =
'/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
964 return $this->
limitResult( $sql, $row_count, $offset );
974 return "[{{int:version-db-mssql-url}} MS SQL Server]";
982 $version = $server_info[
'SQLServerVersion'] ??
'Error';
993 list( $db, $schema, $table ) = $this->
tableName( $table,
'split' );
995 if ( $db !==
false ) {
997 $this->queryLogger->error(
"Attempting to call tableExists on a remote table" );
1001 if ( $schema ===
false ) {
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'" );
1009 if (
$res->numRows() ) {
1024 list( $db, $schema, $table ) = $this->
tableName( $table,
'split' );
1026 if ( $db !==
false ) {
1028 $this->queryLogger->error(
"Attempting to call fieldExists on a remote table" );
1032 $res = $this->
query(
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
1033 WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1035 if (
$res->numRows() ) {
1043 list( $db, $schema, $table ) = $this->
tableName( $table,
'split' );
1045 if ( $db !==
false ) {
1047 $this->queryLogger->error(
"Attempting to call fieldInfo on a remote table" );
1051 $res = $this->
query(
"SELECT * FROM INFORMATION_SCHEMA.COLUMNS
1052 WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1054 $meta =
$res->fetchRow();
1123 return $blob->fetch();
1128 return parent::addQuotes(
$s );
1138 return '[' .
$s .
']';
1146 return strlen( $name ) && $name[0] ==
'[' &&
substr( $name, -1, 1 ) ==
']';
1157 return str_replace( [ $escapeChar,
'%',
'_',
'[',
']',
'^' ],
1158 [
"{$escapeChar}{$escapeChar}",
"{$escapeChar}%",
"{$escapeChar}_",
1159 "{$escapeChar}[",
"{$escapeChar}]",
"{$escapeChar}^" ],
1165 throw new DBExpectedError( $this, __CLASS__ .
": domain schemas are not supported." );
1169 if ( $database !== $this->
getDBname() ) {
1173 throw new DBExpectedError( $this,
"Could not select database '$database'." );
1177 $this->currentDomain = $domain;
1192 foreach (
$options as $key => $option ) {
1194 $noKeyOptions[
$option] =
true;
1202 if (
isset( $noKeyOptions[
'DISTINCT'] ) ||
isset( $noKeyOptions[
'DISTINCTROW'] ) ) {
1203 $startOpts .=
'DISTINCT';
1206 if (
isset( $noKeyOptions[
'FOR XML'] ) ) {
1208 $tailOpts .=
" FOR XML PATH('')";
1224 return implode(
' + ', $stringList );
1248 $this->subqueryId++;
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}))";
1261 if ( $length ===
null ) {
1267 $length = 2147483647;
1269 return 'SUBSTRING(' . implode(
',', [
$input, $startPosition, $length ] ) .
')';
1279 $tableRawArr = explode(
'.',
preg_replace(
'#\[([^\]]*)\]#',
'$1', $table ) );
1282 if ( $this->binaryColumnCache ===
null ) {
1286 return $this->binaryColumnCache[
$tableRaw] ?? [];
1294 $tableRawArr = explode(
'.',
preg_replace(
'#\[([^\]]*)\]#',
'$1', $table ) );
1297 if ( $this->bitColumnCache ===
null ) {
1301 return $this->bitColumnCache[
$tableRaw] ?? [];
1305 $res = $this->
select(
'INFORMATION_SCHEMA.COLUMNS',
'*',
1308 'TABLE_SCHEMA' => $this->
dbSchema(),
1309 'DATA_TYPE' => [
'varbinary',
'binary',
'image',
'bit' ]
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;
1318 $this->binaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
1330 # Replace reserved words with better ones
1348 $table = parent::tableName( $name, $format );
1349 if ( $format ==
'split' ) {
1352 return array_pad( explode(
'.', $table, 3 ), -3,
false );
1364 public function dropTable( $tableName, $fName = __METHOD__ ) {
1365 if ( !$this->
tableExists( $tableName, $fName ) ) {
1370 $sql =
"DROP TABLE " . $this->
tableName( $tableName );
1372 return $this->
query( $sql, $fName );
1406 return "CAST( $field AS NVARCHAR )";
1410 return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS =>
true ];
1417class_alias( DatabaseMssql::class,
'DatabaseMssql' );
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Class to handle database/prefix specification for IDatabase domains.
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
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
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
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
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
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))
if(is_array($mode)) switch( $mode) $input