PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/system/libraries/drivers/Database/Mysql.php

https://github.com/lmorchard/friendfeedarchiver
PHP | 614 lines | 355 code | 101 blank | 158 comment | 43 complexity | 9ae65de40c603eec7234295c3732575c MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * MySQL Database Driver
  4. *
  5. * $Id: Mysql.php 1931 2008-02-05 22:56:30Z PugFish $
  6. *
  7. * @package Core
  8. * @author Kohana Team
  9. * @copyright (c) 2007-2008 Kohana Team
  10. * @license http://kohanaphp.com/license.html
  11. */
  12. class Database_Mysql_Driver extends Database_Driver {
  13. /**
  14. * Database connection link
  15. */
  16. protected $link;
  17. /**
  18. * Database configuration
  19. */
  20. protected $db_config;
  21. /**
  22. * Sets the config for the class.
  23. *
  24. * @param array database configuration
  25. */
  26. public function __construct($config)
  27. {
  28. $this->db_config = $config;
  29. Log::add('debug', 'MySQL Database Driver Initialized');
  30. }
  31. /**
  32. * Closes the database connection.
  33. */
  34. public function __destruct()
  35. {
  36. is_resource($this->link) and mysql_close($this->link);
  37. }
  38. public function connect()
  39. {
  40. // Check if link already exists
  41. if (is_resource($this->link))
  42. return $this->link;
  43. // Import the connect variables
  44. extract($this->db_config['connection']);
  45. // Persistent connections enabled?
  46. $connect = ($this->db_config['persistent'] == TRUE) ? 'mysql_pconnect' : 'mysql_connect';
  47. // Build the connection info
  48. $host = isset($host) ? $host : $socket;
  49. $port = isset($port) ? ':'.$port : '';
  50. // Make the connection and select the database
  51. if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mysql_select_db($database, $this->link))
  52. {
  53. if ($charset = $this->db_config['character_set'])
  54. {
  55. $this->set_charset($charset);
  56. }
  57. // Clear password after successful connect
  58. $this->config['connection']['pass'] = NULL;
  59. return $this->link;
  60. }
  61. return FALSE;
  62. }
  63. public function query($sql)
  64. {
  65. // Only cache if it's turned on, and only cache if it's not a write statement
  66. if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql))
  67. {
  68. $hash = $this->query_hash($sql);
  69. if ( ! isset(self::$query_cache[$hash]))
  70. {
  71. // Set the cached object
  72. self::$query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
  73. }
  74. // Return the cached query
  75. return self::$query_cache[$hash];
  76. }
  77. return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
  78. }
  79. public function set_charset($charset)
  80. {
  81. $this->query('SET NAMES '.$this->escape_str($charset));
  82. }
  83. public function escape_table($table)
  84. {
  85. if (stripos($table, ' AS ') !== FALSE)
  86. {
  87. // Force 'AS' to uppercase
  88. $table = str_ireplace(' AS ', ' AS ', $table);
  89. // Runs escape_table on both sides of an AS statement
  90. $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
  91. // Re-create the AS statement
  92. return implode(' AS ', $table);
  93. }
  94. return '`'.str_replace('.', '`.`', $table).'`';
  95. }
  96. public function escape_column($column)
  97. {
  98. if (strtolower($column) == 'count(*)' OR $column == '*')
  99. return $column;
  100. // This matches any modifiers we support to SELECT.
  101. if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
  102. {
  103. if (stripos($column, ' AS ') !== FALSE)
  104. {
  105. // Force 'AS' to uppercase
  106. $column = str_ireplace(' AS ', ' AS ', $column);
  107. // Runs escape_column on both sides of an AS statement
  108. $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
  109. // Re-create the AS statement
  110. return implode(' AS ', $column);
  111. }
  112. return preg_replace('/[^.*]+/', '`$0`', $column);
  113. }
  114. $parts = explode(' ', $column);
  115. $column = '';
  116. for ($i = 0, $c = count($parts); $i < $c; $i++)
  117. {
  118. // The column is always last
  119. if ($i == ($c - 1))
  120. {
  121. $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
  122. }
  123. else // otherwise, it's a modifier
  124. {
  125. $column .= $parts[$i].' ';
  126. }
  127. }
  128. return $column;
  129. }
  130. public function regex($field, $match = '', $type = 'AND ', $num_regexs)
  131. {
  132. $prefix = ($num_regexs == 0) ? '' : $type;
  133. return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\'';
  134. }
  135. public function notregex($field, $match = '', $type = 'AND ', $num_regexs)
  136. {
  137. $prefix = $num_regexs == 0 ? '' : $type;
  138. return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\'';
  139. }
  140. public function merge($table, $keys, $values)
  141. {
  142. // Escape the column names
  143. foreach ($keys as $key => $value)
  144. {
  145. $keys[$key] = $this->escape_column($value);
  146. }
  147. return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
  148. }
  149. public function limit($limit, $offset = 0)
  150. {
  151. return 'LIMIT '.$offset.', '.$limit;
  152. }
  153. public function stmt_prepare($sql = '')
  154. {
  155. throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
  156. }
  157. public function compile_select($database)
  158. {
  159. $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
  160. $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
  161. if (count($database['from']) > 0)
  162. {
  163. // Escape the tables
  164. $froms = array();
  165. foreach ($database['from'] as $from)
  166. $froms[] = $this->escape_column($from);
  167. $sql .= "\nFROM ";
  168. $sql .= implode(', ', $froms);
  169. }
  170. if (count($database['join']) > 0)
  171. {
  172. $sql .= ' '.implode("\n", $database['join']);
  173. }
  174. if (count($database['where']) > 0)
  175. {
  176. $sql .= "\nWHERE ";
  177. }
  178. $sql .= implode("\n", $database['where']);
  179. if (count($database['groupby']) > 0)
  180. {
  181. $sql .= "\nGROUP BY ";
  182. $sql .= implode(', ', $database['groupby']);
  183. }
  184. if (count($database['having']) > 0)
  185. {
  186. $sql .= "\nHAVING ";
  187. $sql .= implode("\n", $database['having']);
  188. }
  189. if (count($database['orderby']) > 0)
  190. {
  191. $sql .= "\nORDER BY ";
  192. $sql .= implode(', ', $database['orderby']);
  193. }
  194. if (is_numeric($database['limit']))
  195. {
  196. $sql .= "\n";
  197. $sql .= $this->limit($database['limit'], $database['offset']);
  198. }
  199. return $sql;
  200. }
  201. public function escape_str($str)
  202. {
  203. is_resource($this->link) or $this->connect();
  204. return mysql_real_escape_string($str, $this->link);
  205. }
  206. public function list_tables()
  207. {
  208. $sql = 'SHOW TABLES FROM `'.$this->db_config['connection']['database'].'`';
  209. $result = $this->query($sql)->result(FALSE, MYSQL_ASSOC);
  210. $retval = array();
  211. foreach($result as $row)
  212. {
  213. $retval[] = current($row);
  214. }
  215. return $retval;
  216. }
  217. public function show_error()
  218. {
  219. return mysql_error($this->link);
  220. }
  221. public function list_fields($table)
  222. {
  223. static $tables;
  224. if (empty($tables[$table]))
  225. {
  226. foreach($this->field_data($table) as $row)
  227. {
  228. // Make an associative array
  229. $tables[$table][$row->Field] = $this->sql_type($row->Type);
  230. }
  231. }
  232. return $tables[$table];
  233. }
  234. public function field_data($table)
  235. {
  236. $query = mysql_query('SHOW COLUMNS FROM '.$this->escape_table($table), $this->link);
  237. $table = array();
  238. while ($row = mysql_fetch_object($query))
  239. {
  240. $table[] = $row;
  241. }
  242. return $table;
  243. }
  244. } // End Database_Mysql_Driver Class
  245. /**
  246. * MySQL result.
  247. */
  248. class Mysql_Result implements Database_Result, ArrayAccess, Iterator, Countable {
  249. /**
  250. * Result resource
  251. */
  252. protected $result = NULL;
  253. /**
  254. * Total rows
  255. */
  256. protected $total_rows = FALSE;
  257. /**
  258. * Current row
  259. */
  260. protected $current_row = FALSE;
  261. /**
  262. * Last insterted ID
  263. */
  264. protected $insert_id = FALSE;
  265. /**
  266. * Fetch type
  267. */
  268. protected $fetch_type = 'mysql_fetch_object';
  269. /**
  270. * Return type
  271. */
  272. protected $return_type = MYSQL_ASSOC;
  273. /**
  274. * Sets up the result variables.
  275. *
  276. * @param resource query result
  277. * @param resource database link
  278. * @param boolean return objects or arrays
  279. * @param string SQL query that was run
  280. */
  281. public function __construct($result, $link, $object = TRUE, $sql)
  282. {
  283. $this->result = $result;
  284. // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
  285. if (is_resource($result))
  286. {
  287. $this->current_row = 0;
  288. $this->total_rows = mysql_num_rows($this->result);
  289. $this->fetch_type = ($object === TRUE) ? 'mysql_fetch_object' : 'mysql_fetch_array';
  290. }
  291. elseif (is_bool($result))
  292. {
  293. if ($result == FALSE)
  294. {
  295. // SQL error
  296. throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql);
  297. }
  298. else
  299. {
  300. // Its an DELETE, INSERT, REPLACE, or UPDATE query
  301. $this->insert_id = mysql_insert_id($link);
  302. $this->total_rows = mysql_affected_rows($link);
  303. }
  304. }
  305. // Set result type
  306. $this->result($object);
  307. }
  308. /**
  309. * Destruct, the cleanup crew!
  310. */
  311. public function __destruct()
  312. {
  313. if (is_resource($this->result))
  314. {
  315. mysql_free_result($this->result);
  316. }
  317. }
  318. public function result($object = TRUE, $type = MYSQL_ASSOC)
  319. {
  320. $this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array';
  321. // This check has to be outside the previous statement, because we do not
  322. // know the state of fetch_type when $object = NULL
  323. // NOTE - The class set by $type must be defined before fetching the result,
  324. // autoloading is disabled to save a lot of stupid overhead.
  325. if ($this->fetch_type == 'mysql_fetch_object')
  326. {
  327. $this->return_type = class_exists($type, FALSE) ? $type : 'stdClass';
  328. }
  329. else
  330. {
  331. $this->return_type = $type;
  332. }
  333. return $this;
  334. }
  335. public function result_array($object = NULL, $type = MYSQL_ASSOC)
  336. {
  337. $rows = array();
  338. if (is_string($object))
  339. {
  340. $fetch = $object;
  341. }
  342. elseif (is_bool($object))
  343. {
  344. if ($object === TRUE)
  345. {
  346. $fetch = 'mysql_fetch_object';
  347. // NOTE - The class set by $type must be defined before fetching the result,
  348. // autoloading is disabled to save a lot of stupid overhead.
  349. $type = class_exists($type, FALSE) ? $type : 'stdClass';
  350. }
  351. else
  352. {
  353. $fetch = 'mysql_fetch_array';
  354. }
  355. }
  356. else
  357. {
  358. // Use the default config values
  359. $fetch = $this->fetch_type;
  360. if ($fetch == 'mysql_fetch_object')
  361. {
  362. $type = class_exists($type, FALSE) ? $type : 'stdClass';
  363. }
  364. }
  365. if (mysql_num_rows($this->result))
  366. {
  367. // Reset the pointer location to make sure things work properly
  368. mysql_data_seek($this->result, 0);
  369. while ($row = $fetch($this->result, $type))
  370. {
  371. $rows[] = $row;
  372. }
  373. }
  374. return isset($rows) ? $rows : array();
  375. }
  376. public function insert_id()
  377. {
  378. return $this->insert_id;
  379. }
  380. public function list_fields()
  381. {
  382. $field_names = array();
  383. while ($field = mysql_fetch_field($this->result))
  384. {
  385. $field_names[] = $field->name;
  386. }
  387. return $field_names;
  388. }
  389. // End Interface
  390. // Interface: Countable
  391. /**
  392. * Counts the number of rows in the result set.
  393. *
  394. * @return integer
  395. */
  396. public function count()
  397. {
  398. return $this->total_rows;
  399. }
  400. // End Interface
  401. // Interface: ArrayAccess
  402. /**
  403. * Determines if the requested offset of the result set exists.
  404. *
  405. * @param integer offset id
  406. * @return boolean
  407. */
  408. public function offsetExists($offset)
  409. {
  410. if ($this->total_rows > 0)
  411. {
  412. $min = 0;
  413. $max = $this->total_rows - 1;
  414. return ($offset < $min OR $offset > $max) ? FALSE : TRUE;
  415. }
  416. return FALSE;
  417. }
  418. /**
  419. * Retreives the requested query result offset.
  420. *
  421. * @param integer offset id
  422. * @return mixed
  423. */
  424. public function offsetGet($offset)
  425. {
  426. // Check to see if the requested offset exists.
  427. if ( ! $this->offsetExists($offset))
  428. return FALSE;
  429. // Go to the offset
  430. mysql_data_seek($this->result, $offset);
  431. // Return the row
  432. $fetch = $this->fetch_type;
  433. return $fetch($this->result, $this->return_type);
  434. }
  435. /**
  436. * Sets the offset with the provided value. Since you can't modify query result sets, this function just throws an exception.
  437. *
  438. * @param integer offset id
  439. * @param integer value
  440. * @throws Kohana_Database_Exception
  441. */
  442. public function offsetSet($offset, $value)
  443. {
  444. throw new Kohana_Database_Exception('database.result_read_only');
  445. }
  446. /**
  447. * Unsets the offset. Since you can't modify query result sets, this function just throws an exception.
  448. *
  449. * @param integer offset id
  450. * @throws Kohana_Database_Exception
  451. */
  452. public function offsetUnset($offset)
  453. {
  454. throw new Kohana_Database_Exception('database.result_read_only');
  455. }
  456. // End Interface
  457. // Interface: Iterator
  458. /**
  459. * Retrieves the current result set row.
  460. *
  461. * @return mixed
  462. */
  463. public function current()
  464. {
  465. return $this->offsetGet($this->current_row);
  466. }
  467. /**
  468. * Retreives the current row id.
  469. *
  470. * @return integer
  471. */
  472. public function key()
  473. {
  474. return $this->current_row;
  475. }
  476. /**
  477. * Moves the result pointer ahead one step.
  478. *
  479. * @return integer
  480. */
  481. public function next()
  482. {
  483. return ++$this->current_row;
  484. }
  485. /**
  486. * Moves the result pointer back one step.
  487. *
  488. * @return integer
  489. */
  490. public function prev()
  491. {
  492. return --$this->current_row;
  493. }
  494. /**
  495. * Moves the result pointer to the beginning of the result set.
  496. *
  497. * @return integer
  498. */
  499. public function rewind()
  500. {
  501. return $this->current_row = 0;
  502. }
  503. /**
  504. * Determines if the current result pointer is valid.
  505. *
  506. * @return boolean
  507. */
  508. public function valid()
  509. {
  510. return $this->offsetExists($this->current_row);
  511. }
  512. // End Interface
  513. } // End Mysql_Result Class