mssql.php 39 KB

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