MediaWiki  REL1_31
DatabaseMssql.php
Go to the documentation of this file.
1 <?php
28 namespace Wikimedia\Rdbms;
29 
31 use Exception;
32 use stdClass;
33 
37 class 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 
89  public function open( $server, $user, $password, $dbName ) {
90  # Test for driver support, to avoid suppressed fatal error
91  if ( !function_exists( 'sqlsrv_connect' ) ) {
92  throw new DBConnectionError(
93  $this,
94  "Microsoft SQL Server Native (sqlsrv) functions missing.
95  You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
96  );
97  }
98 
99  # e.g. the class is being loaded
100  if ( !strlen( $user ) ) {
101  return null;
102  }
103 
104  $this->close();
105  $this->server = $server;
106  $this->user = $user;
107  $this->password = $password;
108  $this->dbName = $dbName;
109 
110  $connectionInfo = [];
111 
112  if ( $dbName ) {
113  $connectionInfo['Database'] = $dbName;
114  }
115 
116  // Decide which auth scenerio to use
117  // if we are using Windows auth, then don't add credentials to $connectionInfo
118  if ( !$this->useWindowsAuth ) {
119  $connectionInfo['UID'] = $user;
120  $connectionInfo['PWD'] = $password;
121  }
122 
123  Wikimedia\suppressWarnings();
124  $this->conn = sqlsrv_connect( $server, $connectionInfo );
125  Wikimedia\restoreWarnings();
126 
127  if ( $this->conn === false ) {
128  throw new DBConnectionError( $this, $this->lastError() );
129  }
130 
131  $this->opened = true;
132 
133  return $this->conn;
134  }
135 
141  protected function closeConnection() {
142  return sqlsrv_close( $this->conn );
143  }
144 
149  protected function resultObject( $result ) {
150  if ( !$result ) {
151  return false;
152  } elseif ( $result instanceof MssqlResultWrapper ) {
153  return $result;
154  } elseif ( $result === true ) {
155  // Successful write query
156  return $result;
157  } else {
158  return new MssqlResultWrapper( $this, $result );
159  }
160  }
161 
167  protected function doQuery( $sql ) {
168  // several extensions seem to think that all databases support limits
169  // via LIMIT N after the WHERE clause, but MSSQL uses SELECT TOP N,
170  // so to catch any of those extensions we'll do a quick check for a
171  // LIMIT clause and pass $sql through $this->LimitToTopN() which parses
172  // the LIMIT clause and passes the result to $this->limitResult();
173  if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
174  // massage LIMIT -> TopN
175  $sql = $this->LimitToTopN( $sql );
176  }
177 
178  // MSSQL doesn't have EXTRACT(epoch FROM XXX)
179  if ( preg_match( '#\bEXTRACT\s*?\‍(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
180  // This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970
181  $sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
182  }
183 
184  // perform query
185 
186  // SQLSRV_CURSOR_STATIC is slower than SQLSRV_CURSOR_CLIENT_BUFFERED (one of the two is
187  // needed if we want to be able to seek around the result set), however CLIENT_BUFFERED
188  // has a bug in the sqlsrv driver where wchar_t types (such as nvarchar) that are empty
189  // strings make php throw a fatal error "Severe error translating Unicode"
190  if ( $this->scrollableCursor ) {
191  $scrollArr = [ 'Scrollable' => SQLSRV_CURSOR_STATIC ];
192  } else {
193  $scrollArr = [];
194  }
195 
196  if ( $this->prepareStatements ) {
197  // we do prepare + execute so we can get its field metadata for later usage if desired
198  $stmt = sqlsrv_prepare( $this->conn, $sql, [], $scrollArr );
199  $success = sqlsrv_execute( $stmt );
200  } else {
201  $stmt = sqlsrv_query( $this->conn, $sql, [], $scrollArr );
202  $success = (bool)$stmt;
203  }
204 
205  // Make a copy to ensure what we add below does not get reflected in future queries
207 
208  if ( $this->ignoreDupKeyErrors ) {
209  // ignore duplicate key errors
210  // this emulates INSERT IGNORE in MySQL
211  $ignoreErrors[] = '2601'; // duplicate key error caused by unique index
212  $ignoreErrors[] = '2627'; // duplicate key error caused by primary key
213  $ignoreErrors[] = '3621'; // generic "the statement has been terminated" error
214  }
215 
216  if ( $success === false ) {
217  $errors = sqlsrv_errors();
218  $success = true;
219 
220  foreach ( $errors as $err ) {
221  if ( !in_array( $err['code'], $ignoreErrors ) ) {
222  $success = false;
223  break;
224  }
225  }
226 
227  if ( $success === false ) {
228  return false;
229  }
230  }
231  // remember number of rows affected
232  $this->lastAffectedRowCount = sqlsrv_rows_affected( $stmt );
233 
234  return $stmt;
235  }
236 
237  public function freeResult( $res ) {
238  if ( $res instanceof ResultWrapper ) {
239  $res = $res->result;
240  }
241 
242  sqlsrv_free_stmt( $res );
243  }
244 
249  public function fetchObject( $res ) {
250  // $res is expected to be an instance of MssqlResultWrapper here
251  return $res->fetchObject();
252  }
253 
258  public function fetchRow( $res ) {
259  return $res->fetchRow();
260  }
261 
266  public function numRows( $res ) {
267  if ( $res instanceof ResultWrapper ) {
268  $res = $res->result;
269  }
270 
271  $ret = sqlsrv_num_rows( $res );
272 
273  if ( $ret === false ) {
274  // we cannot get an amount of rows from this cursor type
275  // has_rows returns bool true/false if the result has rows
276  $ret = (int)sqlsrv_has_rows( $res );
277  }
278 
279  return $ret;
280  }
281 
286  public function numFields( $res ) {
287  if ( $res instanceof ResultWrapper ) {
288  $res = $res->result;
289  }
290 
291  return sqlsrv_num_fields( $res );
292  }
293 
299  public function fieldName( $res, $n ) {
300  if ( $res instanceof ResultWrapper ) {
301  $res = $res->result;
302  }
303 
304  return sqlsrv_field_metadata( $res )[$n]['Name'];
305  }
306 
311  public function insertId() {
312  return $this->lastInsertId;
313  }
314 
320  public function dataSeek( $res, $row ) {
321  return $res->seek( $row );
322  }
323 
327  public function lastError() {
328  $strRet = '';
329  $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
330  if ( $retErrors != null ) {
331  foreach ( $retErrors as $arrError ) {
332  $strRet .= $this->formatError( $arrError ) . "\n";
333  }
334  } else {
335  $strRet = "No errors found";
336  }
337 
338  return $strRet;
339  }
340 
345  private function formatError( $err ) {
346  return '[SQLSTATE ' .
347  $err['SQLSTATE'] . '][Error Code ' . $err['code'] . ']' . $err['message'];
348  }
349 
353  public function lastErrno() {
354  $err = sqlsrv_errors( SQLSRV_ERR_ALL );
355  if ( $err !== null && isset( $err[0] ) ) {
356  return $err[0]['code'];
357  } else {
358  return 0;
359  }
360  }
361 
362  protected function wasKnownStatementRollbackError() {
363  $errors = sqlsrv_errors( SQLSRV_ERR_ALL );
364  if ( !$errors ) {
365  return false;
366  }
367  // The transaction vs statement rollback behavior depends on XACT_ABORT, so make sure
368  // that the "statement has been terminated" error (3621) is specifically present.
369  // https://docs.microsoft.com/en-us/sql/t-sql/statements/set-xact-abort-transact-sql
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;
377  break;
378  }
379  }
380 
381  return $statementOnly;
382  }
383 
387  protected function fetchAffectedRowCount() {
389  }
390 
409  public function select( $table, $vars, $conds = '', $fname = __METHOD__,
410  $options = [], $join_conds = []
411  ) {
412  $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
413  if ( isset( $options['EXPLAIN'] ) ) {
414  try {
415  $this->scrollableCursor = false;
416  $this->prepareStatements = false;
417  $this->query( "SET SHOWPLAN_ALL ON" );
418  $ret = $this->query( $sql, $fname );
419  $this->query( "SET SHOWPLAN_ALL OFF" );
420  } catch ( DBQueryError $dqe ) {
421  if ( isset( $options['FOR COUNT'] ) ) {
422  // likely don't have privs for SHOWPLAN, so run a select count instead
423  $this->query( "SET SHOWPLAN_ALL OFF" );
424  unset( $options['EXPLAIN'] );
425  $ret = $this->select(
426  $table,
427  'COUNT(*) AS EstimateRows',
428  $conds,
429  $fname,
430  $options,
431  $join_conds
432  );
433  } else {
434  // someone actually wanted the query plan instead of an est row count
435  // let them know of the error
436  $this->scrollableCursor = true;
437  $this->prepareStatements = true;
438  throw $dqe;
439  }
440  }
441  $this->scrollableCursor = true;
442  $this->prepareStatements = true;
443  return $ret;
444  }
445  return $this->query( $sql, $fname );
446  }
447 
461  public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
462  $options = [], $join_conds = []
463  ) {
464  if ( isset( $options['EXPLAIN'] ) ) {
465  unset( $options['EXPLAIN'] );
466  }
467 
468  $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
469 
470  // try to rewrite aggregations of bit columns (currently MAX and MIN)
471  if ( strpos( $sql, 'MAX(' ) !== false || strpos( $sql, 'MIN(' ) !== false ) {
472  $bitColumns = [];
473  if ( is_array( $table ) ) {
474  $tables = $table;
475  while ( $tables ) {
476  $t = array_pop( $tables );
477  if ( is_array( $t ) ) {
478  $tables = array_merge( $tables, $t );
479  } else {
480  $bitColumns += $this->getBitColumns( $this->tableName( $t ) );
481  }
482  }
483  } else {
484  $bitColumns = $this->getBitColumns( $this->tableName( $table ) );
485  }
486 
487  foreach ( $bitColumns as $col => $info ) {
488  $replace = [
489  "MAX({$col})" => "MAX(CAST({$col} AS tinyint))",
490  "MIN({$col})" => "MIN(CAST({$col} AS tinyint))",
491  ];
492  $sql = str_replace( array_keys( $replace ), array_values( $replace ), $sql );
493  }
494  }
495 
496  return $sql;
497  }
498 
499  public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
500  $fname = __METHOD__
501  ) {
502  $this->scrollableCursor = false;
503  try {
504  parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname );
505  } catch ( Exception $e ) {
506  $this->scrollableCursor = true;
507  throw $e;
508  }
509  $this->scrollableCursor = true;
510  }
511 
512  public function delete( $table, $conds, $fname = __METHOD__ ) {
513  $this->scrollableCursor = false;
514  try {
515  parent::delete( $table, $conds, $fname );
516  } catch ( Exception $e ) {
517  $this->scrollableCursor = true;
518  throw $e;
519  }
520  $this->scrollableCursor = true;
521  }
522 
537  public function estimateRowCount( $table, $var = '*', $conds = '',
538  $fname = __METHOD__, $options = [], $join_conds = []
539  ) {
540  $conds = $this->normalizeConditions( $conds, $fname );
541  $column = $this->extractSingleFieldFromList( $var );
542  if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
543  $conds[] = "$column IS NOT NULL";
544  }
545 
546  // http://msdn2.microsoft.com/en-us/library/aa259203.aspx
547  $options['EXPLAIN'] = true;
548  $options['FOR COUNT'] = true;
549  $res = $this->select( $table, $var, $conds, $fname, $options, $join_conds );
550 
551  $rows = -1;
552  if ( $res ) {
553  $row = $this->fetchRow( $res );
554 
555  if ( isset( $row['EstimateRows'] ) ) {
556  $rows = (int)$row['EstimateRows'];
557  }
558  }
559 
560  return $rows;
561  }
562 
571  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
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 ) . "'";
576  $res = $this->query( $sql, $fname );
577 
578  if ( !$res ) {
579  return null;
580  }
581 
582  $result = [];
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;
590  }
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;
597  }
598  }
599  }
600 
601  return $result ?: false;
602  }
603 
619  public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = [] ) {
620  # No rows to insert, easy just return now
621  if ( !count( $arrToInsert ) ) {
622  return true;
623  }
624 
625  if ( !is_array( $options ) ) {
626  $options = [ $options ];
627  }
628 
629  $table = $this->tableName( $table );
630 
631  if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) { // Not multi row
632  $arrToInsert = [ 0 => $arrToInsert ]; // make everything multi row compatible
633  }
634 
635  // We know the table we're inserting into, get its identity column
636  $identity = null;
637  // strip matching square brackets and the db/schema from table name
638  $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
639  $tableRaw = array_pop( $tableRawArr );
640  $res = $this->doQuery(
641  "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
642  "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
643  );
644  if ( $res && sqlsrv_has_rows( $res ) ) {
645  // There is an identity for this table.
646  $identityArr = sqlsrv_fetch_array( $res, SQLSRV_FETCH_ASSOC );
647  $identity = array_pop( $identityArr );
648  }
649  sqlsrv_free_stmt( $res );
650 
651  // Determine binary/varbinary fields so we can encode data as a hex string like 0xABCDEF
652  $binaryColumns = $this->getBinaryColumns( $table );
653 
654  // INSERT IGNORE is not supported by SQL Server
655  // remove IGNORE from options list and set ignore flag to true
656  if ( in_array( 'IGNORE', $options ) ) {
657  $options = array_diff( $options, [ 'IGNORE' ] );
658  $this->ignoreDupKeyErrors = true;
659  }
660 
661  $ret = null;
662  foreach ( $arrToInsert as $a ) {
663  // start out with empty identity column, this is so we can return
664  // it as a result of the INSERT logic
665  $sqlPre = '';
666  $sqlPost = '';
667  $identityClause = '';
668 
669  // if we have an identity column
670  if ( $identity ) {
671  // iterate through
672  foreach ( $a as $k => $v ) {
673  if ( $k == $identity ) {
674  if ( !is_null( $v ) ) {
675  // there is a value being passed to us,
676  // we need to turn on and off inserted identity
677  $sqlPre = "SET IDENTITY_INSERT $table ON;";
678  $sqlPost = ";SET IDENTITY_INSERT $table OFF;";
679  } else {
680  // we can't insert NULL into an identity column,
681  // so remove the column from the insert.
682  unset( $a[$k] );
683  }
684  }
685  }
686 
687  // we want to output an identity column as result
688  $identityClause = "OUTPUT INSERTED.$identity ";
689  }
690 
691  $keys = array_keys( $a );
692 
693  // Build the actual query
694  $sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
695  " INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
696 
697  $first = true;
698  foreach ( $a as $key => $value ) {
699  if ( isset( $binaryColumns[$key] ) ) {
700  $value = new MssqlBlob( $value );
701  }
702  if ( $first ) {
703  $first = false;
704  } else {
705  $sql .= ',';
706  }
707  if ( is_null( $value ) ) {
708  $sql .= 'null';
709  } elseif ( is_array( $value ) || is_object( $value ) ) {
710  if ( is_object( $value ) && $value instanceof Blob ) {
711  $sql .= $this->addQuotes( $value );
712  } else {
713  $sql .= $this->addQuotes( serialize( $value ) );
714  }
715  } else {
716  $sql .= $this->addQuotes( $value );
717  }
718  }
719  $sql .= ')' . $sqlPost;
720 
721  // Run the query
722  $this->scrollableCursor = false;
723  try {
724  $ret = $this->query( $sql );
725  } catch ( Exception $e ) {
726  $this->scrollableCursor = true;
727  $this->ignoreDupKeyErrors = false;
728  throw $e;
729  }
730  $this->scrollableCursor = true;
731 
732  if ( $ret instanceof ResultWrapper && !is_null( $identity ) ) {
733  // Then we want to get the identity column value we were assigned and save it off
734  $row = $ret->fetchObject();
735  if ( is_object( $row ) ) {
736  $this->lastInsertId = $row->$identity;
737  // It seems that mAffectedRows is -1 sometimes when OUTPUT INSERTED.identity is
738  // used if we got an identity back, we know for sure a row was affected, so
739  // adjust that here
740  if ( $this->lastAffectedRowCount == -1 ) {
741  $this->lastAffectedRowCount = 1;
742  }
743  }
744  }
745  }
746 
747  $this->ignoreDupKeyErrors = false;
748 
749  return $ret;
750  }
751 
768  public function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
769  $insertOptions = [], $selectOptions = [], $selectJoinConds = []
770  ) {
771  $this->scrollableCursor = false;
772  try {
773  $ret = parent::nativeInsertSelect(
774  $destTable,
775  $srcTable,
776  $varMap,
777  $conds,
778  $fname,
779  $insertOptions,
780  $selectOptions,
781  $selectJoinConds
782  );
783  } catch ( Exception $e ) {
784  $this->scrollableCursor = true;
785  throw $e;
786  }
787  $this->scrollableCursor = true;
788 
789  return $ret;
790  }
791 
817  function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
818  $table = $this->tableName( $table );
819  $binaryColumns = $this->getBinaryColumns( $table );
820 
821  $opts = $this->makeUpdateOptions( $options );
822  $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET, $binaryColumns );
823 
824  if ( $conds !== [] && $conds !== '*' ) {
825  $sql .= " WHERE " . $this->makeList( $conds, LIST_AND, $binaryColumns );
826  }
827 
828  $this->scrollableCursor = false;
829  try {
830  $this->query( $sql );
831  } catch ( Exception $e ) {
832  $this->scrollableCursor = true;
833  throw $e;
834  }
835  $this->scrollableCursor = true;
836  return true;
837  }
838 
855  public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = [] ) {
856  if ( !is_array( $a ) ) {
857  throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
858  }
859 
860  if ( $mode != LIST_NAMES ) {
861  // In MS SQL, values need to be specially encoded when they are
862  // inserted into binary fields. Perform this necessary encoding
863  // for the specified set of columns.
864  foreach ( array_keys( $a ) as $field ) {
865  if ( !isset( $binaryColumns[$field] ) ) {
866  continue;
867  }
868 
869  if ( is_array( $a[$field] ) ) {
870  foreach ( $a[$field] as &$v ) {
871  $v = new MssqlBlob( $v );
872  }
873  unset( $v );
874  } else {
875  $a[$field] = new MssqlBlob( $a[$field] );
876  }
877  }
878  }
879 
880  return parent::makeList( $a, $mode );
881  }
882 
888  public function textFieldSize( $table, $field ) {
889  $table = $this->tableName( $table );
890  $sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
891  WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
892  $res = $this->query( $sql );
893  $row = $this->fetchRow( $res );
894  $size = -1;
895  if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) {
896  $size = $row['CHARACTER_MAXIMUM_LENGTH'];
897  }
898 
899  return $size;
900  }
901 
912  public function limitResult( $sql, $limit, $offset = false ) {
913  if ( $offset === false || $offset == 0 ) {
914  if ( strpos( $sql, "SELECT" ) === false ) {
915  return "TOP {$limit} " . $sql;
916  } else {
917  return preg_replace( '/\bSELECT(\s+DISTINCT)?\b/Dsi',
918  'SELECT$1 TOP ' . $limit, $sql, 1 );
919  }
920  } else {
921  // This one is fun, we need to pull out the select list as well as any ORDER BY clause
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 );
925  $postOrder = '';
926  $first = $offset + 1;
927  $last = $offset + $limit;
928  $sub1 = 'sub_' . $this->subqueryId;
929  $sub2 = 'sub_' . ( $this->subqueryId + 1 );
930  $this->subqueryId += 2;
931  if ( !$s1 ) {
932  // wat
933  throw new DBUnexpectedError( $this, "Attempting to LIMIT a non-SELECT query\n" );
934  }
935  if ( !$s2 ) {
936  // no ORDER BY
937  $overOrder = 'ORDER BY (SELECT 1)';
938  } else {
939  if ( !isset( $orderby[2] ) || !$orderby[2] ) {
940  // don't need to strip it out if we're using a FOR XML clause
941  $sql = str_replace( $orderby[1], '', $sql );
942  }
943  $overOrder = $orderby[1];
944  $postOrder = ' ' . $overOrder;
945  }
946  $sql = "SELECT {$select[1]}
947  FROM (
948  SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
949  FROM ({$sql}) {$sub1}
950  ) {$sub2}
951  WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
952 
953  return $sql;
954  }
955  }
956 
967  public function LimitToTopN( $sql ) {
968  // Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
969  $pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
970  if ( preg_match( $pattern, $sql, $matches ) ) {
971  $row_count = $matches[4];
972  $offset = $matches[3] ?: $matches[6] ?: false;
973 
974  // strip the matching LIMIT clause out
975  $sql = str_replace( $matches[0], '', $sql );
976 
977  return $this->limitResult( $sql, $row_count, $offset );
978  }
979 
980  return $sql;
981  }
982 
986  public function getSoftwareLink() {
987  return "[{{int:version-db-mssql-url}} MS SQL Server]";
988  }
989 
993  public function getServerVersion() {
994  $server_info = sqlsrv_server_info( $this->conn );
995  $version = 'Error';
996  if ( isset( $server_info['SQLServerVersion'] ) ) {
997  $version = $server_info['SQLServerVersion'];
998  }
999 
1000  return $version;
1001  }
1002 
1008  public function tableExists( $table, $fname = __METHOD__ ) {
1009  list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
1010 
1011  if ( $db !== false ) {
1012  // remote database
1013  $this->queryLogger->error( "Attempting to call tableExists on a remote table" );
1014  return false;
1015  }
1016 
1017  if ( $schema === false ) {
1019  }
1020 
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'" );
1024 
1025  if ( $res->numRows() ) {
1026  return true;
1027  } else {
1028  return false;
1029  }
1030  }
1031 
1039  public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1040  list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
1041 
1042  if ( $db !== false ) {
1043  // remote database
1044  $this->queryLogger->error( "Attempting to call fieldExists on a remote table" );
1045  return false;
1046  }
1047 
1048  $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
1049  WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1050 
1051  if ( $res->numRows() ) {
1052  return true;
1053  } else {
1054  return false;
1055  }
1056  }
1057 
1058  public function fieldInfo( $table, $field ) {
1059  list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
1060 
1061  if ( $db !== false ) {
1062  // remote database
1063  $this->queryLogger->error( "Attempting to call fieldInfo on a remote table" );
1064  return false;
1065  }
1066 
1067  $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS
1068  WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1069 
1070  $meta = $res->fetchRow();
1071  if ( $meta ) {
1072  return new MssqlField( $meta );
1073  }
1074 
1075  return false;
1076  }
1077 
1078  protected function doSavepoint( $identifier, $fname ) {
1079  $this->query( 'SAVE TRANSACTION ' . $this->addIdentifierQuotes( $identifier ), $fname );
1080  }
1081 
1082  protected function doReleaseSavepoint( $identifier, $fname ) {
1083  // Not supported. Also not really needed, a new doSavepoint() for the
1084  // same identifier will overwrite the old.
1085  }
1086 
1087  protected function doRollbackToSavepoint( $identifier, $fname ) {
1088  $this->query( 'ROLLBACK TRANSACTION ' . $this->addIdentifierQuotes( $identifier ), $fname );
1089  }
1090 
1095  protected function doBegin( $fname = __METHOD__ ) {
1096  sqlsrv_begin_transaction( $this->conn );
1097  $this->trxLevel = 1;
1098  }
1099 
1104  protected function doCommit( $fname = __METHOD__ ) {
1105  sqlsrv_commit( $this->conn );
1106  $this->trxLevel = 0;
1107  }
1108 
1114  protected function doRollback( $fname = __METHOD__ ) {
1115  sqlsrv_rollback( $this->conn );
1116  $this->trxLevel = 0;
1117  }
1118 
1123  public function strencode( $s ) {
1124  // Should not be called by us
1125  return str_replace( "'", "''", $s );
1126  }
1127 
1132  public function addQuotes( $s ) {
1133  if ( $s instanceof MssqlBlob ) {
1134  return $s->fetch();
1135  } elseif ( $s instanceof Blob ) {
1136  // this shouldn't really ever be called, but it's here if needed
1137  // (and will quite possibly make the SQL error out)
1138  $blob = new MssqlBlob( $s->fetch() );
1139  return $blob->fetch();
1140  } else {
1141  if ( is_bool( $s ) ) {
1142  $s = $s ? 1 : 0;
1143  }
1144  return parent::addQuotes( $s );
1145  }
1146  }
1147 
1152  public function addIdentifierQuotes( $s ) {
1153  // http://msdn.microsoft.com/en-us/library/aa223962.aspx
1154  return '[' . $s . ']';
1155  }
1156 
1161  public function isQuotedIdentifier( $name ) {
1162  return strlen( $name ) && $name[0] == '[' && substr( $name, -1, 1 ) == ']';
1163  }
1164 
1172  protected function escapeLikeInternal( $s, $escapeChar = '`' ) {
1173  return str_replace( [ $escapeChar, '%', '_', '[', ']', '^' ],
1174  [ "{$escapeChar}{$escapeChar}", "{$escapeChar}%", "{$escapeChar}_",
1175  "{$escapeChar}[", "{$escapeChar}]", "{$escapeChar}^" ],
1176  $s );
1177  }
1178 
1183  public function selectDB( $db ) {
1184  try {
1185  $this->dbName = $db;
1186  $this->query( "USE $db" );
1187  return true;
1188  } catch ( Exception $e ) {
1189  return false;
1190  }
1191  }
1192 
1198  public function makeSelectOptions( $options ) {
1199  $tailOpts = '';
1200  $startOpts = '';
1201 
1202  $noKeyOptions = [];
1203  foreach ( $options as $key => $option ) {
1204  if ( is_numeric( $key ) ) {
1205  $noKeyOptions[$option] = true;
1206  }
1207  }
1208 
1209  $tailOpts .= $this->makeGroupByWithHaving( $options );
1210 
1211  $tailOpts .= $this->makeOrderBy( $options );
1212 
1213  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1214  $startOpts .= 'DISTINCT';
1215  }
1216 
1217  if ( isset( $noKeyOptions['FOR XML'] ) ) {
1218  // used in group concat field emulation
1219  $tailOpts .= " FOR XML PATH('')";
1220  }
1221 
1222  // we want this to be compatible with the output of parent::makeSelectOptions()
1223  return [ $startOpts, '', $tailOpts, '', '' ];
1224  }
1225 
1226  public function getType() {
1227  return 'mssql';
1228  }
1229 
1234  public function buildConcat( $stringList ) {
1235  return implode( ' + ', $stringList );
1236  }
1237 
1255  public function buildGroupConcatField( $delim, $table, $field, $conds = '',
1256  $join_conds = []
1257  ) {
1258  $gcsq = 'gcsq_' . $this->subqueryId;
1259  $this->subqueryId++;
1260 
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}))";
1266 
1267  return $sql;
1268  }
1269 
1270  public function buildSubstring( $input, $startPosition, $length = null ) {
1271  $this->assertBuildSubstringParams( $startPosition, $length );
1272  if ( $length === null ) {
1278  $length = 2147483647;
1279  }
1280  return 'SUBSTRING(' . implode( ',', [ $input, $startPosition, $length ] ) . ')';
1281  }
1282 
1289  private function getBinaryColumns( $table ) {
1290  $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
1291  $tableRaw = array_pop( $tableRawArr );
1292 
1293  if ( $this->binaryColumnCache === null ) {
1294  $this->populateColumnCaches();
1295  }
1296 
1297  return isset( $this->binaryColumnCache[$tableRaw] )
1298  ? $this->binaryColumnCache[$tableRaw]
1299  : [];
1300  }
1301 
1306  private function getBitColumns( $table ) {
1307  $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
1308  $tableRaw = array_pop( $tableRawArr );
1309 
1310  if ( $this->bitColumnCache === null ) {
1311  $this->populateColumnCaches();
1312  }
1313 
1314  return isset( $this->bitColumnCache[$tableRaw] )
1315  ? $this->bitColumnCache[$tableRaw]
1316  : [];
1317  }
1318 
1319  private function populateColumnCaches() {
1320  $res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
1321  [
1322  'TABLE_CATALOG' => $this->dbName,
1323  'TABLE_SCHEMA' => $this->schema,
1324  'DATA_TYPE' => [ 'varbinary', 'binary', 'image', 'bit' ]
1325  ] );
1326 
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;
1332  } else {
1333  $this->binaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
1334  }
1335  }
1336  }
1337 
1343  function tableName( $name, $format = 'quoted' ) {
1344  # Replace reserved words with better ones
1345  switch ( $name ) {
1346  case 'user':
1347  return $this->realTableName( 'mwuser', $format );
1348  default:
1349  return $this->realTableName( $name, $format );
1350  }
1351  }
1352 
1359  function realTableName( $name, $format = 'quoted' ) {
1360  $table = parent::tableName( $name, $format );
1361  if ( $format == 'split' ) {
1362  // Used internally, we want the schema split off from the table name and returned
1363  // as a list with 3 elements (database, schema, table)
1364  $table = explode( '.', $table );
1365  while ( count( $table ) < 3 ) {
1366  array_unshift( $table, false );
1367  }
1368  }
1369  return $table;
1370  }
1371 
1379  public function dropTable( $tableName, $fName = __METHOD__ ) {
1380  if ( !$this->tableExists( $tableName, $fName ) ) {
1381  return false;
1382  }
1383 
1384  // parent function incorrectly appends CASCADE, which we don't want
1385  $sql = "DROP TABLE " . $this->tableName( $tableName );
1386 
1387  return $this->query( $sql, $fName );
1388  }
1389 
1396  public function prepareStatements( $value = null ) {
1397  $old = $this->prepareStatements;
1398  if ( $value !== null ) {
1399  $this->prepareStatements = $value;
1400  }
1401 
1402  return $old;
1403  }
1404 
1411  public function scrollableCursor( $value = null ) {
1412  $old = $this->scrollableCursor;
1413  if ( $value !== null ) {
1414  $this->scrollableCursor = $value;
1415  }
1416 
1417  return $old;
1418  }
1419 }
1420 
1421 class_alias( DatabaseMssql::class, 'DatabaseMssql' );
Wikimedia\Rdbms\DatabaseMssql\indexInfo
indexInfo( $table, $index, $fname=__METHOD__)
Returns information about an index If errors are explicitly ignored, returns NULL on failure.
Definition: DatabaseMssql.php:571
Wikimedia\Rdbms\DatabaseMssql\update
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
Definition: DatabaseMssql.php:817
Wikimedia\Rdbms\DatabaseMssql\buildGroupConcatField
buildGroupConcatField( $delim, $table, $field, $conds='', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
Definition: DatabaseMssql.php:1255
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:48
Wikimedia\Rdbms\DatabaseMssql\$ignoreDupKeyErrors
bool $ignoreDupKeyErrors
Definition: DatabaseMssql.php:57
Wikimedia\Rdbms\DatabaseMssql\limitResult
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset This is used for query pages.
Definition: DatabaseMssql.php:912
Wikimedia\Rdbms\DatabaseMssql\resultObject
resultObject( $result)
Definition: DatabaseMssql.php:149
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
$rows
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:2783
Wikimedia\Rdbms\DatabaseMssql\realTableName
realTableName( $name, $format='quoted')
call this instead of tableName() in the updater when renaming tables
Definition: DatabaseMssql.php:1359
array
the array() calling protocol came about after MediaWiki 1.4rc1.
Wikimedia\Rdbms\DatabaseMssql\freeResult
freeResult( $res)
Free a result object returned by query() or select().
Definition: DatabaseMssql.php:237
Wikimedia\Rdbms\DatabaseMssql\deleteJoin
deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
Definition: DatabaseMssql.php:499
Wikimedia\Rdbms\DatabaseMssql\__construct
__construct(array $params)
Definition: DatabaseMssql.php:73
$last
$last
Definition: profileinfo.php:408
Wikimedia\Rdbms\DatabaseMssql\$useWindowsAuth
bool $useWindowsAuth
Definition: DatabaseMssql.php:41
Wikimedia\Rdbms\Database\$password
string $password
Password used to establish the current connection.
Definition: Database.php:83
Wikimedia\Rdbms\DatabaseMssql\insert
insert( $table, $arrToInsert, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
Definition: DatabaseMssql.php:619
Wikimedia\Rdbms\DatabaseMssql\implicitOrderby
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
Definition: DatabaseMssql.php:65
Wikimedia\Rdbms\DatabaseMssql\$prepareStatements
bool $prepareStatements
Definition: DatabaseMssql.php:51
Wikimedia\Rdbms\DatabaseMssql\getServerVersion
getServerVersion()
Definition: DatabaseMssql.php:993
$ret
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:2005
Wikimedia\Rdbms\DatabaseMssql\lastErrno
lastErrno()
Definition: DatabaseMssql.php:353
Wikimedia\Rdbms\DatabaseMssql\buildConcat
buildConcat( $stringList)
Definition: DatabaseMssql.php:1234
Wikimedia\Rdbms\DatabaseMssql\$bitColumnCache
stdClass[][] null $bitColumnCache
Definition: DatabaseMssql.php:55
Wikimedia\Rdbms
Definition: ChronologyProtector.php:24
Wikimedia\Rdbms\Database\normalizeConditions
normalizeConditions( $conds, $fname)
Definition: Database.php:1796
$params
$params
Definition: styleTest.css.php:40
serialize
serialize()
Definition: ApiMessage.php:184
Wikimedia\Rdbms\DatabaseMssql\$binaryColumnCache
stdClass[][] null $binaryColumnCache
Definition: DatabaseMssql.php:53
$s
$s
Definition: mergeMessageFileList.php:187
Wikimedia\Rdbms\Database\extractSingleFieldFromList
extractSingleFieldFromList( $var)
Definition: Database.php:1819
Wikimedia\Rdbms\DatabaseMssql\implicitGroupby
implicitGroupby()
Returns true if this database does an implicit sort when doing GROUP BY.
Definition: DatabaseMssql.php:61
$res
$res
Definition: database.txt:21
Wikimedia\Rdbms\ResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: ResultWrapper.php:24
Wikimedia\Rdbms\DatabaseMssql\dropTable
dropTable( $tableName, $fName=__METHOD__)
Delete a table.
Definition: DatabaseMssql.php:1379
$success
$success
Definition: NoLocalSettings.php:42
$result
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. '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: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! 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:1993
Wikimedia\Rdbms\DatabaseMssql\populateColumnCaches
populateColumnCaches()
Definition: DatabaseMssql.php:1319
Wikimedia\Rdbms\DatabaseMssql\dataSeek
dataSeek( $res, $row)
Definition: DatabaseMssql.php:320
Wikimedia\Rdbms\DatabaseMssql\makeList
makeList( $a, $mode=LIST_COMMA, $binaryColumns=[])
Makes an encoded list of strings from an array.
Definition: DatabaseMssql.php:855
Wikimedia\Rdbms\DatabaseMssql\getType
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
Definition: DatabaseMssql.php:1226
Wikimedia\Rdbms\DatabaseMssql\isQuotedIdentifier
isQuotedIdentifier( $name)
Definition: DatabaseMssql.php:1161
Wikimedia\Rdbms\DatabaseMssql\wasKnownStatementRollbackError
wasKnownStatementRollbackError()
Definition: DatabaseMssql.php:362
php
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
LIST_AND
const LIST_AND
Definition: Defines.php:53
Wikimedia\Rdbms\DatabaseMssql\tableExists
tableExists( $table, $fname=__METHOD__)
Definition: DatabaseMssql.php:1008
Wikimedia\Rdbms\DatabaseMssql\nativeInsertSelect
nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[], $selectJoinConds=[])
INSERT SELECT wrapper $varMap must be an associative array of the form [ 'dest1' => 'source1',...
Definition: DatabaseMssql.php:768
Wikimedia\Rdbms\Database\assertBuildSubstringParams
assertBuildSubstringParams( $startPosition, $length)
Check type and bounds for parameters to self::buildSubstring()
Definition: Database.php:2161
user
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
Definition: distributors.txt:25
Wikimedia\Rdbms\DatabaseMssql\unionSupportsOrderAndLimit
unionSupportsOrderAndLimit()
Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries within th...
Definition: DatabaseMssql.php:69
Wikimedia\Rdbms\DatabaseMssql\doCommit
doCommit( $fname=__METHOD__)
End a transaction.
Definition: DatabaseMssql.php:1104
$blob
$blob
Definition: testCompression.php:65
Wikimedia\Rdbms\DatabaseMssql\closeConnection
closeConnection()
Closes a database connection, if it is open Returns success, true if already closed.
Definition: DatabaseMssql.php:141
tableName
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
Wikimedia\Rdbms\Database\trxLevel
trxLevel()
Gets the current transaction level.
Definition: Database.php:577
$input
if(is_array( $mode)) switch( $mode) $input
Definition: postprocess-phan.php:141
Wikimedia\Rdbms\DatabaseMssql\$lastInsertId
int null $lastInsertId
Definition: DatabaseMssql.php:43
$matches
$matches
Definition: NoLocalSettings.php:24
Wikimedia\Rdbms\DatabaseMssql\fieldInfo
fieldInfo( $table, $field)
mysql_fetch_field() wrapper Returns false if the field doesn't exist
Definition: DatabaseMssql.php:1058
Wikimedia\Rdbms\DatabaseMssql\estimateRowCount
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...
Definition: DatabaseMssql.php:537
LIST_SET
const LIST_SET
Definition: Defines.php:54
Wikimedia\Rdbms\DatabaseMssql\escapeLikeInternal
escapeLikeInternal( $s, $escapeChar='`')
MS SQL supports more pattern operators than other databases (ex: [,],^)
Definition: DatabaseMssql.php:1172
Wikimedia\Rdbms\Database\$user
string $user
User that this instance is currently connected under the name of.
Definition: Database.php:81
Wikimedia\Rdbms\MssqlBlob
Definition: MssqlBlob.php:5
Wikimedia\Rdbms\DatabaseMssql\select
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
SELECT wrapper.
Definition: DatabaseMssql.php:409
Wikimedia\Rdbms\DatabaseMssql\fetchRow
fetchRow( $res)
Definition: DatabaseMssql.php:258
Wikimedia\Rdbms\DatabaseMssql\prepareStatements
prepareStatements( $value=null)
Called in the installer and updater.
Definition: DatabaseMssql.php:1396
Wikimedia\Rdbms\MssqlField
Definition: MssqlField.php:5
list
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
Wikimedia\Rdbms\DatabaseMssql\fetchAffectedRowCount
fetchAffectedRowCount()
Definition: DatabaseMssql.php:387
Wikimedia\Rdbms\DBQueryError
Definition: DBQueryError.php:27
Wikimedia\Rdbms\Database\makeUpdateOptions
makeUpdateOptions( $options)
Make UPDATE options for the Database::update function.
Definition: Database.php:1995
LIST_COMMA
const LIST_COMMA
Definition: Defines.php:52
$fname
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition: Setup.php:112
$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 & $options
Definition: hooks.txt:2001
Wikimedia\Rdbms\DatabaseMssql\strencode
strencode( $s)
Definition: DatabaseMssql.php:1123
Wikimedia\Rdbms\DatabaseMssql\numRows
numRows( $res)
Definition: DatabaseMssql.php:266
Wikimedia\Rdbms\DatabaseMssql\doRollback
doRollback( $fname=__METHOD__)
Rollback a transaction.
Definition: DatabaseMssql.php:1114
Wikimedia\Rdbms\DatabaseMssql\selectSQLText
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
SELECT wrapper.
Definition: DatabaseMssql.php:461
$value
$value
Definition: styleTest.css.php:45
Wikimedia\Rdbms\DatabaseMssql\$ignoreErrors
string[] $ignoreErrors
Definition: DatabaseMssql.php:59
Wikimedia\Rdbms\DatabaseMssql\lastError
lastError()
Definition: DatabaseMssql.php:327
Wikimedia\Rdbms\DatabaseMssql\fetchObject
fetchObject( $res)
Definition: DatabaseMssql.php:249
Wikimedia\Rdbms\DatabaseMssql\addQuotes
addQuotes( $s)
Definition: DatabaseMssql.php:1132
Wikimedia\Rdbms\DatabaseMssql\doBegin
doBegin( $fname=__METHOD__)
Begin a transaction, committing any previously open transaction.
Definition: DatabaseMssql.php:1095
Wikimedia\Rdbms\DatabaseMssql\insertId
insertId()
This must be called after nextSequenceVal.
Definition: DatabaseMssql.php:311
Wikimedia\Rdbms\DatabaseMssql\textFieldSize
textFieldSize( $table, $field)
Definition: DatabaseMssql.php:888
Wikimedia\Rdbms\DatabaseMssql\doRollbackToSavepoint
doRollbackToSavepoint( $identifier, $fname)
Rollback to a savepoint.
Definition: DatabaseMssql.php:1087
Wikimedia\Rdbms\DatabaseMssql\getBinaryColumns
getBinaryColumns( $table)
Returns an associative array for fields that are of type varbinary, binary, or image $table can be ei...
Definition: DatabaseMssql.php:1289
Wikimedia\Rdbms\DatabaseMssql\formatError
formatError( $err)
Definition: DatabaseMssql.php:345
Wikimedia\Rdbms\Database\$conn
resource null $conn
Database connection.
Definition: Database.php:108
Wikimedia\Rdbms\DatabaseMssql\selectDB
selectDB( $db)
Definition: DatabaseMssql.php:1183
Wikimedia\Rdbms\DatabaseMssql\open
open( $server, $user, $password, $dbName)
Usually aborts on failure.
Definition: DatabaseMssql.php:89
Wikimedia\Rdbms\Database\query
query( $sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition: Database.php:1094
Wikimedia\Rdbms\DatabaseMssql\makeSelectOptions
makeSelectOptions( $options)
Definition: DatabaseMssql.php:1198
Wikimedia\Rdbms\DatabaseMssql\$lastAffectedRowCount
int null $lastAffectedRowCount
Definition: DatabaseMssql.php:45
Wikimedia\Rdbms\DBUnexpectedError
Definition: DBUnexpectedError.php:27
Wikimedia\Rdbms\MssqlResultWrapper
Definition: MssqlResultWrapper.php:7
Wikimedia\Rdbms\DatabaseMssql\doQuery
doQuery( $sql)
Definition: DatabaseMssql.php:167
Wikimedia\Rdbms\Database\$schema
string $schema
Definition: Database.php:126
Wikimedia\Rdbms\Database\close
close()
Close the database connection.
Definition: Database.php:900
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
Wikimedia\Rdbms\DatabaseMssql\fieldName
fieldName( $res, $n)
Definition: DatabaseMssql.php:299
Wikimedia\Rdbms\DatabaseMssql\getBitColumns
getBitColumns( $table)
Definition: DatabaseMssql.php:1306
Wikimedia\Rdbms\DatabaseMssql\$subqueryId
int $subqueryId
Definition: DatabaseMssql.php:47
$tables
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
Definition: hooks.txt:1015
Wikimedia\Rdbms\Database\makeGroupByWithHaving
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1611
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 as
Definition: distributors.txt:22
Wikimedia\Rdbms\DatabaseMssql
Definition: DatabaseMssql.php:37
Wikimedia\Rdbms\DatabaseMssql\LimitToTopN
LimitToTopN( $sql)
If there is a limit clause, parse it, strip it, and pass the remaining SQL through limitResult() with...
Definition: DatabaseMssql.php:967
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
Wikimedia
Wikimedia\Rdbms\Database\$server
string $server
Server that this instance is currently connected to.
Definition: Database.php:79
$keys
$keys
Definition: testCompression.php:67
Wikimedia\Rdbms\Database\makeOrderBy
makeOrderBy( $options)
Returns an optional ORDER BY.
Definition: Database.php:1637
Wikimedia\Rdbms\DatabaseMssql\buildSubstring
buildSubstring( $input, $startPosition, $length=null)
Definition: DatabaseMssql.php:1270
Wikimedia\Rdbms\DatabaseMssql\$scrollableCursor
bool $scrollableCursor
Definition: DatabaseMssql.php:49
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:56
$t
$t
Definition: testCompression.php:69
Wikimedia\Rdbms\DatabaseMssql\doReleaseSavepoint
doReleaseSavepoint( $identifier, $fname)
Release a savepoint.
Definition: DatabaseMssql.php:1082
Wikimedia\Rdbms\DatabaseMssql\tableName
tableName( $name, $format='quoted')
Definition: DatabaseMssql.php:1343
LIST_NAMES
const LIST_NAMES
Definition: Defines.php:55
$vars
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2228
Wikimedia\Rdbms\DatabaseMssql\doSavepoint
doSavepoint( $identifier, $fname)
Create a savepoint.
Definition: DatabaseMssql.php:1078
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 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
Definition: distributors.txt:54
Wikimedia\Rdbms\DatabaseMssql\getSoftwareLink
getSoftwareLink()
Definition: DatabaseMssql.php:986
Wikimedia\Rdbms\DatabaseMssql\scrollableCursor
scrollableCursor( $value=null)
Called in the installer and updater.
Definition: DatabaseMssql.php:1411
Wikimedia\Rdbms\DatabaseMssql\numFields
numFields( $res)
Definition: DatabaseMssql.php:286
Wikimedia\Rdbms\DatabaseMssql\addIdentifierQuotes
addIdentifierQuotes( $s)
Definition: DatabaseMssql.php:1152
Wikimedia\Rdbms\DatabaseMssql\$serverPort
int $serverPort
Definition: DatabaseMssql.php:39
Wikimedia\Rdbms\DatabaseMssql\fieldExists
fieldExists( $table, $field, $fname=__METHOD__)
Query whether a given column exists in the mediawiki schema.
Definition: DatabaseMssql.php:1039
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2171
Wikimedia\Rdbms\Blob
Definition: Blob.php:5
Wikimedia\Rdbms\Database\$dbName
string $dbName
Database that this instance is currently connected to.
Definition: Database.php:85