sqlsrv.php 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175
  1. <?php
  2. // vim: set et ts=4 sw=4 fdm=marker:
  3. // +----------------------------------------------------------------------+
  4. // | PHP versions 4 and 5 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox, |
  7. // | Stig. S. Bakken, Lukas Smith, Frank M. Kromann |
  8. // | All rights reserved. |
  9. // +----------------------------------------------------------------------+
  10. // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB |
  11. // | API as well as database abstraction for PHP applications. |
  12. // | This LICENSE is in the BSD license style. |
  13. // | |
  14. // | Redistribution and use in source and binary forms, with or without |
  15. // | modification, are permitted provided that the following conditions |
  16. // | are met: |
  17. // | |
  18. // | Redistributions of source code must retain the above copyright |
  19. // | notice, this list of conditions and the following disclaimer. |
  20. // | |
  21. // | Redistributions in binary form must reproduce the above copyright |
  22. // | notice, this list of conditions and the following disclaimer in the |
  23. // | documentation and/or other materials provided with the distribution. |
  24. // | |
  25. // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, |
  26. // | Lukas Smith nor the names of his contributors may be used to endorse |
  27. // | or promote products derived from this software without specific prior|
  28. // | written permission. |
  29. // | |
  30. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
  31. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
  32. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
  33. // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
  34. // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
  35. // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
  36. // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
  37. // | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
  38. // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
  39. // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
  40. // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
  41. // | POSSIBILITY OF SUCH DAMAGE. |
  42. // +----------------------------------------------------------------------+
  43. // | Author: Frank M. Kromann <frank@kromann.info> |
  44. // +----------------------------------------------------------------------+
  45. // {{{ Class MDB2_Driver_sqlsrv
  46. /**
  47. * MDB2 MSSQL Server (native) driver
  48. *
  49. * @package MDB2
  50. * @category Database
  51. */
  52. class MDB2_Driver_sqlsrv extends MDB2_Driver_Common
  53. {
  54. // {{{ properties
  55. var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false);
  56. var $identifier_quoting = array('start' => '[', 'end' => ']', 'escape' => ']');
  57. var $connection = null;
  58. // }}}
  59. // {{{ constructor
  60. /**
  61. * Constructor
  62. */
  63. function __construct()
  64. {
  65. parent::__construct();
  66. $this->phptype = 'sqlsrv';
  67. $this->dbsyntax = 'sqlsrv';
  68. $this->supported['sequences'] = 'emulated';
  69. $this->supported['indexes'] = true;
  70. $this->supported['affected_rows'] = true;
  71. $this->supported['summary_functions'] = true;
  72. $this->supported['transactions'] = true;
  73. $this->supported['order_by_text'] = true;
  74. $this->supported['savepoints'] = false;
  75. $this->supported['current_id'] = 'emulated';
  76. $this->supported['limit_queries'] = 'emulated';
  77. $this->supported['LOBs'] = true;
  78. $this->supported['replace'] = 'emulated';
  79. $this->supported['sub_selects'] = true;
  80. $this->supported['triggers'] = true;
  81. $this->supported['auto_increment'] = true;
  82. $this->supported['primary_key'] = true;
  83. $this->supported['result_introspection'] = true;
  84. $this->supported['prepared_statements'] = 'emulated';
  85. $this->supported['identifier_quoting'] = false;
  86. $this->supported['pattern_escaping'] = true;
  87. $this->supported['new_link'] = true;
  88. $this->options['DBA_username'] = false;
  89. $this->options['DBA_password'] = false;
  90. $this->options['database_device'] = false;
  91. $this->options['database_size'] = false;
  92. $this->options['max_identifiers_length'] = 128; // MS Access: 64
  93. }
  94. // }}}
  95. // {{{ errorInfo()
  96. /**
  97. * This method is used to collect information about an error
  98. *
  99. * @param integer $error
  100. * @return array
  101. * @access public
  102. */
  103. function errorInfo($error = null, $connection = null)
  104. {
  105. if (null === $connection) {
  106. $connection = $this->connection;
  107. }
  108. $native_code = null;
  109. $native_msg = null;
  110. if ($connection) {
  111. $retErrors = sqlsrv_errors(SQLSRV_ERR_ALL);
  112. if ($retErrors !== null) {
  113. foreach ($retErrors as $arrError) {
  114. $native_msg .= "SQLState: ".$arrError[ 'SQLSTATE']."\n";
  115. $native_msg .= "Error Code: ".$arrError[ 'code']."\n";
  116. $native_msg .= "Message: ".$arrError[ 'message']."\n";
  117. $native_code = $arrError[ 'code'];
  118. }
  119. }
  120. }
  121. if (null === $error) {
  122. static $ecode_map;
  123. if (empty($ecode_map)) {
  124. $ecode_map = array(
  125. 102 => MDB2_ERROR_SYNTAX,
  126. 110 => MDB2_ERROR_VALUE_COUNT_ON_ROW,
  127. 155 => MDB2_ERROR_NOSUCHFIELD,
  128. 156 => MDB2_ERROR_SYNTAX,
  129. 170 => MDB2_ERROR_SYNTAX,
  130. 207 => MDB2_ERROR_NOSUCHFIELD,
  131. 208 => MDB2_ERROR_NOSUCHTABLE,
  132. 245 => MDB2_ERROR_INVALID_NUMBER,
  133. 319 => MDB2_ERROR_SYNTAX,
  134. 321 => MDB2_ERROR_NOSUCHFIELD,
  135. 325 => MDB2_ERROR_SYNTAX,
  136. 336 => MDB2_ERROR_SYNTAX,
  137. 515 => MDB2_ERROR_CONSTRAINT_NOT_NULL,
  138. 547 => MDB2_ERROR_CONSTRAINT,
  139. 911 => MDB2_ERROR_NOT_FOUND,
  140. 1018 => MDB2_ERROR_SYNTAX,
  141. 1035 => MDB2_ERROR_SYNTAX,
  142. 1801 => MDB2_ERROR_ALREADY_EXISTS,
  143. 1913 => MDB2_ERROR_ALREADY_EXISTS,
  144. 2209 => MDB2_ERROR_SYNTAX,
  145. 2223 => MDB2_ERROR_SYNTAX,
  146. 2248 => MDB2_ERROR_SYNTAX,
  147. 2256 => MDB2_ERROR_SYNTAX,
  148. 2257 => MDB2_ERROR_SYNTAX,
  149. 2627 => MDB2_ERROR_CONSTRAINT,
  150. 2714 => MDB2_ERROR_ALREADY_EXISTS,
  151. 3607 => MDB2_ERROR_DIVZERO,
  152. 3701 => MDB2_ERROR_NOSUCHTABLE,
  153. 7630 => MDB2_ERROR_SYNTAX,
  154. 8134 => MDB2_ERROR_DIVZERO,
  155. 9303 => MDB2_ERROR_SYNTAX,
  156. 9317 => MDB2_ERROR_SYNTAX,
  157. 9318 => MDB2_ERROR_SYNTAX,
  158. 9331 => MDB2_ERROR_SYNTAX,
  159. 9332 => MDB2_ERROR_SYNTAX,
  160. 15253 => MDB2_ERROR_SYNTAX,
  161. );
  162. }
  163. if (isset($ecode_map[$native_code])) {
  164. if ($native_code == 3701
  165. && preg_match('/Cannot drop the index/i', $native_msg)
  166. ) {
  167. $error = MDB2_ERROR_NOT_FOUND;
  168. } else {
  169. $error = $ecode_map[$native_code];
  170. }
  171. }
  172. }
  173. return array($error, $native_code, $native_msg);
  174. }
  175. // }}}
  176. // {{{ function escapePattern($text)
  177. /**
  178. * Quotes pattern (% and _) characters in a string)
  179. *
  180. * @param string the input string to quote
  181. *
  182. * @return string quoted string
  183. *
  184. * @access public
  185. */
  186. function escapePattern($text)
  187. {
  188. $text = str_replace("[", "[ [ ]", $text);
  189. foreach ($this->wildcards as $wildcard) {
  190. $text = str_replace($wildcard, '[' . $wildcard . ']', $text);
  191. }
  192. return $text;
  193. }
  194. // }}}
  195. // {{{ beginTransaction()
  196. /**
  197. * Start a transaction or set a savepoint.
  198. *
  199. * @param string name of a savepoint to set
  200. * @return mixed MDB2_OK on success, a MDB2 error on failure
  201. *
  202. * @access public
  203. */
  204. function beginTransaction($savepoint = null)
  205. {
  206. $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
  207. if (null !== $savepoint) {
  208. if (!$this->in_transaction) {
  209. return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  210. 'savepoint cannot be released when changes are auto committed', __FUNCTION__);
  211. }
  212. $query = 'SAVE TRANSACTION '.$savepoint;
  213. return $this->_doQuery($query, true);
  214. }
  215. if ($this->in_transaction) {
  216. return MDB2_OK; //nothing to do
  217. }
  218. if (!$this->destructor_registered && $this->opened_persistent) {
  219. $this->destructor_registered = true;
  220. register_shutdown_function('MDB2_closeOpenTransactions');
  221. }
  222. if (MDB2::isError(sqlsrv_begin_transaction($this->connection))) {
  223. return MDB2_ERROR;
  224. }
  225. $this->in_transaction = true;
  226. return MDB2_OK;
  227. }
  228. // }}}
  229. // {{{ commit()
  230. /**
  231. * Commit the database changes done during a transaction that is in
  232. * progress or release a savepoint. This function may only be called when
  233. * auto-committing is disabled, otherwise it will fail. Therefore, a new
  234. * transaction is implicitly started after committing the pending changes.
  235. *
  236. * @param string name of a savepoint to release
  237. * @return mixed MDB2_OK on success, a MDB2 error on failure
  238. *
  239. * @access public
  240. */
  241. function commit($savepoint = null)
  242. {
  243. $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
  244. if (!$this->in_transaction) {
  245. return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  246. 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__);
  247. }
  248. if (null !== $savepoint) {
  249. return MDB2_OK;
  250. }
  251. if (MDB2::isError(sqlsrv_commit($this->connection))) {
  252. return MDB2_ERROR;
  253. }
  254. $this->in_transaction = false;
  255. return MDB2_OK;
  256. }
  257. // }}}
  258. // {{{ rollback()
  259. /**
  260. * Cancel any database changes done during a transaction or since a specific
  261. * savepoint that is in progress. This function may only be called when
  262. * auto-committing is disabled, otherwise it will fail. Therefore, a new
  263. * transaction is implicitly started after canceling the pending changes.
  264. *
  265. * @param string name of a savepoint to rollback to
  266. * @return mixed MDB2_OK on success, a MDB2 error on failure
  267. *
  268. * @access public
  269. */
  270. function rollback($savepoint = null)
  271. {
  272. $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint));
  273. if (!$this->in_transaction) {
  274. return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  275. 'rollback cannot be done changes are auto committed', __FUNCTION__);
  276. }
  277. if (null !== $savepoint) {
  278. $query = 'ROLLBACK TRANSACTION '.$savepoint;
  279. return $this->_doQuery($query, true);
  280. }
  281. if (MDB2::isError(sqlsrv_rollback($this->connection))) {
  282. return MDB2_ERROR;
  283. }
  284. $this->in_transaction = false;
  285. return MDB2_OK;
  286. }
  287. // }}}
  288. // {{{ _doConnect()
  289. /**
  290. * do the grunt work of the connect
  291. *
  292. * @return connection on success or MDB2 Error Object on failure
  293. * @access protected
  294. */
  295. function _doConnect($username, $password, $database=null, $persistent = false)
  296. {
  297. if (!extension_loaded($this->phptype)) {
  298. return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null,
  299. 'extension '.$this->phptype.' is not installed PHP', __FUNCTION__);
  300. }
  301. $host = $this->dsn['hostspec'] ? $this->dsn['hostspec'] : '.\\SQLEXPRESS';
  302. $params = array(
  303. 'UID' => $username ? $username : null,
  304. 'PWD' => $password ? $password : null,
  305. );
  306. if ($database) {
  307. $params['Database'] = $database;
  308. }
  309. if ($this->dsn['port'] && $this->dsn['port'] != 1433) {
  310. $host .= ','.$this->dsn['port'];
  311. }
  312. $connection = @sqlsrv_connect($host, $params);
  313. if (!$connection) {
  314. return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null,
  315. 'unable to establish a connection', __FUNCTION__, __FUNCTION__);
  316. }
  317. if (null !== $database) {
  318. $this->connected_database_name = $database;
  319. }
  320. if (!empty($this->dsn['charset'])) {
  321. $result = $this->setCharset($this->dsn['charset'], $connection);
  322. if (MDB2::isError($result)) {
  323. return $result;
  324. }
  325. }
  326. if (empty($this->dsn['disable_iso_date'])) {
  327. @sqlsrv_query($connection,'SET DATEFORMAT ymd');
  328. }
  329. return $connection;
  330. }
  331. // }}}
  332. // {{{ connect()
  333. /**
  334. * Connect to the database
  335. *
  336. * @return true on success, MDB2 Error Object on failure
  337. */
  338. function connect()
  339. {
  340. if (is_resource($this->connection)) {
  341. if (MDB2::areEquals($this->connected_dsn, $this->dsn)) {
  342. return MDB2_OK;
  343. }
  344. $this->disconnect(false);
  345. }
  346. $connection = $this->_doConnect(
  347. $this->dsn['username'],
  348. $this->dsn['password'],
  349. $this->database_name,
  350. $this->options['persistent']
  351. );
  352. if (MDB2::isError($connection)) {
  353. return $connection;
  354. }
  355. $this->connection = $connection;
  356. $this->connected_dsn = $this->dsn;
  357. $this->connected_database_name = $this->database_name;
  358. $this->opened_persistent = $this->options['persistent'];
  359. $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype;
  360. return MDB2_OK;
  361. }
  362. // }}}
  363. // {{{ databaseExists()
  364. /**
  365. * check if given database name exists?
  366. *
  367. * @param string $name name of the database that should be checked
  368. *
  369. * @return mixed true/false on success, a MDB2 error on failure
  370. * @access public
  371. */
  372. function databaseExists($name)
  373. {
  374. $connection = $this->_doConnect($this->dsn['username'],$this->dsn['password']);
  375. if (MDB2::isError($connection)) {
  376. return MDB2_ERROR_CONNECT_FAILED;
  377. }
  378. $result = @sqlsrv_query($connection,'select name from master..sysdatabases where name = \''.strtolower($name).'\'');
  379. if (@sqlsrv_fetch($result)) {
  380. return true;
  381. }
  382. return MDB2_ERROR_NOT_FOUND;
  383. }
  384. // }}}
  385. // {{{ disconnect()
  386. /**
  387. * Log out and disconnect from the database.
  388. *
  389. * @param boolean $force if the disconnect should be forced even if the
  390. * connection is opened persistently
  391. * @return mixed true on success, false if not connected and error
  392. * object on error
  393. * @access public
  394. */
  395. function disconnect($force = true)
  396. {
  397. if (is_resource($this->connection)) {
  398. if ($this->in_transaction) {
  399. $dsn = $this->dsn;
  400. $database_name = $this->database_name;
  401. $persistent = $this->options['persistent'];
  402. $this->dsn = $this->connected_dsn;
  403. $this->database_name = $this->connected_database_name;
  404. $this->options['persistent'] = $this->opened_persistent;
  405. $this->rollback();
  406. $this->dsn = $dsn;
  407. $this->database_name = $database_name;
  408. $this->options['persistent'] = $persistent;
  409. }
  410. @sqlsrv_close($this->connection);
  411. }
  412. return parent::disconnect($force);
  413. }
  414. // }}}
  415. // {{{ standaloneQuery()
  416. /**
  417. * execute a query as DBA
  418. *
  419. * @param string $query the SQL query
  420. * @param mixed $types array that contains the types of the columns in
  421. * the result set
  422. * @param boolean $is_manip if the query is a manipulation query
  423. * @return mixed MDB2_OK on success, a MDB2 error on failure
  424. * @access public
  425. */
  426. function &standaloneQuery($query, $types = null, $is_manip = false)
  427. {
  428. $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username'];
  429. $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password'];
  430. $connection = $this->_doConnect($user, $pass, $this->database_name, $this->options['persistent']);
  431. if (MDB2::isError($connection)) {
  432. return $connection;
  433. }
  434. $query = $this->_modifyQuery($query, $is_manip, $this->limit, $this->offset);
  435. $this->offset = $this->limit = 0;
  436. $result = $this->_doQuery($query, $is_manip, $connection);
  437. if (!MDB2::isError($result)) {
  438. $result = $this->_affectedRows($connection, $result);
  439. }
  440. @sqlsrv_close($connection);
  441. return $result;
  442. }
  443. // }}}
  444. // {{{ _doQuery()
  445. /**
  446. * Execute a query
  447. * @param string $query query
  448. * @param boolean $is_manip if the query is a manipulation query
  449. * @param resource $connection
  450. * @param string $database_name
  451. * @return result or error object
  452. * @access protected
  453. */
  454. function _doQuery($query, $is_manip = false, $connection = null, $database_name = null)
  455. {
  456. $this->last_query = $query;
  457. $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre'));
  458. if ($result) {
  459. if (MDB2::isError($result)) {
  460. return $result;
  461. }
  462. $query = $result;
  463. }
  464. if ($this->options['disable_query']) {
  465. $result = $is_manip ? 0 : null;
  466. return $result;
  467. }
  468. if (null === $connection) {
  469. $connection = $this->getConnection();
  470. if (MDB2::isError($connection)) {
  471. return $connection;
  472. }
  473. }
  474. if (null === $database_name) {
  475. $database_name = $this->database_name;
  476. }
  477. if ($database_name && $database_name != $this->connected_database_name) {
  478. $connection = $this->_doConnect($this->dsn['username'],$this->dsn['password'],$database_name);
  479. if (MDB2::isError($connection)) {
  480. $err = $this->raiseError(null, null, null,
  481. 'Could not select the database: '.$database_name, __FUNCTION__);
  482. return $err;
  483. }
  484. $this->connected_database_name = $database_name;
  485. }
  486. $query = preg_replace('/DATE_FORMAT\((MIN\()?([\w|.]*)(\))?\\Q, \'%Y-%m-%d\')\E/i','CONVERT(varchar(10),$1$2$3,120)',$query);
  487. $query = preg_replace('/DATE_FORMAT\(([\w|.]*)\, \'\%Y\-\%m\-\%d %H\:00\:00\'\)/i','CONVERT(varchar(13),$1,120)+\':00:00\'',$query);
  488. $result = @sqlsrv_query($connection,$query);
  489. if (!$result) {
  490. $err = $this->raiseError(null, null, null,
  491. 'Could not execute statement', __FUNCTION__);
  492. return $err;
  493. }
  494. $this->result = $result;
  495. $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result));
  496. return $result;
  497. }
  498. // }}}
  499. // {{{ _affectedRows()
  500. /**
  501. * Returns the number of rows affected
  502. *
  503. * @param resource $result
  504. * @param resource $connection
  505. * @return mixed MDB2 Error Object or the number of rows affected
  506. * @access private
  507. */
  508. function _affectedRows($connection, $result = null)
  509. {
  510. if (null === $result) {
  511. $result = $this->result;
  512. }
  513. return sqlsrv_rows_affected($this->result);
  514. }
  515. // }}}
  516. // {{{ _modifyQuery()
  517. /**
  518. * Changes a query string for various DBMS specific reasons
  519. *
  520. * @param string $query query to modify
  521. * @param boolean $is_manip if it is a DML query
  522. * @param integer $limit limit the number of rows
  523. * @param integer $offset start reading from given offset
  524. * @return string modified query
  525. * @access protected
  526. */
  527. function _modifyQuery($query, $is_manip, $limit, $offset)
  528. {
  529. if ($limit > 0) {
  530. $fetch = $offset + $limit;
  531. if (!$is_manip) {
  532. return preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',
  533. "\\1SELECT\\2 TOP $fetch", $query);
  534. }
  535. }
  536. return $query;
  537. }
  538. // }}}
  539. // {{{ getServerVersion()
  540. /**
  541. * return version information about the server
  542. *
  543. * @param bool $native determines if the raw version string should be returned
  544. * @return mixed array/string with version information or MDB2 error object
  545. * @access public
  546. */
  547. function getServerVersion($native = false)
  548. {
  549. if ($this->connected_server_info) {
  550. $server_info = $this->connected_server_info;
  551. } else {
  552. $this->connect();
  553. $server_info = sqlsrv_server_info($this->connection);
  554. }
  555. // cache server_info
  556. $this->connected_server_info = $server_info;
  557. $version = $server_info['SQLServerVersion'];
  558. if (!$native) {
  559. if (preg_match('/(\d+)\.(\d+)\.(\d+)/', $version, $tmp)) {
  560. $version = array(
  561. 'major' => $tmp[1],
  562. 'minor' => $tmp[2],
  563. 'patch' => $tmp[3],
  564. 'extra' => null,
  565. 'native' => $version,
  566. );
  567. } else {
  568. $version = array(
  569. 'major' => null,
  570. 'minor' => null,
  571. 'patch' => null,
  572. 'extra' => null,
  573. 'native' => $version,
  574. );
  575. }
  576. }
  577. return $version;
  578. }
  579. // }}}
  580. // {{{ _checkSequence
  581. /**
  582. * Checks if there's a sequence that exists.
  583. *
  584. * @param string $seq_name The sequence name to verify.
  585. * @return bool $tableExists The value if the table exists or not
  586. * @access private
  587. */
  588. function _checkSequence($seq_name)
  589. {
  590. $query = "SELECT * FROM $seq_name";
  591. $tableExists =& $this->_doQuery($query, true);
  592. if (MDB2::isError($tableExists)) {
  593. if ($tableExists->getCode() == MDB2_ERROR_NOSUCHTABLE) {
  594. return false;
  595. }
  596. return false;
  597. }
  598. if (@sqlsrv_fetch($tableExists)) {
  599. return true;
  600. }
  601. return false;
  602. }
  603. // }}}
  604. // {{{ nextID()
  605. /**
  606. * Returns the next free id of a sequence
  607. *
  608. * @param string $seq_name name of the sequence
  609. * @param boolean $ondemand when true the sequence is
  610. * automatic created, if it
  611. * not exists
  612. *
  613. * @return mixed MDB2 Error Object or id
  614. * @access public
  615. */
  616. function nextID($seq_name, $ondemand = true)
  617. {
  618. $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true);
  619. $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true);
  620. $this->pushErrorHandling(PEAR_ERROR_RETURN);
  621. $this->expectError(MDB2_ERROR_NOSUCHTABLE);
  622. $seq_val = $this->_checkSequence($sequence_name);
  623. if ($seq_val) {
  624. $query = "SET IDENTITY_INSERT $sequence_name OFF ".
  625. "INSERT INTO $sequence_name DEFAULT VALUES";
  626. } else {
  627. $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (0)";
  628. }
  629. $result = $this->_doQuery($query, true);
  630. $this->popExpect();
  631. $this->popErrorHandling();
  632. if (MDB2::isError($result)) {
  633. if ($ondemand && !$this->_checkSequence($sequence_name)) {
  634. $this->loadModule('Manager', null, true);
  635. $result = $this->manager->createSequence($seq_name);
  636. if (MDB2::isError($result)) {
  637. return $this->raiseError($result, null, null,
  638. 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__);
  639. } else {
  640. /**
  641. * Little off-by-one problem with the sequence emulation
  642. * here being fixed, that instead of re-calling nextID
  643. * and forcing an increment by one, we simply check if it
  644. * exists, then we get the last inserted id if it does.
  645. *
  646. * In theory, $seq_name should be created otherwise there would
  647. * have been an error thrown somewhere up there..
  648. *
  649. * @todo confirm
  650. */
  651. if ($this->_checkSequence($seq_name)) {
  652. return $this->lastInsertID($seq_name);
  653. }
  654. return $this->nextID($seq_name, false);
  655. }
  656. }
  657. return $result;
  658. }
  659. $value = $this->lastInsertID($sequence_name);
  660. if (is_numeric($value)) {
  661. $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value";
  662. $result = $this->_doQuery($query, true);
  663. if (MDB2::isError($result)) {
  664. $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name;
  665. }
  666. }
  667. return $value;
  668. }
  669. // }}}
  670. // {{{ lastInsertID()
  671. /**
  672. * Returns the autoincrement ID if supported or $id or fetches the current
  673. * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field)
  674. *
  675. * @param string $table name of the table into which a new row was inserted
  676. * @param string $field name of the field into which a new row was inserted
  677. *
  678. * @return mixed MDB2 Error Object or id
  679. * @access public
  680. */
  681. function lastInsertID($table = null, $field = null)
  682. {
  683. return $this->queryOne("SELECT IDENT_CURRENT('$table')", 'integer');
  684. }
  685. // }}}
  686. }
  687. // }}}
  688. // {{{ Class MDB2_Result_mssql
  689. /**
  690. * MDB2 MSSQL Server result driver
  691. *
  692. * @package MDB2
  693. * @category Database
  694. * @author Frank M. Kromann <frank@kromann.info>
  695. */
  696. class MDB2_Result_sqlsrv extends MDB2_Result_Common
  697. {
  698. // {{{ constructor: function __construct($db, $result, $limit = 0, $offset = 0)
  699. /**
  700. * Constructor
  701. */
  702. function __construct($db, $result, $limit = 0, $offset = 0)
  703. {
  704. $this->db = $db;
  705. $this->result = $result;
  706. $this->offset = $offset;
  707. $this->limit = max(0, $limit - 1);
  708. $this->cursor = 0;
  709. $this->rows = array();
  710. $this->numFields = sqlsrv_num_fields($result);
  711. $this->fieldMeta = sqlsrv_field_metadata($result);
  712. $this->numRowsAffected = sqlsrv_rows_affected($result);
  713. while ($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC)) {
  714. if ($row !== null) {
  715. if ($this->offset && $this->offset_count < $this->offset) {
  716. $this->offset_count++;
  717. continue;
  718. }
  719. foreach ($row as $k => $v) {
  720. if (is_object($v) && method_exists($v, 'format')) {
  721. //DateTime Object
  722. $row[$k] = $v->format('Y-m-d H:i:s');
  723. }
  724. }
  725. $this->rows[] = $row; //read results into memory, cursors are not supported
  726. }
  727. }
  728. $this->rowcnt = count($this->rows);
  729. }
  730. // }}}
  731. // {{{ _skipLimitOffset()
  732. /**
  733. * Skip the first row of a result set.
  734. *
  735. * @param resource $result
  736. * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure
  737. * @access protected
  738. */
  739. /* function _skipLimitOffset()
  740. {
  741. if ($this->limit) {
  742. if ($this->rownum >= $this->limit) {
  743. return false;
  744. }
  745. }
  746. if ($this->offset) {
  747. while ($this->offset_count < $this->offset) {
  748. ++$this->offset_count;
  749. if (!is_array(@sqlsrv_fetch_array($this->result))) {
  750. $this->offset_count = $this->limit;
  751. return false;
  752. }
  753. }
  754. }
  755. return MDB2_OK;
  756. }*/
  757. // }}}
  758. function array_to_obj($array, &$obj) {
  759. foreach ($array as $key => $value) {
  760. if (is_array($value)) {
  761. $obj->$key = new stdClass();
  762. array_to_obj($value, $obj->$key);
  763. } else {
  764. $obj->$key = $value;
  765. }
  766. }
  767. return $obj;
  768. }
  769. // {{{ fetchRow()
  770. /**
  771. * Fetch a row and insert the data into an existing array.
  772. *
  773. * @param int $fetchmode how the array data should be indexed
  774. * @param int $rownum number of the row where the data can be found
  775. * @return int data array on success, a MDB2 error on failure
  776. * @access public
  777. */
  778. function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null)
  779. {
  780. if (!$this->result) {
  781. return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, 'no valid statement given', __FUNCTION__);
  782. }
  783. if (($this->limit && $this->rownum >= $this->limit) || ($this->cursor >= $this->rowcnt || $this->rowcnt == 0)) {
  784. return null;
  785. }
  786. if (null !== $rownum) {
  787. $seek = $this->seek($rownum);
  788. if (MDB2::isError($seek)) {
  789. return $seek;
  790. }
  791. }
  792. if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
  793. $fetchmode = $this->db->fetchmode;
  794. }
  795. $row = false;
  796. $arrNum = array();
  797. if ($fetchmode == MDB2_FETCHMODE_ORDERED) {
  798. foreach ($this->rows[$this->cursor] as $key=>$value) {
  799. $arrNum[] = $value;
  800. }
  801. }
  802. switch($fetchmode) {
  803. case MDB2_FETCHMODE_ASSOC:
  804. $row = $this->rows[$this->cursor];
  805. break;
  806. case MDB2_FETCHMODE_ORDERED:
  807. $row = $arrNum;
  808. break;
  809. case MDB2_FETCHMODE_OBJECT:
  810. $o = new $this->db->options['fetch_class'];
  811. $row = $this->array_to_obj($this->rows[$this->cursor], $o);
  812. break;
  813. }
  814. $this->cursor++;
  815. /*
  816. if ($fetchmode == MDB2_FETCHMODE_OBJECT) {
  817. $row = sqlsrv_fetch_object($this->result,$this->db->options['fetch_class']);
  818. } else {
  819. switch($fetchmode) {
  820. case MDB2_FETCHMODE_ASSOC: $fetchmode = SQLSRV_FETCH_ASSOC; break;
  821. case MDB2_FETCHMODE_ORDERED: $fetchmode = SQLSRV_FETCH_NUMERIC; break;
  822. case MDB2_FETCHMODE_DEFAULT:
  823. default:
  824. $fetchmode = SQLSRV_FETCH_BOTH;
  825. }
  826. $row = sqlsrv_fetch_array($this->result,$fetchmode);
  827. }
  828. foreach ($row as $key=>$value) {
  829. if (is_object($value) && method_exists($value, 'format')) {//is DateTime object
  830. $row[$key] = $value->format("Y-m-d H:i:s");
  831. }
  832. }*/
  833. /*if ($fetchmode == MDB2_FETCHMODE_DEFAULT) {
  834. $fetchmode = $this->db->fetchmode;
  835. }*/
  836. if ($fetchmode == MDB2_FETCHMODE_ASSOC && is_array($row) && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
  837. $row = array_change_key_case($row, $this->db->options['field_case']);
  838. }
  839. if (!$row) {
  840. if (false === $this->result) {
  841. $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
  842. 'resultset has already been freed', __FUNCTION__);
  843. return $err;
  844. }
  845. return null;
  846. }
  847. $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL;
  848. $rtrim = false;
  849. if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) {
  850. if (empty($this->types)) {
  851. $mode += MDB2_PORTABILITY_RTRIM;
  852. } else {
  853. $rtrim = true;
  854. }
  855. }
  856. if ($mode) {
  857. $this->db->_fixResultArrayValues($row, $mode);
  858. }
  859. if ( ( $fetchmode != MDB2_FETCHMODE_ASSOC
  860. && $fetchmode != MDB2_FETCHMODE_OBJECT)
  861. && !empty($this->types)
  862. ) {
  863. $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim);
  864. } elseif (($fetchmode == MDB2_FETCHMODE_ASSOC
  865. || $fetchmode == MDB2_FETCHMODE_OBJECT)
  866. && !empty($this->types_assoc)
  867. ) {
  868. $row = $this->db->datatype->convertResultRow($this->types_assoc, $row, $rtrim);
  869. }
  870. if (!empty($this->values)) {
  871. $this->_assignBindColumns($row);
  872. }
  873. ++$this->rownum;
  874. return $row;
  875. }
  876. // }}}
  877. // {{{ _getColumnNames()
  878. /**
  879. * Retrieve the names of columns returned by the DBMS in a query result.
  880. *
  881. * @return mixed Array variable that holds the names of columns as keys
  882. * or an MDB2 error on failure.
  883. * Some DBMS may not return any columns when the result set
  884. * does not contain any rows.
  885. * @access private
  886. */
  887. function _getColumnNames()
  888. {
  889. if (!$this->result) {
  890. return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, 'no valid statement given', __FUNCTION__);
  891. }
  892. $columns = array();
  893. foreach ($this->fieldMeta as $n => $col) {
  894. $columns[$col['Name']] = $n;
  895. }
  896. if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
  897. $columns = array_change_key_case($columns, $this->db->options['field_case']);
  898. }
  899. return $columns;
  900. }
  901. // }}}
  902. // {{{ numCols()
  903. /**
  904. * Count the number of columns returned by the DBMS in a query result.
  905. *
  906. * @return mixed integer value with the number of columns, a MDB2 error
  907. * on failure
  908. * @access public
  909. */
  910. function numCols()
  911. {
  912. if (!$this->result) {
  913. return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, 'no valid statement given', __FUNCTION__);
  914. }
  915. $cols = $this->numFields;
  916. if (!$cols) {
  917. if (false === $this->result) {
  918. return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
  919. 'resultset has already been freed', __FUNCTION__);
  920. }
  921. if (null === $this->result) {
  922. return count($this->types);
  923. }
  924. return $this->db->raiseError(null, null, null,
  925. 'Could not get column count', __FUNCTION__);
  926. }
  927. return $cols;
  928. }
  929. // }}}
  930. // {{{ nextResult()
  931. /**
  932. * Move the internal result pointer to the next available result
  933. *
  934. * @return true on success, false if there is no more result set or an error object on failure
  935. * @access public
  936. */
  937. function nextResult()
  938. {
  939. if (false === $this->result) {
  940. return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null,
  941. 'resultset has already been freed', __FUNCTION__);
  942. }
  943. if (null === $this->result) {
  944. return false;
  945. }
  946. $ret = sqlsrv_next_result($this->result);
  947. if ($ret) {
  948. $this->cursor = 0;
  949. $this->rows = array();
  950. $this->numFields = sqlsrv_num_fields($this->result);
  951. $this->fieldMeta = sqlsrv_field_metadata($this->result);
  952. $this->numRowsAffected = sqlsrv_rows_affected($this->result);
  953. while ($row = sqlsrv_fetch_array($this->result, SQLSRV_FETCH_ASSOC)) {
  954. if ($row !== null) {
  955. if ($this->offset && $this->offset_count < $this->offset) {
  956. $this->offset_count++;
  957. continue;
  958. }
  959. foreach ($row as $k => $v) {
  960. if (is_object($v) && method_exists($v, 'format')) {//DateTime Object
  961. //$v->setTimezone(new DateTimeZone('GMT'));//TS_ISO_8601 with a trailing 'Z' is GMT
  962. $row[$k] = $v->format("Y-m-d H:i:s");
  963. }
  964. }
  965. $this->rows[] = $row;//read results into memory, cursors are not supported
  966. }
  967. }
  968. $this->rowcnt = count($this->rows);
  969. }
  970. return $ret;
  971. }
  972. // }}}
  973. // {{{ free()
  974. /**
  975. * Free the internal resources associated with $result.
  976. *
  977. * @return boolean true on success, false if $result is invalid
  978. * @access public
  979. */
  980. function free()
  981. {
  982. if (is_resource($this->result) && $this->db->connection) {
  983. if (!@sqlsrv_free_stmt($this->result)) {
  984. return $this->db->raiseError(null, null, null,
  985. 'Could not free result', __FUNCTION__);
  986. }
  987. }
  988. $this->result = false;
  989. return MDB2_OK;
  990. }
  991. // }}}
  992. // {{{ function rowCount()
  993. /**
  994. * Returns the actual row number that was last fetched (count from 0)
  995. * @return int
  996. *
  997. * @access public
  998. */
  999. function rowCount()
  1000. {
  1001. return $this->cursor;
  1002. }
  1003. // }}}
  1004. // {{{ function numRows()
  1005. /**
  1006. * Returns the number of rows in a result object
  1007. *
  1008. * @return mixed MDB2 Error Object or the number of rows
  1009. *
  1010. * @access public
  1011. */
  1012. function numRows()
  1013. {
  1014. return $this->rowcnt;
  1015. }
  1016. // }}}
  1017. // {{{ function seek($rownum = 0)
  1018. /**
  1019. * Seek to a specific row in a result set
  1020. *
  1021. * @param int number of the row where the data can be found
  1022. *
  1023. * @return mixed MDB2_OK on success, a MDB2 error on failure
  1024. *
  1025. * @access public
  1026. */
  1027. function seek($rownum = 0)
  1028. {
  1029. $this->cursor = min($rownum, $this->rowcnt);
  1030. return MDB2_OK;
  1031. }
  1032. // }}}
  1033. }
  1034. // }}}
  1035. // {{{ class MDB2_BufferedResult_mssql
  1036. /**
  1037. * MDB2 MSSQL Server buffered result driver
  1038. *
  1039. * @package MDB2
  1040. * @category Database
  1041. * @author Frank M. Kromann <frank@kromann.info>
  1042. */
  1043. class MDB2_BufferedResult_sqlsrv extends MDB2_Result_sqlsrv
  1044. {
  1045. // {{{ valid()
  1046. /**
  1047. * Check if the end of the result set has been reached
  1048. *
  1049. * @return mixed true or false on sucess, a MDB2 error on failure
  1050. * @access public
  1051. */
  1052. function valid()
  1053. {
  1054. $numrows = $this->numRows();
  1055. if (MDB2::isError($numrows)) {
  1056. return $numrows;
  1057. }
  1058. return $this->rownum < ($numrows - 1);
  1059. }
  1060. // }}}
  1061. }
  1062. // }}}
  1063. // {{{ MDB2_Statement_mssql
  1064. /**
  1065. * MDB2 MSSQL Server statement driver
  1066. *
  1067. * @package MDB2
  1068. * @category Database
  1069. * @author Frank M. Kromann <frank@kromann.info>
  1070. */
  1071. class MDB2_Statement_sqlsrv extends MDB2_Statement_Common
  1072. {
  1073. }
  1074. // }}}
  1075. ?>