MediaWiki  1.27.2
DatabaseMssql.php
Go to the documentation of this file.
1 <?php
31 class DatabaseMssql extends Database {
32  protected $mInsertId = null;
33  protected $mLastResult = null;
34  protected $mAffectedRows = null;
35  protected $mSubqueryId = 0;
36  protected $mScrollableCursor = true;
37  protected $mPrepareStatements = true;
38  protected $mBinaryColumnCache = null;
39  protected $mBitColumnCache = null;
40  protected $mIgnoreDupKeyErrors = false;
41  protected $mIgnoreErrors = [];
42 
43  protected $mPort;
44 
45  public function cascadingDeletes() {
46  return true;
47  }
48 
49  public function cleanupTriggers() {
50  return false;
51  }
52 
53  public function strictIPs() {
54  return false;
55  }
56 
57  public function realTimestamps() {
58  return false;
59  }
60 
61  public function implicitGroupby() {
62  return false;
63  }
64 
65  public function implicitOrderby() {
66  return false;
67  }
68 
69  public function functionalIndexes() {
70  return true;
71  }
72 
73  public function unionSupportsOrderAndLimit() {
74  return false;
75  }
76 
86  public function open( $server, $user, $password, $dbName ) {
87  # Test for driver support, to avoid suppressed fatal error
88  if ( !function_exists( 'sqlsrv_connect' ) ) {
89  throw new DBConnectionError(
90  $this,
91  "Microsoft SQL Server Native (sqlsrv) functions missing.
92  You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
93  );
94  }
95 
97 
98  # e.g. the class is being loaded
99  if ( !strlen( $user ) ) {
100  return null;
101  }
102 
103  $this->close();
104  $this->mServer = $server;
105  $this->mPort = $wgDBport;
106  $this->mUser = $user;
107  $this->mPassword = $password;
108  $this->mDBname = $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, don't add credentials to $connectionInfo
118  if ( !$wgDBWindowsAuthentication ) {
119  $connectionInfo['UID'] = $user;
120  $connectionInfo['PWD'] = $password;
121  }
122 
123  MediaWiki\suppressWarnings();
124  $this->mConn = sqlsrv_connect( $server, $connectionInfo );
125  MediaWiki\restoreWarnings();
126 
127  if ( $this->mConn === false ) {
128  throw new DBConnectionError( $this, $this->lastError() );
129  }
130 
131  $this->mOpened = true;
132 
133  return $this->mConn;
134  }
135 
141  protected function closeConnection() {
142  return sqlsrv_close( $this->mConn );
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  if ( $this->debug() ) {
169  wfDebug( "SQL: [$sql]\n" );
170  }
171  $this->offset = 0;
172 
173  // several extensions seem to think that all databases support limits
174  // via LIMIT N after the WHERE clause well, MSSQL uses SELECT TOP N,
175  // so to catch any of those extensions we'll do a quick check for a
176  // LIMIT clause and pass $sql through $this->LimitToTopN() which parses
177  // the limit clause and passes the result to $this->limitResult();
178  if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
179  // massage LIMIT -> TopN
180  $sql = $this->LimitToTopN( $sql );
181  }
182 
183  // MSSQL doesn't have EXTRACT(epoch FROM XXX)
184  if ( preg_match( '#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
185  // This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970
186  $sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
187  }
188 
189  // perform query
190 
191  // SQLSRV_CURSOR_STATIC is slower than SQLSRV_CURSOR_CLIENT_BUFFERED (one of the two is
192  // needed if we want to be able to seek around the result set), however CLIENT_BUFFERED
193  // has a bug in the sqlsrv driver where wchar_t types (such as nvarchar) that are empty
194  // strings make php throw a fatal error "Severe error translating Unicode"
195  if ( $this->mScrollableCursor ) {
196  $scrollArr = [ 'Scrollable' => SQLSRV_CURSOR_STATIC ];
197  } else {
198  $scrollArr = [];
199  }
200 
201  if ( $this->mPrepareStatements ) {
202  // we do prepare + execute so we can get its field metadata for later usage if desired
203  $stmt = sqlsrv_prepare( $this->mConn, $sql, [], $scrollArr );
204  $success = sqlsrv_execute( $stmt );
205  } else {
206  $stmt = sqlsrv_query( $this->mConn, $sql, [], $scrollArr );
207  $success = (bool)$stmt;
208  }
209 
210  // make a copy so that anything we add below does not get reflected in future queries
211  $ignoreErrors = $this->mIgnoreErrors;
212 
213  if ( $this->mIgnoreDupKeyErrors ) {
214  // ignore duplicate key errors
215  // this emulates INSERT IGNORE in MySQL
216  $ignoreErrors[] = '2601'; // duplicate key error caused by unique index
217  $ignoreErrors[] = '2627'; // duplicate key error caused by primary key
218  $ignoreErrors[] = '3621'; // generic "the statement has been terminated" error
219  }
220 
221  if ( $success === false ) {
222  $errors = sqlsrv_errors();
223  $success = true;
224 
225  foreach ( $errors as $err ) {
226  if ( !in_array( $err['code'], $ignoreErrors ) ) {
227  $success = false;
228  break;
229  }
230  }
231 
232  if ( $success === false ) {
233  return false;
234  }
235  }
236  // remember number of rows affected
237  $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
238 
239  return $stmt;
240  }
241 
242  public function freeResult( $res ) {
243  if ( $res instanceof ResultWrapper ) {
244  $res = $res->result;
245  }
246 
247  sqlsrv_free_stmt( $res );
248  }
249 
254  public function fetchObject( $res ) {
255  // $res is expected to be an instance of MssqlResultWrapper here
256  return $res->fetchObject();
257  }
258 
263  public function fetchRow( $res ) {
264  return $res->fetchRow();
265  }
266 
271  public function numRows( $res ) {
272  if ( $res instanceof ResultWrapper ) {
273  $res = $res->result;
274  }
275 
276  $ret = sqlsrv_num_rows( $res );
277 
278  if ( $ret === false ) {
279  // we cannot get an amount of rows from this cursor type
280  // has_rows returns bool true/false if the result has rows
281  $ret = (int)sqlsrv_has_rows( $res );
282  }
283 
284  return $ret;
285  }
286 
291  public function numFields( $res ) {
292  if ( $res instanceof ResultWrapper ) {
293  $res = $res->result;
294  }
295 
296  return sqlsrv_num_fields( $res );
297  }
298 
304  public function fieldName( $res, $n ) {
305  if ( $res instanceof ResultWrapper ) {
306  $res = $res->result;
307  }
308 
309  return sqlsrv_field_metadata( $res )[$n]['Name'];
310  }
311 
316  public function insertId() {
317  return $this->mInsertId;
318  }
319 
325  public function dataSeek( $res, $row ) {
326  return $res->seek( $row );
327  }
328 
332  public function lastError() {
333  $strRet = '';
334  $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
335  if ( $retErrors != null ) {
336  foreach ( $retErrors as $arrError ) {
337  $strRet .= $this->formatError( $arrError ) . "\n";
338  }
339  } else {
340  $strRet = "No errors found";
341  }
342 
343  return $strRet;
344  }
345 
350  private function formatError( $err ) {
351  return '[SQLSTATE ' . $err['SQLSTATE'] . '][Error Code ' . $err['code'] . ']' . $err['message'];
352  }
353 
357  public function lastErrno() {
358  $err = sqlsrv_errors( SQLSRV_ERR_ALL );
359  if ( $err !== null && isset( $err[0] ) ) {
360  return $err[0]['code'];
361  } else {
362  return 0;
363  }
364  }
365 
369  public function affectedRows() {
370  return $this->mAffectedRows;
371  }
372 
391  public function select( $table, $vars, $conds = '', $fname = __METHOD__,
392  $options = [], $join_conds = []
393  ) {
394  $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
395  if ( isset( $options['EXPLAIN'] ) ) {
396  try {
397  $this->mScrollableCursor = false;
398  $this->mPrepareStatements = false;
399  $this->query( "SET SHOWPLAN_ALL ON" );
400  $ret = $this->query( $sql, $fname );
401  $this->query( "SET SHOWPLAN_ALL OFF" );
402  } catch ( DBQueryError $dqe ) {
403  if ( isset( $options['FOR COUNT'] ) ) {
404  // likely don't have privs for SHOWPLAN, so run a select count instead
405  $this->query( "SET SHOWPLAN_ALL OFF" );
406  unset( $options['EXPLAIN'] );
407  $ret = $this->select(
408  $table,
409  'COUNT(*) AS EstimateRows',
410  $conds,
411  $fname,
412  $options,
413  $join_conds
414  );
415  } else {
416  // someone actually wanted the query plan instead of an est row count
417  // let them know of the error
418  $this->mScrollableCursor = true;
419  $this->mPrepareStatements = true;
420  throw $dqe;
421  }
422  }
423  $this->mScrollableCursor = true;
424  $this->mPrepareStatements = true;
425  return $ret;
426  }
427  return $this->query( $sql, $fname );
428  }
429 
443  public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
444  $options = [], $join_conds = []
445  ) {
446  if ( isset( $options['EXPLAIN'] ) ) {
447  unset( $options['EXPLAIN'] );
448  }
449 
450  $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
451 
452  // try to rewrite aggregations of bit columns (currently MAX and MIN)
453  if ( strpos( $sql, 'MAX(' ) !== false || strpos( $sql, 'MIN(' ) !== false ) {
454  $bitColumns = [];
455  if ( is_array( $table ) ) {
456  foreach ( $table as $t ) {
457  $bitColumns += $this->getBitColumns( $this->tableName( $t ) );
458  }
459  } else {
460  $bitColumns = $this->getBitColumns( $this->tableName( $table ) );
461  }
462 
463  foreach ( $bitColumns as $col => $info ) {
464  $replace = [
465  "MAX({$col})" => "MAX(CAST({$col} AS tinyint))",
466  "MIN({$col})" => "MIN(CAST({$col} AS tinyint))",
467  ];
468  $sql = str_replace( array_keys( $replace ), array_values( $replace ), $sql );
469  }
470  }
471 
472  return $sql;
473  }
474 
475  public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
476  $fname = __METHOD__
477  ) {
478  $this->mScrollableCursor = false;
479  try {
480  parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname );
481  } catch ( Exception $e ) {
482  $this->mScrollableCursor = true;
483  throw $e;
484  }
485  $this->mScrollableCursor = true;
486  }
487 
488  public function delete( $table, $conds, $fname = __METHOD__ ) {
489  $this->mScrollableCursor = false;
490  try {
491  parent::delete( $table, $conds, $fname );
492  } catch ( Exception $e ) {
493  $this->mScrollableCursor = true;
494  throw $e;
495  }
496  $this->mScrollableCursor = true;
497  }
498 
512  public function estimateRowCount( $table, $vars = '*', $conds = '',
513  $fname = __METHOD__, $options = []
514  ) {
515  // http://msdn2.microsoft.com/en-us/library/aa259203.aspx
516  $options['EXPLAIN'] = true;
517  $options['FOR COUNT'] = true;
518  $res = $this->select( $table, $vars, $conds, $fname, $options );
519 
520  $rows = -1;
521  if ( $res ) {
522  $row = $this->fetchRow( $res );
523 
524  if ( isset( $row['EstimateRows'] ) ) {
525  $rows = (int)$row['EstimateRows'];
526  }
527  }
528 
529  return $rows;
530  }
531 
540  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
541  # This does not return the same info as MYSQL would, but that's OK
542  # because MediaWiki never uses the returned value except to check for
543  # the existance of indexes.
544  $sql = "sp_helpindex '" . $this->tableName( $table ) . "'";
545  $res = $this->query( $sql, $fname );
546 
547  if ( !$res ) {
548  return null;
549  }
550 
551  $result = [];
552  foreach ( $res as $row ) {
553  if ( $row->index_name == $index ) {
554  $row->Non_unique = !stristr( $row->index_description, "unique" );
555  $cols = explode( ", ", $row->index_keys );
556  foreach ( $cols as $col ) {
557  $row->Column_name = trim( $col );
558  $result[] = clone $row;
559  }
560  } elseif ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) {
561  $row->Non_unique = 0;
562  $cols = explode( ", ", $row->index_keys );
563  foreach ( $cols as $col ) {
564  $row->Column_name = trim( $col );
565  $result[] = clone $row;
566  }
567  }
568  }
569 
570  return empty( $result ) ? false : $result;
571  }
572 
588  public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = [] ) {
589  # No rows to insert, easy just return now
590  if ( !count( $arrToInsert ) ) {
591  return true;
592  }
593 
594  if ( !is_array( $options ) ) {
595  $options = [ $options ];
596  }
597 
598  $table = $this->tableName( $table );
599 
600  if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) { // Not multi row
601  $arrToInsert = [ 0 => $arrToInsert ]; // make everything multi row compatible
602  }
603 
604  // We know the table we're inserting into, get its identity column
605  $identity = null;
606  // strip matching square brackets and the db/schema from table name
607  $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
608  $tableRaw = array_pop( $tableRawArr );
609  $res = $this->doQuery(
610  "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
611  "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
612  );
613  if ( $res && sqlsrv_has_rows( $res ) ) {
614  // There is an identity for this table.
615  $identityArr = sqlsrv_fetch_array( $res, SQLSRV_FETCH_ASSOC );
616  $identity = array_pop( $identityArr );
617  }
618  sqlsrv_free_stmt( $res );
619 
620  // Determine binary/varbinary fields so we can encode data as a hex string like 0xABCDEF
621  $binaryColumns = $this->getBinaryColumns( $table );
622 
623  // INSERT IGNORE is not supported by SQL Server
624  // remove IGNORE from options list and set ignore flag to true
625  if ( in_array( 'IGNORE', $options ) ) {
626  $options = array_diff( $options, [ 'IGNORE' ] );
627  $this->mIgnoreDupKeyErrors = true;
628  }
629 
630  foreach ( $arrToInsert as $a ) {
631  // start out with empty identity column, this is so we can return
632  // it as a result of the insert logic
633  $sqlPre = '';
634  $sqlPost = '';
635  $identityClause = '';
636 
637  // if we have an identity column
638  if ( $identity ) {
639  // iterate through
640  foreach ( $a as $k => $v ) {
641  if ( $k == $identity ) {
642  if ( !is_null( $v ) ) {
643  // there is a value being passed to us,
644  // we need to turn on and off inserted identity
645  $sqlPre = "SET IDENTITY_INSERT $table ON;";
646  $sqlPost = ";SET IDENTITY_INSERT $table OFF;";
647  } else {
648  // we can't insert NULL into an identity column,
649  // so remove the column from the insert.
650  unset( $a[$k] );
651  }
652  }
653  }
654 
655  // we want to output an identity column as result
656  $identityClause = "OUTPUT INSERTED.$identity ";
657  }
658 
659  $keys = array_keys( $a );
660 
661  // Build the actual query
662  $sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
663  " INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
664 
665  $first = true;
666  foreach ( $a as $key => $value ) {
667  if ( isset( $binaryColumns[$key] ) ) {
668  $value = new MssqlBlob( $value );
669  }
670  if ( $first ) {
671  $first = false;
672  } else {
673  $sql .= ',';
674  }
675  if ( is_null( $value ) ) {
676  $sql .= 'null';
677  } elseif ( is_array( $value ) || is_object( $value ) ) {
678  if ( is_object( $value ) && $value instanceof Blob ) {
679  $sql .= $this->addQuotes( $value );
680  } else {
681  $sql .= $this->addQuotes( serialize( $value ) );
682  }
683  } else {
684  $sql .= $this->addQuotes( $value );
685  }
686  }
687  $sql .= ')' . $sqlPost;
688 
689  // Run the query
690  $this->mScrollableCursor = false;
691  try {
692  $ret = $this->query( $sql );
693  } catch ( Exception $e ) {
694  $this->mScrollableCursor = true;
695  $this->mIgnoreDupKeyErrors = false;
696  throw $e;
697  }
698  $this->mScrollableCursor = true;
699 
700  if ( !is_null( $identity ) ) {
701  // then we want to get the identity column value we were assigned and save it off
702  $row = $ret->fetchObject();
703  if ( is_object( $row ) ) {
704  $this->mInsertId = $row->$identity;
705 
706  // it seems that mAffectedRows is -1 sometimes when OUTPUT INSERTED.identity is used
707  // if we got an identity back, we know for sure a row was affected, so adjust that here
708  if ( $this->mAffectedRows == -1 ) {
709  $this->mAffectedRows = 1;
710  }
711  }
712  }
713  }
714  $this->mIgnoreDupKeyErrors = false;
715  return $ret;
716  }
717 
733  public function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
734  $insertOptions = [], $selectOptions = []
735  ) {
736  $this->mScrollableCursor = false;
737  try {
738  $ret = parent::insertSelect(
739  $destTable,
740  $srcTable,
741  $varMap,
742  $conds,
743  $fname,
744  $insertOptions,
745  $selectOptions
746  );
747  } catch ( Exception $e ) {
748  $this->mScrollableCursor = true;
749  throw $e;
750  }
751  $this->mScrollableCursor = true;
752 
753  return $ret;
754  }
755 
782  function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
783  $table = $this->tableName( $table );
784  $binaryColumns = $this->getBinaryColumns( $table );
785 
786  $opts = $this->makeUpdateOptions( $options );
787  $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET, $binaryColumns );
788 
789  if ( $conds !== [] && $conds !== '*' ) {
790  $sql .= " WHERE " . $this->makeList( $conds, LIST_AND, $binaryColumns );
791  }
792 
793  $this->mScrollableCursor = false;
794  try {
795  $ret = $this->query( $sql );
796  } catch ( Exception $e ) {
797  $this->mScrollableCursor = true;
798  throw $e;
799  }
800  $this->mScrollableCursor = true;
801  return true;
802  }
803 
820  public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = [] ) {
821  if ( !is_array( $a ) ) {
822  throw new DBUnexpectedError( $this,
823  'DatabaseBase::makeList called with incorrect parameters' );
824  }
825 
826  if ( $mode != LIST_NAMES ) {
827  // In MS SQL, values need to be specially encoded when they are
828  // inserted into binary fields. Perform this necessary encoding
829  // for the specified set of columns.
830  foreach ( array_keys( $a ) as $field ) {
831  if ( !isset( $binaryColumns[$field] ) ) {
832  continue;
833  }
834 
835  if ( is_array( $a[$field] ) ) {
836  foreach ( $a[$field] as &$v ) {
837  $v = new MssqlBlob( $v );
838  }
839  unset( $v );
840  } else {
841  $a[$field] = new MssqlBlob( $a[$field] );
842  }
843  }
844  }
845 
846  return parent::makeList( $a, $mode );
847  }
848 
854  public function textFieldSize( $table, $field ) {
855  $table = $this->tableName( $table );
856  $sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
857  WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
858  $res = $this->query( $sql );
859  $row = $this->fetchRow( $res );
860  $size = -1;
861  if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) {
862  $size = $row['CHARACTER_MAXIMUM_LENGTH'];
863  }
864 
865  return $size;
866  }
867 
878  public function limitResult( $sql, $limit, $offset = false ) {
879  if ( $offset === false || $offset == 0 ) {
880  if ( strpos( $sql, "SELECT" ) === false ) {
881  return "TOP {$limit} " . $sql;
882  } else {
883  return preg_replace( '/\bSELECT(\s+DISTINCT)?\b/Dsi',
884  'SELECT$1 TOP ' . $limit, $sql, 1 );
885  }
886  } else {
887  // This one is fun, we need to pull out the select list as well as any ORDER BY clause
888  $select = $orderby = [];
889  $s1 = preg_match( '#SELECT\s+(.+?)\s+FROM#Dis', $sql, $select );
890  $s2 = preg_match( '#(ORDER BY\s+.+?)(\s*FOR XML .*)?$#Dis', $sql, $orderby );
891  $overOrder = $postOrder = '';
892  $first = $offset + 1;
893  $last = $offset + $limit;
894  $sub1 = 'sub_' . $this->mSubqueryId;
895  $sub2 = 'sub_' . ( $this->mSubqueryId + 1 );
896  $this->mSubqueryId += 2;
897  if ( !$s1 ) {
898  // wat
899  throw new DBUnexpectedError( $this, "Attempting to LIMIT a non-SELECT query\n" );
900  }
901  if ( !$s2 ) {
902  // no ORDER BY
903  $overOrder = 'ORDER BY (SELECT 1)';
904  } else {
905  if ( !isset( $orderby[2] ) || !$orderby[2] ) {
906  // don't need to strip it out if we're using a FOR XML clause
907  $sql = str_replace( $orderby[1], '', $sql );
908  }
909  $overOrder = $orderby[1];
910  $postOrder = ' ' . $overOrder;
911  }
912  $sql = "SELECT {$select[1]}
913  FROM (
914  SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
915  FROM ({$sql}) {$sub1}
916  ) {$sub2}
917  WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
918 
919  return $sql;
920  }
921  }
922 
933  public function LimitToTopN( $sql ) {
934  // Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
935  $pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
936  if ( preg_match( $pattern, $sql, $matches ) ) {
937  $row_count = $matches[4];
938  $offset = $matches[3] ?: $matches[6] ?: false;
939 
940  // strip the matching LIMIT clause out
941  $sql = str_replace( $matches[0], '', $sql );
942 
943  return $this->limitResult( $sql, $row_count, $offset );
944  }
945 
946  return $sql;
947  }
948 
952  public function getSoftwareLink() {
953  return "[{{int:version-db-mssql-url}} MS SQL Server]";
954  }
955 
959  public function getServerVersion() {
960  $server_info = sqlsrv_server_info( $this->mConn );
961  $version = 'Error';
962  if ( isset( $server_info['SQLServerVersion'] ) ) {
963  $version = $server_info['SQLServerVersion'];
964  }
965 
966  return $version;
967  }
968 
974  public function tableExists( $table, $fname = __METHOD__ ) {
975  list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
976 
977  if ( $db !== false ) {
978  // remote database
979  wfDebug( "Attempting to call tableExists on a remote table" );
980  return false;
981  }
982 
983  if ( $schema === false ) {
985  $schema = $wgDBmwschema;
986  }
987 
988  $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
989  WHERE TABLE_TYPE = 'BASE TABLE'
990  AND TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table'" );
991 
992  if ( $res->numRows() ) {
993  return true;
994  } else {
995  return false;
996  }
997  }
998 
1006  public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1007  list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
1008 
1009  if ( $db !== false ) {
1010  // remote database
1011  wfDebug( "Attempting to call fieldExists on a remote table" );
1012  return false;
1013  }
1014 
1015  $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
1016  WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1017 
1018  if ( $res->numRows() ) {
1019  return true;
1020  } else {
1021  return false;
1022  }
1023  }
1024 
1025  public function fieldInfo( $table, $field ) {
1026  list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
1027 
1028  if ( $db !== false ) {
1029  // remote database
1030  wfDebug( "Attempting to call fieldInfo on a remote table" );
1031  return false;
1032  }
1033 
1034  $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS
1035  WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1036 
1037  $meta = $res->fetchRow();
1038  if ( $meta ) {
1039  return new MssqlField( $meta );
1040  }
1041 
1042  return false;
1043  }
1044 
1049  protected function doBegin( $fname = __METHOD__ ) {
1050  sqlsrv_begin_transaction( $this->mConn );
1051  $this->mTrxLevel = 1;
1052  }
1053 
1058  protected function doCommit( $fname = __METHOD__ ) {
1059  sqlsrv_commit( $this->mConn );
1060  $this->mTrxLevel = 0;
1061  }
1062 
1068  protected function doRollback( $fname = __METHOD__ ) {
1069  sqlsrv_rollback( $this->mConn );
1070  $this->mTrxLevel = 0;
1071  }
1072 
1081  private function escapeIdentifier( $identifier ) {
1082  if ( strlen( $identifier ) == 0 ) {
1083  throw new MWException( "An identifier must not be empty" );
1084  }
1085  if ( strlen( $identifier ) > 128 ) {
1086  throw new MWException( "The identifier '$identifier' is too long (max. 128)" );
1087  }
1088  if ( ( strpos( $identifier, '[' ) !== false )
1089  || ( strpos( $identifier, ']' ) !== false )
1090  ) {
1091  // It may be allowed if you quoted with double quotation marks, but
1092  // that would break if QUOTED_IDENTIFIER is OFF
1093  throw new MWException( "Square brackets are not allowed in '$identifier'" );
1094  }
1095 
1096  return "[$identifier]";
1097  }
1098 
1103  public function strencode( $s ) {
1104  // Should not be called by us
1105 
1106  return str_replace( "'", "''", $s );
1107  }
1108 
1113  public function addQuotes( $s ) {
1114  if ( $s instanceof MssqlBlob ) {
1115  return $s->fetch();
1116  } elseif ( $s instanceof Blob ) {
1117  // this shouldn't really ever be called, but it's here if needed
1118  // (and will quite possibly make the SQL error out)
1119  $blob = new MssqlBlob( $s->fetch() );
1120  return $blob->fetch();
1121  } else {
1122  if ( is_bool( $s ) ) {
1123  $s = $s ? 1 : 0;
1124  }
1125  return parent::addQuotes( $s );
1126  }
1127  }
1128 
1133  public function addIdentifierQuotes( $s ) {
1134  // http://msdn.microsoft.com/en-us/library/aa223962.aspx
1135  return '[' . $s . ']';
1136  }
1137 
1142  public function isQuotedIdentifier( $name ) {
1143  return strlen( $name ) && $name[0] == '[' && substr( $name, -1, 1 ) == ']';
1144  }
1145 
1152  protected function escapeLikeInternal( $s ) {
1153  return addcslashes( $s, '\%_[]^' );
1154  }
1155 
1166  public function buildLike() {
1167  $params = func_get_args();
1168  if ( count( $params ) > 0 && is_array( $params[0] ) ) {
1169  $params = $params[0];
1170  }
1171 
1172  return parent::buildLike( $params ) . " ESCAPE '\' ";
1173  }
1174 
1179  public function selectDB( $db ) {
1180  try {
1181  $this->mDBname = $db;
1182  $this->query( "USE $db" );
1183  return true;
1184  } catch ( Exception $e ) {
1185  return false;
1186  }
1187  }
1188 
1194  public function makeSelectOptions( $options ) {
1195  $tailOpts = '';
1196  $startOpts = '';
1197 
1198  $noKeyOptions = [];
1199  foreach ( $options as $key => $option ) {
1200  if ( is_numeric( $key ) ) {
1201  $noKeyOptions[$option] = true;
1202  }
1203  }
1204 
1205  $tailOpts .= $this->makeGroupByWithHaving( $options );
1206 
1207  $tailOpts .= $this->makeOrderBy( $options );
1208 
1209  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1210  $startOpts .= 'DISTINCT';
1211  }
1212 
1213  if ( isset( $noKeyOptions['FOR XML'] ) ) {
1214  // used in group concat field emulation
1215  $tailOpts .= " FOR XML PATH('')";
1216  }
1217 
1218  // we want this to be compatible with the output of parent::makeSelectOptions()
1219  return [ $startOpts, '', $tailOpts, '' ];
1220  }
1221 
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->mSubqueryId;
1259  $this->mSubqueryId++;
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 
1273  public function getSearchEngine() {
1274  return "SearchMssql";
1275  }
1276 
1283  private function getBinaryColumns( $table ) {
1284  $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
1285  $tableRaw = array_pop( $tableRawArr );
1286 
1287  if ( $this->mBinaryColumnCache === null ) {
1288  $this->populateColumnCaches();
1289  }
1290 
1291  return isset( $this->mBinaryColumnCache[$tableRaw] )
1292  ? $this->mBinaryColumnCache[$tableRaw]
1293  : [];
1294  }
1295 
1300  private function getBitColumns( $table ) {
1301  $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
1302  $tableRaw = array_pop( $tableRawArr );
1303 
1304  if ( $this->mBitColumnCache === null ) {
1305  $this->populateColumnCaches();
1306  }
1307 
1308  return isset( $this->mBitColumnCache[$tableRaw] )
1309  ? $this->mBitColumnCache[$tableRaw]
1310  : [];
1311  }
1312 
1313  private function populateColumnCaches() {
1314  $res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
1315  [
1316  'TABLE_CATALOG' => $this->mDBname,
1317  'TABLE_SCHEMA' => $this->mSchema,
1318  'DATA_TYPE' => [ 'varbinary', 'binary', 'image', 'bit' ]
1319  ] );
1320 
1321  $this->mBinaryColumnCache = [];
1322  $this->mBitColumnCache = [];
1323  foreach ( $res as $row ) {
1324  if ( $row->DATA_TYPE == 'bit' ) {
1325  $this->mBitColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
1326  } else {
1327  $this->mBinaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
1328  }
1329  }
1330  }
1331 
1337  function tableName( $name, $format = 'quoted' ) {
1338  # Replace reserved words with better ones
1339  switch ( $name ) {
1340  case 'user':
1341  return $this->realTableName( 'mwuser', $format );
1342  default:
1343  return $this->realTableName( $name, $format );
1344  }
1345  }
1346 
1353  function realTableName( $name, $format = 'quoted' ) {
1354  $table = parent::tableName( $name, $format );
1355  if ( $format == 'split' ) {
1356  // Used internally, we want the schema split off from the table name and returned
1357  // as a list with 3 elements (database, schema, table)
1358  $table = explode( '.', $table );
1359  while ( count( $table ) < 3 ) {
1360  array_unshift( $table, false );
1361  }
1362  }
1363  return $table;
1364  }
1365 
1373  public function dropTable( $tableName, $fName = __METHOD__ ) {
1374  if ( !$this->tableExists( $tableName, $fName ) ) {
1375  return false;
1376  }
1377 
1378  // parent function incorrectly appends CASCADE, which we don't want
1379  $sql = "DROP TABLE " . $this->tableName( $tableName );
1380 
1381  return $this->query( $sql, $fName );
1382  }
1383 
1390  public function prepareStatements( $value = null ) {
1391  return wfSetVar( $this->mPrepareStatements, $value );
1392  }
1393 
1400  public function scrollableCursor( $value = null ) {
1401  return wfSetVar( $this->mScrollableCursor, $value );
1402  }
1403 
1410  public function ignoreErrors( array $value = null ) {
1411  return wfSetVar( $this->mIgnoreErrors, $value );
1412  }
1413 } // end DatabaseMssql class
1414 
1420 class MssqlField implements Field {
1422 
1423  function __construct( $info ) {
1424  $this->name = $info['COLUMN_NAME'];
1425  $this->tableName = $info['TABLE_NAME'];
1426  $this->default = $info['COLUMN_DEFAULT'];
1427  $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
1428  $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
1429  $this->type = $info['DATA_TYPE'];
1430  }
1431 
1432  function name() {
1433  return $this->name;
1434  }
1435 
1436  function tableName() {
1437  return $this->tableName;
1438  }
1439 
1440  function defaultValue() {
1441  return $this->default;
1442  }
1443 
1444  function maxLength() {
1445  return $this->max_length;
1446  }
1447 
1448  function isNullable() {
1449  return $this->nullable;
1450  }
1451 
1452  function type() {
1453  return $this->type;
1454  }
1455 }
1456 
1457 class MssqlBlob extends Blob {
1458  public function __construct( $data ) {
1459  if ( $data instanceof MssqlBlob ) {
1460  return $data;
1461  } elseif ( $data instanceof Blob ) {
1462  $this->mData = $data->fetch();
1463  } elseif ( is_array( $data ) && is_object( $data ) ) {
1464  $this->mData = serialize( $data );
1465  } else {
1466  $this->mData = $data;
1467  }
1468  }
1469 
1475  public function fetch() {
1476  if ( $this->mData === null ) {
1477  return 'null';
1478  }
1479 
1480  $ret = '0x';
1481  $dataLength = strlen( $this->mData );
1482  for ( $i = 0; $i < $dataLength; $i++ ) {
1483  $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
1484  }
1485 
1486  return $ret;
1487  }
1488 }
1489 
1491  private $mSeekTo = null;
1492 
1496  public function fetchObject() {
1497  $res = $this->result;
1498 
1499  if ( $this->mSeekTo !== null ) {
1500  $result = sqlsrv_fetch_object( $res, 'stdClass', [],
1501  SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
1502  $this->mSeekTo = null;
1503  } else {
1504  $result = sqlsrv_fetch_object( $res );
1505  }
1506 
1507  // MediaWiki expects us to return boolean false when there are no more rows instead of null
1508  if ( $result === null ) {
1509  return false;
1510  }
1511 
1512  return $result;
1513  }
1514 
1518  public function fetchRow() {
1519  $res = $this->result;
1520 
1521  if ( $this->mSeekTo !== null ) {
1522  $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
1523  SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
1524  $this->mSeekTo = null;
1525  } else {
1526  $result = sqlsrv_fetch_array( $res );
1527  }
1528 
1529  // MediaWiki expects us to return boolean false when there are no more rows instead of null
1530  if ( $result === null ) {
1531  return false;
1532  }
1533 
1534  return $result;
1535  }
1536 
1541  public function seek( $row ) {
1542  $res = $this->result;
1543 
1544  // check bounds
1545  $numRows = $this->db->numRows( $res );
1546  $row = intval( $row );
1547 
1548  if ( $numRows === 0 ) {
1549  return false;
1550  } elseif ( $row < 0 || $row > $numRows - 1 ) {
1551  return false;
1552  }
1553 
1554  // Unlike MySQL, the seek actually happens on the next access
1555  $this->mSeekTo = $row;
1556  return true;
1557  }
1558 }
fieldExists($table, $field, $fname=__METHOD__)
Query whether a given column exists in the mediawiki schema.
getBinaryColumns($table)
Returns an associative array for fields that are of type varbinary, binary, or image $table can be ei...
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
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
the array() calling protocol came about after MediaWiki 1.4rc1.
doCommit($fname=__METHOD__)
End a transaction.
Utility classThis allows us to distinguish a blob from a normal string and an array of strings...
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
realTableName($name, $format= 'quoted')
call this instead of tableName() in the updater when renaming tables
$success
buildGroupConcatField($delim, $table, $field, $conds= '', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
doBegin($fname=__METHOD__)
Begin a transaction, committing any previously open transaction.
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:1932
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:1798
const LIST_NAMES
Definition: Defines.php:195
makeOrderBy($options)
Returns an optional ORDER BY.
Definition: Database.php:1217
doRollback($fname=__METHOD__)
Rollback a transaction.
insertSelect($destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[])
INSERT SELECT wrapper $varMap must be an associative array of the form array( 'dest1' => 'source1'...
resource $mConn
Database connection.
Definition: Database.php:52
unionSupportsOrderAndLimit()
Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries within th...
isNullable()
Whether this field can store NULL values.
$wgDBmwschema
Mediawiki schema.
$value
isQuotedIdentifier($name)
Base for all database-specific classes representing information about database fields.
fieldName($res, $n)
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
dataSeek($res, $row)
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
$wgDBWindowsAuthentication
Use Windows Authentication instead of $wgDBuser / $wgDBpassword for MS SQL Server.
close()
Closes a database connection.
Definition: Database.php:694
getBitColumns($table)
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
buildConcat($stringList)
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 '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:Associative array mapping language codes to prefixed links of the form"language:title".&$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':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:1796
freeResult($res)
Free a result object returned by query() or select().
escapeIdentifier($identifier)
Escapes a identifier for use inm SQL.
$last
__construct($info)
estimateRowCount($table, $vars= '*', $conds= '', $fname=__METHOD__, $options=[])
Estimate rows in dataset Returns estimated count, based on SHOWPLAN_ALL output This is not necessaril...
const LIST_AND
Definition: Defines.php:193
name()
Field name.
scrollableCursor($value=null)
Called in the installer and updater.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1004
const LIST_COMMA
Definition: Defines.php:192
indexInfo($table, $index, $fname=__METHOD__)
Returns information about an index If errors are explicitly ignored, returns NULL on failure...
$res
Definition: database.txt:21
fieldInfo($table, $field)
mysql_fetch_field() wrapper Returns false if the field doesn't exist
$params
tableExists($table, $fname=__METHOD__)
deleteJoin($delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
debug($debug=null)
Boolean, controls output of large amounts of debug information.
Definition: Database.php:199
makeSelectOptions($options)
makeList($a, $mode=LIST_COMMA, $binaryColumns=[])
Makes an encoded list of strings from an array.
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
tableName()
Name of table this field belongs to.
const LIST_SET
Definition: Defines.php:194
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
resultObject($result)
escapeLikeInternal($s)
MS SQL supports more pattern operators than other databases (ex: [,],^)
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
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
__construct($data)
tableName($name, $format= 'quoted')
insertId()
This must be called after nextSequenceVal.
LimitToTopN($sql)
If there is a limit clause, parse it, strip it, and pass the remaining SQL through limitResult() with...
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined...
Definition: Setup.php:35
$wgDBport
Database port number (for PostgreSQL and Microsoft SQL Server).
insert($table, $arrToInsert, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
limitResult($sql, $limit, $offset=false)
Construct a LIMIT query with optional offset This is used for query pages.
query($sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition: Database.php:780
closeConnection()
Closes a database connection, if it is open Returns success, true if already closed.
implicitGroupby()
Returns true if this database does an implicit sort when doing GROUP BY.
makeUpdateOptions($options)
Make UPDATE options for the DatabaseBase::update function.
Definition: Database.php:1500
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition: hooks.txt:1004
select($table, $vars, $conds= '', $fname=__METHOD__, $options=[], $join_conds=[])
SELECT wrapper.
fetch()
Returns an unquoted hex representation of a binary string for insertion into varbinary-type fields...
type()
Database type.
Utility class.
$version
Definition: parserTests.php:85
makeGroupByWithHaving($options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1191
Result wrapper for grabbing data queried by someone else.
open($server, $user, $password, $dbName)
Usually aborts on failure.
serialize()
Definition: ApiMessage.php:94
dropTable($tableName, $fName=__METHOD__)
Delete a table.
buildLike()
MS SQL requires specifying the escape character used in a LIKE query or using Square brackets to surr...
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:1996
update($table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
selectSQLText($table, $vars, $conds= '', $fname=__METHOD__, $options=[], $join_conds=[])
SELECT wrapper.
prepareStatements($value=null)
Called in the installer and updater.
ignoreErrors(array $value=null)
Called in the installer and updater.
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
$matches
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310
textFieldSize($table, $field)