odbc.php 36 KB

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