PageRenderTime 45ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/adodb/drivers/adodb-mssqlnative.inc.php

http://github.com/moodle/moodle
PHP | 1200 lines | 1038 code | 51 blank | 111 comment | 65 complexity | 5ea7e4daa51a90b44726e7d62134addd MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /*
  3. @version v5.20.16 12-Jan-2020
  4. @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  5. @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
  6. Released under both BSD license and Lesser GPL library license.
  7. Whenever there is any discrepancy between the two licenses,
  8. the BSD license will take precedence.
  9. Set tabs to 4 for best viewing.
  10. Latest version is available at http://adodb.org/
  11. Native mssql driver. Requires mssql client. Works on Windows.
  12. http://www.microsoft.com/sql/technologies/php/default.mspx
  13. To configure for Unix, see
  14. http://phpbuilder.com/columns/alberto20000919.php3
  15. $stream = sqlsrv_get_field($stmt, $index, SQLSRV_SQLTYPE_STREAM(SQLSRV_ENC_BINARY));
  16. stream_filter_append($stream, "convert.iconv.ucs-2/utf-8"); // Voila, UTF-8 can be read directly from $stream
  17. */
  18. // security - hide paths
  19. if (!defined('ADODB_DIR')) die();
  20. if (!function_exists('sqlsrv_configure')) {
  21. die("mssqlnative extension not installed");
  22. }
  23. if (!function_exists('sqlsrv_set_error_handling')) {
  24. function sqlsrv_set_error_handling($constant) {
  25. sqlsrv_configure("WarningsReturnAsErrors", $constant);
  26. }
  27. }
  28. if (!function_exists('sqlsrv_log_set_severity')) {
  29. function sqlsrv_log_set_severity($constant) {
  30. sqlsrv_configure("LogSeverity", $constant);
  31. }
  32. }
  33. if (!function_exists('sqlsrv_log_set_subsystems')) {
  34. function sqlsrv_log_set_subsystems($constant) {
  35. sqlsrv_configure("LogSubsystems", $constant);
  36. }
  37. }
  38. //----------------------------------------------------------------
  39. // MSSQL returns dates with the format Oct 13 2002 or 13 Oct 2002
  40. // and this causes tons of problems because localized versions of
  41. // MSSQL will return the dates in dmy or mdy order; and also the
  42. // month strings depends on what language has been configured. The
  43. // following two variables allow you to control the localization
  44. // settings - Ugh.
  45. //
  46. // MORE LOCALIZATION INFO
  47. // ----------------------
  48. // To configure datetime, look for and modify sqlcommn.loc,
  49. // typically found in c:\mssql\install
  50. // Also read :
  51. // http://support.microsoft.com/default.aspx?scid=kb;EN-US;q220918
  52. // Alternatively use:
  53. // CONVERT(char(12),datecol,120)
  54. //
  55. // Also if your month is showing as month-1,
  56. // e.g. Jan 13, 2002 is showing as 13/0/2002, then see
  57. // http://phplens.com/lens/lensforum/msgs.php?id=7048&x=1
  58. // it's a localisation problem.
  59. //----------------------------------------------------------------
  60. // has datetime converstion to YYYY-MM-DD format, and also mssql_fetch_assoc
  61. if (ADODB_PHPVER >= 0x4300) {
  62. // docs say 4.2.0, but testing shows only since 4.3.0 does it work!
  63. ini_set('mssql.datetimeconvert',0);
  64. } else {
  65. global $ADODB_mssql_mths; // array, months must be upper-case
  66. $ADODB_mssql_date_order = 'mdy';
  67. $ADODB_mssql_mths = array(
  68. 'JAN'=>1,'FEB'=>2,'MAR'=>3,'APR'=>4,'MAY'=>5,'JUN'=>6,
  69. 'JUL'=>7,'AUG'=>8,'SEP'=>9,'OCT'=>10,'NOV'=>11,'DEC'=>12);
  70. }
  71. class ADODB_mssqlnative extends ADOConnection {
  72. var $databaseType = "mssqlnative";
  73. var $dataProvider = "mssqlnative";
  74. var $replaceQuote = "''"; // string to use to replace quotes
  75. var $fmtDate = "'Y-m-d'";
  76. var $fmtTimeStamp = "'Y-m-d\TH:i:s'";
  77. var $hasInsertID = true;
  78. var $substr = "substring";
  79. var $length = 'len';
  80. var $hasAffectedRows = true;
  81. var $poorAffectedRows = false;
  82. var $metaDatabasesSQL = "select name from sys.sysdatabases where name <> 'master'";
  83. 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'))";
  84. var $metaColumnsSQL =
  85. "select c.name,
  86. t.name as type,
  87. c.length,
  88. c.xprec as precision,
  89. c.xscale as scale,
  90. c.isnullable as nullable,
  91. c.cdefault as default_value,
  92. c.xtype,
  93. t.length as type_length,
  94. sc.is_identity
  95. from syscolumns c
  96. join systypes t on t.xusertype=c.xusertype
  97. join sysobjects o on o.id=c.id
  98. join sys.tables st on st.name=o.name
  99. join sys.columns sc on sc.object_id = st.object_id and sc.name=c.name
  100. where o.name='%s'";
  101. var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE
  102. var $hasGenID = true;
  103. var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)';
  104. var $sysTimeStamp = 'GetDate()';
  105. var $maxParameterLen = 4000;
  106. var $arrayClass = 'ADORecordSet_array_mssqlnative';
  107. var $uniqueSort = true;
  108. var $leftOuter = '*=';
  109. var $rightOuter = '=*';
  110. var $ansiOuter = true; // for mssql7 or later
  111. var $identitySQL = 'select SCOPE_IDENTITY()'; // 'select SCOPE_IDENTITY'; # for mssql 2000
  112. var $uniqueOrderBy = true;
  113. var $_bindInputArray = true;
  114. var $_dropSeqSQL = "drop table %s";
  115. var $connectionInfo = array();
  116. var $cachedSchemaFlush = false;
  117. var $sequences = false;
  118. var $mssql_version = '';
  119. function __construct()
  120. {
  121. if ($this->debug) {
  122. ADOConnection::outp("<pre>");
  123. sqlsrv_set_error_handling( SQLSRV_ERRORS_LOG_ALL );
  124. sqlsrv_log_set_severity( SQLSRV_LOG_SEVERITY_ALL );
  125. sqlsrv_log_set_subsystems(SQLSRV_LOG_SYSTEM_ALL);
  126. sqlsrv_configure('WarningsReturnAsErrors', 0);
  127. } else {
  128. sqlsrv_set_error_handling(0);
  129. sqlsrv_log_set_severity(0);
  130. sqlsrv_log_set_subsystems(SQLSRV_LOG_SYSTEM_ALL);
  131. sqlsrv_configure('WarningsReturnAsErrors', 0);
  132. }
  133. }
  134. /**
  135. * Initializes the SQL Server version.
  136. * Dies if connected to a non-supported version (2000 and older)
  137. */
  138. function ServerVersion() {
  139. $data = $this->ServerInfo();
  140. preg_match('/^\d{2}/', $data['version'], $matches);
  141. $version = (int)reset($matches);
  142. // We only support SQL Server 2005 and up
  143. if($version < 9) {
  144. die("SQL SERVER VERSION {$data['version']} NOT SUPPORTED IN mssqlnative DRIVER");
  145. }
  146. $this->mssql_version = $version;
  147. }
  148. function ServerInfo() {
  149. global $ADODB_FETCH_MODE;
  150. static $arr = false;
  151. if (is_array($arr))
  152. return $arr;
  153. if ($this->fetchMode === false) {
  154. $savem = $ADODB_FETCH_MODE;
  155. $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
  156. } elseif ($this->fetchMode >=0 && $this->fetchMode <=2) {
  157. $savem = $this->fetchMode;
  158. } else
  159. $savem = $this->SetFetchMode(ADODB_FETCH_NUM);
  160. $arrServerInfo = sqlsrv_server_info($this->_connectionID);
  161. $ADODB_FETCH_MODE = $savem;
  162. $arr['description'] = $arrServerInfo['SQLServerName'].' connected to '.$arrServerInfo['CurrentDatabase'];
  163. $arr['version'] = $arrServerInfo['SQLServerVersion'];//ADOConnection::_findvers($arr['description']);
  164. return $arr;
  165. }
  166. function IfNull( $field, $ifNull )
  167. {
  168. return " ISNULL($field, $ifNull) "; // if MS SQL Server
  169. }
  170. function _insertid()
  171. {
  172. // SCOPE_IDENTITY()
  173. // Returns the last IDENTITY value inserted into an IDENTITY column in
  174. // the same scope. A scope is a module -- a stored procedure, trigger,
  175. // function, or batch. Thus, two statements are in the same scope if
  176. // they are in the same stored procedure, function, or batch.
  177. return $this->lastInsertID;
  178. }
  179. function _affectedrows()
  180. {
  181. if ($this->_queryID)
  182. return sqlsrv_rows_affected($this->_queryID);
  183. }
  184. function GenID($seq='adodbseq',$start=1) {
  185. if (!$this->mssql_version)
  186. $this->ServerVersion();
  187. switch($this->mssql_version){
  188. case 9:
  189. case 10:
  190. return $this->GenID2008($seq, $start);
  191. break;
  192. default:
  193. return $this->GenID2012($seq, $start);
  194. break;
  195. }
  196. }
  197. function CreateSequence($seq='adodbseq',$start=1)
  198. {
  199. if (!$this->mssql_version)
  200. $this->ServerVersion();
  201. switch($this->mssql_version){
  202. case 9:
  203. case 10:
  204. return $this->CreateSequence2008($seq, $start);
  205. break;
  206. default:
  207. return $this->CreateSequence2012($seq, $start);
  208. break;
  209. }
  210. }
  211. /**
  212. * For Server 2005,2008, duplicate a sequence with an identity table
  213. */
  214. function CreateSequence2008($seq='adodbseq',$start=1)
  215. {
  216. if($this->debug) ADOConnection::outp("<hr>CreateSequence($seq,$start)");
  217. sqlsrv_begin_transaction($this->_connectionID);
  218. $start -= 1;
  219. $this->Execute("create table $seq (id int)");//was float(53)
  220. $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
  221. if (!$ok) {
  222. if($this->debug) ADOConnection::outp("<hr>Error: ROLLBACK");
  223. sqlsrv_rollback($this->_connectionID);
  224. return false;
  225. }
  226. sqlsrv_commit($this->_connectionID);
  227. return true;
  228. }
  229. /**
  230. * Proper Sequences Only available to Server 2012 and up
  231. */
  232. function CreateSequence2012($seq='adodbseq',$start=1){
  233. if (!$this->sequences){
  234. $sql = "SELECT name FROM sys.sequences";
  235. $this->sequences = $this->GetCol($sql);
  236. }
  237. $ok = $this->Execute("CREATE SEQUENCE $seq START WITH $start INCREMENT BY 1");
  238. if (!$ok)
  239. die("CANNOT CREATE SEQUENCE" . print_r(sqlsrv_errors(),true));
  240. $this->sequences[] = $seq;
  241. }
  242. /**
  243. * For Server 2005,2008, duplicate a sequence with an identity table
  244. */
  245. function GenID2008($seq='adodbseq',$start=1)
  246. {
  247. if($this->debug) ADOConnection::outp("<hr>CreateSequence($seq,$start)");
  248. sqlsrv_begin_transaction($this->_connectionID);
  249. $ok = $this->Execute("update $seq with (tablock,holdlock) set id = id + 1");
  250. if (!$ok) {
  251. $start -= 1;
  252. $this->Execute("create table $seq (id int)");//was float(53)
  253. $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)");
  254. if (!$ok) {
  255. if($this->debug) ADOConnection::outp("<hr>Error: ROLLBACK");
  256. sqlsrv_rollback($this->_connectionID);
  257. return false;
  258. }
  259. }
  260. $num = $this->GetOne("select id from $seq");
  261. sqlsrv_commit($this->_connectionID);
  262. return $num;
  263. }
  264. /**
  265. * Only available to Server 2012 and up
  266. * Cannot do this the normal adodb way by trapping an error if the
  267. * sequence does not exist because sql server will auto create a
  268. * sequence with the starting number of -9223372036854775808
  269. */
  270. function GenID2012($seq='adodbseq',$start=1)
  271. {
  272. /*
  273. * First time in create an array of sequence names that we
  274. * can use in later requests to see if the sequence exists
  275. * the overhead is creating a list of sequences every time
  276. * we need access to at least 1. If we really care about
  277. * performance, we could maybe flag a 'nocheck' class variable
  278. */
  279. if (!$this->sequences){
  280. $sql = "SELECT name FROM sys.sequences";
  281. $this->sequences = $this->GetCol($sql);
  282. }
  283. if (!is_array($this->sequences)
  284. || is_array($this->sequences) && !in_array($seq,$this->sequences)){
  285. $this->CreateSequence2012($seq, $start);
  286. }
  287. $num = $this->GetOne("SELECT NEXT VALUE FOR $seq");
  288. return $num;
  289. }
  290. // Format date column in sql string given an input format that understands Y M D
  291. function SQLDate($fmt, $col=false)
  292. {
  293. if (!$col) $col = $this->sysTimeStamp;
  294. $s = '';
  295. $len = strlen($fmt);
  296. for ($i=0; $i < $len; $i++) {
  297. if ($s) $s .= '+';
  298. $ch = $fmt[$i];
  299. switch($ch) {
  300. case 'Y':
  301. case 'y':
  302. $s .= "datename(yyyy,$col)";
  303. break;
  304. case 'M':
  305. $s .= "convert(char(3),$col,0)";
  306. break;
  307. case 'm':
  308. $s .= "replace(str(month($col),2),' ','0')";
  309. break;
  310. case 'Q':
  311. case 'q':
  312. $s .= "datename(quarter,$col)";
  313. break;
  314. case 'D':
  315. case 'd':
  316. $s .= "replace(str(day($col),2),' ','0')";
  317. break;
  318. case 'h':
  319. $s .= "substring(convert(char(14),$col,0),13,2)";
  320. break;
  321. case 'H':
  322. $s .= "replace(str(datepart(hh,$col),2),' ','0')";
  323. break;
  324. case 'i':
  325. $s .= "replace(str(datepart(mi,$col),2),' ','0')";
  326. break;
  327. case 's':
  328. $s .= "replace(str(datepart(ss,$col),2),' ','0')";
  329. break;
  330. case 'a':
  331. case 'A':
  332. $s .= "substring(convert(char(19),$col,0),18,2)";
  333. break;
  334. default:
  335. if ($ch == '\\') {
  336. $i++;
  337. $ch = substr($fmt,$i,1);
  338. }
  339. $s .= $this->qstr($ch);
  340. break;
  341. }
  342. }
  343. return $s;
  344. }
  345. function BeginTrans()
  346. {
  347. if ($this->transOff) return true;
  348. $this->transCnt += 1;
  349. if ($this->debug) ADOConnection::outp('<hr>begin transaction');
  350. sqlsrv_begin_transaction($this->_connectionID);
  351. return true;
  352. }
  353. function CommitTrans($ok=true)
  354. {
  355. if ($this->transOff) return true;
  356. if ($this->debug) ADOConnection::outp('<hr>commit transaction');
  357. if (!$ok) return $this->RollbackTrans();
  358. if ($this->transCnt) $this->transCnt -= 1;
  359. sqlsrv_commit($this->_connectionID);
  360. return true;
  361. }
  362. function RollbackTrans()
  363. {
  364. if ($this->transOff) return true;
  365. if ($this->debug) ADOConnection::outp('<hr>rollback transaction');
  366. if ($this->transCnt) $this->transCnt -= 1;
  367. sqlsrv_rollback($this->_connectionID);
  368. return true;
  369. }
  370. function SetTransactionMode( $transaction_mode )
  371. {
  372. $this->_transmode = $transaction_mode;
  373. if (empty($transaction_mode)) {
  374. $this->Execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
  375. return;
  376. }
  377. if (!stristr($transaction_mode,'isolation')) $transaction_mode = 'ISOLATION LEVEL '.$transaction_mode;
  378. $this->Execute("SET TRANSACTION ".$transaction_mode);
  379. }
  380. /*
  381. Usage:
  382. $this->BeginTrans();
  383. $this->RowLock('table1,table2','table1.id=33 and table2.id=table1.id'); # lock row 33 for both tables
  384. # some operation on both tables table1 and table2
  385. $this->CommitTrans();
  386. See http://www.swynk.com/friends/achigrik/SQL70Locks.asp
  387. */
  388. function RowLock($tables,$where,$col='1 as adodbignore')
  389. {
  390. if ($col == '1 as adodbignore') $col = 'top 1 null as ignore';
  391. if (!$this->transCnt) $this->BeginTrans();
  392. return $this->GetOne("select $col from $tables with (ROWLOCK,HOLDLOCK) where $where");
  393. }
  394. function SelectDB($dbName)
  395. {
  396. $this->database = $dbName;
  397. $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions
  398. if ($this->_connectionID) {
  399. $rs = $this->Execute('USE '.$dbName);
  400. if($rs) {
  401. return true;
  402. } else return false;
  403. }
  404. else return false;
  405. }
  406. function ErrorMsg()
  407. {
  408. $retErrors = sqlsrv_errors(SQLSRV_ERR_ALL);
  409. if($retErrors != null) {
  410. foreach($retErrors as $arrError) {
  411. $this->_errorMsg .= "SQLState: ".$arrError[ 'SQLSTATE']."\n";
  412. $this->_errorMsg .= "Error Code: ".$arrError[ 'code']."\n";
  413. $this->_errorMsg .= "Message: ".$arrError[ 'message']."\n";
  414. }
  415. }
  416. return $this->_errorMsg;
  417. }
  418. function ErrorNo()
  419. {
  420. $err = sqlsrv_errors(SQLSRV_ERR_ALL);
  421. if($err[0]) return $err[0]['code'];
  422. else return 0;
  423. }
  424. // returns true or false
  425. function _connect($argHostname, $argUsername, $argPassword, $argDatabasename)
  426. {
  427. if (!function_exists('sqlsrv_connect')) return null;
  428. $connectionInfo = $this->connectionInfo;
  429. $connectionInfo["Database"]=$argDatabasename;
  430. $connectionInfo["UID"]=$argUsername;
  431. $connectionInfo["PWD"]=$argPassword;
  432. foreach ($this->connectionParameters as $parameter=>$value)
  433. $connectionInfo[$parameter] = $value;
  434. if ($this->debug) ADOConnection::outp("<hr>connecting... hostname: $argHostname params: ".var_export($connectionInfo,true));
  435. //if ($this->debug) ADOConnection::outp("<hr>_connectionID before: ".serialize($this->_connectionID));
  436. if(!($this->_connectionID = sqlsrv_connect($argHostname,$connectionInfo))) {
  437. if ($this->debug) ADOConnection::outp( "<hr><b>errors</b>: ".print_r( sqlsrv_errors(), true));
  438. return false;
  439. }
  440. //if ($this->debug) ADOConnection::outp(" _connectionID after: ".serialize($this->_connectionID));
  441. //if ($this->debug) ADOConnection::outp("<hr>defined functions: <pre>".var_export(get_defined_functions(),true)."</pre>");
  442. return true;
  443. }
  444. // returns true or false
  445. function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename)
  446. {
  447. //return null;//not implemented. NOTE: Persistent connections have no effect if PHP is used as a CGI program. (FastCGI!)
  448. return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename);
  449. }
  450. function Prepare($sql)
  451. {
  452. return $sql; // prepare does not work properly with bind parameters as bind parameters are managed by sqlsrv_prepare!
  453. $stmt = sqlsrv_prepare( $this->_connectionID, $sql);
  454. if (!$stmt) return $sql;
  455. return array($sql,$stmt);
  456. }
  457. // returns concatenated string
  458. // MSSQL requires integers to be cast as strings
  459. // automatically cast every datatype to VARCHAR(255)
  460. // @author David Rogers (introspectshun)
  461. function Concat()
  462. {
  463. $s = "";
  464. $arr = func_get_args();
  465. // Split single record on commas, if possible
  466. if (sizeof($arr) == 1) {
  467. foreach ($arr as $arg) {
  468. $args = explode(',', $arg);
  469. }
  470. $arr = $args;
  471. }
  472. array_walk(
  473. $arr,
  474. function(&$value, $key) {
  475. $value = "CAST(" . $value . " AS VARCHAR(255))";
  476. }
  477. );
  478. $s = implode('+',$arr);
  479. if (sizeof($arr) > 0) return "$s";
  480. return '';
  481. }
  482. /*
  483. Unfortunately, it appears that mssql cannot handle varbinary > 255 chars
  484. So all your blobs must be of type "image".
  485. Remember to set in php.ini the following...
  486. ; Valid range 0 - 2147483647. Default = 4096.
  487. mssql.textlimit = 0 ; zero to pass through
  488. ; Valid range 0 - 2147483647. Default = 4096.
  489. mssql.textsize = 0 ; zero to pass through
  490. */
  491. function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
  492. {
  493. if (strtoupper($blobtype) == 'CLOB') {
  494. $sql = "UPDATE $table SET $column='" . $val . "' WHERE $where";
  495. return $this->Execute($sql) != false;
  496. }
  497. $sql = "UPDATE $table SET $column=0x".bin2hex($val)." WHERE $where";
  498. return $this->Execute($sql) != false;
  499. }
  500. // returns query ID if successful, otherwise false
  501. function _query($sql,$inputarr=false)
  502. {
  503. $this->_errorMsg = false;
  504. if (is_array($sql)) $sql = $sql[1];
  505. $insert = false;
  506. // handle native driver flaw for retrieving the last insert ID
  507. if(preg_match('/^\W*insert[\s\w()[\]",.]+values\s*\((?:[^;\']|\'\'|(?:(?:\'\')*\'[^\']+\'(?:\'\')*))*;?$/i', $sql)) {
  508. $insert = true;
  509. $sql .= '; '.$this->identitySQL; // select scope_identity()
  510. }
  511. if($inputarr) {
  512. $rez = sqlsrv_query($this->_connectionID, $sql, $inputarr);
  513. } else {
  514. $rez = sqlsrv_query($this->_connectionID,$sql);
  515. }
  516. if ($this->debug) ADOConnection::outp("<hr>running query: ".var_export($sql,true)."<hr>input array: ".var_export($inputarr,true)."<hr>result: ".var_export($rez,true));
  517. if(!$rez) {
  518. $rez = false;
  519. } else if ($insert) {
  520. // retrieve the last insert ID (where applicable)
  521. while ( sqlsrv_next_result($rez) ) {
  522. sqlsrv_fetch($rez);
  523. $this->lastInsertID = sqlsrv_get_field($rez, 0);
  524. }
  525. }
  526. return $rez;
  527. }
  528. // returns true or false
  529. function _close()
  530. {
  531. if ($this->transCnt) $this->RollbackTrans();
  532. $rez = @sqlsrv_close($this->_connectionID);
  533. $this->_connectionID = false;
  534. return $rez;
  535. }
  536. // mssql uses a default date like Dec 30 2000 12:00AM
  537. static function UnixDate($v)
  538. {
  539. return ADORecordSet_array_mssqlnative::UnixDate($v);
  540. }
  541. static function UnixTimeStamp($v)
  542. {
  543. return ADORecordSet_array_mssqlnative::UnixTimeStamp($v);
  544. }
  545. function MetaIndexes($table,$primary=false, $owner = false)
  546. {
  547. $table = $this->qstr($table);
  548. $sql = "SELECT i.name AS ind_name, C.name AS col_name, USER_NAME(O.uid) AS Owner, c.colid, k.Keyno,
  549. 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,
  550. CASE WHEN I.status & 2 = 2 THEN 1 ELSE 0 END AS IsUnique
  551. FROM dbo.sysobjects o INNER JOIN dbo.sysindexes I ON o.id = i.id
  552. INNER JOIN dbo.sysindexkeys K ON I.id = K.id AND I.Indid = K.Indid
  553. INNER JOIN dbo.syscolumns c ON K.id = C.id AND K.colid = C.Colid
  554. WHERE LEFT(i.name, 8) <> '_WA_Sys_' AND o.status >= 0 AND O.Name LIKE $table
  555. ORDER BY O.name, I.Name, K.keyno";
  556. global $ADODB_FETCH_MODE;
  557. $save = $ADODB_FETCH_MODE;
  558. $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
  559. if ($this->fetchMode !== FALSE) {
  560. $savem = $this->SetFetchMode(FALSE);
  561. }
  562. $rs = $this->Execute($sql);
  563. if (isset($savem)) {
  564. $this->SetFetchMode($savem);
  565. }
  566. $ADODB_FETCH_MODE = $save;
  567. if (!is_object($rs)) {
  568. return FALSE;
  569. }
  570. $indexes = array();
  571. while ($row = $rs->FetchRow()) {
  572. if (!$primary && $row[5]) continue;
  573. $indexes[$row[0]]['unique'] = $row[6];
  574. $indexes[$row[0]]['columns'][] = $row[1];
  575. }
  576. return $indexes;
  577. }
  578. function MetaForeignKeys($table, $owner=false, $upper=false)
  579. {
  580. global $ADODB_FETCH_MODE;
  581. $save = $ADODB_FETCH_MODE;
  582. $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
  583. $table = $this->qstr(strtoupper($table));
  584. $sql =
  585. "select object_name(constid) as constraint_name,
  586. col_name(fkeyid, fkey) as column_name,
  587. object_name(rkeyid) as referenced_table_name,
  588. col_name(rkeyid, rkey) as referenced_column_name
  589. from sysforeignkeys
  590. where upper(object_name(fkeyid)) = $table
  591. order by constraint_name, referenced_table_name, keyno";
  592. $constraints =& $this->GetArray($sql);
  593. $ADODB_FETCH_MODE = $save;
  594. $arr = false;
  595. foreach($constraints as $constr) {
  596. //print_r($constr);
  597. $arr[$constr[0]][$constr[2]][] = $constr[1].'='.$constr[3];
  598. }
  599. if (!$arr) return false;
  600. $arr2 = false;
  601. foreach($arr as $k => $v) {
  602. foreach($v as $a => $b) {
  603. if ($upper) $a = strtoupper($a);
  604. $arr2[$a] = $b;
  605. }
  606. }
  607. return $arr2;
  608. }
  609. //From: Fernando Moreira <FMoreira@imediata.pt>
  610. function MetaDatabases()
  611. {
  612. $this->SelectDB("master");
  613. $rs =& $this->Execute($this->metaDatabasesSQL);
  614. $rows = $rs->GetRows();
  615. $ret = array();
  616. for($i=0;$i<count($rows);$i++) {
  617. $ret[] = $rows[$i][0];
  618. }
  619. $this->SelectDB($this->database);
  620. if($ret)
  621. return $ret;
  622. else
  623. return false;
  624. }
  625. // "Stein-Aksel Basma" <basma@accelero.no>
  626. // tested with MSSQL 2000
  627. function MetaPrimaryKeys($table, $owner=false)
  628. {
  629. global $ADODB_FETCH_MODE;
  630. $schema = '';
  631. $this->_findschema($table,$schema);
  632. if (!$schema) $schema = $this->database;
  633. if ($schema) $schema = "and k.table_catalog like '$schema%'";
  634. $sql = "select distinct k.column_name,ordinal_position from information_schema.key_column_usage k,
  635. information_schema.table_constraints tc
  636. where tc.constraint_name = k.constraint_name and tc.constraint_type =
  637. 'PRIMARY KEY' and k.table_name = '$table' $schema order by ordinal_position ";
  638. $savem = $ADODB_FETCH_MODE;
  639. $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
  640. $a = $this->GetCol($sql);
  641. $ADODB_FETCH_MODE = $savem;
  642. if ($a && sizeof($a)>0) return $a;
  643. $false = false;
  644. return $false;
  645. }
  646. function MetaTables($ttype=false,$showSchema=false,$mask=false)
  647. {
  648. if ($mask) {
  649. $save = $this->metaTablesSQL;
  650. $mask = $this->qstr(($mask));
  651. $this->metaTablesSQL .= " AND name like $mask";
  652. }
  653. $ret = ADOConnection::MetaTables($ttype,$showSchema);
  654. if ($mask) {
  655. $this->metaTablesSQL = $save;
  656. }
  657. return $ret;
  658. }
  659. function MetaColumns($table, $upper=true, $schema=false){
  660. # start adg
  661. static $cached_columns = array();
  662. if ($this->cachedSchemaFlush)
  663. $cached_columns = array();
  664. if (array_key_exists($table,$cached_columns)){
  665. return $cached_columns[$table];
  666. }
  667. # end adg
  668. if (!$this->mssql_version)
  669. $this->ServerVersion();
  670. $this->_findschema($table,$schema);
  671. if ($schema) {
  672. $dbName = $this->database;
  673. $this->SelectDB($schema);
  674. }
  675. global $ADODB_FETCH_MODE;
  676. $save = $ADODB_FETCH_MODE;
  677. $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
  678. if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
  679. $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table));
  680. if ($schema) {
  681. $this->SelectDB($dbName);
  682. }
  683. if (isset($savem)) $this->SetFetchMode($savem);
  684. $ADODB_FETCH_MODE = $save;
  685. if (!is_object($rs)) {
  686. $false = false;
  687. return $false;
  688. }
  689. $retarr = array();
  690. while (!$rs->EOF){
  691. $fld = new ADOFieldObject();
  692. if (array_key_exists(0,$rs->fields)) {
  693. $fld->name = $rs->fields[0];
  694. $fld->type = $rs->fields[1];
  695. $fld->max_length = $rs->fields[2];
  696. $fld->precision = $rs->fields[3];
  697. $fld->scale = $rs->fields[4];
  698. $fld->not_null =!$rs->fields[5];
  699. $fld->has_default = $rs->fields[6];
  700. $fld->xtype = $rs->fields[7];
  701. $fld->type_length = $rs->fields[8];
  702. $fld->auto_increment= $rs->fields[9];
  703. } else {
  704. $fld->name = $rs->fields['name'];
  705. $fld->type = $rs->fields['type'];
  706. $fld->max_length = $rs->fields['length'];
  707. $fld->precision = $rs->fields['precision'];
  708. $fld->scale = $rs->fields['scale'];
  709. $fld->not_null =!$rs->fields['nullable'];
  710. $fld->has_default = $rs->fields['default_value'];
  711. $fld->xtype = $rs->fields['xtype'];
  712. $fld->type_length = $rs->fields['type_length'];
  713. $fld->auto_increment= $rs->fields['is_identity'];
  714. }
  715. if ($save == ADODB_FETCH_NUM)
  716. $retarr[] = $fld;
  717. else
  718. $retarr[strtoupper($fld->name)] = $fld;
  719. $rs->MoveNext();
  720. }
  721. $rs->Close();
  722. # start adg
  723. $cached_columns[$table] = $retarr;
  724. # end adg
  725. return $retarr;
  726. }
  727. }
  728. /*--------------------------------------------------------------------------------------
  729. Class Name: Recordset
  730. --------------------------------------------------------------------------------------*/
  731. class ADORecordset_mssqlnative extends ADORecordSet {
  732. var $databaseType = "mssqlnative";
  733. var $canSeek = false;
  734. var $fieldOffset = 0;
  735. // _mths works only in non-localised system
  736. function __construct($id,$mode=false)
  737. {
  738. if ($mode === false) {
  739. global $ADODB_FETCH_MODE;
  740. $mode = $ADODB_FETCH_MODE;
  741. }
  742. $this->fetchMode = $mode;
  743. return parent::__construct($id,$mode);
  744. }
  745. function _initrs()
  746. {
  747. global $ADODB_COUNTRECS;
  748. # KMN # if ($this->connection->debug) ADOConnection::outp("(before) ADODB_COUNTRECS: {$ADODB_COUNTRECS} _numOfRows: {$this->_numOfRows} _numOfFields: {$this->_numOfFields}");
  749. /*$retRowsAff = sqlsrv_rows_affected($this->_queryID);//"If you need to determine the number of rows a query will return before retrieving the actual results, appending a SELECT COUNT ... query would let you get that information, and then a call to next_result would move you to the "real" results."
  750. ADOConnection::outp("rowsaff: ".serialize($retRowsAff));
  751. $this->_numOfRows = ($ADODB_COUNTRECS)? $retRowsAff:-1;*/
  752. $this->_numOfRows = -1;//not supported
  753. $fieldmeta = sqlsrv_field_metadata($this->_queryID);
  754. $this->_numOfFields = ($fieldmeta)? count($fieldmeta):-1;
  755. # KMN # if ($this->connection->debug) ADOConnection::outp("(after) _numOfRows: {$this->_numOfRows} _numOfFields: {$this->_numOfFields}");
  756. /*
  757. * Copy the oracle method and cache the metadata at init time
  758. */
  759. if ($this->_numOfFields>0) {
  760. $this->_fieldobjs = array();
  761. $max = $this->_numOfFields;
  762. for ($i=0;$i<$max; $i++) $this->_fieldobjs[] = $this->_FetchField($i);
  763. }
  764. }
  765. //Contributed by "Sven Axelsson" <sven.axelsson@bokochwebb.se>
  766. // get next resultset - requires PHP 4.0.5 or later
  767. function NextRecordSet()
  768. {
  769. if (!sqlsrv_next_result($this->_queryID)) return false;
  770. $this->_inited = false;
  771. $this->bind = false;
  772. $this->_currentRow = -1;
  773. $this->Init();
  774. return true;
  775. }
  776. /* Use associative array to get fields array */
  777. function Fields($colname)
  778. {
  779. if ($this->fetchMode != ADODB_FETCH_NUM) return $this->fields[$colname];
  780. if (!$this->bind) {
  781. $this->bind = array();
  782. for ($i=0; $i < $this->_numOfFields; $i++) {
  783. $o = $this->FetchField($i);
  784. $this->bind[strtoupper($o->name)] = $i;
  785. }
  786. }
  787. return $this->fields[$this->bind[strtoupper($colname)]];
  788. }
  789. /* Returns: an object containing field information.
  790. Get column information in the Recordset object. fetchField() can be used in order to obtain information about
  791. fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by
  792. fetchField() is retrieved.
  793. Designed By jcortinap#jc.com.mx
  794. */
  795. function _FetchField($fieldOffset = -1)
  796. {
  797. $_typeConversion = array(
  798. -155 => 'datetimeoffset',
  799. -154 => 'char',
  800. -152 => 'xml',
  801. -151 => 'udt',
  802. -11 => 'uniqueidentifier',
  803. -10 => 'ntext',
  804. -9 => 'nvarchar',
  805. -8 => 'nchar',
  806. -7 => 'bit',
  807. -6 => 'tinyint',
  808. -5 => 'bigint',
  809. -4 => 'image',
  810. -3 => 'varbinary',
  811. -2 => 'timestamp',
  812. -1 => 'text',
  813. 1 => 'char',
  814. 2 => 'numeric',
  815. 3 => 'decimal',
  816. 4 => 'int',
  817. 5 => 'smallint',
  818. 6 => 'float',
  819. 7 => 'real',
  820. 12 => 'varchar',
  821. 91 => 'date',
  822. 93 => 'datetime'
  823. );
  824. $fa = @sqlsrv_field_metadata($this->_queryID);
  825. if ($fieldOffset != -1) {
  826. $fa = $fa[$fieldOffset];
  827. }
  828. $false = false;
  829. if (empty($fa)) {
  830. $f = false;//PHP Notice: Only variable references should be returned by reference
  831. }
  832. else
  833. {
  834. // Convert to an object
  835. $fa = array_change_key_case($fa, CASE_LOWER);
  836. $fb = array();
  837. if ($fieldOffset != -1)
  838. {
  839. $fb = array(
  840. 'name' => $fa['name'],
  841. 'max_length' => $fa['size'],
  842. 'column_source' => $fa['name'],
  843. 'type' => $_typeConversion[$fa['type']]
  844. );
  845. }
  846. else
  847. {
  848. foreach ($fa as $key => $value)
  849. {
  850. $fb[] = array(
  851. 'name' => $value['name'],
  852. 'max_length' => $value['size'],
  853. 'column_source' => $value['name'],
  854. 'type' => $_typeConversion[$value['type']]
  855. );
  856. }
  857. }
  858. $f = (object) $fb;
  859. }
  860. return $f;
  861. }
  862. /*
  863. * Fetchfield copies the oracle method, it loads the field information
  864. * into the _fieldobjs array once, to save multiple calls to the
  865. * sqlsrv_field_metadata function
  866. *
  867. * @author KM Newnham
  868. * @date 02/20/2013
  869. */
  870. function FetchField($fieldOffset = -1)
  871. {
  872. return $this->_fieldobjs[$fieldOffset];
  873. }
  874. function _seek($row)
  875. {
  876. return false;//There is no support for cursors in the driver at this time. All data is returned via forward-only streams.
  877. }
  878. // speedup
  879. function MoveNext()
  880. {
  881. //# KMN # if ($this->connection->debug) ADOConnection::outp("movenext()");
  882. //# KMN # if ($this->connection->debug) ADOConnection::outp("eof (beginning): ".$this->EOF);
  883. if ($this->EOF) return false;
  884. $this->_currentRow++;
  885. // # KMN # if ($this->connection->debug) ADOConnection::outp("_currentRow: ".$this->_currentRow);
  886. if ($this->_fetch()) return true;
  887. $this->EOF = true;
  888. //# KMN # if ($this->connection->debug) ADOConnection::outp("eof (end): ".$this->EOF);
  889. return false;
  890. }
  891. // INSERT UPDATE DELETE returns false even if no error occurs in 4.0.4
  892. // also the date format has been changed from YYYY-mm-dd to dd MMM YYYY in 4.0.4. Idiot!
  893. function _fetch($ignore_fields=false)
  894. {
  895. # KMN # if ($this->connection->debug) ADOConnection::outp("_fetch()");
  896. if ($this->fetchMode & ADODB_FETCH_ASSOC) {
  897. if ($this->fetchMode & ADODB_FETCH_NUM) {
  898. //# KMN # if ($this->connection->debug) ADOConnection::outp("fetch mode: both");
  899. $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_BOTH);
  900. } else {
  901. //# KMN # if ($this->connection->debug) ADOConnection::outp("fetch mode: assoc");
  902. $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_ASSOC);
  903. }
  904. if (is_array($this->fields)) {
  905. if (ADODB_ASSOC_CASE == 0) {
  906. foreach($this->fields as $k=>$v) {
  907. $this->fields[strtolower($k)] = $v;
  908. }
  909. } else if (ADODB_ASSOC_CASE == 1) {
  910. foreach($this->fields as $k=>$v) {
  911. $this->fields[strtoupper($k)] = $v;
  912. }
  913. }
  914. }
  915. } else {
  916. //# KMN # if ($this->connection->debug) ADOConnection::outp("fetch mode: num");
  917. $this->fields = @sqlsrv_fetch_array($this->_queryID,SQLSRV_FETCH_NUMERIC);
  918. }
  919. if(is_array($this->fields) && array_key_exists(1,$this->fields) && !array_key_exists(0,$this->fields)) {//fix fetch numeric keys since they're not 0 based
  920. $arrFixed = array();
  921. foreach($this->fields as $key=>$value) {
  922. if(is_numeric($key)) {
  923. $arrFixed[$key-1] = $value;
  924. } else {
  925. $arrFixed[$key] = $value;
  926. }
  927. }
  928. //if($this->connection->debug) ADOConnection::outp("<hr>fixing non 0 based return array, old: ".print_r($this->fields,true)." new: ".print_r($arrFixed,true));
  929. $this->fields = $arrFixed;
  930. }
  931. if(is_array($this->fields)) {
  932. foreach($this->fields as $key=>$value) {
  933. if (is_object($value) && method_exists($value, 'format')) {//is DateTime object
  934. $this->fields[$key] = $value->format("Y-m-d\TH:i:s\Z");
  935. }
  936. }
  937. }
  938. if($this->fields === null) $this->fields = false;
  939. # KMN # if ($this->connection->debug) ADOConnection::outp("<hr>after _fetch, fields: <pre>".print_r($this->fields,true)." backtrace: ".adodb_backtrace(false));
  940. return $this->fields;
  941. }
  942. /* close() only needs to be called if you are worried about using too much memory while your script
  943. is running. All associated result memory for the specified result identifier will automatically be freed. */
  944. function _close()
  945. {
  946. if(is_resource($this->_queryID)) {
  947. $rez = sqlsrv_free_stmt($this->_queryID);
  948. $this->_queryID = false;
  949. return $rez;
  950. }
  951. return true;
  952. }
  953. // mssql uses a default date like Dec 30 2000 12:00AM
  954. static function UnixDate($v)
  955. {
  956. return ADORecordSet_array_mssqlnative::UnixDate($v);
  957. }
  958. static function UnixTimeStamp($v)
  959. {
  960. return ADORecordSet_array_mssqlnative::UnixTimeStamp($v);
  961. }
  962. }
  963. class ADORecordSet_array_mssqlnative extends ADORecordSet_array {
  964. function __construct($id=-1,$mode=false)
  965. {
  966. parent::__construct($id,$mode);
  967. }
  968. // mssql uses a default date like Dec 30 2000 12:00AM
  969. static function UnixDate($v)
  970. {
  971. if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixDate($v);
  972. global $ADODB_mssql_mths,$ADODB_mssql_date_order;
  973. //Dec 30 2000 12:00AM
  974. if ($ADODB_mssql_date_order == 'dmy') {
  975. if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4})|" ,$v, $rr)) {
  976. return parent::UnixDate($v);
  977. }
  978. if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
  979. $theday = $rr[1];
  980. $themth = substr(strtoupper($rr[2]),0,3);
  981. } else {
  982. if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})|" ,$v, $rr)) {
  983. return parent::UnixDate($v);
  984. }
  985. if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
  986. $theday = $rr[2];
  987. $themth = substr(strtoupper($rr[1]),0,3);
  988. }
  989. $themth = $ADODB_mssql_mths[$themth];
  990. if ($themth <= 0) return false;
  991. // h-m-s-MM-DD-YY
  992. return adodb_mktime(0,0,0,$themth,$theday,$rr[3]);
  993. }
  994. static function UnixTimeStamp($v)
  995. {
  996. if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixTimeStamp($v);
  997. global $ADODB_mssql_mths,$ADODB_mssql_date_order;
  998. //Dec 30 2000 12:00AM
  999. if ($ADODB_mssql_date_order == 'dmy') {
  1000. if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|"
  1001. ,$v, $rr)) return parent::UnixTimeStamp($v);
  1002. if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
  1003. $theday = $rr[1];
  1004. $themth = substr(strtoupper($rr[2]),0,3);
  1005. } else {
  1006. if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|"
  1007. ,$v, $rr)) return parent::UnixTimeStamp($v);
  1008. if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0;
  1009. $theday = $rr[2];
  1010. $themth = substr(strtoupper($rr[1]),0,3);
  1011. }
  1012. $themth = $ADODB_mssql_mths[$themth];
  1013. if ($themth <= 0) return false;
  1014. switch (strtoupper($rr[6])) {
  1015. case 'P':
  1016. if ($rr[4]<12) $rr[4] += 12;
  1017. break;
  1018. case 'A':
  1019. if ($rr[4]==12) $rr[4] = 0;
  1020. break;
  1021. default:
  1022. break;
  1023. }
  1024. // h-m-s-MM-DD-YY
  1025. return adodb_mktime($rr[4],$rr[5],0,$themth,$theday,$rr[3]);
  1026. }
  1027. }
  1028. /*
  1029. Code Example 1:
  1030. select object_name(constid) as constraint_name,
  1031. object_name(fkeyid) as table_name,
  1032. col_name(fkeyid, fkey) as column_name,
  1033. object_name(rkeyid) as referenced_table_name,
  1034. col_name(rkeyid, rkey) as referenced_column_name
  1035. from sysforeignkeys
  1036. where object_name(fkeyid) = x
  1037. order by constraint_name, table_name, referenced_table_name, keyno
  1038. Code Example 2:
  1039. select constraint_name,
  1040. column_name,
  1041. ordinal_position
  1042. from information_schema.key_column_usage
  1043. where constraint_catalog = db_name()
  1044. and table_name = x
  1045. order by constraint_name, ordinal_position
  1046. http://www.databasejournal.com/scripts/article.php/1440551
  1047. */