MediaWiki
master
|
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 } 00819 00820 function encodeBlob( $b ) { 00821 // we can't have zero's and such, this is a simple encoding to make sure we don't barf 00822 return base64_encode( $b ); 00823 } 00824 00825 function decodeBlob( $b ) { 00826 // we can't have zero's and such, this is a simple encoding to make sure we don't barf 00827 return base64_decode( $b ); 00828 } 00829 00834 function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) { 00835 $ret = array(); 00836 $retJOIN = array(); 00837 $use_index_safe = is_array( $use_index ) ? $use_index : array(); 00838 $join_conds_safe = is_array( $join_conds ) ? $join_conds : array(); 00839 foreach ( $tables as $table ) { 00840 // Is there a JOIN and INDEX clause for this table? 00841 if ( isset( $join_conds_safe[$table] ) && isset( $use_index_safe[$table] ) ) { 00842 $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table ); 00843 $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) ); 00844 $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')'; 00845 $retJOIN[] = $tableClause; 00846 // Is there an INDEX clause? 00847 } elseif ( isset( $use_index_safe[$table] ) ) { 00848 $tableClause = $this->tableName( $table ); 00849 $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) ); 00850 $ret[] = $tableClause; 00851 // Is there a JOIN clause? 00852 } elseif ( isset( $join_conds_safe[$table] ) ) { 00853 $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table ); 00854 $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')'; 00855 $retJOIN[] = $tableClause; 00856 } else { 00857 $tableClause = $this->tableName( $table ); 00858 $ret[] = $tableClause; 00859 } 00860 } 00861 // We can't separate explicit JOIN clauses with ',', use ' ' for those 00862 $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : ""; 00863 $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : ""; 00864 // Compile our final table clause 00865 return implode( ' ', array( $straightJoins, $otherJoins ) ); 00866 } 00867 00868 function strencode( $s ) { # Should not be called by us 00869 return str_replace( "'", "''", $s ); 00870 } 00871 00872 function addQuotes( $s ) { 00873 if ( $s instanceof Blob ) { 00874 return "'" . $s->fetch( $s ) . "'"; 00875 } else { 00876 return parent::addQuotes( $s ); 00877 } 00878 } 00879 00880 public function addIdentifierQuotes( $s ) { 00881 // http://msdn.microsoft.com/en-us/library/aa223962.aspx 00882 return '[' . $s . ']'; 00883 } 00884 00885 public function isQuotedIdentifier( $name ) { 00886 return $name[0] == '[' && substr( $name, -1, 1 ) == ']'; 00887 } 00888 00889 function selectDB( $db ) { 00890 return ( $this->query( "SET DATABASE $db" ) !== false ); 00891 } 00892 00900 function makeSelectOptions( $options ) { 00901 $tailOpts = ''; 00902 $startOpts = ''; 00903 00904 $noKeyOptions = array(); 00905 foreach ( $options as $key => $option ) { 00906 if ( is_numeric( $key ) ) { 00907 $noKeyOptions[$option] = true; 00908 } 00909 } 00910 00911 if ( isset( $options['GROUP BY'] ) ) { 00912 $tailOpts .= " GROUP BY {$options['GROUP BY']}"; 00913 } 00914 if ( isset( $options['HAVING'] ) ) { 00915 $tailOpts .= " HAVING {$options['GROUP BY']}"; 00916 } 00917 if ( isset( $options['ORDER BY'] ) ) { 00918 $tailOpts .= " ORDER BY {$options['ORDER BY']}"; 00919 } 00920 00921 if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) { 00922 $startOpts .= 'DISTINCT'; 00923 } 00924 00925 // we want this to be compatible with the output of parent::makeSelectOptions() 00926 return array( $startOpts, '' , $tailOpts, '' ); 00927 } 00928 00933 function getType(){ 00934 return 'mssql'; 00935 } 00936 00937 function buildConcat( $stringList ) { 00938 return implode( ' + ', $stringList ); 00939 } 00940 00941 public function getSearchEngine() { 00942 return "SearchMssql"; 00943 } 00944 00950 public function getInfinity() { 00951 return '3000-01-31 00:00:00.000'; 00952 } 00953 00954 } // end DatabaseMssql class 00955 00961 class MssqlField implements Field { 00962 private $name, $tablename, $default, $max_length, $nullable, $type; 00963 function __construct ( $info ) { 00964 $this->name = $info['COLUMN_NAME']; 00965 $this->tablename = $info['TABLE_NAME']; 00966 $this->default = $info['COLUMN_DEFAULT']; 00967 $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH']; 00968 $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' ); 00969 $this->type = $info['DATA_TYPE']; 00970 } 00971 00972 function name() { 00973 return $this->name; 00974 } 00975 00976 function tableName() { 00977 return $this->tableName; 00978 } 00979 00980 function defaultValue() { 00981 return $this->default; 00982 } 00983 00984 function maxLength() { 00985 return $this->max_length; 00986 } 00987 00988 function isNullable() { 00989 return $this->nullable; 00990 } 00991 00992 function type() { 00993 return $this->type; 00994 } 00995 } 00996 01003 class MssqlResult { 01004 01005 public function __construct( $queryresult = false ) { 01006 $this->mCursor = 0; 01007 $this->mRows = array(); 01008 $this->mNumFields = sqlsrv_num_fields( $queryresult ); 01009 $this->mFieldMeta = sqlsrv_field_metadata( $queryresult ); 01010 01011 $rows = sqlsrv_fetch_array( $queryresult, SQLSRV_FETCH_ASSOC ); 01012 01013 foreach( $rows as $row ) { 01014 if ( $row !== null ) { 01015 foreach ( $row as $k => $v ) { 01016 if ( is_object( $v ) && method_exists( $v, 'format' ) ) {// DateTime Object 01017 $row[$k] = $v->format( "Y-m-d\TH:i:s\Z" ); 01018 } 01019 } 01020 $this->mRows[] = $row;// read results into memory, cursors are not supported 01021 } 01022 } 01023 $this->mRowCount = count( $this->mRows ); 01024 sqlsrv_free_stmt( $queryresult ); 01025 } 01026 01027 private function array_to_obj( $array, &$obj ) { 01028 foreach ( $array as $key => $value ) { 01029 if ( is_array( $value ) ) { 01030 $obj->$key = new stdClass(); 01031 $this->array_to_obj( $value, $obj->$key ); 01032 } else { 01033 if ( !empty( $key ) ) { 01034 $obj->$key = $value; 01035 } 01036 } 01037 } 01038 return $obj; 01039 } 01040 01041 public function fetch( $mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass' ) { 01042 if ( $this->mCursor >= $this->mRowCount || $this->mRowCount == 0 ) { 01043 return false; 01044 } 01045 $arrNum = array(); 01046 if ( $mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH ) { 01047 foreach ( $this->mRows[$this->mCursor] as $value ) { 01048 $arrNum[] = $value; 01049 } 01050 } 01051 switch( $mode ) { 01052 case SQLSRV_FETCH_ASSOC: 01053 $ret = $this->mRows[$this->mCursor]; 01054 break; 01055 case SQLSRV_FETCH_NUMERIC: 01056 $ret = $arrNum; 01057 break; 01058 case 'OBJECT': 01059 $o = new $object_class; 01060 $ret = $this->array_to_obj( $this->mRows[$this->mCursor], $o ); 01061 break; 01062 case SQLSRV_FETCH_BOTH: 01063 default: 01064 $ret = $this->mRows[$this->mCursor] + $arrNum; 01065 break; 01066 } 01067 01068 $this->mCursor++; 01069 return $ret; 01070 } 01071 01072 public function get( $pos, $fld ) { 01073 return $this->mRows[$pos][$fld]; 01074 } 01075 01076 public function numrows() { 01077 return $this->mRowCount; 01078 } 01079 01080 public function seek( $iRow ) { 01081 $this->mCursor = min( $iRow, $this->mRowCount ); 01082 } 01083 01084 public function numfields() { 01085 return $this->mNumFields; 01086 } 01087 01088 public function fieldname( $nr ) { 01089 $arrKeys = array_keys( $this->mRows[0] ); 01090 return $arrKeys[$nr]; 01091 } 01092 01093 public function fieldtype( $nr ) { 01094 $i = 0; 01095 $intType = -1; 01096 foreach ( $this->mFieldMeta as $meta ) { 01097 if ( $nr == $i ) { 01098 $intType = $meta['Type']; 01099 break; 01100 } 01101 $i++; 01102 } 01103 // http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table 01104 switch( $intType ) { 01105 case SQLSRV_SQLTYPE_BIGINT: $strType = 'bigint'; break; 01106 case SQLSRV_SQLTYPE_BINARY: $strType = 'binary'; break; 01107 case SQLSRV_SQLTYPE_BIT: $strType = 'bit'; break; 01108 case SQLSRV_SQLTYPE_CHAR: $strType = 'char'; break; 01109 case SQLSRV_SQLTYPE_DATETIME: $strType = 'datetime'; break; 01110 case SQLSRV_SQLTYPE_DECIMAL/*($precision, $scale)*/: $strType = 'decimal'; break; 01111 case SQLSRV_SQLTYPE_FLOAT: $strType = 'float'; break; 01112 case SQLSRV_SQLTYPE_IMAGE: $strType = 'image'; break; 01113 case SQLSRV_SQLTYPE_INT: $strType = 'int'; break; 01114 case SQLSRV_SQLTYPE_MONEY: $strType = 'money'; break; 01115 case SQLSRV_SQLTYPE_NCHAR/*($charCount)*/: $strType = 'nchar'; break; 01116 case SQLSRV_SQLTYPE_NUMERIC/*($precision, $scale)*/: $strType = 'numeric'; break; 01117 case SQLSRV_SQLTYPE_NVARCHAR/*($charCount)*/: $strType = 'nvarchar'; break; 01118 // case SQLSRV_SQLTYPE_NVARCHAR('max'): $strType = 'nvarchar(MAX)'; break; 01119 case SQLSRV_SQLTYPE_NTEXT: $strType = 'ntext'; break; 01120 case SQLSRV_SQLTYPE_REAL: $strType = 'real'; break; 01121 case SQLSRV_SQLTYPE_SMALLDATETIME: $strType = 'smalldatetime'; break; 01122 case SQLSRV_SQLTYPE_SMALLINT: $strType = 'smallint'; break; 01123 case SQLSRV_SQLTYPE_SMALLMONEY: $strType = 'smallmoney'; break; 01124 case SQLSRV_SQLTYPE_TEXT: $strType = 'text'; break; 01125 case SQLSRV_SQLTYPE_TIMESTAMP: $strType = 'timestamp'; break; 01126 case SQLSRV_SQLTYPE_TINYINT: $strType = 'tinyint'; break; 01127 case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER: $strType = 'uniqueidentifier'; break; 01128 case SQLSRV_SQLTYPE_UDT: $strType = 'UDT'; break; 01129 case SQLSRV_SQLTYPE_VARBINARY/*($byteCount)*/: $strType = 'varbinary'; break; 01130 // case SQLSRV_SQLTYPE_VARBINARY('max'): $strType = 'varbinary(MAX)'; break; 01131 case SQLSRV_SQLTYPE_VARCHAR/*($charCount)*/: $strType = 'varchar'; break; 01132 // case SQLSRV_SQLTYPE_VARCHAR('max'): $strType = 'varchar(MAX)'; break; 01133 case SQLSRV_SQLTYPE_XML: $strType = 'xml'; break; 01134 default: $strType = $intType; 01135 } 01136 return $strType; 01137 } 01138 01139 public function free() { 01140 unset( $this->mRows ); 01141 return; 01142 } 01143 }