PageRenderTime 25ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/application/libraries/drivers/Database/Mysql.php

http://github.com/ushahidi/Ushahidi_Web
PHP | 499 lines | 335 code | 87 blank | 77 comment | 52 complexity | 588b14365c17cfce1c3ef474bb1e3ecc MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php defined('SYSPATH') OR die('No direct access allowed.');
  2. /**
  3. * MySQL Database Driver
  4. *
  5. * Overrides core database mysql driver to backport some KO3 features
  6. * - modified escape_column()
  7. *
  8. * @package Ushahidi
  9. * @author Ushahidi Team
  10. * @copyright (c) 2008 Ushahidi Team
  11. * @license http://www.ushahidi.com/license.html
  12. */
  13. class Database_Mysql_Driver extends Database_Driver {
  14. /**
  15. * Database connection link
  16. */
  17. protected $link;
  18. /**
  19. * Database configuration
  20. */
  21. protected $db_config;
  22. /**
  23. * Sets the config for the class.
  24. *
  25. * @param array database configuration
  26. */
  27. public function __construct($config)
  28. {
  29. $this->db_config = $config;
  30. Kohana::log('debug', 'MySQL Database Driver Initialized');
  31. }
  32. /**
  33. * Closes the database connection.
  34. */
  35. public function __destruct()
  36. {
  37. is_resource($this->link) and mysql_close($this->link);
  38. }
  39. public function connect()
  40. {
  41. // Check if link already exists
  42. if (is_resource($this->link))
  43. return $this->link;
  44. // Import the connect variables
  45. extract($this->db_config['connection']);
  46. // Persistent connections enabled?
  47. $connect = ($this->db_config['persistent'] == TRUE) ? 'mysql_pconnect' : 'mysql_connect';
  48. // Build the connection info
  49. $host = isset($host) ? $host : $socket;
  50. $port = isset($port) ? ':'.$port : '';
  51. // Make the connection and select the database
  52. if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mysql_select_db($database, $this->link))
  53. {
  54. if ($charset = $this->db_config['character_set'])
  55. {
  56. $this->set_charset($charset);
  57. }
  58. // Clear password after successful connect
  59. $this->config['connection']['pass'] = NULL;
  60. return $this->link;
  61. }
  62. return FALSE;
  63. }
  64. public function query($sql)
  65. {
  66. // Only cache if it's turned on, and only cache if it's not a write statement
  67. if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql))
  68. {
  69. $hash = $this->query_hash($sql);
  70. if ( ! isset(self::$query_cache[$hash]))
  71. {
  72. // Set the cached object
  73. self::$query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
  74. }
  75. // Return the cached query
  76. return self::$query_cache[$hash];
  77. }
  78. return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
  79. }
  80. public function set_charset($charset)
  81. {
  82. $this->query('SET NAMES '.$this->escape_str($charset));
  83. }
  84. public function escape_table($table)
  85. {
  86. if (!$this->db_config['escape'])
  87. return $table;
  88. if (stripos($table, ' AS ') !== FALSE)
  89. {
  90. // Force 'AS' to uppercase
  91. $table = str_ireplace(' AS ', ' AS ', $table);
  92. // Runs escape_table on both sides of an AS statement
  93. $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
  94. // Re-create the AS statement
  95. return implode(' AS ', $table);
  96. }
  97. return '`'.str_replace('.', '`.`', $table).'`';
  98. }
  99. /**
  100. * Modified to handle Database_Expression
  101. **/
  102. public function escape_column($column)
  103. {
  104. if (!$this->db_config['escape'])
  105. return $column;
  106. if (strtolower($column) == 'count(*)' OR $column == '*')
  107. return $column;
  108. if ($column instanceof Database_Expression)
  109. return $column->compile($this);
  110. // This matches any modifiers we support to SELECT.
  111. 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))
  112. {
  113. if (stripos($column, ' AS ') !== FALSE)
  114. {
  115. // Force 'AS' to uppercase
  116. $column = str_ireplace(' AS ', ' AS ', $column);
  117. // Runs escape_column on both sides of an AS statement
  118. $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
  119. // Re-create the AS statement
  120. return implode(' AS ', $column);
  121. }
  122. return preg_replace('/[^.*]+/', '`$0`', $column);
  123. }
  124. $parts = explode(' ', $column);
  125. $column = '';
  126. for ($i = 0, $c = count($parts); $i < $c; $i++)
  127. {
  128. // The column is always last
  129. if ($i == ($c - 1))
  130. {
  131. $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
  132. }
  133. else // otherwise, it's a modifier
  134. {
  135. $column .= $parts[$i].' ';
  136. }
  137. }
  138. return $column;
  139. }
  140. public function regex($field, $match = '', $type = 'AND ', $num_regexs)
  141. {
  142. $prefix = ($num_regexs == 0) ? '' : $type;
  143. return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\'';
  144. }
  145. public function notregex($field, $match = '', $type = 'AND ', $num_regexs)
  146. {
  147. $prefix = $num_regexs == 0 ? '' : $type;
  148. return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\'';
  149. }
  150. public function merge($table, $keys, $values)
  151. {
  152. // Escape the column names
  153. foreach ($keys as $key => $value)
  154. {
  155. $keys[$key] = $this->escape_column($value);
  156. }
  157. return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
  158. }
  159. public function limit($limit, $offset = 0)
  160. {
  161. return 'LIMIT '.$offset.', '.$limit;
  162. }
  163. public function compile_select($database)
  164. {
  165. $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
  166. $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
  167. if (count($database['from']) > 0)
  168. {
  169. // Escape the tables
  170. $froms = array();
  171. foreach ($database['from'] as $from)
  172. {
  173. $froms[] = $this->escape_column($from);
  174. }
  175. $sql .= "\nFROM ";
  176. $sql .= implode(', ', $froms);
  177. }
  178. if (count($database['join']) > 0)
  179. {
  180. foreach($database['join'] AS $join)
  181. {
  182. $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
  183. }
  184. }
  185. if (count($database['where']) > 0)
  186. {
  187. $sql .= "\nWHERE ";
  188. }
  189. $sql .= implode("\n", $database['where']);
  190. if (count($database['groupby']) > 0)
  191. {
  192. $sql .= "\nGROUP BY ";
  193. $sql .= implode(', ', $database['groupby']);
  194. }
  195. if (count($database['having']) > 0)
  196. {
  197. $sql .= "\nHAVING ";
  198. $sql .= implode("\n", $database['having']);
  199. }
  200. if (count($database['orderby']) > 0)
  201. {
  202. $sql .= "\nORDER BY ";
  203. $sql .= implode(', ', $database['orderby']);
  204. }
  205. if (is_numeric($database['limit']))
  206. {
  207. $sql .= "\n";
  208. $sql .= $this->limit($database['limit'], $database['offset']);
  209. }
  210. return $sql;
  211. }
  212. public function escape_str($str)
  213. {
  214. if (!$this->db_config['escape'])
  215. return $str;
  216. is_resource($this->link) or $this->connect();
  217. return mysql_real_escape_string($str, $this->link);
  218. }
  219. public function list_tables(Database $db)
  220. {
  221. static $tables;
  222. if (empty($tables) AND $query = $db->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database'])))
  223. {
  224. foreach ($query->result(FALSE) as $row)
  225. {
  226. $tables[] = current($row);
  227. }
  228. }
  229. return $tables;
  230. }
  231. public function show_error()
  232. {
  233. return mysql_error($this->link);
  234. }
  235. public function list_fields($table)
  236. {
  237. static $tables;
  238. if (empty($tables[$table]))
  239. {
  240. foreach ($this->field_data($table) as $row)
  241. {
  242. // Make an associative array
  243. $tables[$table][$row->Field] = $this->sql_type($row->Type);
  244. if ($row->Key === 'PRI' AND $row->Extra === 'auto_increment')
  245. {
  246. // For sequenced (AUTO_INCREMENT) tables
  247. $tables[$table][$row->Field]['sequenced'] = TRUE;
  248. }
  249. if ($row->Null === 'YES')
  250. {
  251. // Set NULL status
  252. $tables[$table][$row->Field]['null'] = TRUE;
  253. }
  254. }
  255. }
  256. if (!isset($tables[$table]))
  257. throw new Kohana_Database_Exception('database.table_not_found', $table);
  258. return $tables[$table];
  259. }
  260. public function field_data($table)
  261. {
  262. $columns = array();
  263. if ($query = mysql_query('SHOW COLUMNS FROM '.$this->escape_table($table), $this->link))
  264. {
  265. if (mysql_num_rows($query))
  266. {
  267. while ($row = mysql_fetch_object($query))
  268. {
  269. $columns[] = $row;
  270. }
  271. }
  272. }
  273. return $columns;
  274. }
  275. } // End Database_Mysql_Driver Class
  276. /**
  277. * MySQL Result
  278. */
  279. class Mysql_Result extends Database_Result {
  280. // Fetch function and return type
  281. protected $fetch_type = 'mysql_fetch_object';
  282. protected $return_type = MYSQL_ASSOC;
  283. /**
  284. * Sets up the result variables.
  285. *
  286. * @param resource query result
  287. * @param resource database link
  288. * @param boolean return objects or arrays
  289. * @param string SQL query that was run
  290. */
  291. public function __construct($result, $link, $object = TRUE, $sql)
  292. {
  293. $this->result = $result;
  294. // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
  295. if (is_resource($result))
  296. {
  297. $this->current_row = 0;
  298. $this->total_rows = mysql_num_rows($this->result);
  299. $this->fetch_type = ($object === TRUE) ? 'mysql_fetch_object' : 'mysql_fetch_array';
  300. }
  301. elseif (is_bool($result))
  302. {
  303. if ($result == FALSE)
  304. {
  305. // SQL error
  306. throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql);
  307. }
  308. else
  309. {
  310. // Its an DELETE, INSERT, REPLACE, or UPDATE query
  311. $this->insert_id = mysql_insert_id($link);
  312. $this->total_rows = mysql_affected_rows($link);
  313. }
  314. }
  315. // Set result type
  316. $this->result($object);
  317. // Store the SQL
  318. $this->sql = $sql;
  319. }
  320. /**
  321. * Destruct, the cleanup crew!
  322. */
  323. public function __destruct()
  324. {
  325. if (is_resource($this->result))
  326. {
  327. mysql_free_result($this->result);
  328. }
  329. }
  330. public function result($object = TRUE, $type = MYSQL_ASSOC)
  331. {
  332. $this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array';
  333. // This check has to be outside the previous statement, because we do not
  334. // know the state of fetch_type when $object = NULL
  335. // NOTE - The class set by $type must be defined before fetching the result,
  336. // autoloading is disabled to save a lot of stupid overhead.
  337. if ($this->fetch_type == 'mysql_fetch_object' AND $object === TRUE)
  338. {
  339. $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
  340. }
  341. else
  342. {
  343. $this->return_type = $type;
  344. }
  345. return $this;
  346. }
  347. public function as_array($object = NULL, $type = MYSQL_ASSOC)
  348. {
  349. return $this->result_array($object, $type);
  350. }
  351. public function result_array($object = NULL, $type = MYSQL_ASSOC)
  352. {
  353. $rows = array();
  354. if (is_string($object))
  355. {
  356. $fetch = $object;
  357. }
  358. elseif (is_bool($object))
  359. {
  360. if ($object === TRUE)
  361. {
  362. $fetch = 'mysql_fetch_object';
  363. $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
  364. }
  365. else
  366. {
  367. $fetch = 'mysql_fetch_array';
  368. }
  369. }
  370. else
  371. {
  372. // Use the default config values
  373. $fetch = $this->fetch_type;
  374. if ($fetch == 'mysql_fetch_object')
  375. {
  376. $type = (is_string($this->return_type) AND Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass';
  377. }
  378. }
  379. if (mysql_num_rows($this->result))
  380. {
  381. // Reset the pointer location to make sure things work properly
  382. mysql_data_seek($this->result, 0);
  383. while ($row = $fetch($this->result, $type))
  384. {
  385. $rows[] = $row;
  386. }
  387. }
  388. return isset($rows) ? $rows : array();
  389. }
  390. public function list_fields()
  391. {
  392. $field_names = array();
  393. while ($field = mysql_fetch_field($this->result))
  394. {
  395. $field_names[] = $field->name;
  396. }
  397. return $field_names;
  398. }
  399. public function seek($offset)
  400. {
  401. if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset))
  402. {
  403. // Set the current row to the offset
  404. $this->current_row = $offset;
  405. return TRUE;
  406. }
  407. else
  408. {
  409. return FALSE;
  410. }
  411. }
  412. } // End Mysql_Result Class