Source for file MySQLDatabase.class.php

Documentation is available at MySQLDatabase.class.php

  1. <?php
  2. /**
  3. * @package harmoni.dbc.mysql
  4. *
  5. * @copyright Copyright &copy; 2005, Middlebury College
  6. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
  7. *
  8. * @version $Id: MySQLDatabase.class.php,v 1.37 2007/09/05 21:39:00 adamfranco Exp $
  9. */
  10. require_once(HARMONI."DBHandler/Database.abstract.php");
  11. require_once(HARMONI."DBHandler/MySQL/MySQLSelectQueryResult.class.php");
  12. require_once(HARMONI."DBHandler/MySQL/MySQLInsertQueryResult.class.php");
  13. require_once(HARMONI."DBHandler/MySQL/MySQLUpdateQueryResult.class.php");
  14. require_once(HARMONI."DBHandler/MySQL/MySQLDeleteQueryResult.class.php");
  15. require_once(HARMONI."DBHandler/MySQL/MySQLGenericQueryResult.class.php");
  16. require_once(HARMONI."DBHandler/MySQL/MySQL_SQLGenerator.class.php");
  17.  
  18. /**
  19. * A MySQLDatabase class provides the tools to connect, query, etc., a MySQL database.
  20. * MySQL (at least as of 4.0.17) does not support nested
  21. * transations. Begining a transaction after one is started will commit the
  22. * previous transaction. This is pretty stupid behavior, so this class maintains
  23. * a count of begin and commit calls and only runs the outer begin/commit/rollback
  24. * statements. This allows applications coded for PostgreSQL/Oracle-style nested
  25. * transactions to operate in MySQL.
  26. *
  27. *
  28. * @package harmoni.dbc.mysql
  29. *
  30. * @copyright Copyright &copy; 2005, Middlebury College
  31. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
  32. *
  33. * @version $Id: MySQLDatabase.class.php,v 1.37 2007/09/05 21:39:00 adamfranco Exp $
  34. */
  35. class MySQLDatabase
  36. extends DatabaseAbstract
  37. {
  38.  
  39. /**
  40. * The hostname of the database, i.e. myserver.mydomain.edu.
  41. * The hostname of the database, i.e. myserver.mydomain.edu.
  42. * @var string $_dbHost The hostname of the database, i.e. myserver.mydomain.edu.
  43. * @access private
  44. */
  45. var $_dbHost;
  46. /**
  47. * The name of the default database to use.
  48. * The name of the default database to use.
  49. * @var string $_dbName The name of the default database to use.
  50. * @access private
  51. */
  52. var $_dbName;
  53. /**
  54. * The username with which to connect to the database.
  55. * The username with which to connect to the database.
  56. * @var string $_dbUser The username with which to connect to the database.
  57. * @access private
  58. */
  59. var $_dbUser;
  60. /**
  61. * The password for $_dbUser with which to connect to the database.
  62. * The password for $_dbUser with which to connect to the database.
  63. * @var string $_dbPass The password for $_dbUser with which to connect to the database.
  64. * @access private
  65. */
  66. var $_dbPass;
  67. /**
  68. * Stores the current connection's link identifier.
  69. * If a connection is open, this stores the connection's identifier. Otherwise,
  70. * it stores FALSE.
  71. * @var mixed $_linkId If a connection is open, this stores the connection's identifier. Otherwise,
  72. * it stores FALSE.
  73. * @access private
  74. */
  75. var $_linkId;
  76. /**
  77. * Persistant connections can not be closed or have a new link forced, so
  78. * this property is necessary for determining whether or not a mysql_select_db()
  79. * is needed before queries to ensure that the proper database is selected.
  80. *
  81. * @var boolean $_isConnectionPersistant;
  82. * @access private
  83. * @since 8/18/05
  84. */
  85. var $_isConnectionPersistant;
  86. /**
  87. * The total number of successful queries executed since the last call to connect().
  88. * The total number of failed queries executed since the last call to connect().
  89. * @var integer $_successfulQueries The total number of failed queries executed since the last call to connect().
  90. * @access private
  91. */
  92. var $_successfulQueries;
  93. /**
  94. * The total number of failed queries executed since the last call to connect().
  95. * The total number of failed queries executed since the last call to connect().
  96. * @var integer $_failedQueries The total number of failed queries executed since the last call to connect().
  97. * @access private
  98. */
  99. var $_failedQueries;
  100. /**
  101. * TRUE if this database supports transactions.
  102. * @var boolean $_supportsTransactions
  103. * @access private
  104. */
  105. var $_supportsTransactions = null;
  106.  
  107. /**
  108. * Creates a new database connection.
  109. * @param string $dbHost The hostname for the database, i.e. myserver.mydomain.edu.
  110. * @param string $dbName The name of the default database to use.
  111. * @param string $dbUser The username with which to connect to the database.
  112. * @param string $dbPass The password for $_dbUser with which to connect to the database.
  113. * @return integer $dbIndex The index of the new database
  114. * @access public
  115. */
  116. function MySQLDatabase($dbHost, $dbName, $dbUser, $dbPass) {
  117. // ** parameter validation
  118. $stringRule = StringValidatorRule::getRule();
  119. ArgumentValidator::validate($dbHost, $stringRule, true);
  120. ArgumentValidator::validate($dbName, $stringRule, true);
  121. ArgumentValidator::validate($dbUser, $stringRule, true);
  122. ArgumentValidator::validate($dbPass, $stringRule, true);
  123. // ** end of parameter validation
  124.  
  125. $this->_dbHost = $dbHost;
  126. $this->_dbName = $dbName;
  127. $this->_dbUser = $dbUser;
  128. $this->_dbPass = $dbPass;
  129. $this->_linkId = false;
  130. $this->_isConnectionPersistant = NULL;
  131. $this->_successfulQueries = 0;
  132. $this->_failedQueries = 0;
  133. $this->_startedTransactions = 0;
  134. }
  135. /**
  136. * Returns a short string name for this database type. Example: 'MySQL'
  137. * @access public
  138. * @return string
  139. */
  140. function getStringName() {
  141. return "MySQL";
  142. }
  143.  
  144. /**
  145. * Returns a list of the tables that exist in the currently connected database.
  146. * @return array
  147. * @access public
  148. */
  149. function getTableList() {
  150. $query = new GenericSQLQuery();
  151. $query->addSQLQuery("SHOW TABLES");
  152. $r =$this->query($query);
  153. $res =$r->returnAsSelectQueryResult();
  154. $list = array();
  155. while($res->hasMoreRows()) {
  156. $list[] = $res->field(0);
  157. $res->advanceRow();
  158. }
  159. $res->free();
  160.  
  161. return $list;
  162. }
  163. /**
  164. * Connects to the database.
  165. * Connects to the database.
  166. * @access public
  167. * @return mixed The connection's link identifier, if successful; False, otherwise.
  168. */
  169. function connect() {
  170. // if connected, need to disconnect first
  171. if ($this->isConnected())
  172. return false;
  173. // attempt to connect
  174. // The final TRUE parameter forces a new connection, preventing the need
  175. // for calling mysql_select_db() before every query to ensure the proper
  176. // database is selected
  177. $linkId = mysql_connect($this->_dbHost, $this->_dbUser, $this->_dbPass, true);
  178. $this->_isConnectionPersistant = false;
  179. // see if successful
  180. if ($linkId) {
  181. // reset the query counters
  182. $this->_successfulQueries = 0;
  183. $this->_failedQueries = 0;
  184. // attempt to select the default database;
  185. // if failure, not a big deal, because at this point we are connected
  186. if (!mysql_select_db($this->_dbName, $linkId))
  187. throw new ConnectionDatabaseException($this->getConnectionErrorInfo()."Cannot select database, ".$this->_dbName." : ".mysql_error($linkId));
  188.  
  189. $this->_linkId = $linkId;
  190. return $linkId;
  191. }
  192. else {
  193. $this->_linkId = false;
  194. throw new ConnectionDatabaseException($this->getConnectionErrorInfo()."Cannot connect to database.");
  195.  
  196. return false;
  197. }
  198. }
  199.  
  200. /**
  201. * Makes a persistent database connection.
  202. * Makes a persistent database connection.
  203. * @access public
  204. * @return mixed The connection's link identifier, if successful; False, otherwise.
  205. */
  206. function pConnect() {
  207. // if connected, attempt to reconnect
  208. if ($this->isConnected()) $this->disconnect();
  209. // attempt to connect
  210. $linkId = mysql_pconnect($this->_dbHost, $this->_dbUser, $this->_dbPass);
  211. $this->_isConnectionPersistant = true;
  212. // see if successful
  213. if ($linkId) {
  214. // reset the query counters
  215. $this->_successfulQueries = 0;
  216. $this->_failedQueries = 0;
  217.  
  218. // attempt to select the default database;
  219. // if failure, not a big deal, because at this point we are connected
  220. if (!mysql_select_db($this->_dbName, $linkId))
  221. throw new ConnectionDatabaseException($this->getConnectionErrorInfo()."Cannot select database, ".$this->_dbName." : ".mysql_error($linkId));
  222.  
  223. $this->_linkId = $linkId;
  224. return $linkId;
  225. }
  226. else {
  227. $this->_linkId = false;
  228. throw new ConnectionDatabaseException($this->getConnectionErrorInfo()."Cannot connect to database: ".mysql_error());
  229. return false;
  230. }
  231. }
  232.  
  233.  
  234. /**
  235. * Executes an SQL query.
  236. * Executes an SQL query. The method is passed a query object, which it
  237. * converts to a SQL query string using the appropriate SQLGenerator
  238. * object.
  239. * @access public
  240. * @param object Query $query A Query object from which the SQL query will be constructed.
  241. * @return mixed The appropriate QueryResult object. If the query failed, it would
  242. * return NULL.
  243. */
  244. function query(Query $query) {
  245. // static $time = 0;
  246. // do not attempt, to query, if not connected
  247. if (!$this->isConnected()) {
  248. throw new ConnectionDatabaseException("Attempted to query but there was no database connection.");
  249. return false;
  250. }
  251. // generate the SQL query string
  252. // $t = new Timer();
  253. // $t->start();
  254. $queryString = MySQL_SQLGenerator::generateSQLQuery($query);
  255. // $t->end();
  256. // $time += $t->printTime();
  257. // echo $time;
  258. // echo "<br /> : ";
  259.  
  260. // attempt to run the query
  261. $resourceId = $this->_query($queryString);
  262.  
  263. // if query was unsuccessful, return a null QueryResult object
  264. // if ($resourceId === false)
  265. // throwError( new Error("The query had errors: \n".$queryString, "DBHandler", true));
  266. //
  267. // create the appropriate QueryResult object
  268. switch($query->getType()) {
  269. case INSERT :
  270. $result = new MySQLInsertQueryResult($this->_linkId);
  271. break;
  272. case UPDATE :
  273. $result = new MySQLUpdateQueryResult($this->_linkId);
  274. break;
  275. case DELETE :
  276. $result = new MySQLDeleteQueryResult($this->_linkId);
  277. break;
  278. case SELECT :
  279. $result = new MySQLSelectQueryResult($resourceId, $this->_linkId);
  280. break;
  281. case GENERIC :
  282. $result = new MySQLGenericQueryResult($resourceId, $this->_linkId);
  283. break;
  284. default:
  285. throw new DatabaseException("Unsupported query type.");
  286. } // switch
  287.  
  288. return $result;
  289. }
  290. /**
  291. * Answer the string SQL for the query
  292. *
  293. * @param object $query
  294. * @return string
  295. * @access public
  296. * @since 11/14/06
  297. */
  298. function generateSQL ($query) {
  299. return MySQL_SQLGenerator::generateSQLQuery($query);
  300. }
  301.  
  302. /**
  303. * Executes an SQL query.
  304. * Executes an SQL query.
  305. * @access private
  306. * @param string The SQL query string.
  307. * @return mixed For a SELECT statement, a resource identifier, if
  308. * successful; For INSERT, DELETE, UPDATE statements, TRUE if successful;
  309. * for all: FALSE, if not successful.
  310. */
  311. function _query($query) {
  312. // do not attempt to query, if not connected
  313. if (!$this->isConnected()) {
  314. throw new ConnectionDatabaseException("Attempted to query but there was no database connection.");
  315. return false;
  316. }
  317. if (is_array($query))
  318. $queries = $query;
  319. else if (is_string($query))
  320. $queries = array($query);
  321. // If we have a persistant connection, it might be shared with other
  322. // databases, so make sure our database is selected.
  323. if ($this->_isConnectionPersistant == true) {
  324. if (!mysql_select_db($this->_dbName, $this->_linkId))
  325. throw new ConnectionDatabaseException("Cannot select database, ".$this->_dbName." : ".mysql_error($this->_linkId));
  326. }
  327. foreach ($queries as $q) {
  328. // attempt to execute the query
  329. $resourceId = mysql_query($q, $this->_linkId);
  330. debug::output("<pre>Query: <div>".$query."</div>Result: $resourceId</pre>", 1, "DBHandler");
  331. if ($resourceId === false) {
  332. $this->_failedQueries++;
  333. switch (mysql_errno($this->_linkId)) {
  334. // Duplicate Key
  335. case 1022:
  336. throw new DuplucateKeyDatabaseException("MySQL Error: ".mysql_error($this->_linkId), mysql_errno($this->_linkId));
  337. // max_allowed_packet
  338. case 1153: // Got a packet bigger than 'max_allowed_packet' bytes
  339. case 1162: // Result string is longer than 'max_allowed_packet' bytes
  340. case 1301: // Result of %s() was larger than max_allowed_packet (%ld) - truncated
  341. $size = ByteSize::withValue(strlen($query));
  342. throw new QuerySizeDatabaseException("MySQL Error: ".mysql_error($this->_linkId)." (Query Size: ".$size->asString().")", mysql_errno($this->_linkId));
  343. default:
  344. throw new QueryDatabaseException("MySQL Error: ".mysql_error($this->_linkId), mysql_errno($this->_linkId));
  345. }
  346. }
  347. else
  348. $this->_successfulQueries++;
  349. }
  350. return $resourceId;
  351. }
  352.  
  353.  
  354.  
  355. /**
  356. * Disconnects from the database.
  357. * Disconnects from the database.
  358. * @access public
  359. * @return boolean True, if successful; False, otherwise.
  360. */
  361. function disconnect() {
  362. // do not disconnect, if not connected
  363. if (!$this->isConnected())
  364. return false;
  365. // attempt to disconnect
  366. $isSuccessful = mysql_close($this->_linkId);
  367. if ($isSuccessful) {
  368. $this->_linkId = false;
  369. $this->_isConnectionPersistant = NULL;
  370. }
  371. return $isSuccessful;
  372. }
  373.  
  374.  
  375. /**
  376. * Indicates whether there is an open connection to the database.
  377. * Indicates whether there is an open connection to the database.
  378. * @access public
  379. * @return boolean True if there is an open connection to the database; False, otherwise.
  380. */
  381. function isConnected() {
  382. $isConnected = ($this->_linkId !== false);
  383. return $isConnected;
  384. }
  385.  
  386.  
  387.  
  388.  
  389. /**
  390. * Returns the total number of successful queries executed since the last call to connect().
  391. * Returns the total number of successful queries executed since the last call to connect().
  392. * @access public
  393. * @return integer The total number of successful queries executed since the last call to connect().
  394. ***/
  395. function getNumberSuccessfulQueries() {
  396. return $this->_successfulQueries;
  397. }
  398. /**
  399. * Returns the total number of failed queries executed since the last call to connect().
  400. * Returns the total number of failed queries executed since the last call to connect().
  401. * @access public
  402. * @return integer The total number of failed queries executed since the last call to connect().
  403. ***/
  404. function getNumberFailedQueries() {
  405. return $this->_failedQueries;
  406. }
  407.  
  408. /**
  409. * This method selects the default database to use in queries.
  410. * @access public
  411. * @param string database The name of the default database.
  412. * @return boolean True, if successful; False, otherwise.
  413. */
  414. function selectDatabase($database) {
  415. // ** parameter validation
  416. $stringRule = StringValidatorRule::getRule();
  417. ArgumentValidator::validate($database, $stringRule, true);
  418. // ** end of parameter validation
  419. $this->_dbName = $database;
  420. return mysql_select_db($database, $this->_linkId);
  421. }
  422.  
  423.  
  424. /**
  425. * Converts a DateAndTime object to a proper datetime/timestamp/time representation
  426. * for this Database.
  427. *
  428. * The easiest way to convert is to create an integer (or a string,
  429. * choose which one you think is better, MySQL accepts both, but make
  430. * sure to document) in the following format: YYYYMMDDHHMMSS.
  431. * You can pass this to a MySQL datetime or timestamp column types
  432. * and it gets parsed automatically by MySQL.
  433. *
  434. * @access public
  435. * @param ref object DateAndTime The DateAndTime object to convert.
  436. * @return mixed A proper datetime/timestamp/time representation for this Database.
  437. */
  438. function toDBDate(DateAndTime $dateAndTime) {
  439. $dt =$dateAndTime->asDateAndTime();
  440. $string = sprintf("%s%02d%02d%02d%02d%02d", $dt->year(),
  441. $dt->month(), $dt->dayOfMonth(),
  442. $dt->hour24(), $dt->minute(),
  443. $dt->second());
  444. return "'".$string."'";
  445. }
  446. /**
  447. * Converts a database datetime/timestamp/time value (that has been fetched
  448. * from the db) to a DateAndTime object.
  449. *
  450. * Depending whether the value was fecthed from a datetime, date or
  451. * timestamp column, $value could have the following two formats:
  452. * 'YYYY-MM-DD HH:MM:SS' for datetime
  453. * 'YYYY-MM-DD' for date
  454. * For a timestamp, $value could be any of the following depending on
  455. * the column size.
  456. * TIMESTAMP(14) YYYYMMDDHHMMSS
  457. * TIMESTAMP(12) YYMMDDHHMMSS - NOT SUPPORTED
  458. * TIMESTAMP(10) YYMMDDHHMM - NOT SUPPORTED
  459. * TIMESTAMP(8) YYYYMMDD
  460. * TIMESTAMP(6) YYMMDD - NOT SUPPORTED
  461. * TIMESTAMP(4) YYMM - NOT SUPPORTED
  462. * TIMESTAMP(2) YY - NOT SUPPORTED
  463. *
  464. * From MySQL version 4.1, TIMESTAMP is returned as
  465. * a string with the format 'YYYY-MM-DD HH:MM:SS' and different timestamp
  466. * lengths are no longer supported.
  467. *
  468. * WARNING: Due to the ambiguity of 2-digit years, timestamp formats that
  469. * use 2-digit years are not supported.
  470. *
  471. * @access public
  472. * @param mixed A database datetime/timestamp/time value (that has been fetched
  473. * from the db).
  474. * @return ref object The DateAndTime object.
  475. */
  476. function fromDBDate($value) {
  477. if (in_array($value, array(NULL, '', '0000-00-00 00:00:00')))
  478. $obj = null;
  479. else
  480. $obj = DateAndTime::fromString($value);
  481. return $obj;
  482. }
  483. /**
  484. * Return TRUE if this database supports transactions.
  485. *
  486. * @return boolean
  487. * @access public
  488. * @since 3/9/05
  489. */
  490. function supportsTransactions () {
  491. if ($this->_supportsTransactions == NULL) {
  492. $versionString = mysql_get_server_info($this->_linkId);
  493. if(!preg_match("/^([0-9]+).([0-9]+).([0-9]+)/", $versionString, $matches))
  494. $this->_supportsTransactions = FALSE;
  495. else {
  496. $primaryVersion = $matches[1];
  497. $secondaryVersion = $matches[2];
  498. $terciaryVersion = $matches[3];
  499. if ($primaryVersion >= 4
  500. && ($secondaryVersion > 0
  501. || $terciaryVersion >= 11))
  502. {
  503. $this->_supportsTransactions = TRUE;
  504. } else {
  505. $this->_supportsTransactions = FALSE;
  506. }
  507. }
  508. }
  509. return $this->_supportsTransactions;
  510. }
  511. /**
  512. * Begin a transaction.
  513. *
  514. * @return void
  515. * @access public
  516. * @since 3/9/05
  517. */
  518. function beginTransaction () {
  519. if ($this->_startedTransactions < 0 )
  520. throw new TransactionException("Error: Negative number of BEGIN statements.");
  521.  
  522. if ($this->supportsTransactions()
  523. && $this->_startedTransactions == 0)
  524. {
  525. $this->_query("START TRANSACTION");
  526. }
  527. $this->_startedTransactions++;
  528. }
  529. /**
  530. * Commit a transaction. This will roll-back changes if errors occured in the
  531. * transaction block.
  532. *
  533. * @return void
  534. * @access public
  535. * @since 3/9/05
  536. */
  537. function commitTransaction () {
  538. if ($this->_startedTransactions < 1 )
  539. throw new TransactionException("Error: More COMMIT/ROLLBACK statements than BEGIN statements.");
  540. if ($this->supportsTransactions()
  541. && $this->_startedTransactions == 1)
  542. {
  543. $this->_query("COMMIT");
  544. }
  545. $this->_startedTransactions--;
  546. }
  547. /**
  548. * Roll-back a transaction manually instead of committing
  549. *
  550. * @return void
  551. * @access public
  552. * @since 3/9/05
  553. */
  554. function rollbackTransaction () {
  555. if ($this->_startedTransactions < 1 )
  556. throw new TransactionException("Error: More COMMIT/ROLLBACK statements than BEGIN statements.");
  557. if ($this->supportsTransactions()) {
  558. // Roll-back first, to undo changes.
  559. $this->_query("ROLLBACK");
  560. // If rollback is called inside a nested set of transactions, then the
  561. // resulting state of the the database is undefined.
  562. if ($this->_startedTransactions > 1) {
  563. throw new TransactionException("Error: Unsuported attempt to roll-back a nested transaction. Nested transaction support for MySQL removes all but the outside begin/commit/rollback statements. Rolling-back from an interior transaction would leave the database in an undefined state.");
  564. }
  565. }
  566. $this->_startedTransactions--;
  567. }
  568. }
  569.  
  570. ?>

Documentation generated on Wed, 19 Sep 2007 10:25:10 -0400 by phpDocumentor 1.3.0RC3