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