PageRenderTime 1933ms CodeModel.GetById 26ms RepoModel.GetById 3ms app.codeStats 0ms

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

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