[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/db/ -> DatabaseMssql.php (source)

   1  <?php
   2  /**
   3   * This is the MS SQL Server Native database abstraction layer.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup Database
  22   * @author Joel Penner <a-joelpe at microsoft dot com>
  23   * @author Chris Pucci <a-cpucci at microsoft dot com>
  24   * @author Ryan Biesemeyer <v-ryanbi at microsoft dot com>
  25   * @author Ryan Schmidt <skizzerz at gmail dot com>
  26   */
  27  
  28  /**
  29   * @ingroup Database
  30   */
  31  class DatabaseMssql extends DatabaseBase {
  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  
  42      protected $mPort;
  43  
  44  	public function cascadingDeletes() {
  45          return true;
  46      }
  47  
  48  	public function cleanupTriggers() {
  49          return false;
  50      }
  51  
  52  	public function strictIPs() {
  53          return false;
  54      }
  55  
  56  	public function realTimestamps() {
  57          return false;
  58      }
  59  
  60  	public function implicitGroupby() {
  61          return false;
  62      }
  63  
  64  	public function implicitOrderby() {
  65          return false;
  66      }
  67  
  68  	public function functionalIndexes() {
  69          return true;
  70      }
  71  
  72  	public function unionSupportsOrderAndLimit() {
  73          return false;
  74      }
  75  
  76      /**
  77       * Usually aborts on failure
  78       * @param string $server
  79       * @param string $user
  80       * @param string $password
  81       * @param string $dbName
  82       * @throws DBConnectionError
  83       * @return bool|DatabaseBase|null
  84       */
  85  	public function open( $server, $user, $password, $dbName ) {
  86          # Test for driver support, to avoid suppressed fatal error
  87          if ( !function_exists( 'sqlsrv_connect' ) ) {
  88              throw new DBConnectionError(
  89                  $this,
  90                  "Microsoft SQL Server Native (sqlsrv) functions missing.
  91                  You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
  92              );
  93          }
  94  
  95          global $wgDBport, $wgDBWindowsAuthentication;
  96  
  97          # e.g. the class is being loaded
  98          if ( !strlen( $user ) ) {
  99              return null;
 100          }
 101  
 102          $this->close();
 103          $this->mServer = $server;
 104          $this->mPort = $wgDBport;
 105          $this->mUser = $user;
 106          $this->mPassword = $password;
 107          $this->mDBname = $dbName;
 108  
 109          $connectionInfo = array();
 110  
 111          if ( $dbName ) {
 112              $connectionInfo['Database'] = $dbName;
 113          }
 114  
 115          // Decide which auth scenerio to use
 116          // if we are using Windows auth, don't add credentials to $connectionInfo
 117          if ( !$wgDBWindowsAuthentication ) {
 118              $connectionInfo['UID'] = $user;
 119              $connectionInfo['PWD'] = $password;
 120          }
 121  
 122          wfSuppressWarnings();
 123          $this->mConn = sqlsrv_connect( $server, $connectionInfo );
 124          wfRestoreWarnings();
 125  
 126          if ( $this->mConn === false ) {
 127              throw new DBConnectionError( $this, $this->lastError() );
 128          }
 129  
 130          $this->mOpened = true;
 131  
 132          return $this->mConn;
 133      }
 134  
 135      /**
 136       * Closes a database connection, if it is open
 137       * Returns success, true if already closed
 138       * @return bool
 139       */
 140  	protected function closeConnection() {
 141          return sqlsrv_close( $this->mConn );
 142      }
 143  
 144      /**
 145       * @param bool|MssqlResultWrapper|resource $result
 146       * @return bool|MssqlResultWrapper
 147       */
 148  	public function resultObject( $result ) {
 149          if ( empty( $result ) ) {
 150              return false;
 151          } elseif ( $result instanceof MssqlResultWrapper ) {
 152              return $result;
 153          } elseif ( $result === true ) {
 154              // Successful write query
 155              return $result;
 156          } else {
 157              return new MssqlResultWrapper( $this, $result );
 158          }
 159      }
 160  
 161      /**
 162       * @param string $sql
 163       * @return bool|MssqlResult
 164       * @throws DBUnexpectedError
 165       */
 166  	protected function doQuery( $sql ) {
 167          if ( $this->debug() ) {
 168              wfDebug( "SQL: [$sql]\n" );
 169          }
 170          $this->offset = 0;
 171  
 172          // several extensions seem to think that all databases support limits
 173          // via LIMIT N after the WHERE clause well, MSSQL uses SELECT TOP N,
 174          // so to catch any of those extensions we'll do a quick check for a
 175          // LIMIT clause and pass $sql through $this->LimitToTopN() which parses
 176          // the limit clause and passes the result to $this->limitResult();
 177          if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
 178              // massage LIMIT -> TopN
 179              $sql = $this->LimitToTopN( $sql );
 180          }
 181  
 182          // MSSQL doesn't have EXTRACT(epoch FROM XXX)
 183          if ( preg_match( '#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
 184              // This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970
 185              $sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
 186          }
 187  
 188          // perform query
 189  
 190          // SQLSRV_CURSOR_STATIC is slower than SQLSRV_CURSOR_CLIENT_BUFFERED (one of the two is
 191          // needed if we want to be able to seek around the result set), however CLIENT_BUFFERED
 192          // has a bug in the sqlsrv driver where wchar_t types (such as nvarchar) that are empty
 193          // strings make php throw a fatal error "Severe error translating Unicode"
 194          if ( $this->mScrollableCursor ) {
 195              $scrollArr = array( 'Scrollable' => SQLSRV_CURSOR_STATIC );
 196          } else {
 197              $scrollArr = array();
 198          }
 199  
 200          if ( $this->mPrepareStatements ) {
 201              // we do prepare + execute so we can get its field metadata for later usage if desired
 202              $stmt = sqlsrv_prepare( $this->mConn, $sql, array(), $scrollArr );
 203              $success = sqlsrv_execute( $stmt );
 204          } else {
 205              $stmt = sqlsrv_query( $this->mConn, $sql, array(), $scrollArr );
 206              $success = (bool)$stmt;
 207          }
 208  
 209          if ( $this->mIgnoreDupKeyErrors ) {
 210              // ignore duplicate key errors, but nothing else
 211              // this emulates INSERT IGNORE in MySQL
 212              if ( $success === false ) {
 213                  $errors = sqlsrv_errors( SQLSRV_ERR_ERRORS );
 214                  $success = true;
 215  
 216                  foreach ( $errors as $err ) {
 217                      if ( $err['SQLSTATE'] == '23000' && $err['code'] == '2601' ) {
 218                          continue; // duplicate key error caused by unique index
 219                      } elseif ( $err['SQLSTATE'] == '23000' && $err['code'] == '2627' ) {
 220                          continue; // duplicate key error caused by primary key
 221                      } elseif ( $err['SQLSTATE'] == '01000' && $err['code'] == '3621' ) {
 222                          continue; // generic "the statement has been terminated" error
 223                      }
 224  
 225                      $success = false; // getting here means we got an error we weren't expecting
 226                      break;
 227                  }
 228  
 229                  if ( $success ) {
 230                      $this->mAffectedRows = 0;
 231                      return $stmt;
 232                  }
 233              }
 234          }
 235  
 236          if ( $success === false ) {
 237              return false;
 238          }
 239          // remember number of rows affected
 240          $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
 241  
 242          return $stmt;
 243      }
 244  
 245  	public function freeResult( $res ) {
 246          if ( $res instanceof ResultWrapper ) {
 247              $res = $res->result;
 248          }
 249  
 250          sqlsrv_free_stmt( $res );
 251      }
 252  
 253      /**
 254       * @param MssqlResultWrapper $res
 255       * @return stdClass
 256       */
 257  	public function fetchObject( $res ) {
 258          // $res is expected to be an instance of MssqlResultWrapper here
 259          return $res->fetchObject();
 260      }
 261  
 262      /**
 263       * @param MssqlResultWrapper $res
 264       * @return array
 265       */
 266  	public function fetchRow( $res ) {
 267          return $res->fetchRow();
 268      }
 269  
 270      /**
 271       * @param mixed $res
 272       * @return int
 273       */
 274  	public function numRows( $res ) {
 275          if ( $res instanceof ResultWrapper ) {
 276              $res = $res->result;
 277          }
 278  
 279          return sqlsrv_num_rows( $res );
 280      }
 281  
 282      /**
 283       * @param mixed $res
 284       * @return int
 285       */
 286  	public function numFields( $res ) {
 287          if ( $res instanceof ResultWrapper ) {
 288              $res = $res->result;
 289          }
 290  
 291          return sqlsrv_num_fields( $res );
 292      }
 293  
 294      /**
 295       * @param mixed $res
 296       * @param int $n
 297       * @return int
 298       */
 299  	public function fieldName( $res, $n ) {
 300          if ( $res instanceof ResultWrapper ) {
 301              $res = $res->result;
 302          }
 303  
 304          $metadata = sqlsrv_field_metadata( $res );
 305          return $metadata[$n]['Name'];
 306      }
 307  
 308      /**
 309       * This must be called after nextSequenceVal
 310       * @return int|null
 311       */
 312  	public function insertId() {
 313          return $this->mInsertId;
 314      }
 315  
 316      /**
 317       * @param MssqlResultWrapper $res
 318       * @param int $row
 319       * @return bool
 320       */
 321  	public function dataSeek( $res, $row ) {
 322          return $res->seek( $row );
 323      }
 324  
 325      /**
 326       * @return string
 327       */
 328  	public function lastError() {
 329          $strRet = '';
 330          $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
 331          if ( $retErrors != null ) {
 332              foreach ( $retErrors as $arrError ) {
 333                  $strRet .= $this->formatError( $arrError ) . "\n";
 334              }
 335          } else {
 336              $strRet = "No errors found";
 337          }
 338  
 339          return $strRet;
 340      }
 341  
 342      /**
 343       * @param array $err
 344       * @return string
 345       */
 346  	private function formatError( $err ) {
 347          return '[SQLSTATE ' . $err['SQLSTATE'] . '][Error Code ' . $err['code'] . ']' . $err['message'];
 348      }
 349  
 350      /**
 351       * @return string
 352       */
 353  	public function lastErrno() {
 354          $err = sqlsrv_errors( SQLSRV_ERR_ALL );
 355          if ( $err !== null && isset( $err[0] ) ) {
 356              return $err[0]['code'];
 357          } else {
 358              return 0;
 359          }
 360      }
 361  
 362      /**
 363       * @return int
 364       */
 365  	public function affectedRows() {
 366          return $this->mAffectedRows;
 367      }
 368  
 369      /**
 370       * SELECT wrapper
 371       *
 372       * @param mixed $table Array or string, table name(s) (prefix auto-added)
 373       * @param mixed $vars Array or string, field name(s) to be retrieved
 374       * @param mixed $conds Array or string, condition(s) for WHERE
 375       * @param string $fname Calling function name (use __METHOD__) for logs/profiling
 376       * @param array $options Associative array of options (e.g.
 377       *   array('GROUP BY' => 'page_title')), see Database::makeSelectOptions
 378       *   code for list of supported stuff
 379       * @param array $join_conds Associative array of table join conditions
 380       *   (optional) (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
 381       * @return mixed Database result resource (feed to Database::fetchObject
 382       *   or whatever), or false on failure
 383       */
 384  	public function select( $table, $vars, $conds = '', $fname = __METHOD__,
 385          $options = array(), $join_conds = array()
 386      ) {
 387          $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
 388          if ( isset( $options['EXPLAIN'] ) ) {
 389              try {
 390                  $this->mScrollableCursor = false;
 391                  $this->mPrepareStatements = false;
 392                  $this->query( "SET SHOWPLAN_ALL ON" );
 393                  $ret = $this->query( $sql, $fname );
 394                  $this->query( "SET SHOWPLAN_ALL OFF" );
 395              } catch ( DBQueryError $dqe ) {
 396                  if ( isset( $options['FOR COUNT'] ) ) {
 397                      // likely don't have privs for SHOWPLAN, so run a select count instead
 398                      $this->query( "SET SHOWPLAN_ALL OFF" );
 399                      unset( $options['EXPLAIN'] );
 400                      $ret = $this->select(
 401                          $table,
 402                          'COUNT(*) AS EstimateRows',
 403                          $conds,
 404                          $fname,
 405                          $options,
 406                          $join_conds
 407                      );
 408                  } else {
 409                      // someone actually wanted the query plan instead of an est row count
 410                      // let them know of the error
 411                      $this->mScrollableCursor = true;
 412                      $this->mPrepareStatements = true;
 413                      throw $dqe;
 414                  }
 415              }
 416              $this->mScrollableCursor = true;
 417              $this->mPrepareStatements = true;
 418              return $ret;
 419          }
 420          return $this->query( $sql, $fname );
 421      }
 422  
 423      /**
 424       * SELECT wrapper
 425       *
 426       * @param mixed $table Array or string, table name(s) (prefix auto-added)
 427       * @param mixed $vars Array or string, field name(s) to be retrieved
 428       * @param mixed $conds Array or string, condition(s) for WHERE
 429       * @param string $fname Calling function name (use __METHOD__) for logs/profiling
 430       * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
 431       *   see Database::makeSelectOptions code for list of supported stuff
 432       * @param array $join_conds Associative array of table join conditions (optional)
 433       *    (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
 434       * @return string The SQL text
 435       */
 436  	public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
 437          $options = array(), $join_conds = array()
 438      ) {
 439          if ( isset( $options['EXPLAIN'] ) ) {
 440              unset( $options['EXPLAIN'] );
 441          }
 442  
 443          $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
 444  
 445          // try to rewrite aggregations of bit columns (currently MAX and MIN)
 446          if ( strpos( $sql, 'MAX(' ) !== false || strpos( $sql, 'MIN(' ) !== false ) {
 447              $bitColumns = array();
 448              if ( is_array( $table ) ) {
 449                  foreach ( $table as $t ) {
 450                      $bitColumns += $this->getBitColumns( $this->tableName( $t ) );
 451                  }
 452              } else {
 453                  $bitColumns = $this->getBitColumns( $this->tableName( $table ) );
 454              }
 455  
 456              foreach ( $bitColumns as $col => $info ) {
 457                  $replace = array(
 458                      "MAX({$col})" => "MAX(CAST({$col} AS tinyint))",
 459                      "MIN({$col})" => "MIN(CAST({$col} AS tinyint))",
 460                  );
 461                  $sql = str_replace( array_keys( $replace ), array_values( $replace ), $sql );
 462              }
 463          }
 464  
 465          return $sql;
 466      }
 467  
 468  	public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
 469          $fname = __METHOD__
 470      ) {
 471          $this->mScrollableCursor = false;
 472          try {
 473              parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname );
 474          } catch ( Exception $e ) {
 475              $this->mScrollableCursor = true;
 476              throw $e;
 477          }
 478          $this->mScrollableCursor = true;
 479      }
 480  
 481  	public function delete( $table, $conds, $fname = __METHOD__ ) {
 482          $this->mScrollableCursor = false;
 483          try {
 484              parent::delete( $table, $conds, $fname );
 485          } catch ( Exception $e ) {
 486              $this->mScrollableCursor = true;
 487              throw $e;
 488          }
 489          $this->mScrollableCursor = true;
 490      }
 491  
 492      /**
 493       * Estimate rows in dataset
 494       * Returns estimated count, based on SHOWPLAN_ALL output
 495       * This is not necessarily an accurate estimate, so use sparingly
 496       * Returns -1 if count cannot be found
 497       * Takes same arguments as Database::select()
 498       * @param string $table
 499       * @param string $vars
 500       * @param string $conds
 501       * @param string $fname
 502       * @param array $options
 503       * @return int
 504       */
 505  	public function estimateRowCount( $table, $vars = '*', $conds = '',
 506          $fname = __METHOD__, $options = array()
 507      ) {
 508          // http://msdn2.microsoft.com/en-us/library/aa259203.aspx
 509          $options['EXPLAIN'] = true;
 510          $options['FOR COUNT'] = true;
 511          $res = $this->select( $table, $vars, $conds, $fname, $options );
 512  
 513          $rows = -1;
 514          if ( $res ) {
 515              $row = $this->fetchRow( $res );
 516  
 517              if ( isset( $row['EstimateRows'] ) ) {
 518                  $rows = $row['EstimateRows'];
 519              }
 520          }
 521  
 522          return $rows;
 523      }
 524  
 525      /**
 526       * Returns information about an index
 527       * If errors are explicitly ignored, returns NULL on failure
 528       * @param string $table
 529       * @param string $index
 530       * @param string $fname
 531       * @return array|bool|null
 532       */
 533  	public function indexInfo( $table, $index, $fname = __METHOD__ ) {
 534          # This does not return the same info as MYSQL would, but that's OK
 535          # because MediaWiki never uses the returned value except to check for
 536          # the existance of indexes.
 537          $sql = "sp_helpindex '" . $table . "'";
 538          $res = $this->query( $sql, $fname );
 539          if ( !$res ) {
 540              return null;
 541          }
 542  
 543          $result = array();
 544          foreach ( $res as $row ) {
 545              if ( $row->index_name == $index ) {
 546                  $row->Non_unique = !stristr( $row->index_description, "unique" );
 547                  $cols = explode( ", ", $row->index_keys );
 548                  foreach ( $cols as $col ) {
 549                      $row->Column_name = trim( $col );
 550                      $result[] = clone $row;
 551                  }
 552              } elseif ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) {
 553                  $row->Non_unique = 0;
 554                  $cols = explode( ", ", $row->index_keys );
 555                  foreach ( $cols as $col ) {
 556                      $row->Column_name = trim( $col );
 557                      $result[] = clone $row;
 558                  }
 559              }
 560          }
 561  
 562          return empty( $result ) ? false : $result;
 563      }
 564  
 565      /**
 566       * INSERT wrapper, inserts an array into a table
 567       *
 568       * $arrToInsert may be a single associative array, or an array of these with numeric keys, for
 569       * multi-row insert.
 570       *
 571       * Usually aborts on failure
 572       * If errors are explicitly ignored, returns success
 573       * @param string $table
 574       * @param array $arrToInsert
 575       * @param string $fname
 576       * @param array $options
 577       * @throws DBQueryError
 578       * @return bool
 579       */
 580  	public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = array() ) {
 581          # No rows to insert, easy just return now
 582          if ( !count( $arrToInsert ) ) {
 583              return true;
 584          }
 585  
 586          if ( !is_array( $options ) ) {
 587              $options = array( $options );
 588          }
 589  
 590          $table = $this->tableName( $table );
 591  
 592          if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) { // Not multi row
 593              $arrToInsert = array( 0 => $arrToInsert ); // make everything multi row compatible
 594          }
 595  
 596          // We know the table we're inserting into, get its identity column
 597          $identity = null;
 598          // strip matching square brackets and the db/schema from table name
 599          $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
 600          $tableRaw = array_pop( $tableRawArr );
 601          $res = $this->doQuery(
 602              "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
 603                  "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
 604          );
 605          if ( $res && sqlsrv_has_rows( $res ) ) {
 606              // There is an identity for this table.
 607              $identityArr = sqlsrv_fetch_array( $res, SQLSRV_FETCH_ASSOC );
 608              $identity = array_pop( $identityArr );
 609          }
 610          sqlsrv_free_stmt( $res );
 611  
 612          // Determine binary/varbinary fields so we can encode data as a hex string like 0xABCDEF
 613          $binaryColumns = $this->getBinaryColumns( $table );
 614  
 615          // INSERT IGNORE is not supported by SQL Server
 616          // remove IGNORE from options list and set ignore flag to true
 617          if ( in_array( 'IGNORE', $options ) ) {
 618              $options = array_diff( $options, array( 'IGNORE' ) );
 619              $this->mIgnoreDupKeyErrors = true;
 620          }
 621  
 622          foreach ( $arrToInsert as $a ) {
 623              // start out with empty identity column, this is so we can return
 624              // it as a result of the insert logic
 625              $sqlPre = '';
 626              $sqlPost = '';
 627              $identityClause = '';
 628  
 629              // if we have an identity column
 630              if ( $identity ) {
 631                  // iterate through
 632                  foreach ( $a as $k => $v ) {
 633                      if ( $k == $identity ) {
 634                          if ( !is_null( $v ) ) {
 635                              // there is a value being passed to us,
 636                              // we need to turn on and off inserted identity
 637                              $sqlPre = "SET IDENTITY_INSERT $table ON;";
 638                              $sqlPost = ";SET IDENTITY_INSERT $table OFF;";
 639                          } else {
 640                              // we can't insert NULL into an identity column,
 641                              // so remove the column from the insert.
 642                              unset( $a[$k] );
 643                          }
 644                      }
 645                  }
 646  
 647                  // we want to output an identity column as result
 648                  $identityClause = "OUTPUT INSERTED.$identity ";
 649              }
 650  
 651              $keys = array_keys( $a );
 652  
 653              // Build the actual query
 654              $sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
 655                  " INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
 656  
 657              $first = true;
 658              foreach ( $a as $key => $value ) {
 659                  if ( isset( $binaryColumns[$key] ) ) {
 660                      $value = new MssqlBlob( $value );
 661                  }
 662                  if ( $first ) {
 663                      $first = false;
 664                  } else {
 665                      $sql .= ',';
 666                  }
 667                  if ( is_null( $value ) ) {
 668                      $sql .= 'null';
 669                  } elseif ( is_array( $value ) || is_object( $value ) ) {
 670                      if ( is_object( $value ) && $value instanceof Blob ) {
 671                          $sql .= $this->addQuotes( $value );
 672                      } else {
 673                          $sql .= $this->addQuotes( serialize( $value ) );
 674                      }
 675                  } else {
 676                      $sql .= $this->addQuotes( $value );
 677                  }
 678              }
 679              $sql .= ')' . $sqlPost;
 680  
 681              // Run the query
 682              $this->mScrollableCursor = false;
 683              try {
 684                  $ret = $this->query( $sql );
 685              } catch ( Exception $e ) {
 686                  $this->mScrollableCursor = true;
 687                  $this->mIgnoreDupKeyErrors = false;
 688                  throw $e;
 689              }
 690              $this->mScrollableCursor = true;
 691  
 692              if ( !is_null( $identity ) ) {
 693                  // then we want to get the identity column value we were assigned and save it off
 694                  $row = $ret->fetchObject();
 695                  if( is_object( $row ) ){
 696                      $this->mInsertId = $row->$identity;
 697                  }
 698              }
 699          }
 700          $this->mIgnoreDupKeyErrors = false;
 701          return $ret;
 702      }
 703  
 704      /**
 705       * INSERT SELECT wrapper
 706       * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
 707       * Source items may be literals rather than field names, but strings should
 708       * be quoted with Database::addQuotes().
 709       * @param string $destTable
 710       * @param array|string $srcTable May be an array of tables.
 711       * @param array $varMap
 712       * @param array $conds May be "*" to copy the whole table.
 713       * @param string $fname
 714       * @param array $insertOptions
 715       * @param array $selectOptions
 716       * @throws DBQueryError
 717       * @return null|ResultWrapper
 718       */
 719  	public function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
 720          $insertOptions = array(), $selectOptions = array()
 721      ) {
 722          $this->mScrollableCursor = false;
 723          try {
 724              $ret = parent::insertSelect(
 725                  $destTable,
 726                  $srcTable,
 727                  $varMap,
 728                  $conds,
 729                  $fname,
 730                  $insertOptions,
 731                  $selectOptions
 732              );
 733          } catch ( Exception $e ) {
 734              $this->mScrollableCursor = true;
 735              throw $e;
 736          }
 737          $this->mScrollableCursor = true;
 738  
 739          return $ret;
 740      }
 741  
 742      /**
 743       * UPDATE wrapper. Takes a condition array and a SET array.
 744       *
 745       * @param string $table Name of the table to UPDATE. This will be passed through
 746       *                DatabaseBase::tableName().
 747       *
 748       * @param array $values An array of values to SET. For each array element,
 749       *                the key gives the field name, and the value gives the data
 750       *                to set that field to. The data will be quoted by
 751       *                DatabaseBase::addQuotes().
 752       *
 753       * @param array $conds An array of conditions (WHERE). See
 754       *                DatabaseBase::select() for the details of the format of
 755       *                condition arrays. Use '*' to update all rows.
 756       *
 757       * @param string $fname The function name of the caller (from __METHOD__),
 758       *                for logging and profiling.
 759       *
 760       * @param array $options An array of UPDATE options, can be:
 761       *                   - IGNORE: Ignore unique key conflicts
 762       *                   - LOW_PRIORITY: MySQL-specific, see MySQL manual.
 763       * @return bool
 764       */
 765  	function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
 766          $table = $this->tableName( $table );
 767          $binaryColumns = $this->getBinaryColumns( $table );
 768  
 769          $opts = $this->makeUpdateOptions( $options );
 770          $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET, $binaryColumns );
 771  
 772          if ( $conds !== array() && $conds !== '*' ) {
 773              $sql .= " WHERE " . $this->makeList( $conds, LIST_AND, $binaryColumns );
 774          }
 775  
 776          $this->mScrollableCursor = false;
 777          try {
 778              $ret = $this->query( $sql );
 779          } catch ( Exception $e ) {
 780              $this->mScrollableCursor = true;
 781              throw $e;
 782          }
 783          $this->mScrollableCursor = true;
 784          return true;
 785      }
 786  
 787      /**
 788       * Makes an encoded list of strings from an array
 789       * @param array $a Containing the data
 790       * @param int $mode Constant
 791       *      - LIST_COMMA:          comma separated, no field names
 792       *      - LIST_AND:            ANDed WHERE clause (without the WHERE). See
 793       *        the documentation for $conds in DatabaseBase::select().
 794       *      - LIST_OR:             ORed WHERE clause (without the WHERE)
 795       *      - LIST_SET:            comma separated with field names, like a SET clause
 796       *      - LIST_NAMES:          comma separated field names
 797       * @param array $binaryColumns Contains a list of column names that are binary types
 798       *      This is a custom parameter only present for MS SQL.
 799       *
 800       * @throws MWException|DBUnexpectedError
 801       * @return string
 802       */
 803  	public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = array() ) {
 804          if ( !is_array( $a ) ) {
 805              throw new DBUnexpectedError( $this,
 806                  'DatabaseBase::makeList called with incorrect parameters' );
 807          }
 808  
 809          $first = true;
 810          $list = '';
 811  
 812          foreach ( $a as $field => $value ) {
 813              if ( $mode != LIST_NAMES && isset( $binaryColumns[$field] ) ) {
 814                  if ( is_array( $value ) ) {
 815                      foreach ( $value as &$v ) {
 816                          $v = new MssqlBlob( $v );
 817                      }
 818                  } else {
 819                      $value = new MssqlBlob( $value );
 820                  }
 821              }
 822  
 823              if ( !$first ) {
 824                  if ( $mode == LIST_AND ) {
 825                      $list .= ' AND ';
 826                  } elseif ( $mode == LIST_OR ) {
 827                      $list .= ' OR ';
 828                  } else {
 829                      $list .= ',';
 830                  }
 831              } else {
 832                  $first = false;
 833              }
 834  
 835              if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
 836                  $list .= "($value)";
 837              } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
 838                  $list .= "$value";
 839              } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
 840                  if ( count( $value ) == 0 ) {
 841                      throw new MWException( __METHOD__ . ": empty input for field $field" );
 842                  } elseif ( count( $value ) == 1 ) {
 843                      // Special-case single values, as IN isn't terribly efficient
 844                      // Don't necessarily assume the single key is 0; we don't
 845                      // enforce linear numeric ordering on other arrays here.
 846                      $value = array_values( $value );
 847                      $list .= $field . " = " . $this->addQuotes( $value[0] );
 848                  } else {
 849                      $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
 850                  }
 851              } elseif ( $value === null ) {
 852                  if ( $mode == LIST_AND || $mode == LIST_OR ) {
 853                      $list .= "$field IS ";
 854                  } elseif ( $mode == LIST_SET ) {
 855                      $list .= "$field = ";
 856                  }
 857                  $list .= 'NULL';
 858              } else {
 859                  if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
 860                      $list .= "$field = ";
 861                  }
 862                  $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
 863              }
 864          }
 865  
 866          return $list;
 867      }
 868  
 869      /**
 870       * @param string $table
 871       * @param string $field
 872       * @return int Returns the size of a text field, or -1 for "unlimited"
 873       */
 874  	public function textFieldSize( $table, $field ) {
 875          $table = $this->tableName( $table );
 876          $sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
 877              WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
 878          $res = $this->query( $sql );
 879          $row = $this->fetchRow( $res );
 880          $size = -1;
 881          if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) {
 882              $size = $row['CHARACTER_MAXIMUM_LENGTH'];
 883          }
 884  
 885          return $size;
 886      }
 887  
 888      /**
 889       * Construct a LIMIT query with optional offset
 890       * This is used for query pages
 891       *
 892       * @param string $sql SQL query we will append the limit too
 893       * @param int $limit The SQL limit
 894       * @param bool|int $offset The SQL offset (default false)
 895       * @return array|string
 896       */
 897  	public function limitResult( $sql, $limit, $offset = false ) {
 898          if ( $offset === false || $offset == 0 ) {
 899              if ( strpos( $sql, "SELECT" ) === false ) {
 900                  return "TOP {$limit} " . $sql;
 901              } else {
 902                  return preg_replace( '/\bSELECT(\s+DISTINCT)?\b/Dsi',
 903                      'SELECT$1 TOP ' . $limit, $sql, 1 );
 904              }
 905          } else {
 906              // This one is fun, we need to pull out the select list as well as any ORDER BY clause
 907              $select = $orderby = array();
 908              $s1 = preg_match( '#SELECT\s+(.+?)\s+FROM#Dis', $sql, $select );
 909              $s2 = preg_match( '#(ORDER BY\s+.+?)(\s*FOR XML .*)?$#Dis', $sql, $orderby );
 910              $overOrder = $postOrder = '';
 911              $first = $offset + 1;
 912              $last = $offset + $limit;
 913              $sub1 = 'sub_' . $this->mSubqueryId;
 914              $sub2 = 'sub_' . ( $this->mSubqueryId + 1 );
 915              $this->mSubqueryId += 2;
 916              if ( !$s1 ) {
 917                  // wat
 918                  throw new DBUnexpectedError( $this, "Attempting to LIMIT a non-SELECT query\n" );
 919              }
 920              if ( !$s2 ) {
 921                  // no ORDER BY
 922                  $overOrder = 'ORDER BY (SELECT 1)';
 923              } else {
 924                  if ( !isset( $orderby[2] ) || !$orderby[2] ) {
 925                      // don't need to strip it out if we're using a FOR XML clause
 926                      $sql = str_replace( $orderby[1], '', $sql );
 927                  }
 928                  $overOrder = $orderby[1];
 929                  $postOrder = ' ' . $overOrder;
 930              }
 931              $sql = "SELECT {$select[1]}
 932                      FROM (
 933                          SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
 934                          FROM ({$sql}) {$sub1}
 935                      ) {$sub2}
 936                      WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
 937  
 938              return $sql;
 939          }
 940      }
 941  
 942      /**
 943       * If there is a limit clause, parse it, strip it, and pass the remaining
 944       * SQL through limitResult() with the appropriate parameters. Not the
 945       * prettiest solution, but better than building a whole new parser. This
 946       * exists becase there are still too many extensions that don't use dynamic
 947       * sql generation.
 948       *
 949       * @param string $sql
 950       * @return array|mixed|string
 951       */
 952  	public function LimitToTopN( $sql ) {
 953          // Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
 954          $pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
 955          if ( preg_match( $pattern, $sql, $matches ) ) {
 956              // row_count = $matches[4]
 957              $row_count = $matches[4];
 958              // offset = $matches[3] OR $matches[6]
 959              $offset = $matches[3] or
 960              $offset = $matches[6] or
 961              $offset = false;
 962  
 963              // strip the matching LIMIT clause out
 964              $sql = str_replace( $matches[0], '', $sql );
 965  
 966              return $this->limitResult( $sql, $row_count, $offset );
 967          }
 968  
 969          return $sql;
 970      }
 971  
 972      /**
 973       * @return string Wikitext of a link to the server software's web site
 974       */
 975  	public function getSoftwareLink() {
 976          return "[{{int:version-db-mssql-url}} MS SQL Server]";
 977      }
 978  
 979      /**
 980       * @return string Version information from the database
 981       */
 982  	public function getServerVersion() {
 983          $server_info = sqlsrv_server_info( $this->mConn );
 984          $version = 'Error';
 985          if ( isset( $server_info['SQLServerVersion'] ) ) {
 986              $version = $server_info['SQLServerVersion'];
 987          }
 988  
 989          return $version;
 990      }
 991  
 992      /**
 993       * @param string $table
 994       * @param string $fname
 995       * @return bool
 996       */
 997  	public function tableExists( $table, $fname = __METHOD__ ) {
 998          list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
 999  
1000          if ( $db !== false ) {
1001              // remote database
1002              wfDebug( "Attempting to call tableExists on a remote table" );
1003              return false;
1004          }
1005  
1006          if ( $schema === false ) {
1007              global $wgDBmwschema;
1008              $schema = $wgDBmwschema;
1009          }
1010  
1011          $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
1012              WHERE TABLE_TYPE = 'BASE TABLE'
1013              AND TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table'" );
1014  
1015          if ( $res->numRows() ) {
1016              return true;
1017          } else {
1018              return false;
1019          }
1020      }
1021  
1022      /**
1023       * Query whether a given column exists in the mediawiki schema
1024       * @param string $table
1025       * @param string $field
1026       * @param string $fname
1027       * @return bool
1028       */
1029  	public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1030          list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
1031  
1032          if ( $db !== false ) {
1033              // remote database
1034              wfDebug( "Attempting to call fieldExists on a remote table" );
1035              return false;
1036          }
1037  
1038          $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
1039              WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1040  
1041          if ( $res->numRows() ) {
1042              return true;
1043          } else {
1044              return false;
1045          }
1046      }
1047  
1048  	public function fieldInfo( $table, $field ) {
1049          list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
1050  
1051          if ( $db !== false ) {
1052              // remote database
1053              wfDebug( "Attempting to call fieldInfo on a remote table" );
1054              return false;
1055          }
1056  
1057          $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS
1058              WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
1059  
1060          $meta = $res->fetchRow();
1061          if ( $meta ) {
1062              return new MssqlField( $meta );
1063          }
1064  
1065          return false;
1066      }
1067  
1068      /**
1069       * Begin a transaction, committing any previously open transaction
1070       * @param string $fname
1071       */
1072  	protected function doBegin( $fname = __METHOD__ ) {
1073          sqlsrv_begin_transaction( $this->mConn );
1074          $this->mTrxLevel = 1;
1075      }
1076  
1077      /**
1078       * End a transaction
1079       * @param string $fname
1080       */
1081  	protected function doCommit( $fname = __METHOD__ ) {
1082          sqlsrv_commit( $this->mConn );
1083          $this->mTrxLevel = 0;
1084      }
1085  
1086      /**
1087       * Rollback a transaction.
1088       * No-op on non-transactional databases.
1089       * @param string $fname
1090       */
1091  	protected function doRollback( $fname = __METHOD__ ) {
1092          sqlsrv_rollback( $this->mConn );
1093          $this->mTrxLevel = 0;
1094      }
1095  
1096      /**
1097       * Escapes a identifier for use inm SQL.
1098       * Throws an exception if it is invalid.
1099       * Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
1100       * @param string $identifier
1101       * @throws MWException
1102       * @return string
1103       */
1104  	private function escapeIdentifier( $identifier ) {
1105          if ( strlen( $identifier ) == 0 ) {
1106              throw new MWException( "An identifier must not be empty" );
1107          }
1108          if ( strlen( $identifier ) > 128 ) {
1109              throw new MWException( "The identifier '$identifier' is too long (max. 128)" );
1110          }
1111          if ( ( strpos( $identifier, '[' ) !== false )
1112              || ( strpos( $identifier, ']' ) !== false )
1113          ) {
1114              // It may be allowed if you quoted with double quotation marks, but
1115              // that would break if QUOTED_IDENTIFIER is OFF
1116              throw new MWException( "Square brackets are not allowed in '$identifier'" );
1117          }
1118  
1119          return "[$identifier]";
1120      }
1121  
1122      /**
1123       * @param string $s
1124       * @return string
1125       */
1126  	public function strencode( $s ) { # Should not be called by us
1127          return str_replace( "'", "''", $s );
1128      }
1129  
1130      /**
1131       * @param string $s
1132       * @return string
1133       */
1134  	public function addQuotes( $s ) {
1135          if ( $s instanceof MssqlBlob ) {
1136              return $s->fetch();
1137          } elseif ( $s instanceof Blob ) {
1138              // this shouldn't really ever be called, but it's here if needed
1139              // (and will quite possibly make the SQL error out)
1140              $blob = new MssqlBlob( $s->fetch() );
1141              return $blob->fetch();
1142          } else {
1143              if ( is_bool( $s ) ) {
1144                  $s = $s ? 1 : 0;
1145              }
1146              return parent::addQuotes( $s );
1147          }
1148      }
1149  
1150      /**
1151       * @param string $s
1152       * @return string
1153       */
1154  	public function addIdentifierQuotes( $s ) {
1155          // http://msdn.microsoft.com/en-us/library/aa223962.aspx
1156          return '[' . $s . ']';
1157      }
1158  
1159      /**
1160       * @param string $name
1161       * @return bool
1162       */
1163  	public function isQuotedIdentifier( $name ) {
1164          return strlen( $name ) && $name[0] == '[' && substr( $name, -1, 1 ) == ']';
1165      }
1166  
1167      /**
1168       * @param string $db
1169       * @return bool
1170       */
1171  	public function selectDB( $db ) {
1172          try {
1173              $this->mDBname = $db;
1174              $this->query( "USE $db" );
1175              return true;
1176          } catch ( Exception $e ) {
1177              return false;
1178          }
1179      }
1180  
1181      /**
1182       * @param array $options An associative array of options to be turned into
1183       *   an SQL query, valid keys are listed in the function.
1184       * @return array
1185       */
1186  	public function makeSelectOptions( $options ) {
1187          $tailOpts = '';
1188          $startOpts = '';
1189  
1190          $noKeyOptions = array();
1191          foreach ( $options as $key => $option ) {
1192              if ( is_numeric( $key ) ) {
1193                  $noKeyOptions[$option] = true;
1194              }
1195          }
1196  
1197          $tailOpts .= $this->makeGroupByWithHaving( $options );
1198  
1199          $tailOpts .= $this->makeOrderBy( $options );
1200  
1201          if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1202              $startOpts .= 'DISTINCT';
1203          }
1204  
1205          if ( isset( $noKeyOptions['FOR XML'] ) ) {
1206              // used in group concat field emulation
1207              $tailOpts .= " FOR XML PATH('')";
1208          }
1209  
1210          // we want this to be compatible with the output of parent::makeSelectOptions()
1211          return array( $startOpts, '', $tailOpts, '' );
1212      }
1213  
1214      /**
1215       * Get the type of the DBMS, as it appears in $wgDBtype.
1216       * @return string
1217       */
1218  	public function getType() {
1219          return 'mssql';
1220      }
1221  
1222      /**
1223       * @param array $stringList
1224       * @return string
1225       */
1226  	public function buildConcat( $stringList ) {
1227          return implode( ' + ', $stringList );
1228      }
1229  
1230      /**
1231       * Build a GROUP_CONCAT or equivalent statement for a query.
1232       * MS SQL doesn't have GROUP_CONCAT so we emulate it with other stuff (and boy is it nasty)
1233       *
1234       * This is useful for combining a field for several rows into a single string.
1235       * NULL values will not appear in the output, duplicated values will appear,
1236       * and the resulting delimiter-separated values have no defined sort order.
1237       * Code using the results may need to use the PHP unique() or sort() methods.
1238       *
1239       * @param string $delim Glue to bind the results together
1240       * @param string|array $table Table name
1241       * @param string $field Field name
1242       * @param string|array $conds Conditions
1243       * @param string|array $join_conds Join conditions
1244       * @return string SQL text
1245       * @since 1.23
1246       */
1247  	public function buildGroupConcatField( $delim, $table, $field, $conds = '',
1248          $join_conds = array()
1249      ) {
1250          $gcsq = 'gcsq_' . $this->mSubqueryId;
1251          $this->mSubqueryId++;
1252  
1253          $delimLen = strlen( $delim );
1254          $fld = "{$field} + {$this->addQuotes( $delim )}";
1255          $sql = "(SELECT LEFT({$field}, LEN({$field}) - {$delimLen}) FROM ("
1256              . $this->selectSQLText( $table, $fld, $conds, null, array( 'FOR XML' ), $join_conds )
1257              . ") {$gcsq} ({$field}))";
1258  
1259          return $sql;
1260      }
1261  
1262      /**
1263       * @return string
1264       */
1265  	public function getSearchEngine() {
1266          return "SearchMssql";
1267      }
1268  
1269      /**
1270       * Returns an associative array for fields that are of type varbinary, binary, or image
1271       * $table can be either a raw table name or passed through tableName() first
1272       * @param string $table
1273       * @return array
1274       */
1275  	private function getBinaryColumns( $table ) {
1276          $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
1277          $tableRaw = array_pop( $tableRawArr );
1278  
1279          if ( $this->mBinaryColumnCache === null ) {
1280              $this->populateColumnCaches();
1281          }
1282  
1283          return isset( $this->mBinaryColumnCache[$tableRaw] )
1284              ? $this->mBinaryColumnCache[$tableRaw]
1285              : array();
1286      }
1287  
1288      /**
1289       * @param string $table
1290       * @return array
1291       */
1292  	private function getBitColumns( $table ) {
1293          $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
1294          $tableRaw = array_pop( $tableRawArr );
1295  
1296          if ( $this->mBitColumnCache === null ) {
1297              $this->populateColumnCaches();
1298          }
1299  
1300          return isset( $this->mBitColumnCache[$tableRaw] )
1301              ? $this->mBitColumnCache[$tableRaw]
1302              : array();
1303      }
1304  
1305  	private function populateColumnCaches() {
1306          $res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
1307              array(
1308                  'TABLE_CATALOG' => $this->mDBname,
1309                  'TABLE_SCHEMA' => $this->mSchema,
1310                  'DATA_TYPE' => array( 'varbinary', 'binary', 'image', 'bit' )
1311              ) );
1312  
1313          $this->mBinaryColumnCache = array();
1314          $this->mBitColumnCache = array();
1315          foreach ( $res as $row ) {
1316              if ( $row->DATA_TYPE == 'bit' ) {
1317                  $this->mBitColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
1318              } else {
1319                  $this->mBinaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
1320              }
1321          }
1322      }
1323  
1324      /**
1325       * @param string $name
1326       * @param string $format
1327       * @return string
1328       */
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  
1339      /**
1340       * call this instead of tableName() in the updater when renaming tables
1341       * @param string $name
1342       * @param string $format One of quoted, raw, or split
1343       * @return string
1344       */
1345  	function realTableName( $name, $format = 'quoted' ) {
1346          $table = parent::tableName( $name, $format );
1347          if ( $format == 'split' ) {
1348              // Used internally, we want the schema split off from the table name and returned
1349              // as a list with 3 elements (database, schema, table)
1350              $table = explode( '.', $table );
1351              while ( count( $table ) < 3 ) {
1352                  array_unshift( $table, false );
1353              }
1354          }
1355          return $table;
1356      }
1357  
1358      /**
1359       * Called in the installer and updater.
1360       * Probably doesn't need to be called anywhere else in the codebase.
1361       * @param bool|null $value
1362       * @return bool|null
1363       */
1364  	public function prepareStatements( $value = null ) {
1365          return wfSetVar( $this->mPrepareStatements, $value );
1366      }
1367  
1368      /**
1369       * Called in the installer and updater.
1370       * Probably doesn't need to be called anywhere else in the codebase.
1371       * @param bool|null $value
1372       * @return bool|null
1373       */
1374  	public function scrollableCursor( $value = null ) {
1375          return wfSetVar( $this->mScrollableCursor, $value );
1376      }
1377  } // end DatabaseMssql class
1378  
1379  /**
1380   * Utility class.
1381   *
1382   * @ingroup Database
1383   */
1384  class MssqlField implements Field {
1385      private $name, $tableName, $default, $max_length, $nullable, $type;
1386  
1387  	function __construct( $info ) {
1388          $this->name = $info['COLUMN_NAME'];
1389          $this->tableName = $info['TABLE_NAME'];
1390          $this->default = $info['COLUMN_DEFAULT'];
1391          $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
1392          $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
1393          $this->type = $info['DATA_TYPE'];
1394      }
1395  
1396  	function name() {
1397          return $this->name;
1398      }
1399  
1400  	function tableName() {
1401          return $this->tableName;
1402      }
1403  
1404  	function defaultValue() {
1405          return $this->default;
1406      }
1407  
1408  	function maxLength() {
1409          return $this->max_length;
1410      }
1411  
1412  	function isNullable() {
1413          return $this->nullable;
1414      }
1415  
1416  	function type() {
1417          return $this->type;
1418      }
1419  }
1420  
1421  class MssqlBlob extends Blob {
1422  	public function __construct( $data ) {
1423          if ( $data instanceof MssqlBlob ) {
1424              return $data;
1425          } elseif ( $data instanceof Blob ) {
1426              $this->mData = $data->fetch();
1427          } elseif ( is_array( $data ) && is_object( $data ) ) {
1428              $this->mData = serialize( $data );
1429          } else {
1430              $this->mData = $data;
1431          }
1432      }
1433  
1434      /**
1435       * Returns an unquoted hex representation of a binary string
1436       * for insertion into varbinary-type fields
1437       * @return string
1438       */
1439  	public function fetch() {
1440          if ( $this->mData === null ) {
1441              return 'null';
1442          }
1443  
1444          $ret = '0x';
1445          $dataLength = strlen( $this->mData );
1446          for ( $i = 0; $i < $dataLength; $i++ ) {
1447              $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
1448          }
1449  
1450          return $ret;
1451      }
1452  }
1453  
1454  class MssqlResultWrapper extends ResultWrapper {
1455      private $mSeekTo = null;
1456  
1457      /**
1458       * @return stdClass|bool
1459       */
1460  	public function fetchObject() {
1461          $res = $this->result;
1462  
1463          if ( $this->mSeekTo !== null ) {
1464              $result = sqlsrv_fetch_object( $res, 'stdClass', array(),
1465                  SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
1466              $this->mSeekTo = null;
1467          } else {
1468              $result = sqlsrv_fetch_object( $res );
1469          }
1470  
1471          // MediaWiki expects us to return boolean false when there are no more rows instead of null
1472          if ( $result === null ) {
1473              return false;
1474          }
1475  
1476          return $result;
1477      }
1478  
1479      /**
1480       * @return array|bool
1481       */
1482  	public function fetchRow() {
1483          $res = $this->result;
1484  
1485          if ( $this->mSeekTo !== null ) {
1486              $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
1487                  SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
1488              $this->mSeekTo = null;
1489          } else {
1490              $result = sqlsrv_fetch_array( $res );
1491          }
1492  
1493          // MediaWiki expects us to return boolean false when there are no more rows instead of null
1494          if ( $result === null ) {
1495              return false;
1496          }
1497  
1498          return $result;
1499      }
1500  
1501      /**
1502       * @param int $row
1503       * @return bool
1504       */
1505  	public function seek( $row ) {
1506          $res = $this->result;
1507  
1508          // check bounds
1509          $numRows = $this->db->numRows( $res );
1510          $row = intval( $row );
1511  
1512          if ( $numRows === 0 ) {
1513              return false;
1514          } elseif ( $row < 0 || $row > $numRows - 1 ) {
1515              return false;
1516          }
1517  
1518          // Unlike MySQL, the seek actually happens on the next access
1519          $this->mSeekTo = $row;
1520          return true;
1521      }
1522  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1