00001 <?php
00013 class DatabaseMssql extends Database {
00014
00015 var $mAffectedRows;
00016 var $mLastResult;
00017 var $mLastError;
00018 var $mLastErrorNo;
00019 var $mDatabaseFile;
00020
00024 function __construct($server = false, $user = false, $password = false, $dbName = false,
00025 $failFunction = false, $flags = 0, $tablePrefix = 'get from global') {
00026
00027 global $wgOut, $wgDBprefix, $wgCommandLineMode;
00028 if (!isset($wgOut)) $wgOut = NULL; # Can't get a reference if it hasn't been set yet
00029 $this->mOut =& $wgOut;
00030 $this->mFailFunction = $failFunction;
00031 $this->mFlags = $flags;
00032
00033 if ( $this->mFlags & DBO_DEFAULT ) {
00034 if ( $wgCommandLineMode ) {
00035 $this->mFlags &= ~DBO_TRX;
00036 } else {
00037 $this->mFlags |= DBO_TRX;
00038 }
00039 }
00040
00042 $this->mTablePrefix = $tablePrefix == 'get from global' ? $wgDBprefix : $tablePrefix;
00043
00044 if ($server) $this->open($server, $user, $password, $dbName);
00045
00046 }
00047
00051 function implicitGroupby() { return false; }
00052 function implicitOrderby() { return false; }
00053
00054 static function newFromParams($server, $user, $password, $dbName, $failFunction = false, $flags = 0) {
00055 return new DatabaseMssql($server, $user, $password, $dbName, $failFunction, $flags);
00056 }
00057
00061 function open($server,$user,$password,$dbName) {
00062 wfProfileIn(__METHOD__);
00063
00064 # Test for missing mysql.so
00065 # First try to load it
00066 if (!@extension_loaded('mssql')) {
00067 @dl('mssql.so');
00068 }
00069
00070 # Fail now
00071 # Otherwise we get a suppressed fatal error, which is very hard to track down
00072 if (!function_exists( 'mssql_connect')) {
00073 throw new DBConnectionError( $this, "MSSQL functions missing, have you compiled PHP with the --with-mssql option?\n" );
00074 }
00075
00076 $this->close();
00077 $this->mServer = $server;
00078 $this->mUser = $user;
00079 $this->mPassword = $password;
00080 $this->mDBname = $dbName;
00081
00082 wfProfileIn("dbconnect-$server");
00083
00084 # Try to connect up to three times
00085 # The kernel's default SYN retransmission period is far too slow for us,
00086 # so we use a short timeout plus a manual retry.
00087 $this->mConn = false;
00088 $max = 3;
00089 for ( $i = 0; $i < $max && !$this->mConn; $i++ ) {
00090 if ( $i > 1 ) {
00091 usleep( 1000 );
00092 }
00093 if ($this->mFlags & DBO_PERSISTENT) {
00094 @$this->mConn = mssql_pconnect($server, $user, $password);
00095 } else {
00096 # Create a new connection...
00097 @$this->mConn = mssql_connect($server, $user, $password, true);
00098 }
00099 }
00100
00101 wfProfileOut("dbconnect-$server");
00102
00103 if ($dbName != '') {
00104 if ($this->mConn !== false) {
00105 $success = @mssql_select_db($dbName, $this->mConn);
00106 if (!$success) {
00107 $error = "Error selecting database $dbName on server {$this->mServer} " .
00108 "from client host " . wfHostname() . "\n";
00109 wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n");
00110 wfDebug( $error );
00111 }
00112 } else {
00113 wfDebug("DB connection error\n");
00114 wfDebug("Server: $server, User: $user, Password: ".substr($password, 0, 3)."...\n");
00115 $success = false;
00116 }
00117 } else {
00118 # Delay USE query
00119 $success = (bool)$this->mConn;
00120 }
00121
00122 if (!$success) $this->reportConnectionError();
00123 $this->mOpened = $success;
00124 wfProfileOut(__METHOD__);
00125 return $success;
00126 }
00127
00131 function close() {
00132 $this->mOpened = false;
00133 if ($this->mConn) {
00134 if ($this->trxLevel()) $this->immediateCommit();
00135 return mssql_close($this->mConn);
00136 } else return true;
00137 }
00138
00144 function doQuery($sql) {
00145 if ($sql == 'BEGIN' || $sql == 'COMMIT' || $sql == 'ROLLBACK') return true; # $sql .= ' TRANSACTION';
00146 $sql = preg_replace('|[^\x07-\x7e]|','?',$sql); # TODO: need to fix unicode - just removing it here while testing
00147 $ret = mssql_query($sql, $this->mConn);
00148 if ($ret === false) {
00149 $err = mssql_get_last_message();
00150 if ($err) $this->mlastError = $err;
00151 $row = mssql_fetch_row(mssql_query('select @@ERROR'));
00152 if ($row[0]) $this->mlastErrorNo = $row[0];
00153 } else $this->mlastErrorNo = false;
00154 return $ret;
00155 }
00156
00160 function freeResult( $res ) {
00161 if ( $res instanceof ResultWrapper ) {
00162 $res = $res->result;
00163 }
00164 if ( !@mssql_free_result( $res ) ) {
00165 throw new DBUnexpectedError( $this, "Unable to free MSSQL result" );
00166 }
00167 }
00168
00178 function fetchObject( $res ) {
00179 if ( $res instanceof ResultWrapper ) {
00180 $res = $res->result;
00181 }
00182 @$row = mssql_fetch_object( $res );
00183 if ( $this->lastErrno() ) {
00184 throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
00185 }
00186 return $row;
00187 }
00188
00197 function fetchRow( $res ) {
00198 if ( $res instanceof ResultWrapper ) {
00199 $res = $res->result;
00200 }
00201 @$row = mssql_fetch_array( $res );
00202 if ( $this->lastErrno() ) {
00203 throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
00204 }
00205 return $row;
00206 }
00207
00211 function numRows( $res ) {
00212 if ( $res instanceof ResultWrapper ) {
00213 $res = $res->result;
00214 }
00215 @$n = mssql_num_rows( $res );
00216 if ( $this->lastErrno() ) {
00217 throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
00218 }
00219 return $n;
00220 }
00221
00227 function numFields( $res ) {
00228 if ( $res instanceof ResultWrapper ) {
00229 $res = $res->result;
00230 }
00231 return mssql_num_fields( $res );
00232 }
00233
00241 function fieldName( $res, $n ) {
00242 if ( $res instanceof ResultWrapper ) {
00243 $res = $res->result;
00244 }
00245 return mssql_field_name( $res, $n );
00246 }
00247
00258 function insertId() {
00259 $row = mssql_fetch_row(mssql_query('select @@IDENTITY'));
00260 return $row[0];
00261 }
00262
00269 function dataSeek( $res, $row ) {
00270 if ( $res instanceof ResultWrapper ) {
00271 $res = $res->result;
00272 }
00273 return mssql_data_seek( $res, $row );
00274 }
00275
00279 function lastErrno() {
00280 return $this->mlastErrorNo;
00281 }
00282
00286 function lastError() {
00287 return $this->mlastError;
00288 }
00289
00293 function affectedRows() {
00294 return mssql_rows_affected( $this->mConn );
00295 }
00296
00305 function set( $table, $var, $value, $cond, $fname = 'Database::set' )
00306 {
00307 if ($value == "NULL") $value = "''"; # see comments in makeListWithoutNulls()
00308 $table = $this->tableName( $table );
00309 $sql = "UPDATE $table SET $var = '" .
00310 $this->strencode( $value ) . "' WHERE ($cond)";
00311 return (bool)$this->query( $sql, $fname );
00312 }
00313
00319 function selectField( $table, $var, $cond='', $fname = 'Database::selectField', $options = array() ) {
00320 if ( !is_array( $options ) ) {
00321 $options = array( $options );
00322 }
00323 $options['LIMIT'] = 1;
00324
00325 $res = $this->select( $table, $var, $cond, $fname, $options );
00326 if ( $res === false || !$this->numRows( $res ) ) {
00327 return false;
00328 }
00329 $row = $this->fetchRow( $res );
00330 if ( $row !== false ) {
00331 $this->freeResult( $res );
00332 return $row[0];
00333 } else {
00334 return false;
00335 }
00336 }
00337
00348 function makeSelectOptions( $options ) {
00349 $preLimitTail = $postLimitTail = '';
00350 $startOpts = '';
00351
00352 $noKeyOptions = array();
00353 foreach ( $options as $key => $option ) {
00354 if ( is_numeric( $key ) ) {
00355 $noKeyOptions[$option] = true;
00356 }
00357 }
00358
00359 if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
00360 if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
00361 if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
00362
00363
00364
00365
00366
00367
00368
00369 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
00370 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
00371 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
00372
00373 # Various MySQL extensions
00374 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */';
00375 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY';
00376 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT';
00377 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT';
00378 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) $startOpts .= ' SQL_SMALL_RESULT';
00379 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS';
00380 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) $startOpts .= ' SQL_CACHE';
00381 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) $startOpts .= ' SQL_NO_CACHE';
00382
00383 if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
00384 $useIndex = $this->useIndexClause( $options['USE INDEX'] );
00385 } else {
00386 $useIndex = '';
00387 }
00388
00389 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
00390 }
00391
00403 function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array() )
00404 {
00405 if( is_array( $vars ) ) {
00406 $vars = implode( ',', $vars );
00407 }
00408 if( !is_array( $options ) ) {
00409 $options = array( $options );
00410 }
00411 if( is_array( $table ) ) {
00412 if ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
00413 $from = ' FROM ' . $this->tableNamesWithUseIndex( $table, $options['USE INDEX'] );
00414 else
00415 $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
00416 } elseif ($table!='') {
00417 if ($table{0}==' ') {
00418 $from = ' FROM ' . $table;
00419 } else {
00420 $from = ' FROM ' . $this->tableName( $table );
00421 }
00422 } else {
00423 $from = '';
00424 }
00425
00426 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
00427
00428 if( !empty( $conds ) ) {
00429 if ( is_array( $conds ) ) {
00430 $conds = $this->makeList( $conds, LIST_AND );
00431 }
00432 $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
00433 } else {
00434 $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
00435 }
00436
00437 if (isset($options['LIMIT']))
00438 $sql = $this->limitResult($sql, $options['LIMIT'],
00439 isset($options['OFFSET']) ? $options['OFFSET'] : false);
00440 $sql = "$sql $postLimitTail";
00441
00442 if (isset($options['EXPLAIN'])) {
00443 $sql = 'EXPLAIN ' . $sql;
00444 }
00445 return $this->query( $sql, $fname );
00446 }
00447
00453 function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
00454 $rows = 0;
00455 $res = $this->select ($table, 'COUNT(*)', $conds, $fname, $options );
00456 if ($res) {
00457 $row = $this->fetchObject($res);
00458 $rows = $row[0];
00459 }
00460 $this->freeResult($res);
00461 return $rows;
00462 }
00463
00469 function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) {
00470 $table = $this->tableName( $table );
00471 $sql = "SELECT TOP 1 * FROM $table";
00472 $res = $this->query( $sql, 'Database::fieldExists' );
00473
00474 $found = false;
00475 while ( $row = $this->fetchArray( $res ) ) {
00476 if ( isset($row[$field]) ) {
00477 $found = true;
00478 break;
00479 }
00480 }
00481
00482 $this->freeResult( $res );
00483 return $found;
00484 }
00485
00490 function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) {
00491
00492 throw new DBUnexpectedError( $this, 'Database::indexInfo called which is not supported yet' );
00493 return NULL;
00494
00495 $table = $this->tableName( $table );
00496 $sql = 'SHOW INDEX FROM '.$table;
00497 $res = $this->query( $sql, $fname );
00498 if ( !$res ) {
00499 return NULL;
00500 }
00501
00502 $result = array();
00503 while ( $row = $this->fetchObject( $res ) ) {
00504 if ( $row->Key_name == $index ) {
00505 $result[] = $row;
00506 }
00507 }
00508 $this->freeResult($res);
00509
00510 return empty($result) ? false : $result;
00511 }
00512
00516 function tableExists( $table ) {
00517 $table = $this->tableName( $table );
00518 $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '$table'" );
00519 $exist = ($res->numRows() > 0);
00520 $this->freeResult($res);
00521 return $exist;
00522 }
00523
00531 function fieldInfo( $table, $field ) {
00532 $table = $this->tableName( $table );
00533 $res = $this->query( "SELECT TOP 1 * FROM $table" );
00534 $n = mssql_num_fields( $res->result );
00535 for( $i = 0; $i < $n; $i++ ) {
00536 $meta = mssql_fetch_field( $res->result, $i );
00537 if( $field == $meta->name ) {
00538 return new MSSQLField($meta);
00539 }
00540 }
00541 return false;
00542 }
00543
00547 function fieldType( $res, $index ) {
00548 if ( $res instanceof ResultWrapper ) {
00549 $res = $res->result;
00550 }
00551 return mssql_field_type( $res, $index );
00552 }
00553
00566 function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
00567 # No rows to insert, easy just return now
00568 if ( !count( $a ) ) {
00569 return true;
00570 }
00571 $table = $this->tableName( $table );
00572 if ( !is_array( $options ) ) {
00573 $options = array( $options );
00574 }
00575
00576 # todo: need to record primary keys at table create time, and remove NULL assignments to them
00577 if ( isset( $a[0] ) && is_array( $a[0] ) ) {
00578 $multi = true;
00579 $keys = array_keys( $a[0] );
00580 # if (ereg('_id$',$keys[0])) {
00581 foreach ($a as $i) {
00582 if (is_null($i[$keys[0]])) unset($i[$keys[0]]); # remove primary-key column from multiple insert lists if empty value
00583 }
00584 # }
00585 $keys = array_keys( $a[0] );
00586 } else {
00587 $multi = false;
00588 $keys = array_keys( $a );
00589 # if (ereg('_id$',$keys[0]) && empty($a[$keys[0]])) unset($a[$keys[0]]); # remove primary-key column from insert list if empty value
00590 if (is_null($a[$keys[0]])) unset($a[$keys[0]]); # remove primary-key column from insert list if empty value
00591 $keys = array_keys( $a );
00592 }
00593
00594 # handle IGNORE option
00595 # example:
00596 # MySQL: INSERT IGNORE INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
00597 # MSSQL: IF NOT EXISTS (SELECT * FROM user_groups WHERE ug_user = '1') INSERT INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
00598 $ignore = in_array('IGNORE',$options);
00599
00600 # remove IGNORE from options list
00601 if ($ignore) {
00602 $oldoptions = $options;
00603 $options = array();
00604 foreach ($oldoptions as $o) if ($o != 'IGNORE') $options[] = $o;
00605 }
00606
00607 $keylist = implode(',', $keys);
00608 $sql = 'INSERT '.implode(' ', $options)." INTO $table (".implode(',', $keys).') VALUES ';
00609 if ($multi) {
00610 if ($ignore) {
00611 # If multiple and ignore, then do each row as a separate conditional insert
00612 foreach ($a as $row) {
00613 $prival = $row[$keys[0]];
00614 $sql = "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival') $sql";
00615 if (!$this->query("$sql (".$this->makeListWithoutNulls($row).')', $fname)) return false;
00616 }
00617 return true;
00618 } else {
00619 $first = true;
00620 foreach ($a as $row) {
00621 if ($first) $first = false; else $sql .= ',';
00622 $sql .= '('.$this->makeListWithoutNulls($row).')';
00623 }
00624 }
00625 } else {
00626 if ($ignore) {
00627 $prival = $a[$keys[0]];
00628 $sql = "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival') $sql";
00629 }
00630 $sql .= '('.$this->makeListWithoutNulls($a).')';
00631 }
00632 return (bool)$this->query( $sql, $fname );
00633 }
00634
00641 function makeListWithoutNulls($a, $mode = LIST_COMMA) {
00642 return str_replace("NULL","''",$this->makeList($a,$mode));
00643 }
00644
00657 function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
00658 $table = $this->tableName( $table );
00659 $opts = $this->makeUpdateOptions( $options );
00660 $sql = "UPDATE $opts $table SET " . $this->makeListWithoutNulls( $values, LIST_SET );
00661 if ( $conds != '*' ) {
00662 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
00663 }
00664 return $this->query( $sql, $fname );
00665 }
00666
00674 function makeUpdateOptions( $options ) {
00675 if( !is_array( $options ) ) {
00676 $options = array( $options );
00677 }
00678 $opts = array();
00679 if ( in_array( 'LOW_PRIORITY', $options ) )
00680 $opts[] = $this->lowPriorityOption();
00681 if ( in_array( 'IGNORE', $options ) )
00682 $opts[] = 'IGNORE';
00683 return implode(' ', $opts);
00684 }
00685
00689 function selectDB( $db ) {
00690 $this->mDBname = $db;
00691 return mssql_select_db( $db, $this->mConn );
00692 }
00693
00697 function tableName($name) {
00698 return strpos($name, $this->mTablePrefix) === 0 ? $name : "{$this->mTablePrefix}$name";
00699 }
00700
00706 function strencode($s) {
00707 return str_replace("'","''",$s);
00708 }
00709
00713 function useIndexClause( $index ) {
00714 return "";
00715 }
00716
00730 function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
00731 $table = $this->tableName( $table );
00732
00733 # Single row case
00734 if ( !is_array( reset( $rows ) ) ) {
00735 $rows = array( $rows );
00736 }
00737
00738 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) .') VALUES ';
00739 $first = true;
00740 foreach ( $rows as $row ) {
00741 if ( $first ) {
00742 $first = false;
00743 } else {
00744 $sql .= ',';
00745 }
00746 $sql .= '(' . $this->makeList( $row ) . ')';
00747 }
00748 return $this->query( $sql, $fname );
00749 }
00750
00767 function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) {
00768 if ( !$conds ) {
00769 throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' );
00770 }
00771
00772 $delTable = $this->tableName( $delTable );
00773 $joinTable = $this->tableName( $joinTable );
00774 $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
00775 if ( $conds != '*' ) {
00776 $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
00777 }
00778
00779 return $this->query( $sql, $fname );
00780 }
00781
00785 function textFieldSize( $table, $field ) {
00786 $table = $this->tableName( $table );
00787 $sql = "SELECT TOP 1 * FROM $table;";
00788 $res = $this->query( $sql, 'Database::textFieldSize' );
00789 $row = $this->fetchObject( $res );
00790 $this->freeResult( $res );
00791
00792 $m = array();
00793 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
00794 $size = $m[1];
00795 } else {
00796 $size = -1;
00797 }
00798 return $size;
00799 }
00800
00804 function lowPriorityOption() {
00805 return 'LOW_PRIORITY';
00806 }
00807
00815 function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect',
00816 $insertOptions = array(), $selectOptions = array() )
00817 {
00818 $destTable = $this->tableName( $destTable );
00819 if ( is_array( $insertOptions ) ) {
00820 $insertOptions = implode( ' ', $insertOptions );
00821 }
00822 if( !is_array( $selectOptions ) ) {
00823 $selectOptions = array( $selectOptions );
00824 }
00825 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
00826 if( is_array( $srcTable ) ) {
00827 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
00828 } else {
00829 $srcTable = $this->tableName( $srcTable );
00830 }
00831 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
00832 " SELECT $startOpts " . implode( ',', $varMap ) .
00833 " FROM $srcTable $useIndex ";
00834 if ( $conds != '*' ) {
00835 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
00836 }
00837 $sql .= " $tailOpts";
00838 return $this->query( $sql, $fname );
00839 }
00840
00848 function limitResult($sql, $limit, $offset=false) {
00849 if( !is_numeric($limit) ) {
00850 throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
00851 }
00852 if ($offset) {
00853 throw new DBUnexpectedError( $this, 'Database::limitResult called with non-zero offset which is not supported yet' );
00854 } else {
00855 $sql = ereg_replace("^SELECT", "SELECT TOP $limit", $sql);
00856 }
00857 return $sql;
00858 }
00859
00868 function conditional( $cond, $trueVal, $falseVal ) {
00869 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
00870 }
00871
00876 function wasDeadlock() {
00877 return $this->lastErrno() == 1205;
00878 }
00879
00884 function immediateBegin( $fname = 'Database::immediateBegin' ) {
00885 $this->begin();
00886 }
00887
00892 function immediateCommit( $fname = 'Database::immediateCommit' ) {
00893 $this->commit();
00894 }
00895
00899 function timestamp( $ts=0 ) {
00900 return wfTimestamp(TS_MW,$ts);
00901 }
00902
00906 function timestampOrNull( $ts = null ) {
00907 if( is_null( $ts ) ) {
00908 return null;
00909 } else {
00910 return $this->timestamp( $ts );
00911 }
00912 }
00913
00917 function getSoftwareLink() {
00918 return "[http://www.microsoft.com/sql/default.mspx Microsoft SQL Server 2005 Home]";
00919 }
00920
00924 function getServerVersion() {
00925 $row = mssql_fetch_row(mssql_query('select @@VERSION'));
00926 return ereg("^(.+[0-9]+\\.[0-9]+\\.[0-9]+) ",$row[0],$m) ? $m[1] : $row[0];
00927 }
00928
00929 function limitResultForUpdate($sql, $num) {
00930 return $sql;
00931 }
00932
00936 public function setTimeout($timeout) { return; }
00937
00938 function ping() {
00939 wfDebug("Function ping() not written for MSSQL yet");
00940 return true;
00941 }
00942
00946 public function getLag() {
00947 return 0;
00948 }
00949
00954 public function setup_database() {
00955 global $IP,$wgDBTableOptions;
00956 $wgDBTableOptions = '';
00957 $mysql_tmpl = "$IP/maintenance/tables.sql";
00958 $mysql_iw = "$IP/maintenance/interwiki.sql";
00959 $mssql_tmpl = "$IP/maintenance/mssql/tables.sql";
00960
00961 # Make an MSSQL template file if it doesn't exist (based on the same one MySQL uses to create a new wiki db)
00962 if (!file_exists($mssql_tmpl)) { # todo: make this conditional again
00963 $sql = file_get_contents($mysql_tmpl);
00964 $sql = preg_replace('/^\s*--.*?$/m','',$sql); # strip comments
00965 $sql = preg_replace('/^\s*(UNIQUE )?(INDEX|KEY|FULLTEXT).+?$/m', '', $sql); # These indexes should be created with a CREATE INDEX query
00966 $sql = preg_replace('/(\sKEY) [^\(]+\(/is', '$1 (', $sql); # "KEY foo (foo)" should just be "KEY (foo)"
00967 $sql = preg_replace('/(varchar\([0-9]+\))\s+binary/i', '$1', $sql); # "varchar(n) binary" cannot be followed by "binary"
00968 $sql = preg_replace('/(var)?binary\(([0-9]+)\)/ie', '"varchar(".strlen(pow(2,$2)).")"', $sql); # use varchar(chars) not binary(bits)
00969 $sql = preg_replace('/ (var)?binary/i', ' varchar', $sql); # use varchar not binary
00970 $sql = preg_replace('/(varchar\([0-9]+\)(?! N))/', '$1 NULL', $sql); # MSSQL complains if NULL is put into a varchar
00971 #$sql = preg_replace('/ binary/i',' varchar',$sql); # MSSQL binary's can't be assigned with strings, so use varchar's instead
00972 #$sql = preg_replace('/(binary\([0-9]+\) (NOT NULL )?default) [\'"].*?[\'"]/i','$1 0',$sql); # binary default cannot be string
00973 $sql = preg_replace('/[a-z]*(blob|text)([ ,])/i', 'text$2', $sql); # no BLOB types in MSSQL
00974 $sql = preg_replace('/\).+?;/',');', $sql); # remove all table options
00975 $sql = preg_replace('/ (un)?signed/i', '', $sql);
00976 $sql = preg_replace('/ENUM\(.+?\)/','TEXT',$sql); # Make ENUM's into TEXT's
00977 $sql = str_replace(' bool ', ' bit ', $sql);
00978 $sql = str_replace('auto_increment', 'IDENTITY(1,1)', $sql);
00979 #$sql = preg_replace('/NOT NULL(?! IDENTITY)/', 'NULL', $sql); # Allow NULL's for non IDENTITY columns
00980
00981 # Tidy up and write file
00982 $sql = preg_replace('/,\s*\)/s', "\n)", $sql); # Remove spurious commas left after INDEX removals
00983 $sql = preg_replace('/^\s*^/m', '', $sql); # Remove empty lines
00984 $sql = preg_replace('/;$/m', ";\n", $sql); # Separate each statement with an empty line
00985 file_put_contents($mssql_tmpl, $sql);
00986 }
00987
00988 # Parse the MSSQL template replacing inline variables such as
00989 $err = $this->sourceFile($mssql_tmpl);
00990 if ($err !== true) $this->reportQueryError($err,0,$sql,__FUNCTION__);
00991
00992 # Use DatabasePostgres's code to populate interwiki from MySQL template
00993 $f = fopen($mysql_iw,'r');
00994 if ($f == false) dieout("<li>Could not find the interwiki.sql file");
00995 $sql = "INSERT INTO {$this->mTablePrefix}interwiki(iw_prefix,iw_url,iw_local) VALUES ";
00996 while (!feof($f)) {
00997 $line = fgets($f,1024);
00998 $matches = array();
00999 if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) continue;
01000 $this->query("$sql $matches[1],$matches[2])");
01001 }
01002 }
01003
01007 public function lock( $lockName, $method ) {
01008 return true;
01009 }
01010 public function unlock( $lockName, $method ) {
01011 return true;
01012 }
01013
01014 public function getSearchEngine() {
01015 return "SearchEngineDummy";
01016 }
01017
01018 }
01019
01023 class MSSQLField extends MySQLField {
01024
01025 function __construct() {
01026 }
01027
01028 static function fromText($db, $table, $field) {
01029 $n = new MSSQLField;
01030 $n->name = $field;
01031 $n->tablename = $table;
01032 return $n;
01033 }
01034
01035 }
01036