See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 /** 3 * Native MSSQL driver. 4 * 5 * Requires mssql client. Works on Windows. 6 * https://docs.microsoft.com/sql/connect/php 7 * 8 * This file is part of ADOdb, a Database Abstraction Layer library for PHP. 9 * 10 * @package ADOdb 11 * @link https://adodb.org Project's web site and documentation 12 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker 13 * 14 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause 15 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option, 16 * any later version. This means you can use it in proprietary products. 17 * See the LICENSE.md file distributed with this source code for details. 18 * @license BSD-3-Clause 19 * @license LGPL-2.1-or-later 20 * 21 * @copyright 2000-2013 John Lim 22 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community 23 */ 24 25 // security - hide paths 26 if (!defined('ADODB_DIR')) die(); 27 28 if (!function_exists('sqlsrv_configure')) { 29 die("mssqlnative extension not installed"); 30 } 31 32 if (!function_exists('sqlsrv_set_error_handling')) { 33 function sqlsrv_set_error_handling($constant) { 34 sqlsrv_configure("WarningsReturnAsErrors", $constant); 35 } 36 } 37 if (!function_exists('sqlsrv_log_set_severity')) { 38 function sqlsrv_log_set_severity($constant) { 39 sqlsrv_configure("LogSeverity", $constant); 40 } 41 } 42 if (!function_exists('sqlsrv_log_set_subsystems')) { 43 function sqlsrv_log_set_subsystems($constant) { 44 sqlsrv_configure("LogSubsystems", $constant); 45 } 46 } 47 48 class ADODB_mssqlnative extends ADOConnection { 49 var $databaseType = "mssqlnative"; 50 var $dataProvider = "mssqlnative"; 51 var $replaceQuote = "''"; // string to use to replace quotes 52 var $fmtDate = "'Y-m-d'"; 53 var $fmtTimeStamp = "'Y-m-d\TH:i:s'"; 54 /** 55 * Enabling InsertID capability will cause execution of an extra query 56 * {@see $identitySQL} after each INSERT statement. To improve performance 57 * when inserting a large number of records, you should switch this off by 58 * calling {@see enableLastInsertID enableLastInsertID(false)}. 59 * @var bool $hasInsertID 60 */ 61 var $hasInsertID = true; 62 var $substr = "substring"; 63 var $length = 'len'; 64 var $hasAffectedRows = true; 65 var $poorAffectedRows = false; 66 var $metaDatabasesSQL = "select name from sys.sysdatabases where name <> 'master'"; 67 var $metaTablesSQL="select name,case when type='U' then 'T' else 'V' end from sysobjects where (type='U' or type='V') and (name not in ('sysallocations','syscolumns','syscomments','sysdepends','sysfilegroups','sysfiles','sysfiles1','sysforeignkeys','sysfulltextcatalogs','sysindexes','sysindexkeys','sysmembers','sysobjects','syspermissions','sysprotects','sysreferences','systypes','sysusers','sysalternates','sysconstraints','syssegments','REFERENTIAL_CONSTRAINTS','CHECK_CONSTRAINTS','CONSTRAINT_TABLE_USAGE','CONSTRAINT_COLUMN_USAGE','VIEWS','VIEW_TABLE_USAGE','VIEW_COLUMN_USAGE','SCHEMATA','TABLES','TABLE_CONSTRAINTS','TABLE_PRIVILEGES','COLUMNS','COLUMN_DOMAIN_USAGE','COLUMN_PRIVILEGES','DOMAINS','DOMAIN_CONSTRAINTS','KEY_COLUMN_USAGE','dtproperties'))"; 68 var $metaColumnsSQL = 69 "select c.name, 70 t.name as type, 71 c.length, 72 c.xprec as precision, 73 c.xscale as scale, 74 c.isnullable as nullable, 75 c.cdefault as default_value, 76 c.xtype, 77 t.length as type_length, 78 sc.is_identity 79 from syscolumns c 80 join systypes t on t.xusertype=c.xusertype 81 join sysobjects o on o.id=c.id 82 join sys.tables st on st.name=o.name 83 join sys.columns sc on sc.object_id = st.object_id and sc.name=c.name 84 where o.name='%s'"; 85 var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE 86 var $hasGenID = true; 87 var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)'; 88 var $sysTimeStamp = 'GetDate()'; 89 var $maxParameterLen = 4000; 90 var $arrayClass = 'ADORecordSet_array_mssqlnative'; 91 var $uniqueSort = true; 92 var $leftOuter = '*='; 93 var $rightOuter = '=*'; 94 var $ansiOuter = true; // for mssql7 or later 95 var $identitySQL = 'select SCOPE_IDENTITY()'; // 'select SCOPE_IDENTITY'; # for mssql 2000 96 var $uniqueOrderBy = true; 97 var $_bindInputArray = true; 98 var $_dropSeqSQL = "drop table %s"; 99 100 var $connectionInfo = array('ReturnDatesAsStrings'=>true); 101 var $cachedSchemaFlush = false; 102 103 var $sequences = false; 104 var $mssql_version = ''; 105 106 function __construct() 107 { 108 if ($this->debug) { 109 ADOConnection::outp("<pre>"); 110 sqlsrv_set_error_handling( SQLSRV_ERRORS_LOG_ALL ); 111 sqlsrv_log_set_severity( SQLSRV_LOG_SEVERITY_ALL ); 112 sqlsrv_log_set_subsystems(SQLSRV_LOG_SYSTEM_ALL); 113 sqlsrv_configure('WarningsReturnAsErrors', 0); 114 } else { 115 sqlsrv_set_error_handling(0); 116 sqlsrv_log_set_severity(0); 117 sqlsrv_log_set_subsystems(SQLSRV_LOG_SYSTEM_ALL); 118 sqlsrv_configure('WarningsReturnAsErrors', 0); 119 } 120 } 121 122 /** 123 * Initializes the SQL Server version. 124 * Dies if connected to a non-supported version (2000 and older) 125 */ 126 function ServerVersion() { 127 $data = $this->ServerInfo(); 128 preg_match('/^\d{2}/', $data['version'], $matches); 129 $version = (int)reset($matches); 130 131 // We only support SQL Server 2005 and up 132 if($version < 9) { 133 die("SQL SERVER VERSION {$data['version']} NOT SUPPORTED IN mssqlnative DRIVER"); 134 } 135 136 $this->mssql_version = $version; 137 } 138 139 function ServerInfo() { 140 global $ADODB_FETCH_MODE; 141 static $arr = false; 142 if (is_array($arr)) 143 return $arr; 144 if ($this->fetchMode === false) { 145 $savem = $ADODB_FETCH_MODE; 146 $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 147 } elseif ($this->fetchMode >=0 && $this->fetchMode <=2) { 148 $savem = $this->fetchMode; 149 } else 150 $savem = $this->SetFetchMode(ADODB_FETCH_NUM); 151 152 $arrServerInfo = sqlsrv_server_info($this->_connectionID); 153 $ADODB_FETCH_MODE = $savem; 154 155 $arr = array(); 156 $arr['description'] = $arrServerInfo['SQLServerName'].' connected to '.$arrServerInfo['CurrentDatabase']; 157 $arr['version'] = $arrServerInfo['SQLServerVersion'];//ADOConnection::_findvers($arr['description']); 158 return $arr; 159 } 160 161 function IfNull( $field, $ifNull ) 162 { 163 return " ISNULL($field, $ifNull) "; // if MS SQL Server 164 } 165 166 public function enableLastInsertID($enable = true) { 167 $this->hasInsertID = $enable; 168 $this->lastInsID = false; 169 } 170 171 /** 172 * Get the last value inserted into an IDENTITY column. 173 * 174 * The value will actually be set in {@see _query()} when executing an 175 * INSERT statement, but only if the connection's $hasInsertId property 176 * is true; this can be set with {@see enableLastInsertId()}. 177 * 178 * @inheritDoc 179 */ 180 protected function _insertID($table = '', $column = '') 181 { 182 return $this->lastInsID; 183 } 184 185 function _affectedrows() 186 { 187 if ($this->_queryID) 188 return sqlsrv_rows_affected($this->_queryID); 189 } 190 191 function GenID($seq='adodbseq',$start=1) { 192 switch($this->mssql_version){ 193 case 9: 194 case 10: 195 return $this->GenID2008($seq, $start); 196 break; 197 default: 198 return $this->GenID2012($seq, $start); 199 break; 200 } 201 } 202 203 function CreateSequence($seq='adodbseq',$start=1) 204 { 205 switch($this->mssql_version){ 206 case 9: 207 case 10: 208 return $this->CreateSequence2008($seq, $start); 209 break; 210 default: 211 return $this->CreateSequence2012($seq, $start); 212 break; 213 } 214 } 215 216 /** 217 * For Server 2005,2008, duplicate a sequence with an identity table 218 */ 219 function CreateSequence2008($seq='adodbseq',$start=1) 220 { 221 if($this->debug) ADOConnection::outp("<hr>CreateSequence($seq,$start)"); 222 sqlsrv_begin_transaction($this->_connectionID); 223 $start -= 1; 224 $this->Execute("create table $seq (id int)");//was float(53) 225 $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)"); 226 if (!$ok) { 227 if($this->debug) ADOConnection::outp("<hr>Error: ROLLBACK"); 228 sqlsrv_rollback($this->_connectionID); 229 return false; 230 } 231 sqlsrv_commit($this->_connectionID); 232 return true; 233 } 234 235 /** 236 * Proper Sequences Only available to Server 2012 and up 237 */ 238 function CreateSequence2012($seq='adodbseq',$start=1){ 239 if (!$this->sequences){ 240 $sql = "SELECT name FROM sys.sequences"; 241 $this->sequences = $this->GetCol($sql); 242 } 243 $ok = $this->Execute("CREATE SEQUENCE $seq START WITH $start INCREMENT BY 1"); 244 if (!$ok) 245 die("CANNOT CREATE SEQUENCE" . print_r(sqlsrv_errors(),true)); 246 $this->sequences[] = $seq; 247 } 248 249 /** 250 * For Server 2005,2008, duplicate a sequence with an identity table 251 */ 252 function GenID2008($seq='adodbseq',$start=1) 253 { 254 if($this->debug) ADOConnection::outp("<hr>CreateSequence($seq,$start)"); 255 sqlsrv_begin_transaction($this->_connectionID); 256 $ok = $this->Execute("update $seq with (tablock,holdlock) set id = id + 1"); 257 if (!$ok) { 258 $start -= 1; 259 $this->Execute("create table $seq (id int)");//was float(53) 260 $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)"); 261 if (!$ok) { 262 if($this->debug) ADOConnection::outp("<hr>Error: ROLLBACK"); 263 sqlsrv_rollback($this->_connectionID); 264 return false; 265 } 266 } 267 $num = $this->GetOne("select id from $seq"); 268 sqlsrv_commit($this->_connectionID); 269 return $num; 270 } 271 /** 272 * Only available to Server 2012 and up 273 * Cannot do this the normal adodb way by trapping an error if the 274 * sequence does not exist because sql server will auto create a 275 * sequence with the starting number of -9223372036854775808 276 */ 277 function GenID2012($seq='adodbseq',$start=1) 278 { 279 280 /* 281 * First time in create an array of sequence names that we 282 * can use in later requests to see if the sequence exists 283 * the overhead is creating a list of sequences every time 284 * we need access to at least 1. If we really care about 285 * performance, we could maybe flag a 'nocheck' class variable 286 */ 287 if (!$this->sequences){ 288 $sql = "SELECT name FROM sys.sequences"; 289 $this->sequences = $this->GetCol($sql); 290 } 291 if (!is_array($this->sequences) 292 || is_array($this->sequences) && !in_array($seq,$this->sequences)){ 293 $this->CreateSequence2012($seq, $start); 294 295 } 296 $num = $this->GetOne("SELECT NEXT VALUE FOR $seq"); 297 return $num; 298 } 299 300 // Format date column in sql string given an input format that understands Y M D 301 function SQLDate($fmt, $col=false) 302 { 303 if (!$col) { 304 $col = $this->sysTimeStamp; 305 } 306 $s = ''; 307 308 $ConvertableFmt=array( 309 "m/d/Y"=>101, "m/d/y"=>101 // US 310 ,"Y.m.d"=>102, "y.m.d"=>102 // ANSI 311 ,"d/m/Y"=>103, "d/m/y"=>103 // French /english 312 ,"d.m.Y"=>104, "d.m.y"=>104 // German 313 ,"d-m-Y"=>105, "d-m-y"=>105 // Italian 314 ,"m-d-Y"=>110, "m-d-y"=>110 // US Dash 315 ,"Y/m/d"=>111, "y/m/d"=>111 // Japan 316 ,"Ymd"=>112, "ymd"=>112 // ISO 317 ,"H:i:s"=>108 // Time 318 ); 319 if (key_exists($fmt,$ConvertableFmt)) { 320 return "convert (varchar ,$col," . $ConvertableFmt[$fmt] . ")"; 321 } 322 323 $len = strlen($fmt); 324 for ($i=0; $i < $len; $i++) { 325 if ($s) $s .= '+'; 326 $ch = $fmt[$i]; 327 switch($ch) { 328 case 'Y': 329 case 'y': 330 $s .= "datename(yyyy,$col)"; 331 break; 332 case 'M': 333 $s .= "convert(char(3),$col,0)"; 334 break; 335 case 'm': 336 $s .= "replace(str(month($col),2),' ','0')"; 337 break; 338 case 'Q': 339 case 'q': 340 $s .= "datename(quarter,$col)"; 341 break; 342 case 'D': 343 case 'd': 344 $s .= "replace(str(day($col),2),' ','0')"; 345 break; 346 case 'h': 347 $s .= "substring(convert(char(14),$col,0),13,2)"; 348 break; 349 350 case 'H': 351 $s .= "replace(str(datepart(hh,$col),2),' ','0')"; 352 break; 353 354 case 'i': 355 $s .= "replace(str(datepart(mi,$col),2),' ','0')"; 356 break; 357 case 's': 358 $s .= "replace(str(datepart(ss,$col),2),' ','0')"; 359 break; 360 case 'a': 361 case 'A': 362 $s .= "substring(convert(char(19),$col,0),18,2)"; 363 break; 364 case 'l': 365 $s .= "datename(dw,$col)"; 366 break; 367 default: 368 if ($ch == '\\') { 369 $i++; 370 $ch = substr($fmt,$i,1); 371 } 372 $s .= $this->qstr($ch); 373 break; 374 } 375 } 376 return $s; 377 } 378 379 380 function BeginTrans() 381 { 382 if ($this->transOff) return true; 383 $this->transCnt += 1; 384 if ($this->debug) ADOConnection::outp('<hr>begin transaction'); 385 sqlsrv_begin_transaction($this->_connectionID); 386 return true; 387 } 388 389 function CommitTrans($ok=true) 390 { 391 if ($this->transOff) return true; 392 if ($this->debug) ADOConnection::outp('<hr>commit transaction'); 393 if (!$ok) return $this->RollbackTrans(); 394 if ($this->transCnt) $this->transCnt -= 1; 395 sqlsrv_commit($this->_connectionID); 396 return true; 397 } 398 399 function RollbackTrans() 400 { 401 if ($this->transOff) return true; 402 if ($this->debug) ADOConnection::outp('<hr>rollback transaction'); 403 if ($this->transCnt) $this->transCnt -= 1; 404 sqlsrv_rollback($this->_connectionID); 405 return true; 406 } 407 408 function SetTransactionMode( $transaction_mode ) 409 { 410 $this->_transmode = $transaction_mode; 411 if (empty($transaction_mode)) { 412 $this->Execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED'); 413 return; 414 } 415 if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode; 416 $this->Execute("SET TRANSACTION ".$transaction_mode); 417 } 418 419 /* 420 Usage: 421 422 $this->BeginTrans(); 423 $this->RowLock('table1,table2','table1.id=33 and table2.id=table1.id'); # lock row 33 for both tables 424 425 # some operation on both tables table1 and table2 426 427 $this->CommitTrans(); 428 429 See http://www.swynk.com/friends/achigrik/SQL70Locks.asp 430 */ 431 function RowLock($tables,$where,$col='1 as adodbignore') 432 { 433 if ($col == '1 as adodbignore') $col = 'top 1 null as ignore'; 434 if (!$this->transCnt) $this->BeginTrans(); 435 return $this->GetOne("select $col from $tables with (ROWLOCK,HOLDLOCK) where $where"); 436 } 437 438 function SelectDB($dbName) 439 { 440 $this->database = $dbName; 441 $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions 442 if ($this->_connectionID) { 443 $rs = $this->Execute('USE '.$dbName); 444 if($rs) { 445 return true; 446 } else return false; 447 } 448 else return false; 449 } 450 451 function ErrorMsg() 452 { 453 $retErrors = sqlsrv_errors(SQLSRV_ERR_ALL); 454 if($retErrors != null) { 455 foreach($retErrors as $arrError) { 456 $this->_errorMsg .= "SQLState: ".$arrError[ 'SQLSTATE']."\n"; 457 $this->_errorMsg .= "Error Code: ".$arrError[ 'code']."\n"; 458 $this->_errorMsg .= "Message: ".$arrError[ 'message']."\n"; 459 } 460 } 461 return $this->_errorMsg; 462 } 463 464 function ErrorNo() 465 { 466 $err = sqlsrv_errors(SQLSRV_ERR_ALL); 467 if ($err && $err[0]) 468 return $err[0]['code']; 469 else 470 return 0; 471 } 472 473 // returns true or false 474 function _connect($argHostname, $argUsername, $argPassword, $argDatabasename) 475 { 476 if (!function_exists('sqlsrv_connect')) 477 { 478 if ($this->debug) 479 ADOConnection::outp('Microsoft SQL Server native driver (mssqlnative) not installed'); 480 return null; 481 } 482 483 if (!empty($this->port)) 484 /* 485 * Port uses a comma 486 */ 487 $argHostname .= ",".$this->port; 488 489 $connectionInfo = $this->connectionInfo; 490 $connectionInfo["Database"] = $argDatabasename; 491 if ((string)$argUsername != '' || (string)$argPassword != '') 492 { 493 /* 494 * If they pass either a userid or password, we assume 495 * SQL Server authentication 496 */ 497 $connectionInfo["UID"] = $argUsername; 498 $connectionInfo["PWD"] = $argPassword; 499 500 if ($this->debug) 501 ADOConnection::outp('userid or password supplied, attempting connection with SQL Server Authentication'); 502 503 } 504 else 505 { 506 /* 507 * If they don't pass either value, we won't add them to the 508 * connection parameters. This will then force an attempt 509 * to use windows authentication 510 */ 511 if ($this->debug) 512 513 ADOConnection::outp('No userid or password supplied, attempting connection with Windows Authentication'); 514 } 515 516 517 /* 518 * Now merge in the passed connection parameters setting 519 */ 520 foreach ($this->connectionParameters as $options) 521 { 522 foreach($options as $parameter=>$value) 523 $connectionInfo[$parameter] = $value; 524 } 525 526 if ($this->debug) ADOConnection::outp("connecting to host: $argHostname params: ".var_export($connectionInfo,true)); 527 if(!($this->_connectionID = @sqlsrv_connect($argHostname,$connectionInfo))) 528 { 529 if ($this->debug) 530 ADOConnection::outp( 'Connection Failed: '.print_r( sqlsrv_errors(), true)); 531 return false; 532 } 533 534 $this->ServerVersion(); 535 536 return true; 537 } 538 539 // returns true or false 540 function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) 541 { 542 //return null;//not implemented. NOTE: Persistent connections have no effect if PHP is used as a CGI program. (FastCGI!) 543 return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename); 544 } 545 546 547 function Prepare($sql) 548 { 549 return $sql; // prepare does not work properly with bind parameters as bind parameters are managed by sqlsrv_prepare! 550 } 551 552 // returns concatenated string 553 // MSSQL requires integers to be cast as strings 554 // automatically cast every datatype to VARCHAR(255) 555 // @author David Rogers (introspectshun) 556 function Concat() 557 { 558 $s = ""; 559 $arr = func_get_args(); 560 561 // Split single record on commas, if possible 562 if (sizeof($arr) == 1) { 563 foreach ($arr as $arg) { 564 $args = explode(',', $arg); 565 } 566 $arr = $args; 567 } 568 569 array_walk( 570 $arr, 571 function(&$value, $key) { 572 $value = "CAST(" . $value . " AS VARCHAR(255))"; 573 } 574 ); 575 $s = implode('+',$arr); 576 if (sizeof($arr) > 0) return "$s"; 577 578 return ''; 579 } 580 581 /* 582 Unfortunately, it appears that mssql cannot handle varbinary > 255 chars 583 So all your blobs must be of type "image". 584 585 Remember to set in php.ini the following... 586 587 ; Valid range 0 - 2147483647. Default = 4096. 588 mssql.textlimit = 0 ; zero to pass through 589 590 ; Valid range 0 - 2147483647. Default = 4096. 591 mssql.textsize = 0 ; zero to pass through 592 */ 593 function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') 594 { 595 596 if (strtoupper($blobtype) == 'CLOB') { 597 $sql = "UPDATE $table SET $column='" . $val . "' WHERE $where"; 598 return $this->Execute($sql) != false; 599 } 600 $sql = "UPDATE $table SET $column=0x".bin2hex($val)." WHERE $where"; 601 return $this->Execute($sql) != false; 602 } 603 604 /** 605 * Execute a query. 606 * 607 * If executing an INSERT statement and $hasInsertId is true, will set 608 * $lastInsId. 609 * 610 * @param string $sql 611 * @param array $inputarr 612 * @return resource|false Query Id if successful, otherwise false 613 */ 614 function _query($sql, $inputarr = false) 615 { 616 $this->_errorMsg = false; 617 618 if (is_array($sql)) { 619 $sql = $sql[1]; 620 } 621 622 // Handle native driver flaw for retrieving the last insert ID 623 if ($this->hasInsertID) { 624 // Check if it's an INSERT statement 625 $retrieveLastInsertID = preg_match( 626 '/^\W*insert[\s\w()[\]",.]+values\s*\((?:[^;\']|\'\'|(?:(?:\'\')*\'[^\']+\'(?:\'\')*))*;?$/i', 627 $sql 628 ); 629 if ($retrieveLastInsertID) { 630 // Append the identity SQL, so it is executed in the same 631 // scope as the insert query. 632 $sql .= '; ' . $this->identitySQL; 633 } 634 } else { 635 $retrieveLastInsertID = false; 636 } 637 638 if ($inputarr) { 639 // Ensure that the input array is indexed numerically, as required 640 // by sqlsrv_query(). If param() was used to create portable binds 641 // then the array might be associative. 642 $inputarr = array_values($inputarr); 643 $rez = sqlsrv_query($this->_connectionID, $sql, $inputarr); 644 } else { 645 $rez = sqlsrv_query($this->_connectionID, $sql); 646 } 647 648 if ($this->debug) { 649 ADOConnection::outp("<hr>running query: " . var_export($sql, true) 650 . "<hr>input array: " . var_export($inputarr, true) 651 . "<hr>result: " . var_export($rez, true) 652 ); 653 } 654 655 $this->lastInsID = false; 656 if (!$rez) { 657 $rez = false; 658 } elseif ($retrieveLastInsertID) { 659 // Get the inserted id from the last result 660 // Note: loop is required as server may return more than one row, 661 // e.g. if triggers are involved (see #41) 662 while (sqlsrv_next_result($rez)) { 663 sqlsrv_fetch($rez); 664 $this->lastInsID = sqlsrv_get_field($rez, 0, SQLSRV_PHPTYPE_INT); 665 } 666 } 667 return $rez; 668 } 669 670 // returns true or false 671 function _close() 672 { 673 if ($this->transCnt) { 674 $this->RollbackTrans(); 675 } 676 if($this->_connectionID) { 677 $rez = sqlsrv_close($this->_connectionID); 678 } 679 $this->_connectionID = false; 680 return $rez; 681 } 682 683 684 function MetaIndexes($table,$primary=false, $owner = false) 685 { 686 $table = $this->qstr($table); 687 688 $sql = "SELECT i.name AS ind_name, C.name AS col_name, USER_NAME(O.uid) AS Owner, c.colid, k.Keyno, 689 CASE WHEN I.indid BETWEEN 1 AND 254 AND (I.status & 2048 = 2048 OR I.Status = 16402 AND O.XType = 'V') THEN 1 ELSE 0 END AS IsPK, 690 CASE WHEN I.status & 2 = 2 THEN 1 ELSE 0 END AS IsUnique 691 FROM dbo.sysobjects o INNER JOIN dbo.sysindexes I ON o.id = i.id 692 INNER JOIN dbo.sysindexkeys K ON I.id = K.id AND I.Indid = K.Indid 693 INNER JOIN dbo.syscolumns c ON K.id = C.id AND K.colid = C.Colid 694 WHERE LEFT(i.name, 8) <> '_WA_Sys_' AND o.status >= 0 AND O.Name LIKE $table 695 ORDER BY O.name, I.Name, K.keyno"; 696 697 global $ADODB_FETCH_MODE; 698 $save = $ADODB_FETCH_MODE; 699 $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 700 if ($this->fetchMode !== FALSE) { 701 $savem = $this->SetFetchMode(FALSE); 702 } 703 704 $rs = $this->Execute($sql); 705 if (isset($savem)) { 706 $this->SetFetchMode($savem); 707 } 708 $ADODB_FETCH_MODE = $save; 709 710 if (!is_object($rs)) { 711 return FALSE; 712 } 713 714 $indexes = array(); 715 while ($row = $rs->FetchRow()) { 716 if (!$primary && $row[5]) continue; 717 718 $indexes[$row[0]]['unique'] = $row[6]; 719 $indexes[$row[0]]['columns'][] = $row[1]; 720 } 721 return $indexes; 722 } 723 724 public function metaForeignKeys($table, $owner = '', $upper = false, $associative = false) 725 { 726 global $ADODB_FETCH_MODE; 727 728 $save = $ADODB_FETCH_MODE; 729 $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 730 $table = $this->qstr(strtoupper($table)); 731 732 $sql = 733 "select object_name(constid) as constraint_name, 734 col_name(fkeyid, fkey) as column_name, 735 object_name(rkeyid) as referenced_table_name, 736 col_name(rkeyid, rkey) as referenced_column_name 737 from sysforeignkeys 738 where upper(object_name(fkeyid)) = $table 739 order by constraint_name, referenced_table_name, keyno"; 740 741 $constraints = $this->GetArray($sql); 742 743 $ADODB_FETCH_MODE = $save; 744 745 $arr = false; 746 foreach($constraints as $constr) { 747 //print_r($constr); 748 $arr[$constr[0]][$constr[2]][] = $constr[1].'='.$constr[3]; 749 } 750 if (!$arr) return false; 751 752 $arr2 = false; 753 754 foreach($arr as $k => $v) { 755 foreach($v as $a => $b) { 756 if ($upper) $a = strtoupper($a); 757 if (is_array($arr2[$a])) { // a previous foreign key was define for this reference table, we merge the new one 758 $arr2[$a] = array_merge($arr2[$a], $b); 759 } else { 760 $arr2[$a] = $b; 761 } 762 } 763 } 764 return $arr2; 765 } 766 767 //From: Fernando Moreira <FMoreira@imediata.pt> 768 function MetaDatabases() 769 { 770 $this->SelectDB("master"); 771 $rs = $this->Execute($this->metaDatabasesSQL); 772 $rows = $rs->GetRows(); 773 $ret = array(); 774 for($i=0;$i<count($rows);$i++) { 775 $ret[] = $rows[$i][0]; 776 } 777 $this->SelectDB($this->database); 778 if($ret) 779 return $ret; 780 else 781 return false; 782 } 783 784 // "Stein-Aksel Basma" <basma@accelero.no> 785 // tested with MSSQL 2000 786 function MetaPrimaryKeys($table, $owner=false) 787 { 788 global $ADODB_FETCH_MODE; 789 790 $schema = ''; 791 $this->_findschema($table,$schema); 792 if (!$schema) $schema = $this->database; 793 if ($schema) $schema = "and k.table_catalog like '$schema%'"; 794 795 $sql = "select distinct k.column_name,ordinal_position from information_schema.key_column_usage k, 796 information_schema.table_constraints tc 797 where tc.constraint_name = k.constraint_name and tc.constraint_type = 798 'PRIMARY KEY' and k.table_name = '$table' $schema order by ordinal_position "; 799 800 $savem = $ADODB_FETCH_MODE; 801 $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 802 $a = $this->GetCol($sql); 803 $ADODB_FETCH_MODE = $savem; 804 805 if ($a && sizeof($a)>0) return $a; 806 $false = false; 807 return $false; 808 } 809 810 811 function MetaTables($ttype=false,$showSchema=false,$mask=false) 812 { 813 if ($mask) { 814 $save = $this->metaTablesSQL; 815 $mask = $this->qstr(($mask)); 816 $this->metaTablesSQL .= " AND name like $mask"; 817 } 818 $ret = ADOConnection::MetaTables($ttype,$showSchema); 819 820 if ($mask) { 821 $this->metaTablesSQL = $save; 822 } 823 return $ret; 824 } 825 function MetaColumns($table, $upper=true, $schema=false){ 826 827 /* 828 * A simple caching mechanism, to be replaced in ADOdb V6 829 */ 830 static $cached_columns = array(); 831 if ($this->cachedSchemaFlush) 832 $cached_columns = array(); 833 834 if (array_key_exists($table,$cached_columns)){ 835 return $cached_columns[$table]; 836 } 837 838 839 $this->_findschema($table,$schema); 840 if ($schema) { 841 $dbName = $this->database; 842 $this->SelectDB($schema); 843 } 844 global $ADODB_FETCH_MODE; 845 $save = $ADODB_FETCH_MODE; 846 $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 847 848 if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); 849 $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table)); 850 851 if ($schema) { 852 $this->SelectDB($dbName); 853 } 854 855 if (isset($savem)) $this->SetFetchMode($savem); 856 $ADODB_FETCH_MODE = $save; 857 if (!is_object($rs)) { 858 $false = false; 859 return $false; 860 } 861 862 $retarr = array(); 863 while (!$rs->EOF){ 864 865 $fld = new ADOFieldObject(); 866 if (array_key_exists(0,$rs->fields)) { 867 $fld->name = $rs->fields[0]; 868 $fld->type = $rs->fields[1]; 869 $fld->max_length = $rs->fields[2]; 870 $fld->precision = $rs->fields[3]; 871 $fld->scale = $rs->fields[4]; 872 $fld->not_null =!$rs->fields[5]; 873 $fld->has_default = $rs->fields[6]; 874 $fld->xtype = $rs->fields[7]; 875 $fld->type_length = $rs->fields[8]; 876 $fld->auto_increment= $rs->fields[9]; 877 } else { 878 $fld->name = $rs->fields['name']; 879 $fld->type = $rs->fields['type']; 880 $fld->max_length = $rs->fields['length']; 881 $fld->precision = $rs->fields['precision']; 882 $fld->scale = $rs->fields['scale']; 883 $fld->not_null =!$rs->fields['nullable']; 884 $fld->has_default = $rs->fields['default_value']; 885 $fld->xtype = $rs->fields['xtype']; 886 $fld->type_length = $rs->fields['type_length']; 887 $fld->auto_increment= $rs->fields['is_identity']; 888 } 889 890 if ($save == ADODB_FETCH_NUM) 891 $retarr[] = $fld; 892 else 893 $retarr[strtoupper($fld->name)] = $fld; 894 895 $rs->MoveNext(); 896 897 } 898 $rs->Close(); 899 $cached_columns[$table] = $retarr; 900 901 return $retarr; 902 } 903 904 /** 905 * Returns a substring of a varchar type field 906 * 907 * The SQL server version varies because the length is mandatory, so 908 * we append a reasonable string length 909 * 910 * @param string $fld The field to sub-string 911 * @param int $start The start point 912 * @param int $length An optional length 913 * 914 * @return The SQL text 915 */ 916 function substr($fld,$start,$length=0) 917 { 918 if ($length == 0) 919 /* 920 * The length available to varchar is 2GB, but that makes no 921 * sense in a substring, so I'm going to arbitrarily limit 922 * the length to 1K, but you could change it if you want 923 */ 924 $length = 1024; 925 926 $text = "SUBSTRING($fld,$start,$length)"; 927 return $text; 928 } 929 930 /** 931 * Returns the maximum size of a MetaType C field. Because of the 932 * database design, SQL Server places no limits on the size of data inserted 933 * Although the actual limit is 2^31-1 bytes. 934 * 935 * @return int 936 */ 937 function charMax() 938 { 939 return ADODB_STRINGMAX_NOLIMIT; 940 } 941 942 /** 943 * Returns the maximum size of a MetaType X field. Because of the 944 * database design, SQL Server places no limits on the size of data inserted 945 * Although the actual limit is 2^31-1 bytes. 946 * 947 * @return int 948 */ 949 function textMax() 950 { 951 return ADODB_STRINGMAX_NOLIMIT; 952 } 953 /** 954 * Lists procedures, functions and methods in an array. 955 * 956 * @param string $procedureNamePattern (optional) 957 * @param string $catalog (optional) 958 * @param string $schemaPattern (optional) 959 960 * @return array of stored objects in current database. 961 * 962 */ 963 public function metaProcedures($procedureNamePattern = null, $catalog = null, $schemaPattern = null) 964 { 965 $metaProcedures = array(); 966 $procedureSQL = ''; 967 $catalogSQL = ''; 968 $schemaSQL = ''; 969 970 if ($procedureNamePattern) 971 $procedureSQL = "AND ROUTINE_NAME LIKE " . strtoupper($this->qstr($procedureNamePattern)); 972 973 if ($catalog) 974 $catalogSQL = "AND SPECIFIC_SCHEMA=" . strtoupper($this->qstr($catalog)); 975 976 if ($schemaPattern) 977 $schemaSQL = "AND ROUTINE_SCHEMA LIKE {$this->qstr($schemaPattern)}"; 978 979 $fields = " ROUTINE_NAME,ROUTINE_TYPE,ROUTINE_SCHEMA,ROUTINE_CATALOG"; 980 981 $SQL = "SELECT $fields 982 FROM {$this->database}.information_schema.routines 983 WHERE 1=1 984 $procedureSQL 985 $catalogSQL 986 $schemaSQL 987 ORDER BY ROUTINE_NAME 988 "; 989 990 $result = $this->execute($SQL); 991 992 if (!$result) 993 return false; 994 while ($r = $result->fetchRow()){ 995 if (!isset($r[0])) 996 /* 997 * Convert to numeric 998 */ 999 $r = array_values($r); 1000 1001 $procedureName = $r[0]; 1002 $schemaName = $r[2]; 1003 $routineCatalog= $r[3]; 1004 $metaProcedures[$procedureName] = array('type'=> $r[1], 1005 'catalog' => $routineCatalog, 1006 'schema' => $schemaName, 1007 'remarks' => '', 1008 ); 1009 } 1010 1011 return $metaProcedures; 1012 } 1013 1014 /** 1015 * An SQL Statement that adds a specific number of 1016 * days or part to local datetime 1017 * 1018 * @param float $dayFraction 1019 * @param string $date 1020 * 1021 * @return string 1022 */ 1023 public function offsetDate($dayFraction, $date = false) 1024 { 1025 if (!$date) 1026 /* 1027 * Use GETDATE() via systTimestamp; 1028 */ 1029 $date = $this->sysTimeStamp; 1030 1031 /* 1032 * seconds, number of seconds, date base 1033 */ 1034 $dateFormat = "DATEADD(s, %s, %s)"; 1035 1036 /* 1037 * Adjust the offset back to seconds 1038 */ 1039 $fraction = $dayFraction * 24 * 3600; 1040 1041 return sprintf($dateFormat,$fraction,$date); 1042 1043 } 1044 1045 } 1046 1047 /*-------------------------------------------------------------------------------------- 1048 Class Name: Recordset 1049 --------------------------------------------------------------------------------------*/ 1050 1051 class ADORecordset_mssqlnative extends ADORecordSet { 1052 1053 var $databaseType = "mssqlnative"; 1054 var $canSeek = false; 1055 var $fieldOffset = 0; 1056 // _mths works only in non-localised system 1057 1058 /** 1059 * @var bool True if we have retrieved the fields metadata 1060 */ 1061 private $fieldObjectsRetrieved = false; 1062 1063 /* 1064 * Cross-reference the objects by name for easy access 1065 */ 1066 private $fieldObjectsIndex = array(); 1067 1068 /* 1069 * Cross references the dateTime objects for faster decoding 1070 */ 1071 private $dateTimeObjects = array(); 1072 1073 /* 1074 * flags that we have dateTimeObjects to handle 1075 */ 1076 private $hasDateTimeObjects = false; 1077 1078 /* 1079 * This is cross reference between how the types are stored 1080 * in SQL Server and their english-language description 1081 * -154 is a time field, see #432 1082 */ 1083 private $_typeConversion = array( 1084 -155 => 'datetimeoffset', 1085 -154 => 'char', 1086 -152 => 'xml', 1087 -151 => 'udt', 1088 -11 => 'uniqueidentifier', 1089 -10 => 'ntext', 1090 -9 => 'nvarchar', 1091 -8 => 'nchar', 1092 -7 => 'bit', 1093 -6 => 'tinyint', 1094 -5 => 'bigint', 1095 -4 => 'image', 1096 -3 => 'varbinary', 1097 -2 => 'timestamp', 1098 -1 => 'text', 1099 1 => 'char', 1100 2 => 'numeric', 1101 3 => 'decimal', 1102 4 => 'int', 1103 5 => 'smallint', 1104 6 => 'float', 1105 7 => 'real', 1106 12 => 'varchar', 1107 91 => 'date', 1108 93 => 'datetime' 1109 ); 1110 1111 1112 1113 1114 function __construct($id,$mode=false) 1115 { 1116 if ($mode === false) { 1117 global $ADODB_FETCH_MODE; 1118 $mode = $ADODB_FETCH_MODE; 1119 1120 } 1121 $this->fetchMode = $mode; 1122 parent::__construct($id); 1123 } 1124 1125 1126 function _initrs() 1127 { 1128 $this->_numOfRows = -1;//not supported 1129 // Cache the metadata right now 1130 $this->_fetchField(); 1131 1132 } 1133 1134 1135 //Contributed by "Sven Axelsson" <sven.axelsson@bokochwebb.se> 1136 // get next resultset - requires PHP 4.0.5 or later 1137 function NextRecordSet() 1138 { 1139 if (!sqlsrv_next_result($this->_queryID)) return false; 1140 $this->_inited = false; 1141 $this->bind = false; 1142 $this->_currentRow = -1; 1143 $this->Init(); 1144 return true; 1145 } 1146 1147 /* Use associative array to get fields array */ 1148 function Fields($colname) 1149 { 1150 if (!is_array($this->fields)) 1151 /* 1152 * Too early 1153 */ 1154 return; 1155 if ($this->fetchMode != ADODB_FETCH_NUM) 1156 return $this->fields[$colname]; 1157 1158 if (!$this->bind) { 1159 $this->bind = array(); 1160 for ($i=0; $i < $this->_numOfFields; $i++) { 1161 $o = $this->FetchField($i); 1162 $this->bind[strtoupper($o->name)] = $i; 1163 } 1164 } 1165 1166 return $this->fields[$this->bind[strtoupper($colname)]]; 1167 } 1168 1169 /** 1170 * Returns: an object containing field information. 1171 * 1172 * Get column information in the Recordset object. fetchField() 1173 * can be used in order to obtain information about fields in a 1174 * certain query result. If the field offset isn't specified, 1175 * the next field that wasn't yet retrieved by fetchField() 1176 * is retrieved. 1177 * 1178 * @param int $fieldOffset (optional default=-1 for all 1179 * @return mixed an ADOFieldObject, or array of objects 1180 */ 1181 private function _fetchField($fieldOffset = -1) 1182 { 1183 if ($this->fieldObjectsRetrieved) { 1184 if ($this->fieldObjectsCache) { 1185 // Already got the information 1186 if ($fieldOffset == -1) { 1187 return $this->fieldObjectsCache; 1188 } else { 1189 return $this->fieldObjectsCache[$fieldOffset]; 1190 } 1191 } else { 1192 // No metadata available 1193 return false; 1194 } 1195 } 1196 1197 $this->fieldObjectsRetrieved = true; 1198 /* 1199 * Retrieve all metadata in one go. This is always returned as a 1200 * numeric array. 1201 */ 1202 $fieldMetaData = sqlsrv_field_metadata($this->_queryID); 1203 1204 if (!$fieldMetaData) { 1205 // Not a statement that gives us metaData 1206 return false; 1207 } 1208 1209 $this->_numOfFields = count($fieldMetaData); 1210 foreach ($fieldMetaData as $key=>$value) { 1211 $fld = new ADOFieldObject; 1212 // Caution - keys are case-sensitive, must respect casing of values 1213 $fld->name = $value['Name']; 1214 $fld->max_length = $value['Size']; 1215 $fld->column_source = $value['Name']; 1216 $fld->type = $this->_typeConversion[$value['Type']]; 1217 1218 $this->fieldObjectsCache[$key] = $fld; 1219 $this->fieldObjectsIndex[$fld->name] = $key; 1220 } 1221 if ($fieldOffset == -1) { 1222 return $this->fieldObjectsCache; 1223 } 1224 1225 return $this->fieldObjectsCache[$fieldOffset]; 1226 } 1227 1228 /* 1229 * Fetchfield copies the oracle method, it loads the field information 1230 * into the _fieldobjs array once, to save multiple calls to the 1231 * sqlsrv_field_metadata function 1232 * 1233 * @param int $fieldOffset (optional) 1234 * 1235 * @return adoFieldObject 1236 * 1237 * @author KM Newnham 1238 * @date 02/20/2013 1239 */ 1240 function fetchField($fieldOffset = -1) 1241 { 1242 return $this->fieldObjectsCache[$fieldOffset]; 1243 } 1244 1245 function _seek($row) 1246 { 1247 return false;//There is no support for cursors in the driver at this time. All data is returned via forward-only streams. 1248 } 1249 1250 // speedup 1251 function MoveNext() 1252 { 1253 if ($this->EOF) 1254 return false; 1255 1256 $this->_currentRow++; 1257 1258 if ($this->_fetch()) 1259 return true; 1260 $this->EOF = true; 1261 1262 return false; 1263 } 1264 1265 function _fetch($ignore_fields=false) 1266 { 1267 if ($this->fetchMode & ADODB_FETCH_ASSOC) { 1268 if ($this->fetchMode & ADODB_FETCH_NUM) 1269 $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_BOTH); 1270 else 1271 $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_ASSOC); 1272 1273 if (is_array($this->fields)) 1274 { 1275 1276 if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_LOWER) 1277 $this->fields = array_change_key_case($this->fields,CASE_LOWER); 1278 else if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_UPPER) 1279 $this->fields = array_change_key_case($this->fields,CASE_UPPER); 1280 1281 } 1282 } 1283 else 1284 $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_NUMERIC); 1285 1286 if (!$this->fields) 1287 return false; 1288 1289 return $this->fields; 1290 } 1291 1292 /** 1293 * close() only needs to be called if you are worried about using too much 1294 * memory while your script is running. All associated result memory for 1295 * the specified result identifier will automatically be freed. 1296 * 1297 * @return bool tru if we succeeded in closing down 1298 */ 1299 function _close() 1300 { 1301 /* 1302 * If we are closing down a failed query, collect any 1303 * error messages. This is a hack fix to the "close too early" 1304 * problem so this might go away later 1305 */ 1306 $this->connection->errorMsg(); 1307 if(is_resource($this->_queryID)) { 1308 $rez = sqlsrv_free_stmt($this->_queryID); 1309 $this->_queryID = false; 1310 return $rez; 1311 } 1312 1313 return true; 1314 } 1315 1316 } 1317 1318 1319 class ADORecordSet_array_mssqlnative extends ADORecordSet_array {} 1320 1321 /* 1322 Code Example 1: 1323 1324 select object_name(constid) as constraint_name, 1325 object_name(fkeyid) as table_name, 1326 col_name(fkeyid, fkey) as column_name, 1327 object_name(rkeyid) as referenced_table_name, 1328 col_name(rkeyid, rkey) as referenced_column_name 1329 from sysforeignkeys 1330 where object_name(fkeyid) = x 1331 order by constraint_name, table_name, referenced_table_name, keyno 1332 1333 Code Example 2: 1334 select constraint_name, 1335 column_name, 1336 ordinal_position 1337 from information_schema.key_column_usage 1338 where constraint_catalog = db_name() 1339 and table_name = x 1340 order by constraint_name, ordinal_position 1341 1342 http://www.databasejournal.com/scripts/article.php/1440551 1343 */
title
Description
Body
title
Description
Body
title
Description
Body
title
Body