MediaWiki
REL1_21
|
00001 <?php 00030 class DatabaseMssql extends DatabaseBase { 00031 var $mInsertId = null; 00032 var $mLastResult = null; 00033 var $mAffectedRows = null; 00034 00035 var $mPort; 00036 00037 function cascadingDeletes() { 00038 return true; 00039 } 00040 function cleanupTriggers() { 00041 return true; 00042 } 00043 function strictIPs() { 00044 return true; 00045 } 00046 function realTimestamps() { 00047 return true; 00048 } 00049 function implicitGroupby() { 00050 return false; 00051 } 00052 function implicitOrderby() { 00053 return false; 00054 } 00055 function functionalIndexes() { 00056 return true; 00057 } 00058 function unionSupportsOrderAndLimit() { 00059 return false; 00060 } 00061 00071 function open( $server, $user, $password, $dbName ) { 00072 # Test for driver support, to avoid suppressed fatal error 00073 if ( !function_exists( 'sqlsrv_connect' ) ) { 00074 throw new DBConnectionError( $this, "MS Sql Server Native (sqlsrv) functions missing. You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n" ); 00075 } 00076 00077 global $wgDBport; 00078 00079 if ( !strlen( $user ) ) { # e.g. the class is being loaded 00080 return; 00081 } 00082 00083 $this->close(); 00084 $this->mServer = $server; 00085 $this->mPort = $wgDBport; 00086 $this->mUser = $user; 00087 $this->mPassword = $password; 00088 $this->mDBname = $dbName; 00089 00090 $connectionInfo = array(); 00091 00092 if( $dbName ) { 00093 $connectionInfo['Database'] = $dbName; 00094 } 00095 00096 // Start NT Auth Hack 00097 // Quick and dirty work around to provide NT Auth designation support. 00098 // Current solution requires installer to know to input 'ntauth' for both username and password 00099 // to trigger connection via NT Auth. - ugly, ugly, ugly 00100 // TO-DO: Make this better and add NT Auth choice to MW installer when SQL Server option is chosen. 00101 $ntAuthUserTest = strtolower( $user ); 00102 $ntAuthPassTest = strtolower( $password ); 00103 00104 // Decide which auth scenerio to use 00105 if( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ) { 00106 // Don't add credentials to $connectionInfo 00107 } else { 00108 $connectionInfo['UID'] = $user; 00109 $connectionInfo['PWD'] = $password; 00110 } 00111 // End NT Auth Hack 00112 00113 wfSuppressWarnings(); 00114 $this->mConn = sqlsrv_connect( $server, $connectionInfo ); 00115 wfRestoreWarnings(); 00116 00117 if ( $this->mConn === false ) { 00118 wfDebug( "DB connection error\n" ); 00119 wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" ); 00120 wfDebug( $this->lastError() . "\n" ); 00121 return false; 00122 } 00123 00124 $this->mOpened = true; 00125 return $this->mConn; 00126 } 00127 00133 protected function closeConnection() { 00134 return sqlsrv_close( $this->mConn ); 00135 } 00136 00137 protected function doQuery( $sql ) { 00138 wfDebug( "SQL: [$sql]\n" ); 00139 $this->offset = 0; 00140 00141 // several extensions seem to think that all databases support limits via LIMIT N after the WHERE clause 00142 // well, MSSQL uses SELECT TOP N, so to catch any of those extensions we'll do a quick check for a LIMIT 00143 // clause and pass $sql through $this->LimitToTopN() which parses the limit clause and passes the result to 00144 // $this->limitResult(); 00145 if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) { 00146 // massage LIMIT -> TopN 00147 $sql = $this->LimitToTopN( $sql ); 00148 } 00149 00150 // MSSQL doesn't have EXTRACT(epoch FROM XXX) 00151 if ( preg_match('#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) { 00152 // This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970 00153 $sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql ); 00154 } 00155 00156 // perform query 00157 $stmt = sqlsrv_query( $this->mConn, $sql ); 00158 if ( $stmt == false ) { 00159 $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" . 00160 "Query: " . htmlentities( $sql ) . "\n" . 00161 "Function: " . __METHOD__ . "\n"; 00162 // process each error (our driver will give us an array of errors unlike other providers) 00163 foreach ( sqlsrv_errors() as $error ) { 00164 $message .= $message . "ERROR[" . $error['code'] . "] " . $error['message'] . "\n"; 00165 } 00166 00167 throw new DBUnexpectedError( $this, $message ); 00168 } 00169 // remember number of rows affected 00170 $this->mAffectedRows = sqlsrv_rows_affected( $stmt ); 00171 00172 // if it is a SELECT statement, or an insert with a request to output something we want to return a row. 00173 if ( ( preg_match( '#\bSELECT\s#i', $sql ) ) || 00174 ( preg_match( '#\bINSERT\s#i', $sql ) && preg_match( '#\bOUTPUT\s+INSERTED\b#i', $sql ) ) ) { 00175 // this is essentially a rowset, but Mediawiki calls these 'result' 00176 // the rowset owns freeing the statement 00177 $res = new MssqlResult( $stmt ); 00178 } else { 00179 // otherwise we simply return it was successful, failure throws an exception 00180 $res = true; 00181 } 00182 return $res; 00183 } 00184 00185 function freeResult( $res ) { 00186 if ( $res instanceof ResultWrapper ) { 00187 $res = $res->result; 00188 } 00189 $res->free(); 00190 } 00191 00192 function fetchObject( $res ) { 00193 if ( $res instanceof ResultWrapper ) { 00194 $res = $res->result; 00195 } 00196 $row = $res->fetch( 'OBJECT' ); 00197 return $row; 00198 } 00199 00200 function getErrors() { 00201 $strRet = ''; 00202 $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL ); 00203 if ( $retErrors != null ) { 00204 foreach ( $retErrors as $arrError ) { 00205 $strRet .= "SQLState: " . $arrError[ 'SQLSTATE'] . "\n"; 00206 $strRet .= "Error Code: " . $arrError[ 'code'] . "\n"; 00207 $strRet .= "Message: " . $arrError[ 'message'] . "\n"; 00208 } 00209 } else { 00210 $strRet = "No errors found"; 00211 } 00212 return $strRet; 00213 } 00214 00215 function fetchRow( $res ) { 00216 if ( $res instanceof ResultWrapper ) { 00217 $res = $res->result; 00218 } 00219 $row = $res->fetch( SQLSRV_FETCH_BOTH ); 00220 return $row; 00221 } 00222 00223 function numRows( $res ) { 00224 if ( $res instanceof ResultWrapper ) { 00225 $res = $res->result; 00226 } 00227 return ( $res ) ? $res->numrows() : 0; 00228 } 00229 00230 function numFields( $res ) { 00231 if ( $res instanceof ResultWrapper ) { 00232 $res = $res->result; 00233 } 00234 return ( $res ) ? $res->numfields() : 0; 00235 } 00236 00237 function fieldName( $res, $n ) { 00238 if ( $res instanceof ResultWrapper ) { 00239 $res = $res->result; 00240 } 00241 return ( $res ) ? $res->fieldname( $n ) : 0; 00242 } 00243 00248 function insertId() { 00249 return $this->mInsertId; 00250 } 00251 00252 function dataSeek( $res, $row ) { 00253 if ( $res instanceof ResultWrapper ) { 00254 $res = $res->result; 00255 } 00256 return ( $res ) ? $res->seek( $row ) : false; 00257 } 00258 00259 function lastError() { 00260 if ( $this->mConn ) { 00261 return $this->getErrors(); 00262 } else { 00263 return "No database connection"; 00264 } 00265 } 00266 00267 function lastErrno() { 00268 $err = sqlsrv_errors( SQLSRV_ERR_ALL ); 00269 if ( $err[0] ) { 00270 return $err[0]['code']; 00271 } else { 00272 return 0; 00273 } 00274 } 00275 00276 function affectedRows() { 00277 return $this->mAffectedRows; 00278 } 00279 00293 function select( $table, $vars, $conds = '', $fname = 'DatabaseMssql::select', $options = array(), $join_conds = array() ) 00294 { 00295 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); 00296 if ( isset( $options['EXPLAIN'] ) ) { 00297 sqlsrv_query( $this->mConn, "SET SHOWPLAN_ALL ON;" ); 00298 $ret = $this->query( $sql, $fname ); 00299 sqlsrv_query( $this->mConn, "SET SHOWPLAN_ALL OFF;" ); 00300 return $ret; 00301 } 00302 return $this->query( $sql, $fname ); 00303 } 00304 00318 function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseMssql::select', $options = array(), $join_conds = array() ) { 00319 if ( isset( $options['EXPLAIN'] ) ) { 00320 unset( $options['EXPLAIN'] ); 00321 } 00322 return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); 00323 } 00324 00333 function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseMssql::estimateRowCount', $options = array() ) { 00334 $options['EXPLAIN'] = true;// http://msdn2.microsoft.com/en-us/library/aa259203.aspx 00335 $res = $this->select( $table, $vars, $conds, $fname, $options ); 00336 00337 $rows = -1; 00338 if ( $res ) { 00339 $row = $this->fetchRow( $res ); 00340 if ( isset( $row['EstimateRows'] ) ) $rows = $row['EstimateRows']; 00341 } 00342 return $rows; 00343 } 00344 00350 function indexInfo( $table, $index, $fname = 'DatabaseMssql::indexExists' ) { 00351 # This does not return the same info as MYSQL would, but that's OK because MediaWiki never uses the 00352 # returned value except to check for the existance of indexes. 00353 $sql = "sp_helpindex '" . $table . "'"; 00354 $res = $this->query( $sql, $fname ); 00355 if ( !$res ) { 00356 return null; 00357 } 00358 00359 $result = array(); 00360 foreach ( $res as $row ) { 00361 if ( $row->index_name == $index ) { 00362 $row->Non_unique = !stristr( $row->index_description, "unique" ); 00363 $cols = explode( ", ", $row->index_keys ); 00364 foreach ( $cols as $col ) { 00365 $row->Column_name = trim( $col ); 00366 $result[] = clone $row; 00367 } 00368 } elseif ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) { 00369 $row->Non_unique = 0; 00370 $cols = explode( ", ", $row->index_keys ); 00371 foreach ( $cols as $col ) { 00372 $row->Column_name = trim( $col ); 00373 $result[] = clone $row; 00374 } 00375 } 00376 } 00377 return empty( $result ) ? false : $result; 00378 } 00379 00395 function insert( $table, $arrToInsert, $fname = 'DatabaseMssql::insert', $options = array() ) { 00396 # No rows to insert, easy just return now 00397 if ( !count( $arrToInsert ) ) { 00398 return true; 00399 } 00400 00401 if ( !is_array( $options ) ) { 00402 $options = array( $options ); 00403 } 00404 00405 $table = $this->tableName( $table ); 00406 00407 if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) {// Not multi row 00408 $arrToInsert = array( 0 => $arrToInsert );// make everything multi row compatible 00409 } 00410 00411 $allOk = true; 00412 00413 // We know the table we're inserting into, get its identity column 00414 $identity = null; 00415 $tableRaw = preg_replace( '#\[([^\]]*)\]#', '$1', $table ); // strip matching square brackets from table name 00416 $res = $this->doQuery( "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'" ); 00417 if( $res && $res->numrows() ) { 00418 // There is an identity for this table. 00419 $identity = array_pop( $res->fetch( SQLSRV_FETCH_ASSOC ) ); 00420 } 00421 unset( $res ); 00422 00423 foreach ( $arrToInsert as $a ) { 00424 // start out with empty identity column, this is so we can return it as a result of the insert logic 00425 $sqlPre = ''; 00426 $sqlPost = ''; 00427 $identityClause = ''; 00428 00429 // if we have an identity column 00430 if( $identity ) { 00431 // iterate through 00432 foreach ($a as $k => $v ) { 00433 if ( $k == $identity ) { 00434 if( !is_null($v) ) { 00435 // there is a value being passed to us, we need to turn on and off inserted identity 00436 $sqlPre = "SET IDENTITY_INSERT $table ON;"; 00437 $sqlPost = ";SET IDENTITY_INSERT $table OFF;"; 00438 00439 } else { 00440 // we can't insert NULL into an identity column, so remove the column from the insert. 00441 unset( $a[$k] ); 00442 } 00443 } 00444 } 00445 $identityClause = "OUTPUT INSERTED.$identity "; // we want to output an identity column as result 00446 } 00447 00448 $keys = array_keys( $a ); 00449 00450 // INSERT IGNORE is not supported by SQL Server 00451 // remove IGNORE from options list and set ignore flag to true 00452 $ignoreClause = false; 00453 foreach ( $options as $k => $v ) { 00454 if ( strtoupper( $v ) == "IGNORE" ) { 00455 unset( $options[$k] ); 00456 $ignoreClause = true; 00457 } 00458 } 00459 00460 // translate MySQL INSERT IGNORE to something SQL Server can use 00461 // example: 00462 // MySQL: INSERT IGNORE INTO user_groups (ug_user,ug_group) VALUES ('1','sysop') 00463 // MSSQL: IF NOT EXISTS (SELECT * FROM user_groups WHERE ug_user = '1') INSERT INTO user_groups (ug_user,ug_group) VALUES ('1','sysop') 00464 if ( $ignoreClause ) { 00465 $prival = $a[$keys[0]]; 00466 $sqlPre .= "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival')"; 00467 } 00468 00469 // Build the actual query 00470 $sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) . 00471 " INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES ("; 00472 00473 $first = true; 00474 foreach ( $a as $value ) { 00475 if ( $first ) { 00476 $first = false; 00477 } else { 00478 $sql .= ','; 00479 } 00480 if ( is_string( $value ) ) { 00481 $sql .= $this->addQuotes( $value ); 00482 } elseif ( is_null( $value ) ) { 00483 $sql .= 'null'; 00484 } elseif ( is_array( $value ) || is_object( $value ) ) { 00485 if ( is_object( $value ) && strtolower( get_class( $value ) ) == 'blob' ) { 00486 $sql .= $this->addQuotes( $value ); 00487 } else { 00488 $sql .= $this->addQuotes( serialize( $value ) ); 00489 } 00490 } else { 00491 $sql .= $value; 00492 } 00493 } 00494 $sql .= ')' . $sqlPost; 00495 00496 // Run the query 00497 $ret = sqlsrv_query( $this->mConn, $sql ); 00498 00499 if ( $ret === false ) { 00500 throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), $sql, $fname ); 00501 } elseif ( $ret != null ) { 00502 // remember number of rows affected 00503 $this->mAffectedRows = sqlsrv_rows_affected( $ret ); 00504 if ( !is_null($identity) ) { 00505 // then we want to get the identity column value we were assigned and save it off 00506 $row = sqlsrv_fetch_object( $ret ); 00507 $this->mInsertId = $row->$identity; 00508 } 00509 sqlsrv_free_stmt( $ret ); 00510 continue; 00511 } 00512 $allOk = false; 00513 } 00514 return $allOk; 00515 } 00516 00533 function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect', 00534 $insertOptions = array(), $selectOptions = array() ) { 00535 $ret = parent::insertSelect( $destTable, $srcTable, $varMap, $conds, $fname, $insertOptions, $selectOptions ); 00536 00537 if ( $ret === false ) { 00538 throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), /*$sql*/ '', $fname ); 00539 } elseif ( $ret != null ) { 00540 // remember number of rows affected 00541 $this->mAffectedRows = sqlsrv_rows_affected( $ret ); 00542 return $ret; 00543 } 00544 return null; 00545 } 00546 00551 function nextSequenceValue( $seqName ) { 00552 if ( !$this->tableExists( 'sequence_' . $seqName ) ) { 00553 sqlsrv_query( $this->mConn, "CREATE TABLE [sequence_$seqName] (id INT NOT NULL IDENTITY PRIMARY KEY, junk varchar(10) NULL)" ); 00554 } 00555 sqlsrv_query( $this->mConn, "INSERT INTO [sequence_$seqName] (junk) VALUES ('')" ); 00556 $ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" ); 00557 $row = sqlsrv_fetch_array( $ret, SQLSRV_FETCH_ASSOC );// KEEP ASSOC THERE, weird weird bug dealing with the return value if you don't 00558 00559 sqlsrv_free_stmt( $ret ); 00560 $this->mInsertId = $row['id']; 00561 return $row['id']; 00562 } 00563 00568 function currentSequenceValue( $seqName ) { 00569 $ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" ); 00570 if ( $ret !== false ) { 00571 $row = sqlsrv_fetch_array( $ret ); 00572 sqlsrv_free_stmt( $ret ); 00573 return $row['id']; 00574 } else { 00575 return $this->nextSequenceValue( $seqName ); 00576 } 00577 } 00578 00579 # Returns the size of a text field, or -1 for "unlimited" 00580 function textFieldSize( $table, $field ) { 00581 $table = $this->tableName( $table ); 00582 $sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns 00583 WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'"; 00584 $res = $this->query( $sql ); 00585 $row = $this->fetchRow( $res ); 00586 $size = -1; 00587 if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) { 00588 $size = $row['CHARACTER_MAXIMUM_LENGTH']; 00589 } 00590 return $size; 00591 } 00592 00601 function limitResult( $sql, $limit, $offset = false ) { 00602 if ( $offset === false || $offset == 0 ) { 00603 if ( strpos( $sql, "SELECT" ) === false ) { 00604 return "TOP {$limit} " . $sql; 00605 } else { 00606 return preg_replace( '/\bSELECT(\s*DISTINCT)?\b/Dsi', 'SELECT$1 TOP ' . $limit, $sql, 1 ); 00607 } 00608 } else { 00609 $sql = ' 00610 SELECT * FROM ( 00611 SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 FROM ( 00612 SELECT 1 AS line2, sub1.* FROM (' . $sql . ') AS sub1 00613 ) as sub2 00614 ) AS sub3 00615 WHERE line3 BETWEEN ' . ( $offset + 1 ) . ' AND ' . ( $offset + $limit ); 00616 return $sql; 00617 } 00618 } 00619 00620 // If there is a limit clause, parse it, strip it, and pass the remaining sql through limitResult() 00621 // with the appropriate parameters. Not the prettiest solution, but better than building a whole new parser. 00622 // This exists becase there are still too many extensions that don't use dynamic sql generation. 00623 function LimitToTopN( $sql ) { 00624 // Matches: LIMIT {[offset,] row_count | row_count OFFSET offset} 00625 $pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i'; 00626 if ( preg_match( $pattern, $sql, $matches ) ) { 00627 // row_count = $matches[4] 00628 $row_count = $matches[4]; 00629 // offset = $matches[3] OR $matches[6] 00630 $offset = $matches[3] or 00631 $offset = $matches[6] or 00632 $offset = false; 00633 00634 // strip the matching LIMIT clause out 00635 $sql = str_replace( $matches[0], '', $sql ); 00636 return $this->limitResult( $sql, $row_count, $offset ); 00637 } 00638 return $sql; 00639 } 00640 00641 function timestamp( $ts = 0 ) { 00642 return wfTimestamp( TS_ISO_8601, $ts ); 00643 } 00644 00648 public static function getSoftwareLink() { 00649 return "[http://www.microsoft.com/sql/ MS SQL Server]"; 00650 } 00651 00655 function getServerVersion() { 00656 $server_info = sqlsrv_server_info( $this->mConn ); 00657 $version = 'Error'; 00658 if ( isset( $server_info['SQLServerVersion'] ) ) { 00659 $version = $server_info['SQLServerVersion']; 00660 } 00661 return $version; 00662 } 00663 00664 function tableExists ( $table, $fname = __METHOD__, $schema = false ) { 00665 $res = sqlsrv_query( $this->mConn, "SELECT * FROM information_schema.tables 00666 WHERE table_type='BASE TABLE' AND table_name = '$table'" ); 00667 if ( $res === false ) { 00668 print( "Error in tableExists query: " . $this->getErrors() ); 00669 return false; 00670 } 00671 if ( sqlsrv_fetch( $res ) ) { 00672 return true; 00673 } else { 00674 return false; 00675 } 00676 } 00677 00682 function fieldExists( $table, $field, $fname = 'DatabaseMssql::fieldExists' ) { 00683 $table = $this->tableName( $table ); 00684 $res = sqlsrv_query( $this->mConn, "SELECT DATA_TYPE FROM INFORMATION_SCHEMA.Columns 00685 WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" ); 00686 if ( $res === false ) { 00687 print( "Error in fieldExists query: " . $this->getErrors() ); 00688 return false; 00689 } 00690 if ( sqlsrv_fetch( $res ) ) { 00691 return true; 00692 } else { 00693 return false; 00694 } 00695 } 00696 00697 function fieldInfo( $table, $field ) { 00698 $table = $this->tableName( $table ); 00699 $res = sqlsrv_query( $this->mConn, "SELECT * FROM INFORMATION_SCHEMA.Columns 00700 WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" ); 00701 if ( $res === false ) { 00702 print( "Error in fieldInfo query: " . $this->getErrors() ); 00703 return false; 00704 } 00705 $meta = $this->fetchRow( $res ); 00706 if ( $meta ) { 00707 return new MssqlField( $meta ); 00708 } 00709 return false; 00710 } 00711 00715 protected function doBegin( $fname = 'DatabaseMssql::begin' ) { 00716 sqlsrv_begin_transaction( $this->mConn ); 00717 $this->mTrxLevel = 1; 00718 } 00719 00723 protected function doCommit( $fname = 'DatabaseMssql::commit' ) { 00724 sqlsrv_commit( $this->mConn ); 00725 $this->mTrxLevel = 0; 00726 } 00727 00732 protected function doRollback( $fname = 'DatabaseMssql::rollback' ) { 00733 sqlsrv_rollback( $this->mConn ); 00734 $this->mTrxLevel = 0; 00735 } 00736 00745 private function escapeIdentifier( $identifier ) { 00746 if ( strlen( $identifier ) == 0 ) { 00747 throw new MWException( "An identifier must not be empty" ); 00748 } 00749 if ( strlen( $identifier ) > 128 ) { 00750 throw new MWException( "The identifier '$identifier' is too long (max. 128)" ); 00751 } 00752 if ( ( strpos( $identifier, '[' ) !== false ) || ( strpos( $identifier, ']' ) !== false ) ) { 00753 // It may be allowed if you quoted with double quotation marks, but that would break if QUOTED_IDENTIFIER is OFF 00754 throw new MWException( "You can't use square brackers in the identifier '$identifier'" ); 00755 } 00756 return "[$identifier]"; 00757 } 00758 00764 function initial_setup( $dbName, $newUser, $loginPassword ) { 00765 $dbName = $this->escapeIdentifier( $dbName ); 00766 00767 // It is not clear what can be used as a login, 00768 // From http://msdn.microsoft.com/en-us/library/ms173463.aspx 00769 // a sysname may be the same as an identifier. 00770 $newUser = $this->escapeIdentifier( $newUser ); 00771 $loginPassword = $this->addQuotes( $loginPassword ); 00772 00773 $this->doQuery( "CREATE DATABASE $dbName;" ); 00774 $this->doQuery( "USE $dbName;" ); 00775 $this->doQuery( "CREATE SCHEMA $dbName;" ); 00776 $this->doQuery( " 00777 CREATE 00778 LOGIN $newUser 00779 WITH 00780 PASSWORD=$loginPassword 00781 ; 00782 " ); 00783 $this->doQuery( " 00784 CREATE 00785 USER $newUser 00786 FOR 00787 LOGIN $newUser 00788 WITH 00789 DEFAULT_SCHEMA=$dbName 00790 ; 00791 " ); 00792 $this->doQuery( " 00793 GRANT 00794 BACKUP DATABASE, 00795 BACKUP LOG, 00796 CREATE DEFAULT, 00797 CREATE FUNCTION, 00798 CREATE PROCEDURE, 00799 CREATE RULE, 00800 CREATE TABLE, 00801 CREATE VIEW, 00802 CREATE FULLTEXT CATALOG 00803 ON 00804 DATABASE::$dbName 00805 TO $newUser 00806 ; 00807 " ); 00808 $this->doQuery( " 00809 GRANT 00810 CONTROL 00811 ON 00812 SCHEMA::$dbName 00813 TO $newUser 00814 ; 00815 " ); 00816 } 00817 00818 function encodeBlob( $b ) { 00819 // we can't have zero's and such, this is a simple encoding to make sure we don't barf 00820 return base64_encode( $b ); 00821 } 00822 00823 function decodeBlob( $b ) { 00824 // we can't have zero's and such, this is a simple encoding to make sure we don't barf 00825 return base64_decode( $b ); 00826 } 00827 00832 function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) { 00833 $ret = array(); 00834 $retJOIN = array(); 00835 $use_index_safe = is_array( $use_index ) ? $use_index : array(); 00836 $join_conds_safe = is_array( $join_conds ) ? $join_conds : array(); 00837 foreach ( $tables as $table ) { 00838 // Is there a JOIN and INDEX clause for this table? 00839 if ( isset( $join_conds_safe[$table] ) && isset( $use_index_safe[$table] ) ) { 00840 $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table ); 00841 $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) ); 00842 $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')'; 00843 $retJOIN[] = $tableClause; 00844 // Is there an INDEX clause? 00845 } elseif ( isset( $use_index_safe[$table] ) ) { 00846 $tableClause = $this->tableName( $table ); 00847 $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) ); 00848 $ret[] = $tableClause; 00849 // Is there a JOIN clause? 00850 } elseif ( isset( $join_conds_safe[$table] ) ) { 00851 $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table ); 00852 $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')'; 00853 $retJOIN[] = $tableClause; 00854 } else { 00855 $tableClause = $this->tableName( $table ); 00856 $ret[] = $tableClause; 00857 } 00858 } 00859 // We can't separate explicit JOIN clauses with ',', use ' ' for those 00860 $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : ""; 00861 $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : ""; 00862 // Compile our final table clause 00863 return implode( ' ', array( $straightJoins, $otherJoins ) ); 00864 } 00865 00866 function strencode( $s ) { # Should not be called by us 00867 return str_replace( "'", "''", $s ); 00868 } 00869 00870 function addQuotes( $s ) { 00871 if ( $s instanceof Blob ) { 00872 return "'" . $s->fetch( $s ) . "'"; 00873 } else { 00874 return parent::addQuotes( $s ); 00875 } 00876 } 00877 00878 public function addIdentifierQuotes( $s ) { 00879 // http://msdn.microsoft.com/en-us/library/aa223962.aspx 00880 return '[' . $s . ']'; 00881 } 00882 00883 public function isQuotedIdentifier( $name ) { 00884 return $name[0] == '[' && substr( $name, -1, 1 ) == ']'; 00885 } 00886 00887 function selectDB( $db ) { 00888 return ( $this->query( "SET DATABASE $db" ) !== false ); 00889 } 00890 00898 function makeSelectOptions( $options ) { 00899 $tailOpts = ''; 00900 $startOpts = ''; 00901 00902 $noKeyOptions = array(); 00903 foreach ( $options as $key => $option ) { 00904 if ( is_numeric( $key ) ) { 00905 $noKeyOptions[$option] = true; 00906 } 00907 } 00908 00909 $tailOpts .= $this->makeGroupByWithHaving( $options ); 00910 00911 $tailOpts .= $this->makeOrderBy( $options ); 00912 00913 if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) { 00914 $startOpts .= 'DISTINCT'; 00915 } 00916 00917 // we want this to be compatible with the output of parent::makeSelectOptions() 00918 return array( $startOpts, '', $tailOpts, '' ); 00919 } 00920 00925 function getType() { 00926 return 'mssql'; 00927 } 00928 00929 function buildConcat( $stringList ) { 00930 return implode( ' + ', $stringList ); 00931 } 00932 00933 public function getSearchEngine() { 00934 return "SearchMssql"; 00935 } 00936 00942 public function getInfinity() { 00943 return '3000-01-31 00:00:00.000'; 00944 } 00945 00946 } // end DatabaseMssql class 00947 00953 class MssqlField implements Field { 00954 private $name, $tablename, $default, $max_length, $nullable, $type; 00955 function __construct ( $info ) { 00956 $this->name = $info['COLUMN_NAME']; 00957 $this->tablename = $info['TABLE_NAME']; 00958 $this->default = $info['COLUMN_DEFAULT']; 00959 $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH']; 00960 $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' ); 00961 $this->type = $info['DATA_TYPE']; 00962 } 00963 00964 function name() { 00965 return $this->name; 00966 } 00967 00968 function tableName() { 00969 return $this->tableName; 00970 } 00971 00972 function defaultValue() { 00973 return $this->default; 00974 } 00975 00976 function maxLength() { 00977 return $this->max_length; 00978 } 00979 00980 function isNullable() { 00981 return $this->nullable; 00982 } 00983 00984 function type() { 00985 return $this->type; 00986 } 00987 } 00988 00995 class MssqlResult { 00996 00997 public function __construct( $queryresult = false ) { 00998 $this->mCursor = 0; 00999 $this->mRows = array(); 01000 $this->mNumFields = sqlsrv_num_fields( $queryresult ); 01001 $this->mFieldMeta = sqlsrv_field_metadata( $queryresult ); 01002 01003 $rows = sqlsrv_fetch_array( $queryresult, SQLSRV_FETCH_ASSOC ); 01004 01005 foreach( $rows as $row ) { 01006 if ( $row !== null ) { 01007 foreach ( $row as $k => $v ) { 01008 if ( is_object( $v ) && method_exists( $v, 'format' ) ) {// DateTime Object 01009 $row[$k] = $v->format( "Y-m-d\TH:i:s\Z" ); 01010 } 01011 } 01012 $this->mRows[] = $row;// read results into memory, cursors are not supported 01013 } 01014 } 01015 $this->mRowCount = count( $this->mRows ); 01016 sqlsrv_free_stmt( $queryresult ); 01017 } 01018 01019 private function array_to_obj( $array, &$obj ) { 01020 foreach ( $array as $key => $value ) { 01021 if ( is_array( $value ) ) { 01022 $obj->$key = new stdClass(); 01023 $this->array_to_obj( $value, $obj->$key ); 01024 } else { 01025 if ( !empty( $key ) ) { 01026 $obj->$key = $value; 01027 } 01028 } 01029 } 01030 return $obj; 01031 } 01032 01033 public function fetch( $mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass' ) { 01034 if ( $this->mCursor >= $this->mRowCount || $this->mRowCount == 0 ) { 01035 return false; 01036 } 01037 $arrNum = array(); 01038 if ( $mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH ) { 01039 foreach ( $this->mRows[$this->mCursor] as $value ) { 01040 $arrNum[] = $value; 01041 } 01042 } 01043 switch( $mode ) { 01044 case SQLSRV_FETCH_ASSOC: 01045 $ret = $this->mRows[$this->mCursor]; 01046 break; 01047 case SQLSRV_FETCH_NUMERIC: 01048 $ret = $arrNum; 01049 break; 01050 case 'OBJECT': 01051 $o = new $object_class; 01052 $ret = $this->array_to_obj( $this->mRows[$this->mCursor], $o ); 01053 break; 01054 case SQLSRV_FETCH_BOTH: 01055 default: 01056 $ret = $this->mRows[$this->mCursor] + $arrNum; 01057 break; 01058 } 01059 01060 $this->mCursor++; 01061 return $ret; 01062 } 01063 01064 public function get( $pos, $fld ) { 01065 return $this->mRows[$pos][$fld]; 01066 } 01067 01068 public function numrows() { 01069 return $this->mRowCount; 01070 } 01071 01072 public function seek( $iRow ) { 01073 $this->mCursor = min( $iRow, $this->mRowCount ); 01074 } 01075 01076 public function numfields() { 01077 return $this->mNumFields; 01078 } 01079 01080 public function fieldname( $nr ) { 01081 $arrKeys = array_keys( $this->mRows[0] ); 01082 return $arrKeys[$nr]; 01083 } 01084 01085 public function fieldtype( $nr ) { 01086 $i = 0; 01087 $intType = -1; 01088 foreach ( $this->mFieldMeta as $meta ) { 01089 if ( $nr == $i ) { 01090 $intType = $meta['Type']; 01091 break; 01092 } 01093 $i++; 01094 } 01095 // http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table 01096 switch( $intType ) { 01097 case SQLSRV_SQLTYPE_BIGINT: $strType = 'bigint'; break; 01098 case SQLSRV_SQLTYPE_BINARY: $strType = 'binary'; break; 01099 case SQLSRV_SQLTYPE_BIT: $strType = 'bit'; break; 01100 case SQLSRV_SQLTYPE_CHAR: $strType = 'char'; break; 01101 case SQLSRV_SQLTYPE_DATETIME: $strType = 'datetime'; break; 01102 case SQLSRV_SQLTYPE_DECIMAL/*($precision, $scale)*/: $strType = 'decimal'; break; 01103 case SQLSRV_SQLTYPE_FLOAT: $strType = 'float'; break; 01104 case SQLSRV_SQLTYPE_IMAGE: $strType = 'image'; break; 01105 case SQLSRV_SQLTYPE_INT: $strType = 'int'; break; 01106 case SQLSRV_SQLTYPE_MONEY: $strType = 'money'; break; 01107 case SQLSRV_SQLTYPE_NCHAR/*($charCount)*/: $strType = 'nchar'; break; 01108 case SQLSRV_SQLTYPE_NUMERIC/*($precision, $scale)*/: $strType = 'numeric'; break; 01109 case SQLSRV_SQLTYPE_NVARCHAR/*($charCount)*/: $strType = 'nvarchar'; break; 01110 // case SQLSRV_SQLTYPE_NVARCHAR('max'): $strType = 'nvarchar(MAX)'; break; 01111 case SQLSRV_SQLTYPE_NTEXT: $strType = 'ntext'; break; 01112 case SQLSRV_SQLTYPE_REAL: $strType = 'real'; break; 01113 case SQLSRV_SQLTYPE_SMALLDATETIME: $strType = 'smalldatetime'; break; 01114 case SQLSRV_SQLTYPE_SMALLINT: $strType = 'smallint'; break; 01115 case SQLSRV_SQLTYPE_SMALLMONEY: $strType = 'smallmoney'; break; 01116 case SQLSRV_SQLTYPE_TEXT: $strType = 'text'; break; 01117 case SQLSRV_SQLTYPE_TIMESTAMP: $strType = 'timestamp'; break; 01118 case SQLSRV_SQLTYPE_TINYINT: $strType = 'tinyint'; break; 01119 case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER: $strType = 'uniqueidentifier'; break; 01120 case SQLSRV_SQLTYPE_UDT: $strType = 'UDT'; break; 01121 case SQLSRV_SQLTYPE_VARBINARY/*($byteCount)*/: $strType = 'varbinary'; break; 01122 // case SQLSRV_SQLTYPE_VARBINARY('max'): $strType = 'varbinary(MAX)'; break; 01123 case SQLSRV_SQLTYPE_VARCHAR/*($charCount)*/: $strType = 'varchar'; break; 01124 // case SQLSRV_SQLTYPE_VARCHAR('max'): $strType = 'varchar(MAX)'; break; 01125 case SQLSRV_SQLTYPE_XML: $strType = 'xml'; break; 01126 default: $strType = $intType; 01127 } 01128 return $strType; 01129 } 01130 01131 public function free() { 01132 unset( $this->mRows ); 01133 } 01134 }