PageRenderTime 47ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/core/src/main/php/rdbms/mysql/MySQLConnection.class.php

http://github.com/xp-framework/xp-framework
PHP | 259 lines | 140 code | 27 blank | 92 comment | 30 complexity | ac636cac3884048641e7976f7661eaad MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. uses(
  7. 'rdbms.DBConnection',
  8. 'rdbms.mysql.MySQLResultSet',
  9. 'rdbms.Transaction',
  10. 'rdbms.StatementFormatter',
  11. 'rdbms.mysql.MysqlDialect'
  12. );
  13. /**
  14. * Connection to MySQL Databases
  15. *
  16. * @see http://mysql.org/
  17. * @ext mysql
  18. * @test xp://net.xp_framework.unittest.rdbms.TokenizerTest
  19. * @test xp://net.xp_framework.unittest.rdbms.DBTest
  20. * @test net.xp_framework.unittest.rdbms.integration.MySQLIntegrationTest
  21. * @purpose Database connection
  22. */
  23. class MySQLConnection extends DBConnection {
  24. static function __static() {
  25. DriverManager::register('mysql+std', new XPClass(__CLASS__));
  26. }
  27. /**
  28. * Constructor
  29. *
  30. * @param rdbms.DSN dsn
  31. */
  32. public function __construct($dsn) {
  33. parent::__construct($dsn);
  34. $this->formatter= new StatementFormatter($this, new MysqlDialect());
  35. }
  36. /**
  37. * Set Timeout
  38. *
  39. * @param int timeout
  40. */
  41. public function setTimeout($timeout) {
  42. ini_set('mysql.connect_timeout', $timeout);
  43. parent::setTimeout($timeout);
  44. }
  45. /**
  46. * Connect
  47. *
  48. * @param bool reconnect default FALSE
  49. * @return bool success
  50. * @throws rdbms.SQLConnectException
  51. */
  52. public function connect($reconnect= FALSE) {
  53. if (is_resource($this->handle)) return TRUE; // Already connected
  54. if (!$reconnect && (FALSE === $this->handle)) return FALSE; // Previously failed connecting
  55. // Connect via local sockets if "." is passed. This will not work on
  56. // Windows with the mysqlnd extension (see PHP bug #48082: "mysql_connect
  57. // does not work with named pipes"). For mysqlnd, we default to mysqlx
  58. // anyways, so this works transparently.
  59. $host= $this->dsn->getHost();
  60. $ini= NULL;
  61. if ('.' === $host) {
  62. $sock= $this->dsn->getProperty('socket', NULL);
  63. if (0 === strncasecmp(PHP_OS, 'Win', 3)) {
  64. $connect= '.';
  65. if (NULL !== $sock) {
  66. $ini= ini_set('mysql.default_socket');
  67. ini_set('mysql.default_socket', substr($sock, 9)); // 9 = strlen("\\\\.\\pipe\\")
  68. }
  69. } else {
  70. $connect= NULL === $sock ? 'localhost' : ':'.$sock;
  71. }
  72. } else if ('localhost' === $host) {
  73. $connect= '127.0.0.1:'.$this->dsn->getPort(3306); // Force TCP/IP
  74. } else {
  75. $connect= $host.':'.$this->dsn->getPort(3306);
  76. }
  77. $this->_obs && $this->notifyObservers(new DBEvent(DBEvent::CONNECT, $reconnect));
  78. if ($this->flags & DB_PERSISTENT) {
  79. $this->handle= mysql_pconnect(
  80. $connect,
  81. $this->dsn->getUser(),
  82. $this->dsn->getPassword()
  83. );
  84. } else {
  85. $this->handle= mysql_connect(
  86. $connect,
  87. $this->dsn->getUser(),
  88. $this->dsn->getPassword(),
  89. ($this->flags & DB_NEWLINK)
  90. );
  91. }
  92. $this->_obs && $this->notifyObservers(new DBEvent(DBEvent::CONNECTED, $reconnect));
  93. $ini && ini_set('mysql.default_socket', $ini);
  94. if (!is_resource($this->handle)) {
  95. $e= new SQLConnectException('#'.mysql_errno().': '.mysql_error(), $this->dsn);
  96. xp::gc(__FILE__);
  97. throw $e;
  98. }
  99. mysql_query('set names LATIN1', $this->handle);
  100. // Figure out sql_mode and update formatter's escaperules accordingly
  101. // - See: http://bugs.mysql.com/bug.php?id=10214
  102. // - Possible values: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
  103. // "modes is a list of different modes separated by comma (,) characters."
  104. $modes= array_flip(explode(',', current(mysql_fetch_row(mysql_query(
  105. "show variables like 'sql_mode'",
  106. $this->handle
  107. )))));
  108. // NO_BACKSLASH_ESCAPES: Disable the use of the backslash character
  109. // (\) as an escape character within strings. With this mode enabled,
  110. // backslash becomes any ordinary character like any other.
  111. // (Implemented in MySQL 5.0.1)
  112. isset($modes['NO_BACKSLASH_ESCAPES']) && $this->formatter->dialect->setEscapeRules(array(
  113. '"' => '""'
  114. ));
  115. return parent::connect();
  116. }
  117. /**
  118. * Disconnect
  119. *
  120. * @return bool success
  121. */
  122. public function close() {
  123. if ($this->handle && $r= mysql_close($this->handle)) {
  124. $this->handle= NULL;
  125. return $r;
  126. }
  127. return FALSE;
  128. }
  129. /**
  130. * Select database
  131. *
  132. * @param string db name of database to select
  133. * @return bool success
  134. * @throws rdbms.SQLStatementFailedException
  135. */
  136. public function selectdb($db) {
  137. if (!mysql_select_db($db, $this->handle)) {
  138. throw new SQLStatementFailedException(
  139. 'Cannot select database: '.mysql_error($this->handle),
  140. 'use '.$db,
  141. mysql_errno($this->handle)
  142. );
  143. }
  144. return TRUE;
  145. }
  146. /**
  147. * Retrieve identity
  148. *
  149. * @return var identity value
  150. */
  151. public function identity($field= NULL) {
  152. $i= $this->query('select last_insert_id() as xp_id')->next('xp_id');
  153. $this->_obs && $this->notifyObservers(new DBEvent(DBEvent::IDENTITY, $i));
  154. return $i;
  155. }
  156. /**
  157. * Retrieve number of affected rows for last query
  158. *
  159. * @return int
  160. */
  161. protected function affectedRows() {
  162. return mysql_affected_rows($this->handle);
  163. }
  164. /**
  165. * Execute any statement
  166. *
  167. * @param string sql
  168. * @param bool buffered default TRUE
  169. * @return rdbms.ResultSet or TRUE if no resultset was created
  170. * @throws rdbms.SQLException
  171. */
  172. protected function query0($sql, $buffered= TRUE) {
  173. if (!is_resource($this->handle)) {
  174. if (!($this->flags & DB_AUTOCONNECT)) throw new SQLStateException('Not connected');
  175. $c= $this->connect();
  176. // Check for subsequent connection errors
  177. if (FALSE === $c) throw new SQLStateException('Previously failed to connect.');
  178. }
  179. if (!$buffered || $this->flags & DB_UNBUFFERED) {
  180. $result= @mysql_unbuffered_query($sql, $this->handle);
  181. } else {
  182. $result= @mysql_query($sql, $this->handle);
  183. }
  184. if (FALSE === $result) {
  185. $code= mysql_errno($this->handle);
  186. $message= 'Statement failed: '.mysql_error($this->handle).' @ '.$this->dsn->getHost();
  187. switch ($code) {
  188. case 2006: // MySQL server has gone away
  189. case 2013: // Lost connection to MySQL server during query
  190. throw new SQLConnectionClosedException('Statement failed: '.$message, $sql, $code);
  191. case 1213: // Deadlock
  192. throw new SQLDeadlockException($message, $sql, $code);
  193. default: // Other error
  194. throw new SQLStatementFailedException($message, $sql, $code);
  195. }
  196. }
  197. return (TRUE === $result
  198. ? $result
  199. : new MySQLResultSet($result, $this->tz)
  200. );
  201. }
  202. /**
  203. * Begin a transaction
  204. *
  205. * @param rdbms.Transaction transaction
  206. * @return rdbms.Transaction
  207. */
  208. public function begin($transaction) {
  209. if (!$this->query('begin')) return FALSE;
  210. $transaction->db= $this;
  211. return $transaction;
  212. }
  213. /**
  214. * Rollback a transaction
  215. *
  216. * @param string name
  217. * @return bool success
  218. */
  219. public function rollback($name) {
  220. return $this->query('rollback');
  221. }
  222. /**
  223. * Commit a transaction
  224. *
  225. * @param string name
  226. * @return bool success
  227. */
  228. public function commit($name) {
  229. return $this->query('commit');
  230. }
  231. }
  232. ?>