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